[9610] drivers: rtc: created rtc driver for S2MPU09
authorJieun Yi <jieun.yi@samsung.com>
Fri, 11 May 2018 04:40:09 +0000 (13:40 +0900)
committerJaehyoung Choi <jkkkkk.choi@samsung.com>
Fri, 11 May 2018 07:39:06 +0000 (16:39 +0900)
Change-Id: Iddbbc11dd7beacd6cac21a922accafe5a23db899
Signed-off-by: Jieun Yi <jieun.yi@samsung.com>
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/rtc-s2mpu09.c [new file with mode: 0644]
include/linux/mfd/samsung/rtc-s2mp.h [new file with mode: 0644]

index e0e58f3b14209a7fe908adb0cf8ca544a93c65a2..df9679d7e4b4cb956ea4dbbcd763be0f10bd493d 100644 (file)
@@ -631,6 +631,15 @@ config RTC_DRV_S5M
          This driver can also be built as a module. If so, the module
          will be called rtc-s5m.
 
+config RTC_DRV_S2MPU09
+       tristate "Samsung PMIC series RTC"
+       depends on MFD_S2MPU09
+       help
+         If you say yes here you will get support for the
+         RTC of Samsung PMIC series.
+         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 0bf1fc02b82c451348ba6110457bd8189b5e73e5..23951ee3dafee17f8d4e4f68917139cde8059d21 100644 (file)
@@ -142,6 +142,7 @@ obj-$(CONFIG_RTC_DRV_RX8025)        += rtc-rx8025.o
 obj-$(CONFIG_RTC_DRV_RX8581)   += rtc-rx8581.o
 obj-$(CONFIG_RTC_DRV_S35390A)  += rtc-s35390a.o
 obj-$(CONFIG_RTC_DRV_S3C)      += rtc-s3c.o
+obj-$(CONFIG_RTC_DRV_S2MPU09)   += rtc-s2mpu09.o
 obj-$(CONFIG_RTC_DRV_S5M)      += rtc-s5m.o
 obj-$(CONFIG_RTC_DRV_SA1100)   += rtc-sa1100.o
 obj-$(CONFIG_RTC_DRV_SH)       += rtc-sh.o
