From 58bc6e72754adb33ec504ff1ec51742cf03fee46 Mon Sep 17 00:00:00 2001 From: Syed Rafiuddin Date: Tue, 18 Dec 2012 20:38:31 +0530 Subject: rtc: add ams as3720 rtc driver addition of AMS as3720 rtc driver Change-Id: Ie8737a1d29d02b8e129d888f77210dd3da96c6dc Signed-off-by: Victor Weinstein Signed-off-by: Syed Rafiuddin Signed-off-by: Bibek Basu Reviewed-on: http://git-master/r/146197 Reviewed-by: Mrutyunjay Sawant Tested-by: Mrutyunjay Sawant --- drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-as3720.c | 324 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 drivers/rtc/rtc-as3720.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2bc82b2cbef8..27396cefbcc0 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -498,6 +498,16 @@ config RTC_DRV_RV3029C2 This driver can also be built as a module. If so, the module will be called rtc-rv3029c2. +config RTC_DRV_AS3720 + tristate "ams AS3720" + depends on MFD_AS3720 + help + If you say Y here you will get support for the RTC feature + of the AS3720 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-as3720. + endif # I2C comment "SPI RTC drivers" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 8fc396239bd2..613ade4b278f 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o obj-$(CONFIG_RTC_DRV_88PM80X) += rtc-88pm80x.o obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o +obj-$(CONFIG_RTC_DRV_AS3720) += rtc-as3720.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o diff --git a/drivers/rtc/rtc-as3720.c b/drivers/rtc/rtc-as3720.c new file mode 100644 index 000000000000..293c249e3217 --- /dev/null +++ b/drivers/rtc/rtc-as3720.c @@ -0,0 +1,324 @@ +/* + * Real Time Clock driver for ams AS3720 PMICs + * + * Copyright (C) 2012 ams AG. + * + * Author: Bernhard Breinbauer + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AS3720_SET_ALM_RETRIES 5 +#define AS3720_SET_TIME_RETRIES 5 +#define AS3720_GET_TIME_RETRIES 5 + +/* + * Read current time and date in RTC + */ +static int as3720_rtc_readtime(struct device *dev, struct rtc_time *tm) +{ + struct as3720 *as3720 = dev_get_drvdata(dev->parent); + struct as3720_platform_data *pdata = as3720->dev->platform_data; + u8 as_sec; + u8 as_min_array[3]; + int as_min; + long time, start_time; + struct rtc_time start_tm; + int ret; + + as3720_reg_read(as3720, AS3720_RTC_SECOND_REG, &as_sec); + ret = as3720_block_read(as3720, AS3720_RTC_MINUTE1_REG, + 3, as_min_array); + if (ret < 0) { + dev_err(dev, "failed to read time with err:%d\n", ret); + return ret; + } + as_min = (as_min_array[2] << 16) + | (as_min_array[1] << 8) + | (as_min_array[0]); + time = as_min*60 + as_sec; + start_tm.tm_year = (pdata->rtc_start_year - 1900); + start_tm.tm_mon = 0; + start_tm.tm_mday = 1; + start_tm.tm_hour = 0; + start_tm.tm_min = 0; + start_tm.tm_sec = 0; + rtc_tm_to_time(&start_tm, &start_time); + time = time + start_time; + rtc_time_to_tm(time, tm); + return 0; +} + +/* + * Set current time and date in RTC + */ +static int as3720_rtc_settime(struct device *dev, struct rtc_time *tm) +{ + struct as3720 *as3720 = dev_get_drvdata(dev->parent); + struct as3720_platform_data *pdata = as3720->dev->platform_data; + long time, start_time; + u8 as_sec; + u8 as_min_array[3]; + int as_min; + struct rtc_time start_tm; + int ret; + + /* Write time to RTC */ + rtc_tm_to_time(tm, &time); + start_tm.tm_year = (pdata->rtc_start_year - 1900); + start_tm.tm_mon = 0; + start_tm.tm_mday = 1; + start_tm.tm_hour = 0; + start_tm.tm_min = 0; + start_tm.tm_sec = 0; + rtc_tm_to_time(&start_tm, &start_time); + time = time - start_time; + as_min = time / 60; + as_sec = time % 60; + as_min_array[2] = (as_min & 0xFF0000) >> 16; + as_min_array[1] = (as_min & 0xFF00) >> 8; + as_min_array[0] = as_min & 0xFF; + as3720_reg_write(as3720, AS3720_RTC_SECOND_REG, as_sec); + ret = as3720_block_write(as3720, AS3720_RTC_MINUTE1_REG, 3, + as_min_array); + if (ret < 0) { + dev_err(dev, "failed to set time with err:%d\n", ret); + return ret; + } + return 0; +} + +/* + * Read alarm time and date in RTC + */ +static int as3720_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct as3720 *as3720 = dev_get_drvdata(dev->parent); + struct as3720_platform_data *pdata = as3720->dev->platform_data; + u8 as_sec; + u8 as_min_array[3]; + int as_min; + long time, start_time; + struct rtc_time start_tm; + int ret; + + as3720_reg_read(as3720, AS3720_RTC_ALARM_SECOND_REG, &as_sec); + ret = as3720_block_read(as3720, AS3720_RTC_ALARM_MINUTE1_REG, + 3, as_min_array); + if (ret < 0) { + dev_err(dev, "failed to read alarm with err:%d\n", ret); + return ret; + } + as_min = (as_min_array[2] << 16) + | (as_min_array[1] << 8) + | (as_min_array[0]); + time = as_min*60 + as_sec; + start_tm.tm_year = (pdata->rtc_start_year - 1900); + start_tm.tm_mon = 0; + start_tm.tm_mday = 1; + start_tm.tm_hour = 0; + start_tm.tm_min = 0; + start_tm.tm_sec = 0; + rtc_tm_to_time(&start_tm, &start_time); + time = time + start_time; + rtc_time_to_tm(time, &alrm->time); + return 0; +} + +static int as3720_rtc_stop_alarm(struct as3720 *as3720) +{ + /* disable rtc alarm interrupt */ + return as3720_set_bits(as3720, AS3720_INTERRUPTMASK2_REG, + AS3720_IRQ_RTC_ALARM, 1); +} + +static int as3720_rtc_start_alarm(struct as3720 *as3720) +{ + /* enable rtc alarm interrupt */ + return as3720_set_bits(as3720, AS3720_INTERRUPTMASK2_REG, + AS3720_IRQ_RTC_ALARM, 0); +} + +static int as3720_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct as3720 *as3720 = dev_get_drvdata(dev->parent); + + if (enabled) + return as3720_rtc_start_alarm(as3720); + else + return as3720_rtc_stop_alarm(as3720); +} + +static int as3720_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct as3720 *as3720 = dev_get_drvdata(dev->parent); + struct as3720_platform_data *pdata = as3720->dev->platform_data; + long time, start_time; + u8 as_sec; + u8 as_min_array[3]; + int as_min; + struct rtc_time start_tm; + int ret; + + /* Write time to RTC */ + rtc_tm_to_time(&alrm->time, &time); + start_tm.tm_year = (pdata->rtc_start_year - 1900); + start_tm.tm_mon = 0; + start_tm.tm_mday = 1; + start_tm.tm_hour = 0; + start_tm.tm_min = 0; + start_tm.tm_sec = 0; + rtc_tm_to_time(&start_tm, &start_time); + time = time - start_time; + as_min = time / 60; + as_sec = time % 60; + as_min_array[2] = (as_min & 0xFF0000) >> 16; + as_min_array[1] = (as_min & 0xFF00) >> 8; + as_min_array[0] = as_min & 0xFF; + + /* Write time to RTC */ + as3720_reg_write(as3720, AS3720_RTC_ALARM_SECOND_REG, as_sec); + ret = as3720_block_write(as3720, AS3720_RTC_ALARM_MINUTE1_REG, 3, + as_min_array); + if (ret < 0) { + dev_err(dev, "failed to set alarm with err:%d\n", ret); + return ret; + } + return 0; +} + +static const struct rtc_class_ops as3720_rtc_ops = { + .read_time = as3720_rtc_readtime, + .set_time = as3720_rtc_settime, + .read_alarm = as3720_rtc_readalarm, + .set_alarm = as3720_rtc_setalarm, + .alarm_irq_enable = as3720_rtc_alarm_irq_enable, +}; + +#ifdef CONFIG_PM_SLEEP +static int as3720_rtc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct as3720 *as3720 = dev_get_drvdata(pdev->dev.parent); + int ret = 0; + u32 reg; + + as3720_reg_read(as3720, AS3720_INTERRUPTMASK3_REG, ®); + + if (device_may_wakeup(dev) && + reg & AS3720_IRQ_MASK_RTC_ALARM) { + ret = as3720_rtc_stop_alarm(as3720); + if (ret != 0) + dev_err(dev, "Failed to stop RTC alarm: %d\n", + ret); + } + + return ret; +} + +static int as3720_rtc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct as3720 *as3720 = dev_get_drvdata(pdev->dev.parent); + int ret; + + if (as3720->rtc.alarm_enabled) { + ret = as3720_rtc_start_alarm(as3720); + if (ret != 0) + dev_err(dev, + "Failed to restart RTC alarm: %d\n", ret); + } + + return 0; +} + +#define DEV_PM_OPS (&as3720_rtc_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +static int as3720_rtc_probe(struct platform_device *pdev) +{ + struct as3720 *as3720 = dev_get_drvdata(pdev->dev.parent); + struct as3720_platform_data *pdata = dev_get_platdata(pdev->dev.parent); + struct as3720_rtc *rtc = &as3720->rtc; + int ret = 0; + u8 ctrl; + + /* enable the RTC if it's not already enabled */ + as3720_reg_read(as3720, AS3720_RTC_CONTROL_REG, &ctrl); + if (!(ctrl & AS3720_RTC_ON_MASK)) { + dev_info(&pdev->dev, "Starting RTC\n"); + + ret = as3720_set_bits(as3720, AS3720_RTC_CONTROL_REG, + AS3720_RTC_ON_MASK, AS3720_RTC_ON_MASK); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable RTC: %d\n", ret); + return ret; + } + } + /* enable alarm wakeup */ + as3720_set_bits(as3720, AS3720_RTC_CONTROL_REG, + AS3720_RTC_ALARM_WAKEUP_EN_MASK, + AS3720_RTC_ALARM_WAKEUP_EN_MASK); + + device_init_wakeup(&pdev->dev, 1); + + rtc->rtc = rtc_device_register("as3720", &pdev->dev, + &as3720_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rtc)) { + ret = PTR_ERR(rtc->rtc); + dev_err(&pdev->dev, "failed to register RTC: %d\n", ret); + return ret; + } + + return 0; +} + +static int __devexit as3720_rtc_remove(struct platform_device *pdev) +{ + struct as3720 *as3720 = platform_get_drvdata(pdev); + struct as3720_rtc *rtc = &as3720->rtc; + + rtc_device_unregister(rtc->rtc); + + return 0; +} + +static const struct dev_pm_ops as3720_rtc_pm_ops = { + .suspend = as3720_rtc_suspend, + .resume = as3720_rtc_resume, +}; + +static struct platform_driver as3720_rtc_driver = { + .probe = as3720_rtc_probe, + .remove = __devexit_p(as3720_rtc_remove), + .driver = { + .name = "as3720-rtc", + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(as3720_rtc_driver); + +MODULE_AUTHOR("Bernhard Breinbauer "); +MODULE_DESCRIPTION("RTC driver for AS3720 PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:as3720-rtc"); -- cgit v1.2.3