[RAMEN9610-12054][COMMON] rtc: support s2mpu09 RTC alarm1 function
authorJieun Yi <jieun.yi@samsung.com>
Mon, 11 Feb 2019 06:14:56 +0000 (15:14 +0900)
committerCosmin Tanislav <demonsingur@gmail.com>
Mon, 22 Apr 2024 17:23:15 +0000 (20:23 +0300)
Alarm1 function allows the device to wake up from the power-off state.

Change-Id: I1dc648fba2e7639b1305466a7323d7552d16b002
Signed-off-by: Jieun Yi <jieun.yi@samsung.com>
drivers/rtc/Kconfig
drivers/rtc/interface.c
drivers/rtc/rtc-s2mpu09.c
drivers/rtc/rtc-sysfs.c
include/linux/mfd/samsung/rtc-s2mp.h
include/linux/rtc.h

index 198617a07dd67b83cb4f8034c34a7d14ce5696df..e602e6e5a98d4d666e381811cf0e4666d26ffe6a 100644 (file)
@@ -642,6 +642,15 @@ config RTC_DRV_S2MPU09
          This driver can also be built as a module. If so, the module
          will be called rtc-s2mpu09.
 
+config RTC_BOOT_ALARM
+       tristate "Samsung PMIC series RTC"
+       depends on RTC_DRV_S2MPU09
+       help
+         If you say yes here you will get support for the function which
+         allows the device to be turned on by RTC alarm.
+         This driver can also be built as a module. If so, the module
+         will be called rtc-s2mpu09.
+
 endif # I2C
 
 comment "SPI RTC drivers"
index e4f951e968a4f069d12e503c79f49f3f2f8b4fc3..f32fd4499332f8d0b7e62d1d0eac603485191745 100644 (file)
@@ -384,6 +384,46 @@ int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
 }
 EXPORT_SYMBOL_GPL(rtc_set_alarm);
 