diff --git a/drivers/rtc/rtc-s2mpu09.c b/drivers/rtc/rtc-s2mpu09.c
new file mode 100644 (file)
index 0000000..599e0ee
--- /dev/null
@@ -0,0 +1,750 @@
+/*
+ * rtc-s2mpu09.c
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd
+ *              http://www.samsung.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  2013-12-11  Performance improvements and code clean up by
+ *             Minsung Kim <ms925.kim@samsung.com>
+ *
+ */
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/rtc.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/samsung/rtc-s2mp.h>
+#include <linux/mfd/samsung/s2mpu09.h>
+#include <linux/mfd/samsung/s2mpu09-regulator.h>
+
+/*#define CONFIG_WEEKDAY_ALARM_ENABLE*/
+
+struct s2m_rtc_info {
+       struct device           *dev;
+       struct i2c_client   *i2c;
+       struct i2c_client   *pmic_i2c;
+       struct s2mpu09_dev      *iodev;
+       struct rtc_device       *rtc_dev;
+       struct mutex            lock;
+       struct work_struct      irq_work;
+       int                     irq;
+       bool                    use_irq;
+       bool                    wtsr_en;
+       bool                    smpl_en;
+       bool                    alarm_enabled;
+       u8                      update_reg;
+       bool                    use_alarm_workaround;
+       bool                    alarm_check;
+       u8                      wudr_mask;
+       u8                      audr_mask;
+};
+
+static struct wakeup_source *rtc_ws;
+
+enum S2M_RTC_OP {
+       S2M_RTC_READ,
+       S2M_RTC_WRITE_TIME,
+       S2M_RTC_WRITE_ALARM,
+};
+
+static void s2m_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+//     tm->tm_msec = (data[RTC_MSEC] & 0x0f) + (data[RTC_MSEC] & 0xf0) * 10;
+       tm->tm_sec = data[RTC_SEC] & 0x7f;
+       tm->tm_min = data[RTC_MIN] & 0x7f;
+       tm->tm_hour = data[RTC_HOUR] & 0x1f;
+       tm->tm_wday = __fls(data[RTC_WEEKDAY] & 0x7f);
+       tm->tm_mday = data[RTC_DATE] & 0x1f;
+       tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+       tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100;
+       tm->tm_yday = 0;
+       tm->tm_isdst = 0;
+}
+
+static int s2m_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+//     data[RTC_MSEC] = ((tm->tm_msec / 10) << 4) | (tm->tm_msec % 10);
+       data[RTC_SEC] = tm->tm_sec;
+       data[RTC_MIN] = tm->tm_min;
+
+       if (tm->tm_hour >= 12)
+               data[RTC_HOUR] = tm->tm_hour | HOUR_PM_MASK;
+       else
+               data[RTC_HOUR] = tm->tm_hour;
+
+       data[RTC_WEEKDAY] = BIT(tm->tm_wday);
+       data[RTC_DATE] = tm->tm_mday;
+       data[RTC_MONTH] = tm->tm_mon + 1;
+       data[RTC_YEAR] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0 ;
+
+       if (tm->tm_year < 100) {
+               pr_warn("%s: SEC RTC cannot handle the year %d\n", __func__,
+                               1900 + tm->tm_year);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int s2m_rtc_update(struct s2m_rtc_info *info,
+                                enum S2M_RTC_OP op)
+{
+       u8 data;
+       int ret;
+
+       if (!info || !info->iodev) {
+               pr_err("%s: Invalid argument\n", __func__);
+               return -EINVAL;
+       }
+
+       switch (op) {
+       case S2M_RTC_READ:
+               data = RTC_RUDR_MASK;
+               break;
+       case S2M_RTC_WRITE_TIME:
+               data = RTC_WUDR_MASK_REV;
+               break;
+       case S2M_RTC_WRITE_ALARM:
+               data = RTC_AUDR_MASK_REV;
+               break;
+       default:
+               dev_err(info->dev, "%s: invalid op(%d)\n", __func__, op);
+               return -EINVAL;
+       }
+
+       data |= info->update_reg;
+
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_UPDATE, data);
+       if (ret < 0)
+               dev_err(info->dev, "%s: fail to write update reg(%d,%u)\n",
+                               __func__, ret, data);
+       else
+               usleep_range(1000, 1000);
+
+       return ret;
+}
+
+static int s2m_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       u8 data[NR_RTC_CNT_REGS];
+       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_SEC, NR_RTC_CNT_REGS,
+                       data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__,
+                       ret);
+               goto out;
+       }
+
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)%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");
+
+       s2m_data_to_tm(data, tm);
+       ret = rtc_valid_tm(tm);
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+static int s2m_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       u8 data[NR_RTC_CNT_REGS];
+       int ret;
+
+       ret = s2m_tm_to_data(tm, data);
+       if (ret < 0)
+               return ret;
+
+       dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)%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");
+
+       mutex_lock(&info->lock);
+       ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_SEC, NR_RTC_CNT_REGS,
+                       data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__,
+                       ret);
+               goto out;
+       }
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_TIME);
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+/* This is a workaround for the problem that RTC TIME is overwirted by write
+ * buffer when setting RTC ALARM. It is quite rare but it does happen. The root
+ * cuase is that clear signal of RUDR & WUDR is 1 clock delay while it should be
+ * 2 clock delay.
+ */
+static int s2m_rtc_check_rtc_time(struct s2m_rtc_info *info)
+{
+       u8 data[NR_RTC_CNT_REGS];
+       struct rtc_time tm;
+       struct timeval sys_time;
+       unsigned long rtc_time;
+       int ret;
+
+       /* Read RTC TIME */
+       ret = s2m_rtc_update(info, S2M_RTC_READ);
+       if (ret < 0)
+               goto out;
+
+       ret = s2mpu09_bulk_read(info->i2c, S2MP_RTC_REG_SEC, NR_RTC_CNT_REGS,
+                       data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__,
+                       ret);
+               goto out;
+       }
+
+       /* Get system time */
+       do_gettimeofday(&sys_time);
+
+       /* Convert RTC TIME to seconds since 01-01-1970 00:00:00. */
+       s2m_data_to_tm(data, &tm);
+       rtc_tm_to_time(&tm, &rtc_time);
+
+       if (abs(rtc_time - sys_time.tv_sec) > 2) {
+               /* Set RTC TIME */
+               rtc_time_to_tm(sys_time.tv_sec, &tm);
+               ret = s2m_tm_to_data(&tm, data);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to tm_to_data(%d)\n",
+                                       __func__, ret);
+                       goto out;
+               }
+
+               ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_SEC,
+                               NR_RTC_CNT_REGS, data);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to write time reg(%d)\n",
+                                       __func__, ret);
+                       goto out;
+               }
+
+               ret = s2m_rtc_update(info, S2M_RTC_WRITE_TIME);
+
+               dev_warn(info->dev, "%s: adjust RTC TIME: sys_time: %lu, "
+                               "rtc_time: %lu\n", __func__, sys_time.tv_sec,
+                               rtc_time);
+
+               dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d(0x%02x)%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");
+       }
+out:
+       return ret;
+}
+
+static int s2m_rtc_read_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_A0SEC, NR_RTC_CNT_REGS,
+                       data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s:%d fail to read alarm 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:
+               /* If this happens the core funtion has a problem */
+               BUG();
+       }
+
+       ret = s2mpu09_read_reg(info->pmic_i2c, reg, &val); /* i2c for PM */
+       if (ret < 0) {
+               dev_err(info->dev, "%s:%d fail to read STATUS2 reg(%d)\n",
+                       __func__, __LINE__, ret);
+               goto out;
+       }
+
+       if (val & RTCA0E)
+               alrm->pending = 1;
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+static int s2m_rtc_set_alarm_enable(struct s2m_rtc_info *info, bool enabled)
+{
+       if (!info->use_irq)
+               return -EPERM;
+
+       if (enabled && !info->alarm_enabled) {
+               info->alarm_enabled = true;
+               enable_irq(info->irq);
+       } else if (!enabled && info->alarm_enabled) {
+               info->alarm_enabled = false;
+               disable_irq(info->irq);
+       }
+       return 0;
+}
+
+static int s2m_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       u8 data[NR_RTC_CNT_REGS];
+       int ret, i;
+
+       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\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");
+
+       if (info->alarm_check) {
+               for (i = 0; i < NR_RTC_CNT_REGS; i++)
+                       data[i] &= ~ALARM_ENABLE_MASK;
+
+               ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_A0SEC, NR_RTC_CNT_REGS,
+                               data);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to disable alarm reg(%d)\n",
+                               __func__, ret);
+                       goto out;
+               }
+
+               ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+               if (ret < 0)
+                       goto out;
+       }
+
+       for (i = 0; i < NR_RTC_CNT_REGS; i++)
+               data[i] |= ALARM_ENABLE_MASK;
+
+       ret = s2mpu09_bulk_write(info->i2c, S2MP_RTC_REG_A0SEC, NR_RTC_CNT_REGS,
+                       data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+                       __func__, ret);
+               goto out;
+       }
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+       if (ret < 0)
+               goto out;
+
+       if (info->use_alarm_workaround) {
+               ret = s2m_rtc_check_rtc_time(info);
+               if (ret < 0)
+                       goto out;
+       }
+
+       ret = s2m_rtc_set_alarm_enable(info, alrm->enabled);
+out:
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+static int s2m_rtc_alarm_irq_enable(struct device *dev,
+                                       unsigned int enabled)
+{
+       struct s2m_rtc_info *info = dev_get_drvdata(dev);
+       int ret;
+
+       mutex_lock(&info->lock);
+       ret = s2m_rtc_set_alarm_enable(info, enabled);
+       mutex_unlock(&info->lock);
+       return ret;
+}
+
+static irqreturn_t s2m_rtc_alarm_irq(int irq, void *data)
+{
+       struct s2m_rtc_info *info = data;
+
+       if (!info->rtc_dev)
+               return IRQ_HANDLED;
+
+       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;
+}
+
+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,
+};
+
+static void s2m_rtc_optimize_osc(struct s2m_rtc_info *info,
+                                               struct s2mpu09_platform_data *pdata)
+{
+       int ret = 0;
+
+       /* edit option for OSC_BIAS_UP */
+       if (pdata->osc_bias_up >= 0) {
+               ret = s2mpu09_update_reg(info->i2c, S2MP_RTC_REG_CAPSEL,
+                       pdata->osc_bias_up << OSC_BIAS_UP_SHIFT,
+                       OSC_BIAS_UP_MASK);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to write OSC_BIAS_UP(%d)\n",
+                               __func__, pdata->osc_bias_up);
+                       return;
+               }
+       }
+
+       /* edit option for CAP_SEL */
+       if (pdata->cap_sel >= 0) {
+               s2mpu09_update_reg(info->i2c, S2MP_RTC_REG_CAPSEL,
+                       pdata->cap_sel << CAP_SEL_SHIFT, CAP_SEL_MASK);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to write CAP_SEL(%d)\n",
+                       __func__, pdata->cap_sel);
+                       return;
+               }
+       }
+
+       /* edit option for OSC_CTRL */
+       if (pdata->osc_xin >= 0) {
+               s2mpu09_update_reg(info->i2c, S2MP_RTC_REG_OSCCTRL,
+                       pdata->osc_xin << OSC_XIN_SHIFT, OSC_XIN_MASK);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to write OSC_CTRL(%d)\n",
+                       __func__,  pdata->osc_xin);
+                       return;
+               }
+       }
+       if (pdata->osc_xout >= 0) {
+               s2mpu09_update_reg(info->i2c, S2MP_RTC_REG_OSCCTRL,
+                       pdata->osc_xout << OSC_XOUT_SHIFT, OSC_XOUT_MASK);
+               if (ret < 0) {
+                       dev_err(info->dev, "%s: fail to write OSC_CTRL(%d)\n",
+                       __func__, pdata->osc_xout);
+                       return;
+               }
+       }
+}
+
+static bool s2m_is_jigonb_low(struct s2m_rtc_info *info)
+{
+       int ret, reg;
+       u8 val, mask;
+
+       switch (info->iodev->device_type) {
+       case S2MPU09X:
+               reg = S2MPU09_PMIC_REG_STATUS1;
+               mask = BIT(1);
+               break;
+       default:
+               BUG();
+       }
+
+       ret = s2mpu09_read_reg(info->i2c, reg, &val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to read status1 reg(%d)\n",
+                       __func__, ret);
+               return false;
+       }
+
+       return !(val & mask);
+}
+
+static void s2m_rtc_enable_wtsr_smpl(struct s2m_rtc_info *info,
+                                               struct s2mpu09_platform_data *pdata)
+{
+       u8 val;
+       int ret;
+
+       if (pdata->wtsr_smpl->check_jigon && s2m_is_jigonb_low(info))
+               pdata->wtsr_smpl->smpl_en = false;
+
+       val = (pdata->wtsr_smpl->wtsr_en << WTSR_EN_SHIFT)
+               | (pdata->wtsr_smpl->smpl_en << SMPL_EN_SHIFT)
+               | WTSR_TIMER_BITS(pdata->wtsr_smpl->wtsr_timer_val)
+               | SMPL_TIMER_BITS(pdata->wtsr_smpl->smpl_timer_val);
+
+       dev_info(info->dev, "%s: WTSR: %s, SMPL: %s\n", __func__,
+                       pdata->wtsr_smpl->wtsr_en ? "enable" : "disable",
+                       pdata->wtsr_smpl->smpl_en ? "enable" : "disable");
+
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_WTSR_SMPL, val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write WTSR/SMPL reg(%d)\n",
+                               __func__, ret);
+               return;
+       }
+       info->wtsr_en = pdata->wtsr_smpl->wtsr_en;
+       info->smpl_en = pdata->wtsr_smpl->smpl_en;
+}
+
+static void s2m_rtc_disable_wtsr_smpl(struct s2m_rtc_info *info,
+                                       struct s2mpu09_platform_data *pdata)
+{
+       int ret;
+
+       dev_info(info->dev, "%s: disable WTSR\n", __func__);
+       ret = s2mpu09_update_reg(info->i2c, S2MP_RTC_REG_WTSR_SMPL, 0,
+                       WTSR_EN_MASK | SMPL_EN_MASK);
+       if (ret < 0)
+               dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+                               __func__, ret);
+}
+
+static int s2m_rtc_init_reg(struct s2m_rtc_info *info,
+                               struct s2mpu09_platform_data *pdata)
+{
+       u8 data, update_val, ctrl_val, capsel_val;
+       int ret;
+
+       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",
+                       __func__, ret);
+               return ret;
+       }
+
+       info->update_reg =
+               update_val & ~(RTC_WUDR_MASK_REV | RTC_FREEZE_MASK | RTC_RUDR_MASK | RTC_AUDR_MASK_REV);
+
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_UPDATE, info->update_reg);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write update reg(%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       s2m_rtc_update(info, S2M_RTC_READ);
+
+       ret = s2mpu09_read_reg(info->i2c, S2MP_RTC_REG_CTRL, &ctrl_val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to read control reg(%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       ret = s2mpu09_read_reg(info->i2c, S2MP_RTC_REG_CAPSEL, &capsel_val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to read cap_sel reg(%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       /* If the value of RTC_CTRL register is 0, RTC registers were reset */
+       if ((ctrl_val & MODEL24_MASK) && ((capsel_val & 0xf0) == 0xf0))
+               return 0;
+
+       /* Set RTC control register : Binary mode, 24hour mode */
+       data = MODEL24_MASK;
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_CTRL, data);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write CTRL reg(%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       ret = s2m_rtc_update(info, S2M_RTC_WRITE_ALARM);
+       if (ret < 0)
+               return ret;
+
+       capsel_val |= 0xf0;
+       ret = s2mpu09_write_reg(info->i2c, S2MP_RTC_REG_CAPSEL, capsel_val);
+       if (ret < 0) {
+               dev_err(info->dev, "%s: fail to write CAP_SEL reg(%d)\n",
+                               __func__, ret);
+               return ret;
+       }
+
+       if (pdata->init_time) {
+               dev_info(info->dev, "%s: initialize RTC time\n", __func__);
+               ret = s2m_rtc_set_time(info->dev, pdata->init_time);
+       }
+       else {
+               dev_info(info->dev, "%s: RTC initialize is not operated: "
+                               "This causes a weekday problem\n", __func__);
+       }
+       return ret;
+}
+
+
+static int s2m_rtc_probe(struct platform_device *pdev)
+{
+       struct s2mpu09_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+       struct s2mpu09_platform_data *pdata = dev_get_platdata(iodev->dev);
+       struct s2m_rtc_info *info;
+       int irq_base;
+       int ret = 0;
+
+       info = devm_kzalloc(&pdev->dev, sizeof(struct s2m_rtc_info),
+                               GFP_KERNEL);
+
+       if (!info)
+               return -ENOMEM;
+
+       irq_base = pdata->irq_base;
+       if (!irq_base) {
+               dev_err(&pdev->dev, "Failed to get irq base %d\n", irq_base);
+               return -ENODEV;
+       }
+
+       mutex_init(&info->lock);
+       info->dev = &pdev->dev;
+       info->iodev = iodev;
+       info->i2c = iodev->rtc;
+       info->pmic_i2c = iodev->pmic;
+       info->alarm_check = true;
+       info->use_alarm_workaround = false;
+
+       switch (info->iodev->device_type) {
+       case S2MPU09X:
+               info->irq = irq_base + S2MPU09_PMIC_IRQ_RTCA0_INT2;
+               break;
+       default:
+               /* If this happens the core funtion has a problem */
+               BUG();
+       }
+
+       platform_set_drvdata(pdev, info);
+
+       ret = s2m_rtc_init_reg(info, pdata);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret);
+               goto err_rtc_init_reg;
+       }
+
+       /* enable wtsrm smpl */
+       if (pdata->wtsr_smpl)
+               s2m_rtc_enable_wtsr_smpl(info, pdata);
+
+       s2m_rtc_optimize_osc(info, pdata);
+
+       device_init_wakeup(&pdev->dev, true);
+       rtc_ws = wakeup_source_register("rtc-s2mp");
+
+       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",
+                       info->irq, ret);
+               goto err_rtc_irq;
+       }
+       disable_irq(info->irq);
+       disable_irq(info->irq);
+       info->use_irq = true;
+
+       info->rtc_dev = devm_rtc_device_register(&pdev->dev, "s2mp-rtc",
+                       &s2m_rtc_ops, THIS_MODULE);
+
+       if (IS_ERR(info->rtc_dev)) {
+               ret = PTR_ERR(info->rtc_dev);
+               dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+               goto err_rtc_dev_register;
+       }
+       enable_irq(info->irq);
+       return 0;
+
+err_rtc_dev_register:
+       devm_free_irq(&pdev->dev, info->irq, info);
+       enable_irq(info->irq);
+       enable_irq(info->irq);
+err_rtc_irq:
+       wakeup_source_unregister(rtc_ws);
+err_rtc_init_reg:
+       platform_set_drvdata(pdev, NULL);
+       mutex_destroy(&info->lock);
+
+       return ret;
+}
+
+static int s2m_rtc_remove(struct platform_device *pdev)
+{
+       struct s2m_rtc_info *info = platform_get_drvdata(pdev);
+
+       if (!info->alarm_enabled)
+               enable_irq(info->irq);
+
+       wakeup_source_unregister(rtc_ws);
+
+       return 0;
+}
+
+static void s2m_rtc_shutdown(struct platform_device *pdev)
+{
+       /*disable wtsr, smpl */
+       struct s2m_rtc_info *info = platform_get_drvdata(pdev);
+       struct s2mpu09_platform_data *pdata =
+                       dev_get_platdata(info->iodev->dev);
+
+       if (info->wtsr_en || info->smpl_en)
+               s2m_rtc_disable_wtsr_smpl(info, pdata);
+}
+
+static const struct platform_device_id s2m_rtc_id[] = {
+       { "s2mpu09-rtc", 0 },
+};
+
+static struct platform_driver s2m_rtc_driver = {
+       .driver         = {
+               .name   = "s2mpu09-rtc",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = s2m_rtc_probe,
+       .remove         = s2m_rtc_remove,
+       .shutdown       = s2m_rtc_shutdown,
+       .id_table       = s2m_rtc_id,
+};
+
+module_platform_driver(s2m_rtc_driver);
+
+/* Module information */
+MODULE_DESCRIPTION("Samsung RTC driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/samsung/rtc-s2mp.h b/include/linux/mfd/samsung/rtc-s2mp.h
new file mode 100644 (file)
index 0000000..43a6f64
--- /dev/null
@@ -0,0 +1,146 @@
+/*  rtc-s2mp.h
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd
+ *              http://www.samsung.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __LINUX_MFD_SEC_RTC_H
+#define __LINUX_MFD_SEC_RTC_H
+
+/* Slave addr = 0x0C */
+/* RTC Registers */
+#if defined(CONFIG_RTC_DRV_S2MP)
+#define S2MP_RTC_REG_CTRL      0x00
+#define S2MP_RTC_REG_WTSR_SMPL 0x01
+#define S2MP_RTC_REG_UPDATE    0x02
+#define S2MP_RTC_REG_CAPSEL    0x03
+#define S2MP_RTC_REG_SEC       0x04
+#define S2MP_RTC_REG_MIN       0x05
+#define S2MP_RTC_REG_HOUR      0x06
+#define S2MP_RTC_REG_WEEK      0x07
+#define S2MP_RTC_REG_DAY       0x08
+#define S2MP_RTC_REG_MON       0x09
+#define S2MP_RTC_REG_YEAR      0x0A
+#define S2MP_RTC_REG_A0SEC     0x0B
+#define S2MP_RTC_REG_A0MIN     0x0C
+#define S2MP_RTC_REG_A0HOUR    0x0D
+#define S2MP_RTC_REG_A0WEEK    0x0E
+#define S2MP_RTC_REG_A0DAY     0x0F
+#define S2MP_RTC_REG_A0MON     0x10
+#define S2MP_RTC_REG_A0YEAR    0x11
+#define S2MP_RTC_REG_A1SEC     0x12
+#define S2MP_RTC_REG_A1MIN     0x13
+#define S2MP_RTC_REG_A1HOUR    0x14
+#define S2MP_RTC_REG_A1WEEK    0x15
+#define S2MP_RTC_REG_A1DAY     0x16
+#define S2MP_RTC_REG_A1MON     0x17
+#define S2MP_RTC_REG_A1YEAR    0x18
+#else
+#define S2MP_RTC_REG_CTRL      0x00
+#define S2MP_RTC_REG_WTSR_SMPL 0x01
+#define S2MP_RTC_REG_UPDATE    0x02
+#define S2MP_RTC_REG_CAPSEL    0x03
+#define S2MP_RTC_REG_MSEC      0x04
+#define S2MP_RTC_REG_SEC       0x05
+#define S2MP_RTC_REG_MIN       0x06
+#define S2MP_RTC_REG_HOUR      0x07
+#define S2MP_RTC_REG_WEEK      0x08
+#define S2MP_RTC_REG_DAY       0x09
+#define S2MP_RTC_REG_MON       0x0A
+#define S2MP_RTC_REG_YEAR      0x0B
+#define S2MP_RTC_REG_A0SEC     0x0C
+#define S2MP_RTC_REG_A0MIN     0x0D
+#define S2MP_RTC_REG_A0HOUR    0x0E
+#define S2MP_RTC_REG_A0WEEK    0x0F
+#define S2MP_RTC_REG_A0DAY     0x10
+#define S2MP_RTC_REG_A0MON     0x11
+#define S2MP_RTC_REG_A0YEAR    0x12
+#define S2MP_RTC_REG_A1SEC     0x13
+#define S2MP_RTC_REG_A1MIN     0x14
+#define S2MP_RTC_REG_A1HOUR    0x15
+#define S2MP_RTC_REG_A1WEEK    0x16
+#define S2MP_RTC_REG_A1DAY     0x17
+#define S2MP_RTC_REG_A1MON     0x18
+#define S2MP_RTC_REG_A1YEAR    0x19
+#define S2MP_RTC_REG_OSCCTRL   0x1A
+#endif
+
+/* RTC Control Register */
+#define BCD_EN_SHIFT                   0
+#define BCD_EN_MASK                    (1 << BCD_EN_SHIFT)
+#define MODEL24_SHIFT                  1
+#define MODEL24_MASK                   (1 << MODEL24_SHIFT)
+/* WTSR and SMPL Register */
+#define WTSRT_SHIFT                    0
+#define SMPLT_SHIFT                    3
+#define WTSRT_MASK                     (7 << WTSRT_SHIFT)
+#define SMPLT_MASK                     (7 << SMPLT_SHIFT)
+#define WTSR_EN_SHIFT                  6
+#define SMPL_EN_SHIFT                  7
+#define WTSR_EN_MASK                   (1 << WTSR_EN_SHIFT)
+#define SMPL_EN_MASK                   (1 << SMPL_EN_SHIFT)
+/* RTC Update Register */
+#define RTC_RUDR_SHIFT                 0
+#define RTC_RUDR_MASK                  (1 << RTC_RUDR_SHIFT)
+#define RTC_AUDR_SHIFT_REV             4
+#define RTC_AUDR_MASK_REV              (1 << RTC_AUDR_SHIFT_REV)
+#define RTC_FREEZE_SHIFT               2
+#define RTC_FREEZE_MASK                        (1 << RTC_FREEZE_SHIFT)
+#define RTC_WUDR_SHIFT_REV             1
+#define RTC_WUDR_MASK_REV              (1 << RTC_WUDR_SHIFT_REV)
+/* RTC HOUR Register */
+#define HOUR_PM_SHIFT                  6
+#define HOUR_PM_MASK                   (1 << HOUR_PM_SHIFT)
+/* RTC Alarm Enable */
+#define ALARM_ENABLE_SHIFT             7
+#define ALARM_ENABLE_MASK              (1 << ALARM_ENABLE_SHIFT)
+/* PMIC STATUS2 Register */
+#define RTCA0E                         (1<<2)
+#define RTCA1E                         (1<<1)
+
+#define WTSR_TIMER_BITS(v)             (((v) << WTSRT_SHIFT) & WTSRT_MASK)
+#define SMPL_TIMER_BITS(v)             (((v) << SMPLT_SHIFT) & SMPLT_MASK)
+
+/* RTC Optimize */
+#define OSC_BIAS_UP_SHIFT              2
+#define OSC_BIAS_UP_MASK               (1 << OSC_BIAS_UP_SHIFT)
+#define CAP_SEL_SHIFT                  0
+#define CAP_SEL_MASK                   (0x03 << CAP_SEL_SHIFT)
+#define OSC_XIN_SHIFT                  5
+#define OSC_XIN_MASK                   (0x07 << OSC_XIN_SHIFT)
+#define OSC_XOUT_SHIFT                 2
+#define OSC_XOUT_MASK                  (0x07 << OSC_XOUT_SHIFT)
+
+/* RTC Counter Register offsets */
+#ifdef CONFIG_RTC_DRV_S2MP
+enum {
+       RTC_SEC = 0,
+       RTC_MIN,
+       RTC_HOUR,
+       RTC_WEEKDAY,
+       RTC_DATE,
+       RTC_MONTH,
+       RTC_YEAR,
+       NR_RTC_CNT_REGS,
+};
+#else
+enum {
+/*     RTC_MSEC = 0, */
+       RTC_SEC = 0,
+       RTC_MIN,
+       RTC_HOUR,
+       RTC_WEEKDAY,
+       RTC_DATE,
+       RTC_MONTH,
+       RTC_YEAR,
+       NR_RTC_CNT_REGS,
+};
+
+#endif
+#endif /*  __LINUX_MFD_SEC_RTC_H */