@@ -155,6 +155,16 @@ config RTC_DRV_88PM80X
This driver can also be built as a module. If so, the module
will be called rtc-88pm80x.
+config RTC_DRV_DA9058
+ tristate "Dialog DA9058"
+ depends on MFD_DA9058
+ help
+ If you say yes here you will get support for the
+ RTC of the Dialog DA9058 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-da9058.
+
config RTC_DRV_DS1307
tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025"
help
@@ -32,6 +32,7 @@ obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o
obj-$(CONFIG_RTC_DRV_DA9052) += rtc-da9052.o
obj-$(CONFIG_RTC_DRV_DA9055) += rtc-da9055.o
obj-$(CONFIG_RTC_DRV_DAVINCI) += rtc-davinci.o
+obj-$(CONFIG_RTC_DRV_DA9058) += rtc-da9058.o
obj-$(CONFIG_RTC_DRV_DM355EVM) += rtc-dm355evm.o
obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o
obj-$(CONFIG_RTC_DRV_DS1216) += rtc-ds1216.o
new file mode 100644
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2012, 2013 Dialog Semiconductor Ltd.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/da9058/registers.h>
+#include <linux/mfd/da9058/core.h>
+#include <linux/mfd/da9058/irq.h>
+#include <linux/mfd/da9058/rtc.h>
+
+/*
+ * Limit values
+ */
+#define DA9058_RTC_SECONDS_LIMIT 59
+#define DA9058_RTC_MINUTES_LIMIT 59
+#define DA9058_RTC_HOURS_LIMIT 23
+#define DA9058_RTC_DAYS_LIMIT 31
+#define DA9058_RTC_MONTHS_LIMIT 12
+#define DA9058_RTC_YEARS_LIMIT 63
+
+struct da9058_rtc {
+ struct da9058 *da9058;
+ struct platform_device *pdev;
+ struct rtc_device *rtc_dev;
+ int alarm_irq;
+ int tick_irq;
+ int alarm_enabled; /* used over suspend/resume */
+};
+
+static int da9058_rtc_check_param(struct rtc_time *rtc_tm)
+{
+ if ((rtc_tm->tm_sec > DA9058_RTC_SECONDS_LIMIT) || (rtc_tm->tm_sec < 0))
+ return -EIO;
+
+ if ((rtc_tm->tm_min > DA9058_RTC_MINUTES_LIMIT) || (rtc_tm->tm_min < 0))
+ return -EIO;
+
+ if ((rtc_tm->tm_hour > DA9058_RTC_HOURS_LIMIT) || (rtc_tm->tm_hour < 0))
+ return -EIO;
+
+ if ((rtc_tm->tm_mday > DA9058_RTC_DAYS_LIMIT) || (rtc_tm->tm_mday <= 0))
+ return -EIO;
+
+ if ((rtc_tm->tm_mon > DA9058_RTC_MONTHS_LIMIT) || (rtc_tm->tm_mon <= 0))
+ return -EIO;
+
+ if ((rtc_tm->tm_year > DA9058_RTC_YEARS_LIMIT) || (rtc_tm->tm_year < 0))
+ return -EIO;
+
+ return 0;
+}
+
+static int da9058_rtc_readtime(struct device *dev, struct rtc_time *tm)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+ struct da9058 *da9058 = rtc->da9058;
+ u8 rtc_time[6];
+ int ret;
+
+ ret = da9058_bulk_read(da9058, DA9058_COUNTS_REG, rtc_time, 6);
+ if (ret)
+ return ret;
+
+ tm->tm_sec = rtc_time[0] & DA9058_RTC_SECS_MASK;
+
+ tm->tm_min = rtc_time[1] & DA9058_RTC_MINS_MASK;
+
+ tm->tm_hour = rtc_time[2] & DA9058_RTC_HRS_MASK;
+
+ tm->tm_mday = (rtc_time[3] & DA9058_RTC_DAY_MASK);
+
+ tm->tm_mon = (rtc_time[4] & DA9058_RTC_MTH_MASK);
+
+ tm->tm_year = (rtc_time[5] & DA9058_RTC_YRS_MASK);
+
+ ret = da9058_rtc_check_param(tm);
+
+ if (ret)
+ return ret;
+
+ tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon,
+ tm->tm_year);
+ tm->tm_year += 100;
+ tm->tm_mon -= 1;
+
+ return 0;
+}
+
+static int da9058_rtc_settime(struct device *dev, struct rtc_time *tm)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+ struct da9058 *da9058 = rtc->da9058;
+ unsigned int rtc_ctrl, val;
+ u8 rtc_time[6];
+ int ret;
+
+ tm->tm_year -= 100;
+ tm->tm_mon += 1;
+
+ ret = da9058_rtc_check_param(tm);
+ if (ret < 0)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_COUNTS_REG, &rtc_ctrl);
+ if (ret)
+ return ret;
+ rtc_ctrl &= ~DA9058_RTC_SECS_MASK;
+
+ rtc_time[0] = rtc_ctrl | tm->tm_sec;
+ rtc_time[1] = tm->tm_min;
+ rtc_time[2] = tm->tm_hour;
+ rtc_time[3] = tm->tm_mday;
+ rtc_time[4] = tm->tm_mon;
+ rtc_time[5] = tm->tm_year;
+
+ ret = da9058_bulk_write(da9058, DA9058_COUNTS_REG, rtc_time, 6);
+ if (ret) {
+ dev_dbg(dev, "failed %d to write to RTC\n", ret);
+ return ret;
+ }
+ ret = da9058_reg_read(da9058, DA9058_COUNTY_REG, &val);
+ if (ret)
+ return ret;
+
+ val &= DA9058_COUNTY_MONITOR;
+ if (val)
+ return 0;
+
+ ret = da9058_set_bits(da9058, DA9058_COUNTY_REG, DA9058_COUNTY_MONITOR);
+
+ return ret;
+}
+
+static int da9058_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+ struct da9058 *da9058 = rtc->da9058;
+ struct rtc_time *tm = &alrm->time;
+ unsigned int val;
+ u8 alm_time[6];
+ int ret;
+
+ ret = da9058_bulk_read(da9058, DA9058_ALARMS_REG, alm_time, 6);
+ if (ret)
+ return ret;
+
+ tm->tm_min = alm_time[0] & DA9058_RTC_ALMSECS_MASK;
+
+ tm->tm_min = alm_time[1] & DA9058_RTC_ALMMINS_MASK;
+
+ tm->tm_hour = alm_time[2] & DA9058_RTC_ALMHRS_MASK;
+
+ tm->tm_mday = alm_time[3] & DA9058_RTC_ALMDAY_MASK;
+
+ tm->tm_mon = alm_time[4] & DA9058_RTC_ALMMTH_MASK;
+
+ tm->tm_year = alm_time[5] & DA9058_RTC_ALMYRS_MASK;
+
+ ret = da9058_rtc_check_param(tm);
+ if (ret < 0)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &val);
+ if (ret)
+ return ret;
+
+ alrm->enabled = val & DA9058_ALARMY_ALARMON;
+
+ tm->tm_year += 100;
+ tm->tm_mon -= 1;
+
+ return 0;
+}
+
+static int da9058_rtc_stop_alarm(struct da9058_rtc *rtc)
+{
+ return da9058_clear_bits(rtc->da9058, DA9058_ALARMY_REG,
+ DA9058_ALARMY_ALARMON);
+}
+
+static int da9058_rtc_start_alarm(struct da9058_rtc *rtc)
+{
+ return da9058_set_bits(rtc->da9058, DA9058_ALARMY_REG,
+ DA9058_ALARMY_ALARMON);
+}
+
+static int da9058_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+ struct da9058 *da9058 = rtc->da9058;
+ struct rtc_time *tm = &alrm->time;
+ unsigned int rtc_ctrl;
+ u8 alm_time[6];
+ int ret;
+
+ tm->tm_year -= 100;
+ tm->tm_mon += 1;
+
+ ret = da9058_rtc_check_param(tm);
+ if (ret < 0)
+ return ret;
+
+ memset(alm_time, 0, sizeof(alm_time));
+
+ if (tm->tm_sec != -1)
+ alm_time[0] |= tm->tm_sec;
+ else
+ alm_time[0] |= DA9058_RTC_ALMSECS_MASK;
+
+ ret = da9058_reg_read(da9058, DA9058_ALARMMI_REG, &rtc_ctrl);
+ if (ret)
+ return ret;
+ rtc_ctrl &= ~DA9058_RTC_ALMMINS_MASK;
+
+ if (tm->tm_min != -1)
+ alm_time[1] = rtc_ctrl | tm->tm_min;
+ else
+ alm_time[1] = rtc_ctrl | DA9058_RTC_ALMMINS_MASK;
+
+ if (tm->tm_hour != -1)
+ alm_time[2] |= tm->tm_hour;
+ else
+ alm_time[2] |= DA9058_RTC_ALMHRS_MASK;
+
+ if (tm->tm_mday != -1)
+ alm_time[3] |= tm->tm_mday;
+ else
+ alm_time[3] |= DA9058_RTC_ALMDAY_MASK;
+
+ if (tm->tm_mon != -1)
+ alm_time[4] |= tm->tm_mon;
+ else
+ alm_time[4] |= DA9058_RTC_ALMMTH_MASK;
+
+ ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &rtc_ctrl);
+ if (ret)
+ return ret;
+
+ rtc_ctrl &= ~DA9058_RTC_ALMYRS_MASK;
+
+ if (tm->tm_year != -1)
+ alm_time[5] = rtc_ctrl | tm->tm_year;
+ else
+ alm_time[5] = rtc_ctrl | DA9058_RTC_ALMYRS_MASK;
+
+ ret = da9058_rtc_stop_alarm(rtc);
+ if (ret < 0)
+ return ret;
+
+ ret = da9058_bulk_write(da9058, DA9058_ALARMS_REG, alm_time, 6);
+ if (ret)
+ return ret;
+
+ if (alrm->enabled)
+ ret = da9058_rtc_start_alarm(rtc);
+
+ return ret;
+}
+
+static int da9058_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+
+ if (enabled)
+ return da9058_rtc_start_alarm(rtc);
+ else
+ return da9058_rtc_stop_alarm(rtc);
+}
+
+static irqreturn_t da9058_rtc_timer_alarm_handler(int irq, void *data)
+{
+ struct da9058_rtc *rtc = data;
+
+ da9058_rtc_stop_alarm(rtc);
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t da9058_rtc_tick_alarm_handler(int irq, void *data)
+{
+ struct da9058_rtc *rtc = data;
+
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_PF | RTC_IRQF);
+
+ return IRQ_HANDLED;
+}
+static int da9058_rtc_proc(struct device *dev, struct seq_file *seq)
+{
+ struct da9058_rtc *rtc = dev_get_drvdata(dev);
+ struct da9058 *da9058 = rtc->da9058;
+ unsigned int rtc_ctrl;
+ int ret;
+
+ ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &rtc_ctrl);
+
+ seq_puts(seq, rtc_ctrl & DA9058_ALARMY_ALARMON ?
+ "ALRM is running\n" : "ALRM is not running\n");
+
+ return 0;
+}
+
+static const struct rtc_class_ops da9058_rtc_ops = {
+ .read_time = da9058_rtc_readtime,
+ .set_time = da9058_rtc_settime,
+ .read_alarm = da9058_rtc_readalarm,
+ .set_alarm = da9058_rtc_setalarm,
+ .proc = da9058_rtc_proc,
+ .alarm_irq_enable = da9058_rtc_alarm_irq_enable,
+};
+
+static int da9058_rtc_probe(struct platform_device *pdev)
+{
+ struct da9058 *da9058 = dev_get_drvdata(pdev->dev.parent);
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct da9058_rtc_pdata *rtc_pdata;
+ struct da9058_rtc *rtc;
+ int ret;
+
+ if (cell == NULL) {
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ rtc_pdata = cell->platform_data;
+
+ if (rtc_pdata == NULL) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ rtc = devm_kzalloc(&pdev->dev, sizeof(struct da9058_rtc), GFP_KERNEL);
+ if (!rtc) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, rtc);
+
+ rtc->da9058 = da9058;
+ rtc->pdev = pdev;
+ ret = da9058_clear_bits(da9058, DA9058_WAITCONT_REG,
+ DA9058_WAITCONT_RTCCLOCK);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to set RTC running: %d\n", ret);
+ goto err2;
+ }
+
+ ret = da9058_set_bits(da9058, DA9058_COUNTY_REG, DA9058_COUNTY_MONITOR);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed No gating RTC: %d\n", ret);
+ goto err2;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ rtc->rtc_dev = rtc_device_register("da9058", &pdev->dev,
+ &da9058_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc->rtc_dev)) {
+ ret = PTR_ERR(rtc->rtc_dev);
+ dev_err(&pdev->dev, "failed to register RTC: %d\n", ret);
+ goto err2;
+ }
+
+ rtc->alarm_irq = platform_get_irq(pdev, 0);
+ if (rtc->alarm_irq < 0) {
+ dev_err(&pdev->dev, "cannot get RTC ALARM IRQ error=%d\n",
+ rtc->alarm_irq);
+ ret = -ENODEV;
+ goto err1;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev,
+ da9058_to_virt_irq_num(da9058, rtc->alarm_irq),
+ NULL, da9058_rtc_timer_alarm_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "DA9058 RTC Timer Alarm", rtc);
+
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to get rtc timer alarm IRQ %d: %d\n",
+ rtc->alarm_irq, ret);
+ goto err1;
+ }
+
+ rtc->tick_irq = platform_get_irq(pdev, 1);
+ if (rtc->tick_irq < 0) {
+ dev_err(&pdev->dev, "cannot get RTC TICK IRQ error=%d\n",
+ rtc->tick_irq);
+ ret = -ENODEV;
+ goto err1;
+ }
+ ret = devm_request_threaded_irq(&pdev->dev,
+ da9058_to_virt_irq_num(da9058, rtc->tick_irq),
+ NULL, da9058_rtc_tick_alarm_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "DA9058 RTC Tick Alarm", rtc);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to get rtc timer alarm IRQ %d: %d\n",
+ DA9058_IRQ_ETICK, ret);
+ goto err1;
+ }
+ goto exit;
+
+err1:
+ rtc_device_unregister(rtc->rtc_dev);
+err2:
+ platform_set_drvdata(pdev, NULL);
+exit:
+ return ret;
+}
+
+static int da9058_rtc_remove(struct platform_device *pdev)
+{
+ struct da9058_rtc *rtc = platform_get_drvdata(pdev);
+
+ rtc_device_unregister(rtc->rtc_dev);
+
+ return 0;
+}
+
+static struct platform_driver da9058_rtc_driver = {
+ .probe = da9058_rtc_probe,
+ .remove = da9058_rtc_remove,
+ .driver = {
+ .name = "da9058-rtc",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(da9058_rtc_driver);
+
+MODULE_DESCRIPTION("Dialog DA9058 PMIC Real Time Clock Driver");
+MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:da9058-rtc");