[prev in list] [next in list] [prev in thread] [next in thread]
List: lm-sensors
Subject: [lm-sensors] [RFC PATCH 3/8] rtc: Add RTC driver for DA906x PMIC.
From: Krystian Garbaciak <krystian.garbaciak () diasemi ! com>
Date: 2012-08-24 14:00:00
Message-ID: 201208241500 () sw-eng-lt-dc-vm2
[Download RAW message or body]
DA906x RTC driver supports date/time and alarm.
In hardware, PMIC supports alarm setting with a resolution of one minute and
tick event (every second update event). The driver combines it, providing alarm
with one second resolution.
The driver requires MFD core driver for operation.
Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
drivers/rtc/Kconfig | 7 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-da906x.c | 379 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 387 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-da906x.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index fabc99a..e6037cd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -571,6 +571,13 @@ config RTC_DRV_DA9052
Say y here to support the RTC driver for Dialog Semiconductor
DA9052-BC and DA9053-AA/Bx PMICs.
+config RTC_DRV_DA906X
+ tristate "Dialog DA906X RTC"
+ depends on MFD_DA906X
+ help
+ Say y here to support the RTC driver for
+ Dialog Semiconductor DA906x PMIC.
+
config RTC_DRV_EFI
tristate "EFI RTC"
depends on IA64
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 0d5b2b6..d9c1e9f 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o
obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o
obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o
obj-$(CONFIG_RTC_DRV_DA9052) += rtc-da9052.o
+obj-$(CONFIG_RTC_DRV_DA906X) += rtc-da906x.o
obj-$(CONFIG_RTC_DRV_DAVINCI) += rtc-davinci.o
obj-$(CONFIG_RTC_DRV_DM355EVM) += rtc-dm355evm.o
obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o
diff --git a/drivers/rtc/rtc-da906x.c b/drivers/rtc/rtc-da906x.c
new file mode 100644
index 0000000..0b4fecc
--- /dev/null
+++ b/drivers/rtc/rtc-da906x.c
@@ -0,0 +1,379 @@
+/*
+ * Real Time Clock driver for DA906x PMIC family
+ *
+ * Copyright 2012 Dialog Semiconductors Ltd.
+ *
+ * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mfd/da906x/registers.h>
+#include <linux/mfd/da906x/core.h>
+
+#define YEARS_TO_DA906X(year) ((year) - 100)
+#define MONTHS_TO_DA906X(month) ((month) + 1)
+#define YEARS_FROM_DA906X(year) ((year) + 100)
+#define MONTHS_FROM_DA906X(month) ((month) - 1)
+
+#define CLOCK_DATA_LEN (DA906X_REG_COUNT_Y - DA906X_REG_COUNT_S + 1)
+#define ALARM_DATA_LEN (DA906X_REG_ALARM_Y - DA906X_REG_ALARM_MI + 1)
+enum {
+ DATA_SEC = 0,
+ DATA_MIN,
+ DATA_HOUR,
+ DATA_DAY,
+ DATA_MONTH,
+ DATA_YEAR,
+};
+
+struct da906x_rtc {
+ struct rtc_device *rtc_dev;
+ struct da906x *hw;
+ int irq_alarm;
+ int irq_tick;
+
+ /* Config flag */
+ int tick_wake;
+
+ /* Used to expand alarm precision from minutes up to seconds
+ using hardware ticks */
+ unsigned int alarmSecs;
+ unsigned int alarmTicks;
+};
+
+static void da906x_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ tm->tm_sec = data[DATA_SEC] & DA906X_COUNT_SEC_MASK;
+ tm->tm_min = data[DATA_MIN] & DA906X_COUNT_MIN_MASK;
+ tm->tm_hour = data[DATA_HOUR] & DA906X_COUNT_HOUR_MASK;
+ tm->tm_mday = data[DATA_DAY] & DA906X_COUNT_DAY_MASK;
+ tm->tm_mon = MONTHS_FROM_DA906X(data[DATA_MONTH] &
+ DA906X_COUNT_MONTH_MASK);
+ tm->tm_year = YEARS_FROM_DA906X(data[DATA_YEAR] &
+ DA906X_COUNT_YEAR_MASK);
+}
+
+static void da906x_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[DATA_SEC] &= ~DA906X_COUNT_SEC_MASK;
+ data[DATA_SEC] |= tm->tm_sec & DA906X_COUNT_SEC_MASK;
+ data[DATA_MIN] &= ~DA906X_COUNT_MIN_MASK;
+ data[DATA_MIN] |= tm->tm_min & DA906X_COUNT_MIN_MASK;
+ data[DATA_HOUR] &= ~DA906X_COUNT_HOUR_MASK;
+ data[DATA_HOUR] |= tm->tm_hour & DA906X_COUNT_HOUR_MASK;
+ data[DATA_DAY] &= ~DA906X_COUNT_DAY_MASK;
+ data[DATA_DAY] |= tm->tm_mday & DA906X_COUNT_DAY_MASK;
+ data[DATA_MONTH] &= ~DA906X_COUNT_MONTH_MASK;
+ data[DATA_MONTH] |= MONTHS_TO_DA906X(tm->tm_mon) &
+ DA906X_COUNT_MONTH_MASK;
+ data[DATA_YEAR] &= ~DA906X_COUNT_YEAR_MASK;
+ data[DATA_YEAR] |= YEARS_TO_DA906X(tm->tm_year) &
+ DA906X_COUNT_YEAR_MASK;
+}
+
+#define DA906X_ALARM_DELAY INT_MAX
+static int da906x_rtc_test_delay(struct rtc_time *alarm, struct rtc_time *cur)
+{
+ unsigned long a_time, c_time;
+
+ rtc_tm_to_time(alarm, &a_time);
+ rtc_tm_to_time(cur, &c_time);
+
+ /* Alarm time has already passed */
+ if (a_time < c_time)
+ return -1;
+
+ /* If alarm is set for current minute, return ticks to count down.
+ If alarm is set for following minutes, return DA906X_ALARM_DELAY
+ to set alarm first.
+ But when it is less than 2 seconds for the former to become true,
+ return ticks, because alarm needs some time to synchronise. */
+ if (a_time - c_time < alarm->tm_sec + 2)
+ return a_time - c_time;
+ else
+ return DA906X_ALARM_DELAY;
+}
+
+static int da906x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct da906x_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN];
+ int ret;
+
+ ret = da906x_block_read(rtc->hw,
+ DA906X_REG_COUNT_S, CLOCK_DATA_LEN, data);
+ if (ret < 0)
+ return ret;
+
+ /* Check, if RTC logic is initialised */
+ if (!(data[DATA_SEC] & DA906X_RTC_READ))
+ return -EBUSY;
+
+ da906x_data_to_tm(data, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int da906x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct da906x_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ int ret;
+
+ da906x_tm_to_data(tm, data);
+
+ ret = da906x_block_write(rtc->hw,
+ DA906X_REG_COUNT_S, CLOCK_DATA_LEN, data);
+
+ return ret;
+}
+
+static int da906x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct da906x_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN];
+ int ret;
+
+ ret = da906x_block_read(rtc->hw, DA906X_REG_ALARM_MI, ALARM_DATA_LEN,
+ &data[DATA_MIN]);
+ if (ret < 0)
+ return ret;
+
+ da906x_data_to_tm(data, &alrm->time);
+ alrm->time.tm_sec = rtc->alarmSecs;
+ alrm->enabled = !!(data[DATA_YEAR] & DA906X_ALARM_ON);
+
+ /* If there is no ticks left to count down and RTC event is
+ not processed yet, indicate pending */
+ if (rtc->alarmTicks == 0) {
+ ret = da906x_reg_read(rtc->hw, DA906X_REG_EVENT_A);
+ if (ret < 0)
+ return ret;
+ if (ret & (DA906X_E_ALARM | DA906X_E_TICK))
+ alrm->pending = 1;
+ } else {
+ alrm->pending = 0;
+ }
+
+ return 0;
+}
+
+static int da906x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct da906x_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ struct rtc_time cur_tm;
+ int cmp_val;
+ int ret;
+
+ data[DATA_MIN] = DA906X_ALARM_STATUS_ALARM;
+ data[DATA_MONTH] = DA906X_TICK_TYPE_SEC;
+ if (rtc->tick_wake)
+ data[DATA_MONTH] |= DA906X_TICK_WAKE;
+
+ ret = da906x_rtc_read_time(dev, &cur_tm);
+ if (ret < 0)
+ return ret;
+
+ if (alrm->enabled) {
+ cmp_val = da906x_rtc_test_delay(&alrm->time, &cur_tm);
+ if (cmp_val == DA906X_ALARM_DELAY) {
+ /* Set alarm for longer delay */
+ data[DATA_YEAR] |= DA906X_ALARM_ON;
+ } else if (cmp_val > 0) {
+ /* Count ticks for shorter delay */
+ rtc->alarmTicks = cmp_val - 1;
+ data[DATA_YEAR] |= DA906X_TICK_ON;
+ } else if (cmp_val == 0) {
+ /* Just about time - report event */
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+ }
+ }
+
+ da906x_tm_to_data(&alrm->time, data);
+ rtc->alarmSecs = alrm->time.tm_sec;
+
+ return da906x_block_write(rtc->hw, DA906X_REG_ALARM_MI, ALARM_DATA_LEN,
+ &data[DATA_MIN]);
+}
+
+static int da906x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct da906x_rtc *rtc = dev_get_drvdata(dev);
+ struct rtc_wkalrm alrm;
+ int ret;
+
+ ret = da906x_reg_read(rtc->hw, DATA_YEAR);
+ if (ret < 0)
+ return ret;
+
+ if (enabled) {
+ /* Enable alarm, if it is not enabled already */
+ if (!(ret & (DA906X_ALARM_ON | DA906X_TICK_ON))) {
+ ret = da906x_rtc_read_alarm(dev, &alrm);
+ if (ret < 0)
+ return ret;
+
+ alrm.enabled = 1;
+ ret = da906x_rtc_set_alarm(dev, &alrm);
+ }
+ } else {
+ ret = da906x_reg_clear_bits(rtc->hw, DA906X_REG_ALARM_Y,
+ DA906X_ALARM_ON);
+ }
+
+ return ret;
+}
+
+/* On alarm interrupt, start to count ticks to enable seconds precision
+ (if alarm seconds != 0). */
+static irqreturn_t da906x_alarm_event(int irq, void *data)
+{
+ struct da906x_rtc *rtc = data;
+
+ if (rtc->alarmSecs) {
+ rtc->alarmTicks = rtc->alarmSecs - 1;
+ da906x_reg_update(rtc->hw, DA906X_REG_ALARM_Y,
+ DA906X_ALARM_ON | DA906X_TICK_ON,
+ DA906X_TICK_ON);
+ } else {
+ da906x_reg_clear_bits(rtc->hw, DA906X_REG_ALARM_Y,
+ DA906X_ALARM_ON);
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* On tick interrupt, count down seconds left to timeout */
+static irqreturn_t da906x_tick_event(int irq, void *data)
+{
+ struct da906x_rtc *rtc = data;
+
+ if (rtc->alarmTicks-- == 0) {
+ da906x_reg_clear_bits(rtc->hw,
+ DA906X_REG_ALARM_Y, DA906X_TICK_ON);
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_UF);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops da906x_rtc_ops = {
+ .read_time = da906x_rtc_read_time,
+ .set_time = da906x_rtc_set_time,
+ .read_alarm = da906x_rtc_read_alarm,
+ .set_alarm = da906x_rtc_set_alarm,
+ .alarm_irq_enable = da906x_rtc_alarm_irq_enable,
+};
+
+static __devinit int da906x_rtc_probe(struct platform_device *pdev)
+{
+ struct da906x *da906x = dev_get_drvdata(pdev->dev.parent);
+ struct da906x_rtc *rtc;
+ int ret;
+ int alarm_mo;
+
+ /* Enable RTC hardware */
+ ret = da906x_reg_set_bits(da906x, DA906X_REG_CONTROL_E, DA906X_RTC_EN);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to enable RTC.\n");
+ return ret;
+ }
+
+ ret = da906x_reg_set_bits(da906x, DA906X_REG_EN_32K, DA906X_CRYSTAL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to run 32 KHz OSC.\n");
+ return ret;
+ }
+
+ ret = da906x_reg_read(da906x, DA906X_REG_ALARM_MO);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to read RTC register.\n");
+ return ret;
+ }
+ alarm_mo = ret;
+
+ /* Register RTC device */
+ rtc = devm_kzalloc(&pdev->dev, sizeof *rtc, GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, rtc);
+
+ rtc->hw = da906x;
+ rtc->rtc_dev = rtc_device_register(DA906X_DRVNAME_RTC, &pdev->dev,
+ &da906x_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc->rtc_dev)) {
+ dev_err(&pdev->dev, "Failed to register RTC device: %ld\n",
+ PTR_ERR(rtc->rtc_dev));
+ return PTR_ERR(rtc->rtc_dev);
+ }
+
+ if (alarm_mo & DA906X_TICK_WAKE)
+ rtc->tick_wake = 1;
+
+ /* Register interrupts. Complain on errors but let device
+ to be registered at least for date/time. */
+ rtc->irq_alarm = platform_get_irq_byname(pdev, "ALARM");
+ ret = request_threaded_irq(rtc->irq_alarm, NULL, da906x_alarm_event,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT, "ALARM", rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request ALARM IRQ.\n");
+ rtc->irq_alarm = -ENXIO;
+ return 0;
+ }
+
+ rtc->irq_tick = platform_get_irq_byname(pdev, "TICK");
+ ret = request_threaded_irq(rtc->irq_tick, NULL, da906x_tick_event,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "TICK", rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request TICK IRQ.\n");
+ rtc->irq_tick = -ENXIO;
+ }
+
+ return 0;
+}
+
+static int __devexit da906x_rtc_remove(struct platform_device *pdev)
+{
+ struct da906x_rtc *rtc = platform_get_drvdata(pdev);
+
+ if (rtc->irq_alarm >= 0)
+ free_irq(rtc->irq_alarm, rtc);
+
+ if (rtc->irq_tick >= 0)
+ free_irq(rtc->irq_tick, rtc);
+
+ rtc_device_unregister(rtc->rtc_dev);
+ return 0;
+}
+
+static struct platform_driver da906x_rtc_driver = {
+ .probe = da906x_rtc_probe,
+ .remove = __devexit_p(da906x_rtc_remove),
+ .driver = {
+ .name = DA906X_DRVNAME_RTC,
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(da906x_rtc_driver);
+
+/* Module information */
+MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>");
+MODULE_DESCRIPTION("DA906x RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DA906X_DRVNAME_RTC);
--
1.7.0.4
_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic