From e23b037a2c2df177c12c579ab8e265aa82f60908 Mon Sep 17 00:00:00 2001 From: Jieun Yi Date: Fri, 11 May 2018 13:40:09 +0900 Subject: [PATCH] [9610] drivers: rtc: created rtc driver for S2MPU09 Change-Id: Iddbbc11dd7beacd6cac21a922accafe5a23db899 Signed-off-by: Jieun Yi --- drivers/rtc/Kconfig | 9 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-s2mpu09.c | 750 +++++++++++++++++++++++++++ include/linux/mfd/samsung/rtc-s2mp.h | 146 ++++++ 4 files changed, 906 insertions(+) create mode 100644 drivers/rtc/rtc-s2mpu09.c create mode 100644 include/linux/mfd/samsung/rtc-s2mp.h diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index e0e58f3b1420..df9679d7e4b4 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -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" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 0bf1fc02b82c..23951ee3dafe 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -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 index 000000000000..599e0eef9609 --- /dev/null +++ b/drivers/rtc/rtc-s2mpu09.c @@ -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 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*#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 index 000000000000..43a6f648561f --- /dev/null +++ b/include/linux/mfd/samsung/rtc-s2mp.h @@ -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 */ -- 2.20.1