+#ifdef CONFIG_RTC_BOOT_ALARM
+int rtc_read_boot_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
+{
+       int err;
+
+       err = mutex_lock_interruptible(&rtc->ops_lock);
+       if (err)
+               return err;
+
+       if (rtc->ops->read_boot_alarm)
+               err = rtc->ops->read_boot_alarm(rtc->dev.parent, alarm);
+       else
+               err = -EINVAL;
+
+       mutex_unlock(&rtc->ops_lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(rtc_read_boot_alarm);
+
+int rtc_set_boot_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
+{
+       int err;
+
+       err = mutex_lock_interruptible(&rtc->ops_lock);
+       if (err)
+               return err;
+
+       if (rtc->ops->set_boot_alarm)
+               err = rtc->ops->set_boot_alarm(rtc->dev.parent, alarm);
+       else
+               err = -EINVAL;
+
+       mutex_unlock(&rtc->ops_lock);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(rtc_set_boot_alarm);
+#endif
+
 /* Called once per device from rtc_device_register */
 int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
 {
index 599e0eef9609570af1fdd4e6eae5e4031ca3beb1..231fd551cf49d3a36bd901299b81ce7b2909032c 100644 (file)
 #include <linux/mfd/samsung/rtc-s2mp.h>
 #include <linux/mfd/samsung/s2mpu09.h>
 #include <linux/mfd/samsung/s2mpu09-regulator.h>
+#ifdef CONFIG_RTC_BOOT_ALARM
+#include <linux/reboot.h>
+#include <linux/wakelock.h>
+#endif
 
 /*#define CONFIG_WEEKDAY_ALARM_ENABLE*/
 
@@ -36,6 +40,9 @@ struct s2m_rtc_info {
        struct mutex            lock;
        struct work_struct      irq_work;
        int                     irq;
+#ifdef CONFIG_RTC_BOOT_ALARM
+       int             boot_alarm_irq;
+#endif
        bool                    use_irq;
        bool                    wtsr_en;
        bool                    smpl_en;
@@ -417,12 +424,200 @@ static irqreturn_t s2m_rtc_alarm_irq(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+#ifdef CONFIG_RTC_BOOT_ALARM
+static int s2m_rtc_stop_boot_alarm(struct s2m_rtc_info *info)
+{
+       u8 data[7];
+       int ret, i;
+       struct rtc_time tm;
+
+       ret = s2mpu09_bulk_read(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0)
+               return ret;
+
+       s2m_data_to_tm(data, &tm);
+
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(%d)\n", __func__,
+                       1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+                       tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+       for (i = 0; i < 7; i++)
+               data[i] &= ~ALARM_ENABLE_MASK;
+
+       ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0)
+               return ret;
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+
+       return ret;
+}
+
+static int s2m_rtc_start_boot_alarm(struct s2m_rtc_info *info)
+{
+       int ret;
+       u8 data[7];
+       struct rtc_time tm;
+
+       ret = s2mpu09_bulk_read(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0)
+               return ret;
+
+       s2m_data_to_tm(data, &tm);
+
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(%d)\n", __func__,
+                       1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+                       tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+       data[RTC_SEC] |= ALARM_ENABLE_MASK;
+       data[RTC_MIN] |= ALARM_ENABLE_MASK;
+       data[RTC_HOUR] |= ALARM_ENABLE_MASK;
+       data[RTC_WEEKDAY] &= 0x00;
+       if (data[RTC_DATE] & 0x1F)
+               data[RTC_DATE] |= ALARM_ENABLE_MASK;
+       if (data[RTC_MONTH] & 0x0F)
+               data[RTC_MONTH] |= ALARM_ENABLE_MASK;
+       if (data[RTC_YEAR] & 0x7F)
+               data[RTC_YEAR] |= ALARM_ENABLE_MASK;
+
+       ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0)
+               return ret;
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+
+       return ret;
+}
+
+static int s2m_rtc_read_boot_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       u8 data[NR_RTC_CNT_REGS];
+       u8 reg, val;
+       int ret;
+
+       mutex_lock(&info->lock);
+       ret = s2m_rtc_update(info, S2M_RTC_READ);
+       if (ret < 0)
+               goto out;
+       ret = s2mpu09_bulk_read(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s:%d fail to read alarm1 reg(%d)\n",
+                       __func__, __LINE__, ret);
+               goto out;
+       }
+
+       s2m_data_to_tm(data, &alrm->time);
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(%d)\n", __func__,
+                               alrm->time.tm_year + 1900, alrm->time.tm_mon + 1,
+                               alrm->time.tm_mday, alrm->time.tm_hour,
+                               alrm->time.tm_min, alrm->time.tm_sec,
+                               alrm->time.tm_wday);
+
+       alrm->enabled = info->alarm_enabled;
+       alrm->pending = 0;
+
+       switch(info->iodev->device_type) {
+       case S2MPU09X:
+               reg = S2MPU09_PMIC_REG_STATUS2;
+               break;
+       default:
+               BUG();
+       }
+
+       ret = s2mpu09_read_reg(info->pmic_i2c, reg, &val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s:%d fail to read STATUS2 reg(%d)\n",
+                       __func__, __LINE__, ret);
+               goto out;
+       }
+
+       if (val & RTCA1E)
+               alrm->pending = 1;
+
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+static int s2m_rtc_set_boot_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       u8 data[7];
+       int ret;
+
+       mutex_lock(&info->lock);
+
+       ret = s2m_tm_to_data(&alrm->time, data);
+       if (ret < 0)
+               goto out;
+
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)%s, [%s]\n",
+                               __func__, data[RTC_YEAR] + 2000, data[RTC_MONTH],
+                               data[RTC_DATE], data[RTC_HOUR] & 0x1F, data[RTC_MIN],
+                               data[RTC_SEC], data[RTC_WEEKDAY],
+                               data[RTC_HOUR] & HOUR_PM_MASK ? "PM" : "AM",
+                               alrm->enabled ? "enabled" : "disabled");
+
+       ret = s2m_rtc_stop_boot_alarm(info);
+       if (ret < 0)
+               return ret;
+
+       ret = s2mpu09_read_reg(info->i2c, S2MP_RTC_REG_UPDATE, &info->update_reg);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: read fail\n", __func__);
+               return ret;
+       }
+
+       if (alrm->enabled)
+               info->update_reg |= RTC_WAKE_MASK;
+       else
+               info->update_reg &= ~RTC_WAKE_MASK;
+
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_UPDATE, (char)info->update_reg);
+       if (ret < 0)
+               dev_err(info->dev, "%s: fail to write update reg(%d)\n", __func__, ret);
+       else
+               usleep_range(1000, 1000);
+
+       ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data);
+       if (ret < 0)
+               return ret;
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+       if (ret < 0)
+               return ret;
+
+       if (alrm->enabled)
+               ret = s2m_rtc_start_boot_alarm(info);
+
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+static irqreturn_t s2m_rtc_boot_alarm_irq(int irq, void *data)
+{
+       struct s2m_rtc_info *info = data;
+
+       dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+       rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+       __pm_wakeup_event(rtc_ws, 500);
+
+       return IRQ_HANDLED;
+}
+#endif
+
 static const struct rtc_class_ops s2m_rtc_ops = {
        .read_time = s2m_rtc_read_time,
        .set_time = s2m_rtc_set_time,
        .read_alarm = s2m_rtc_read_alarm,
        .set_alarm = s2m_rtc_set_alarm,
        .alarm_irq_enable = s2m_rtc_alarm_irq_enable,
+#ifdef CONFIG_RTC_BOOT_ALARM
+       .read_boot_alarm = s2m_rtc_read_boot_alarm,
+       .set_boot_alarm = s2m_rtc_set_boot_alarm,
+#endif
 };
 
 static void s2m_rtc_optimize_osc(struct s2m_rtc_info *info,
@@ -545,6 +740,23 @@ static int s2m_rtc_init_reg(struct s2m_rtc_info *info,
        u8 data, update_val, ctrl_val, capsel_val;
        int ret;
 
+#ifdef CONFIG_RTC_BOOT_ALARM
+       u8 data_alrm1[7];
+       struct rtc_time alrm;
+
+       ret = s2mpu09_bulk_read(info->i2c, S2MP_RTC_REG_A1SEC, NR_RTC_CNT_REGS, data_alrm1);
+       if (ret < 0)
+               return ret;
+
+       s2m_data_to_tm(data_alrm1, &alrm);
+
+       dev_info(info->dev, "%s [boot alarm]: %d-%02d-%02d %02d:%02d:%02d(%d)\n", __func__,
+                               alrm.tm_year + 1900, alrm.tm_mon + 1,
+                               alrm.tm_mday, alrm.tm_hour,
+                               alrm.tm_min, alrm.tm_sec,
+                               alrm.tm_wday);
+#endif
+
        ret = s2mpu09_read_reg(info->i2c, S2MP_RTC_REG_UPDATE, &update_val);
        if (ret < 0) {
                dev_err(info->dev, "%s: fail to read update reg(%d)\n",
@@ -646,6 +858,9 @@ static int s2m_rtc_probe(struct platform_device *pdev)
        switch (info->iodev->device_type) {
        case S2MPU09X:
                info->irq = irq_base + S2MPU09_PMIC_IRQ_RTCA0_INT2;
+#ifdef CONFIG_RTC_BOOT_ALARM
+               info->boot_alarm_irq = irq_base + S2MPU09_PMIC_IRQ_RTCA1_INT2;
+#endif
                break;
        default:
                /* If this happens the core funtion has a problem */
@@ -672,12 +887,22 @@ static int s2m_rtc_probe(struct platform_device *pdev)
        ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
                        s2m_rtc_alarm_irq, 0, "rtc-alarm0", info);
        if (ret < 0) {
-               dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+               dev_err(&pdev->dev, "Failed to request alarm0 IRQ: %d: %d\n",
                        info->irq, ret);
                goto err_rtc_irq;
        }
        disable_irq(info->irq);
        disable_irq(info->irq);
+
+#ifdef CONFIG_RTC_BOOT_ALARM
+       ret = devm_request_threaded_irq(&pdev->dev, info->boot_alarm_irq, NULL,
+                       s2m_rtc_boot_alarm_irq, 0, "rtc-alarm1", info);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Failed to request alarm1 IRQ: %d: %d\n",
+                       info->boot_alarm_irq, ret);
+               goto err_rtc_irq;
+       }
+#endif
        info->use_irq = true;
 
        info->rtc_dev = devm_rtc_device_register(&pdev->dev, "s2mp-rtc",
@@ -693,6 +918,9 @@ static int s2m_rtc_probe(struct platform_device *pdev)
 
 err_rtc_dev_register:
        devm_free_irq(&pdev->dev, info->irq, info);
+#ifdef CONFIG_RTC_BOOT_ALARM
+       devm_free_irq(&pdev->dev, info->boot_alarm_irq, info);
+#endif
        enable_irq(info->irq);
        enable_irq(info->irq);
 err_rtc_irq:
@@ -710,6 +938,9 @@ static int s2m_rtc_remove(struct platform_device *pdev)
 
        if (!info->alarm_enabled)
                enable_irq(info->irq);
+#ifdef CONFIG_RTC_BOOT_ALARM
+       devm_free_irq(&pdev->dev, info->boot_alarm_irq, info);
+#endif
 
        wakeup_source_unregister(rtc_ws);
 
index e364550eb9a7fad8ef4efed7ca2f200116832e9c..7e3d9ada774e295bb66198b471093f5fa01f11df 100644 (file)
@@ -219,6 +219,76 @@ wakealarm_store(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RW(wakealarm);
 
+#ifdef CONFIG_RTC_BOOT_ALARM
+static ssize_t
+bootalarm_show(struct device *dev, struct device_attribute *attr,
+               char *buf)
+{
+       ssize_t retval;
+       struct rtc_wkalrm alarm;
+
+       /* Don't show disabled alarms.  For uniformity, RTC alarms are
+        * conceptually one-shot, even though some common RTCs (on PCs)
+        * don't actually work that way.
+        *
+        * NOTE: RTC implementations where the alarm doesn't match an
+        * exact YYYY-MM-DD HH:MM[:SS] date *must* disable their RTC
+        * alarms after they trigger, to ensure one-shot semantics.
+        */
+
+       retval = rtc_read_boot_alarm(to_rtc_device(dev), &alarm);
+       if (retval == 0) {
+               retval = sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d(%d) [%s, %s]\n",
+                               alarm.time.tm_year + 1900, alarm.time.tm_mon + 1, alarm.time.tm_mday,
+                               alarm.time.tm_hour, alarm.time.tm_min, alarm.time.tm_sec,
+                               alarm.time.tm_wday,
+                               ((alarm.enabled == 1) ? "Enable" : "Disable"),
+                               ((alarm.pending == 1 ) ? "Pending" : "Not Pending"));
+       }
+
+       return retval;
+}
+
+static ssize_t
+bootalarm_store(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t n)
+{
+       ssize_t retval;
+       unsigned long now, alrm_long;
+       struct rtc_wkalrm alarm;
+       struct rtc_device *rtc = to_rtc_device(dev);
+       char *buf_ptr;
+       int adjust = 0;
+
+       /* Only request alarms to be triggered in the future.
+        * by write another time, e.g. 0 meaning Jan 1 1970 UTC.
+        */
+
+       retval = rtc_read_time(rtc, &alarm.time);
+       if (retval < 0)
+               return retval;
+       rtc_tm_to_time(&alarm.time, &now);
+
+       buf_ptr = (char *)buf;
+       if (*buf_ptr == '+') {
+               buf_ptr++;
+               adjust = 1;
+       }
+
+       alrm_long = simple_strtoul(buf_ptr, NULL, 0);
+
+       if (adjust)
+               alrm_long += now;
+
+       rtc_time_to_tm(alrm_long, &alarm.time);
+
+       alarm.enabled = 1;
+       retval = rtc_set_boot_alarm(rtc, &alarm);
+       return (retval < 0) ? retval : n;
+}
+static DEVICE_ATTR_RW(bootalarm);
+#endif
+
 static ssize_t
 offset_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
@@ -256,6 +326,9 @@ static struct attribute *rtc_attrs[] = {
        &dev_attr_hctosys.attr,
        &dev_attr_wakealarm.attr,
        &dev_attr_offset.attr,
+#ifdef CONFIG_RTC_BOOT_ALARM
+       &dev_attr_bootalarm.attr,
+#endif
        NULL,
 };
 
index 43a6f648561f4f008644e580905db3baac6b3a1d..5ee6936a9ff4b26254bc459bd5b1c3065717299f 100644 (file)
 #define RTC_RUDR_MASK                  (1 << RTC_RUDR_SHIFT)
 #define RTC_AUDR_SHIFT_REV             4
 #define RTC_AUDR_MASK_REV              (1 << RTC_AUDR_SHIFT_REV)
+#ifdef CONFIG_RTC_BOOT_ALARM
+#define RTC_WAKE_SHIFT                 3
+#define RTC_WAKE_MASK                  (1 << RTC_WAKE_SHIFT)
+#endif
 #define RTC_FREEZE_SHIFT               2
 #define RTC_FREEZE_MASK                        (1 << RTC_FREEZE_SHIFT)
 #define RTC_WUDR_SHIFT_REV             1
index f6d7ee98d93c3afb7c8b205a75361bd101a56600..b726c26a7cbf66a3a9ad0047b020ee039cc3e49b 100644 (file)
@@ -85,6 +85,10 @@ struct rtc_class_ops {
        int (*alarm_irq_enable)(struct device *, unsigned int enabled);
        int (*read_offset)(struct device *, long *offset);
        int (*set_offset)(struct device *, long offset);
+#ifdef CONFIG_RTC_BOOT_ALARM
+       int (*read_boot_alarm)(struct device *, struct rtc_wkalrm *);
+       int (*set_boot_alarm)(struct device *, struct rtc_wkalrm *);
+#endif
 };
 
 #define RTC_DEVICE_NAME_SIZE 20
@@ -179,6 +183,10 @@ extern int rtc_read_alarm(struct rtc_device *rtc,
                        struct rtc_wkalrm *alrm);
 extern int rtc_set_alarm(struct rtc_device *rtc,
                                struct rtc_wkalrm *alrm);
+#ifdef CONFIG_RTC_BOOT_ALARM
+extern int rtc_read_boot_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm);
+extern int rtc_set_boot_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm);
+#endif
 extern int rtc_initialize_alarm(struct rtc_device *rtc,
                                struct rtc_wkalrm *alrm);
 extern void rtc_update_irq(struct rtc_device *rtc,