diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 89 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 12 | ||||
-rw-r--r-- | drivers/rtc/rtc-as3720.c | 324 | ||||
-rw-r--r-- | drivers/rtc/rtc-as3722.c | 387 | ||||
-rw-r--r-- | drivers/rtc/rtc-max77660.c | 445 | ||||
-rw-r--r-- | drivers/rtc/rtc-max77663.c | 643 | ||||
-rw-r--r-- | drivers/rtc/rtc-max8907c.c | 323 | ||||
-rw-r--r-- | drivers/rtc/rtc-palmas.c | 119 | ||||
-rw-r--r-- | drivers/rtc/rtc-ricoh583.c | 404 | ||||
-rw-r--r-- | drivers/rtc/rtc-tegra.c | 3 | ||||
-rw-r--r-- | drivers/rtc/rtc-tps6591x.c | 567 |
11 files changed, 3293 insertions, 23 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b9838130a7b0..64f33eabf909 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -133,6 +133,16 @@ comment "I2C RTC drivers" if I2C +config RTC_DRV_AS3722 + tristate "ams AS3722" + depends on MFD_AS3722 + help + If you say Y here you will get support for the RTC feature + of the ams AS3722 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-as3722. + config RTC_DRV_88PM860X tristate "Marvell 88PM860x" depends on I2C && MFD_88PM860X @@ -267,6 +277,26 @@ config RTC_DRV_MAX77686 This driver can also be built as a module. If so, the module will be called rtc-max77686. +config RTC_DRV_MAX8907C + tristate "Maxim MAX8907C" + depends on MFD_MAX8907C + help + If you say yes here you will get support for the + RTC of Maxim MAX8907C PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max8907c. + +config RTC_DRV_MAX77663 + tristate "Maxim MAX77663" + depends on MFD_MAX77663 + help + If you say yes here you will get support for the + RTC of Maxim MAX77663 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max77663. + config RTC_DRV_RS5C372 tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A" help @@ -303,16 +333,6 @@ config RTC_DRV_X1205 This driver can also be built as a module. If so, the module will be called rtc-x1205. -config RTC_DRV_PALMAS - tristate "TI Palmas RTC driver" - depends on MFD_PALMAS - help - If you say yes here you get support for the RTC of TI PALMA series PMIC - chips. - - This driver can also be built as a module. If so, the module - will be called rtc-palma. - config RTC_DRV_PCF8523 tristate "NXP PCF8523" help @@ -488,6 +508,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" @@ -1180,8 +1210,7 @@ config RTC_DRV_TEGRA tristate "NVIDIA Tegra Internal RTC driver" depends on ARCH_TEGRA help - If you say yes here you get support for the - Tegra 200 series internal RTC module. + If you say yes here you get support for the Tegra internal RTC module. This drive can also be built as a module. If so, the module will be called rtc-tegra. @@ -1193,6 +1222,23 @@ config RTC_DRV_TILE Enable support for the Linux driver side of the Tilera hypervisor's real-time clock interface. +config RTC_DRV_TPS6591x + tristate "TPS6591x RTC driver" + depends on MFD_TPS6591X + default n + help + If you say yes here you get support for the TPS6591x RTC module. + + This driver can also be built as a module. If so, the module + will be called rtc-tps6591x. + +config RTC_DRV_PALMAS + tristate "PALMAS RTC driver" + depends on MFD_PALMAS + default n + help + If you say yes here you get support for the PALMAS RTC module. + config RTC_DRV_PUV3 tristate "PKUnity v3 RTC support" depends on ARCH_PUV3 @@ -1212,6 +1258,25 @@ config RTC_DRV_LOONGSON1 This driver can also be built as a module. If so, the module will be called rtc-ls1x. +config RTC_DRV_RC5T583 + tristate "RICOH RC5T583 PMU RTC driver" + depends on MFD_RICOH583 + default n + help + If you say yes here you get support for the RICOH RC5T583 RTC module. + + This driver can also be built as a module. If so, the module + will be called rtc-rc5t583. +config RTC_DRV_MAX77660 + tristate "Maxim MAX77660" + depends on MFD_MAX77660 + help + If you say yes here you will get support for the + RTC of Maxim MAX77660 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max77660. + config RTC_DRV_MXC tristate "Freescale MXC Real Time Clock" depends on ARCH_MXC diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index c33f86f1a69b..8a01aa569d20 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -2,6 +2,8 @@ # Makefile for RTC class/drivers. # +GCOV_PROFILE := y + ccflags-$(CONFIG_RTC_DEBUG) := -DDEBUG obj-$(CONFIG_RTC_LIB) += rtc-lib.o @@ -20,6 +22,8 @@ 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_AS3722) += rtc-as3722.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 @@ -73,15 +77,16 @@ obj-$(CONFIG_RTC_DRV_MAX8907) += rtc-max8907.o obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o +obj-$(CONFIG_RTC_DRV_MAX8907C) += rtc-max8907c.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o obj-$(CONFIG_RTC_DRV_MAX77686) += rtc-max77686.o obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o +obj-$(CONFIG_RTC_DRV_MAX77663) += rtc-max77663.o obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o obj-$(CONFIG_RTC_DRV_NUC900) += rtc-nuc900.o obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o -obj-$(CONFIG_RTC_DRV_PALMAS) += rtc-palmas.o obj-$(CONFIG_RTC_DRV_PCAP) += rtc-pcap.o obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o @@ -114,9 +119,13 @@ obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o obj-$(CONFIG_RTC_DRV_STMP) += rtc-stmp3xxx.o obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o +CFLAGS_rtc-tegra.o = -Werror obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TILE) += rtc-tile.o +obj-$(CONFIG_RTC_DRV_TPS6591x) += rtc-tps6591x.o +obj-$(CONFIG_RTC_DRV_PALMAS) += rtc-palmas.o +#obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-ricoh583.o obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TPS6586X) += rtc-tps6586x.o obj-$(CONFIG_RTC_DRV_TPS65910) += rtc-tps65910.o @@ -128,3 +137,4 @@ obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o +obj-$(CONFIG_RTC_DRV_MAX77660) += rtc-max77660.o diff --git a/drivers/rtc/rtc-as3720.c b/drivers/rtc/rtc-as3720.c new file mode 100644 index 000000000000..da082f211171 --- /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 <Bernhard.Breinbauer@ams.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/module.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/interrupt.h> +#include <linux/ioctl.h> +#include <linux/completion.h> +#include <linux/mfd/as3720.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#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 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 = as3720_rtc_remove, + .driver = { + .name = "as3720-rtc", + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(as3720_rtc_driver); + +MODULE_AUTHOR("Bernhard Breinbauer <bernhard.breinbauer@ams.com>"); +MODULE_DESCRIPTION("RTC driver for AS3720 PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:as3720-rtc"); diff --git a/drivers/rtc/rtc-as3722.c b/drivers/rtc/rtc-as3722.c new file mode 100644 index 000000000000..ab03a68c30d9 --- /dev/null +++ b/drivers/rtc/rtc-as3722.c @@ -0,0 +1,387 @@ +/* + * rtc-as3722.c - Real Time Clock driver for ams AS3722 PMICs + * + * Copyright (C) 2013 ams AG + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. + * + * Author: Florian Lobmaier <florian.lobmaier@ams.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/interrupt.h> +#include <linux/ioctl.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/mfd/as3722-reg.h> +#include <linux/mfd/as3722-plat.h> + +/* RTC defines: + * start year has to be a century that rtc works + * correctly with leap years, etc. + */ +#define AS3722_RTC_START_YEAR 2000 +#define AS3722_SET_ALM_RETRIES 5 +#define AS3722_SET_TIME_RETRIES 5 +#define AS3722_GET_TIME_RETRIES 5 + +/* + * Read current time and date in RTC + */ +static int as3722_rtc_readtime(struct device *dev, struct rtc_time *tm) +{ + struct as3722 *as3722 = dev_get_drvdata(dev->parent); + u8 as_time_array[6]; + int ret; + + ret = as3722_block_read(as3722, AS3722_RTC_SECOND_REG, + 6, as_time_array); + if (ret < 0) + return ret; + + tm->tm_sec = ((as_time_array[0] & 0xF0) >> 4) * 10 + + (as_time_array[0] & 0x0F); + tm->tm_min = ((as_time_array[1] & 0xF0) >> 4) * 10 + + (as_time_array[1] & 0x0F); + tm->tm_hour = ((as_time_array[2] & 0xF0) >> 4) * 10 + + (as_time_array[2] & 0x0F); + tm->tm_mday = ((as_time_array[3] & 0xF0) >> 4) * 10 + + (as_time_array[3] & 0x0F); + tm->tm_mon = ((as_time_array[4] & 0xF0) >> 4) * 10 + + (as_time_array[4] & 0x0F); + tm->tm_year = (AS3722_RTC_START_YEAR - 1900) + + ((as_time_array[5] & 0xF0) >> 4) * 10 + + (as_time_array[5] & 0x0F); + + return 0; +} + +/* + * Set current time and date in RTC + */ +static int as3722_rtc_settime(struct device *dev, struct rtc_time *tm) +{ + struct as3722 *as3722 = dev_get_drvdata(dev->parent); + u8 as_time_array[6]; + int ret; + + /* Write time to RTC */ + as_time_array[0] = ((tm->tm_sec / 10) << 4) + + (tm->tm_sec % 10); + as_time_array[1] = ((tm->tm_min / 10) << 4) + + (tm->tm_min % 10); + as_time_array[2] = ((tm->tm_hour / 10) << 4) + + (tm->tm_hour % 10); + as_time_array[3] = ((tm->tm_mday / 10) << 4) + + (tm->tm_mday % 10); + as_time_array[4] = ((tm->tm_mon / 10) << 4) + + (tm->tm_mon % 10); + if (tm->tm_year >= (AS3722_RTC_START_YEAR - 1900)) + as_time_array[5] = (((tm->tm_year + - (AS3722_RTC_START_YEAR - 1900)) / 10) << 4) + + ((tm->tm_year + - (AS3722_RTC_START_YEAR - 1900)) % 10); + else + return -1; + + ret = as3722_block_write(as3722, AS3722_RTC_SECOND_REG, 6, + as_time_array); + + return ret; +} + +/* + * Read alarm time and date in RTC + */ +static int as3722_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct as3722 *as3722 = dev_get_drvdata(dev->parent); + u8 as_time_array[6]; + int ret; + + ret = as3722_block_read(as3722, AS3722_RTC_SECOND_REG, + 6, as_time_array); + if (ret < 0) + return ret; + + alrm->time.tm_sec = ((as_time_array[0] & 0xF0) >> 4) * 10 + + (as_time_array[0] & 0x0F); + alrm->time.tm_min = ((as_time_array[1] & 0xF0) >> 4) * 10 + + (as_time_array[1] & 0x0F); + alrm->time.tm_hour = ((as_time_array[2] & 0xF0) >> 4) * 10 + + (as_time_array[2] & 0x0F); + alrm->time.tm_mday = ((as_time_array[3] & 0xF0) >> 4) * 10 + + (as_time_array[3] & 0x0F); + alrm->time.tm_mon = ((as_time_array[4] & 0xF0) >> 4) * 10 + + (as_time_array[4] & 0x0F); + alrm->time.tm_year = (AS3722_RTC_START_YEAR - 1900) + + ((as_time_array[5] & 0xF0) >> 4) * 10 + + (as_time_array[5] & 0x0F); + + return 0; +} + +static int as3722_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct as3722 *as3722 = dev_get_drvdata(dev->parent); + u8 as_time_array[6]; + int ret; + + /* Write time to RTC */ + as_time_array[0] = ((alrm->time.tm_sec / 10) << 4) + + (alrm->time.tm_sec % 10); + as_time_array[1] = ((alrm->time.tm_min / 10) << 4) + + (alrm->time.tm_min % 10); + as_time_array[2] = ((alrm->time.tm_hour / 10) << 4) + + (alrm->time.tm_hour % 10); + as_time_array[3] = ((alrm->time.tm_mday / 10) << 4) + + (alrm->time.tm_mday % 10); + as_time_array[4] = ((alrm->time.tm_mon / 10) << 4) + + (alrm->time.tm_mon % 10); + if (alrm->time.tm_year >= (AS3722_RTC_START_YEAR - 1900)) + as_time_array[5] = (((alrm->time.tm_year + - (AS3722_RTC_START_YEAR - 1900)) / 10) << 4) + + ((alrm->time.tm_year + - (AS3722_RTC_START_YEAR - 1900)) % 10); + else + return -1; + + ret = as3722_block_write(as3722, AS3722_RTC_SECOND_REG, 6, + as_time_array); + + return ret; +} + +static int as3722_rtc_stop_alarm(struct as3722 *as3722) +{ + /* disable rtc alarm interrupt */ + return as3722_set_bits(as3722, AS3722_INTERRUPTMASK3_REG, + AS3722_IRQ_MASK_RTC_ALARM, AS3722_IRQ_BIT_RTC_ALARM); +} + +static int as3722_rtc_start_alarm(struct as3722 *as3722) +{ + /* enable rtc alarm interrupt */ + return as3722_set_bits(as3722, AS3722_INTERRUPTMASK3_REG, + AS3722_IRQ_MASK_RTC_ALARM, 0); +} + +static int as3722_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct as3722 *as3722 = dev_get_drvdata(dev->parent); + + if (enabled) + return as3722_rtc_start_alarm(as3722); + else + return as3722_rtc_stop_alarm(as3722); +} + +static irqreturn_t as3722_alarm_irq(int irq, void *data) +{ + struct as3722 *as3722 = data; + struct rtc_device *rtc = as3722->rtc.rtc; + + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops as3722_rtc_ops = { + .read_time = as3722_rtc_readtime, + .set_time = as3722_rtc_settime, + .read_alarm = as3722_rtc_readalarm, + .set_alarm = as3722_rtc_setalarm, + .alarm_irq_enable = as3722_rtc_alarm_irq_enable, +}; + +#ifdef CONFIG_PM +static int as3722_rtc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + int ret = 0; + u32 reg; + + as3722_reg_read(as3722, AS3722_INTERRUPTMASK3_REG, ®); + + if (device_may_wakeup(dev) && + reg & AS3722_IRQ_MASK_RTC_ALARM) { + ret = as3722_rtc_stop_alarm(as3722); + if (ret != 0) + dev_err(dev, "Failed to stop RTC alarm: %d\n", + ret); + } + + return ret; +} + +static int as3722_rtc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + int ret; + + if (as3722->rtc.alarm_enabled) { + ret = as3722_rtc_start_alarm(as3722); + if (ret != 0) + dev_err(dev, + "Failed to restart RTC alarm: %d\n", ret); + } + + return 0; +} + +/* Unconditionally disable the alarm */ +static int as3722_rtc_freeze(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + int ret; + + ret = as3722_set_bits(as3722, AS3722_RTC_CONTROL_REG, + AS3722_RTC_ALARM_WAKEUP_EN_MASK, 0); + if (ret != 0) + dev_err(&pdev->dev, "Failed to stop RTC alarm: %d\n", ret); + + return 0; +} + +#define DEV_PM_OPS (&as3722_rtc_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +static int as3722_rtc_probe(struct platform_device *pdev) +{ + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + struct as3722_rtc *rtc = &as3722->rtc; + struct as3722_platform_data *pdata = as3722->dev->platform_data; + struct as3722_rtc_platform_data *rtc_pdata = NULL; + + int alarm_irq = regmap_irq_get_virq(as3722->irq_data, + AS3722_IRQ_RTC_ALARM); + int ret = 0; + u32 ctrl; + + if (pdata) + rtc_pdata = pdata->rtc_pdata; + /* enable the RTC if it's not already enabled */ + as3722_reg_read(as3722, AS3722_RTC_CONTROL_REG, &ctrl); + if (!(ctrl & AS3722_RTC_ON_MASK)) { + dev_info(&pdev->dev, "Starting RTC\n"); + + ret = as3722_set_bits(as3722, AS3722_RTC_CONTROL_REG, + AS3722_RTC_ON_MASK, AS3722_RTC_ON_MASK); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to enable RTC: %d\n", ret); + return ret; + } + } + + if (rtc_pdata && rtc_pdata->enable_clk32k) { + /* Enable CLK32OUT Pin*/ + ret = as3722_set_bits(as3722, AS3722_RTC_CONTROL_REG, + AS3722_RTC_32KCLK_MASK, + AS3722_RTC_32KCLK_MASK); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to enable clk32k: %d\n", ret); + return ret; + } + } else { + /* Disable CLK32OUT Pin*/ + ret = as3722_set_bits(as3722, AS3722_RTC_CONTROL_REG, + AS3722_RTC_32KCLK_MASK, + 0); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to disable clk32k: %d\n", ret); + return ret; + } + } + + + /* enable alarm wakeup */ + as3722_set_bits(as3722, AS3722_RTC_CONTROL_REG, + AS3722_RTC_ALARM_WAKEUP_EN_MASK, + AS3722_RTC_ALARM_WAKEUP_EN_MASK); + + device_init_wakeup(&pdev->dev, 1); + + rtc->rtc = rtc_device_register("as3722", &pdev->dev, + &as3722_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; + } + + ret = request_threaded_irq(alarm_irq, NULL, as3722_alarm_irq, + pdata->irq_type, "RTC alarm", + rtc->rtc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n", + alarm_irq, ret); + } + + return 0; +} + +static int as3722_rtc_remove(struct platform_device *pdev) +{ + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + struct as3722_rtc *rtc = &as3722->rtc; + int alarm_irq = regmap_irq_get_virq(as3722->irq_data, + AS3722_IRQ_RTC_ALARM); + + free_irq(alarm_irq, rtc->rtc); + rtc_device_unregister(rtc->rtc); + + return 0; +} + +static const struct dev_pm_ops as3722_rtc_pm_ops = { + .suspend = as3722_rtc_suspend, + .resume = as3722_rtc_resume, + .freeze = as3722_rtc_freeze, + .thaw = as3722_rtc_resume, + .restore = as3722_rtc_resume, + .poweroff = as3722_rtc_suspend, +}; + +static struct platform_driver as3722_rtc_driver = { + .probe = as3722_rtc_probe, + .remove = as3722_rtc_remove, + .driver = { + .name = "as3722-rtc", + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(as3722_rtc_driver); + +MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); +MODULE_DESCRIPTION("RTC driver for AS3722 PMICs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:as3722-rtc"); diff --git a/drivers/rtc/rtc-max77660.c b/drivers/rtc/rtc-max77660.c new file mode 100644 index 000000000000..e438dd78f876 --- /dev/null +++ b/drivers/rtc/rtc-max77660.c @@ -0,0 +1,445 @@ +/* + * drivers/rtc/rtc-max77660.c + * Max77660 RTC driver + * + * Copyright 2011-2012, Maxim Integrated Products, Inc. + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * + * 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/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/pm.h> +#include <linux/rtc.h> +#include <linux/mfd/max77660/max77660-core.h> + +/* RTC Year base */ +#define RTC_YEAR_BASE 100 + +/* Read/write buffer update time as per datasheet */ +#define MAX77660_RD_WR_BUFFER_UPDATE_TIME 300 + +enum { + RTC_SEC, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_MONTH, + RTC_YEAR, + RTC_MONTHDAY, + RTC_MAX_BUF +}; + +struct max77660_rtc { + struct rtc_device *rtc; + struct device *dev; + struct device *parent; + int irq; + bool shutdown_ongoing; + bool alarm1_enabled; + struct mutex rtc_reg_lock; +}; + +static void max77660_register_to_time(struct rtc_time *time, u8 *buf) +{ + time->tm_sec = buf[RTC_SEC] & MAX77660_RTC_SEC_MASK; + time->tm_min = buf[RTC_MIN] & MAX77660_RTC_MIN_MASK; + time->tm_hour = buf[RTC_HOUR] & MAX77660_RTC_HOUR_MASK; + time->tm_wday = ffs(buf[RTC_WEEKDAY] & MAX77660_RTC_WEEKDAY_MASK) - 1; + time->tm_mon = (buf[RTC_MONTH] & MAX77660_RTC_MONTH_MASK) - 1; + time->tm_year = (buf[RTC_YEAR] & MAX77660_RTC_YEAR_MASK) + + RTC_YEAR_BASE; + time->tm_mday = buf[RTC_MONTHDAY] & MAX77660_RTC_MONTHDAY_MASK; +} + +static void max77660_time_to_register(struct rtc_time *time, u8 *buf) +{ + buf[RTC_SEC] = time->tm_sec & MAX77660_RTC_SEC_MASK; + buf[RTC_MIN] = time->tm_min & MAX77660_RTC_MIN_MASK; + buf[RTC_HOUR] = time->tm_hour & MAX77660_RTC_HOUR_MASK; + buf[RTC_WEEKDAY] = BIT(time->tm_wday & MAX77660_RTC_WEEKDAY_MASK); + buf[RTC_MONTH] = (time->tm_mon + 1) & MAX77660_RTC_MONTH_MASK; + buf[RTC_YEAR] = (time->tm_year - RTC_YEAR_BASE) & + MAX77660_RTC_YEAR_MASK; + buf[RTC_MONTHDAY] = time->tm_mday & MAX77660_RTC_MONTHDAY_MASK; +} + +static int max77660_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_MAX_BUF]; + int ret; + + mutex_lock(&rtc->rtc_reg_lock); + + /* Update the read buffer */ + ret = max77660_reg_set_bits(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_UPDATE0, MAX77660_RTC_RB_UPDATE_MASK); + if (ret < 0) { + dev_err(rtc->dev, "RTC_UPDATE0 update failed: %d\n", ret); + goto out; + } + + /* Wait for update */ + udelay(MAX77660_RD_WR_BUFFER_UPDATE_TIME); + + ret = max77660_reg_reads(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_SEC, RTC_MAX_BUF, buf); + if (ret < 0) { + dev_err(rtc->dev, "RTC_SEC read failed: %d\n", ret); + goto out; + } + + max77660_register_to_time(tm, buf); + + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + +out: + mutex_unlock(&rtc->rtc_reg_lock); + return 0; +} + +static int max77660_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_MAX_BUF]; + int ret; + + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + max77660_time_to_register(tm, buf); + + mutex_lock(&rtc->rtc_reg_lock); + + ret = max77660_reg_writes(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_SEC, RTC_MAX_BUF, buf); + if (ret < 0) { + dev_err(rtc->dev, "RTC_SEC write failed: %d\n", ret); + goto out; + } + + /* Update from write buffer */ + ret = max77660_reg_set_bits(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_UPDATE0, MAX77660_RTC_WB_UPDATE_MASK); + if (ret < 0) { + dev_err(rtc->dev, "RTC_UPDATE0 update failed: %d\n", ret); + goto out; + } + + /* Wait for update */ + udelay(MAX77660_RD_WR_BUFFER_UPDATE_TIME); +out: + mutex_unlock(&rtc->rtc_reg_lock); + return 0; +} + +static int max77660_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + int ret = 0; + + if (rtc->shutdown_ongoing) { + dev_warn(rtc->dev, + "device is shutdown: skipping alarm enable\n"); + return -ESHUTDOWN; + } + + dev_dbg(dev, "%s(): enabled %u\n", __func__, enabled); + + if (enabled) + ret = max77660_reg_clr_bits(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_IRQ_MASK, MAX77660_RTC_IRQ_ALARM1_MASK); + else + ret = max77660_reg_set_bits(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_IRQ_MASK, MAX77660_RTC_IRQ_ALARM1_MASK); + if (ret < 0) { + dev_err(rtc->dev, "RTC_IRQ_MASK update failed: %d\n", ret); + return ret; + } + rtc->alarm1_enabled = enabled; + return 0; +} + +static int max77660_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_MAX_BUF]; + int ret; + + if (rtc->shutdown_ongoing) { + dev_warn(rtc->dev, + "device is shutdown: skipping alarm reading\n"); + return -ESHUTDOWN; + } + + ret = max77660_reg_reads(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_ALARM_SEC1, RTC_MAX_BUF, buf); + if (ret < 0) { + dev_err(rtc->dev, "RTC_ALARM1 read failed: %d\n", ret); + return ret; + } + max77660_register_to_time(&alrm->time, buf); + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", __func__, + alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday, + alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec); + alrm->enabled = rtc->alarm1_enabled; + return 0; +} + +static int max77660_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_MAX_BUF]; + int ret; + + if (rtc->shutdown_ongoing) { + dev_warn(rtc->dev, + "device is shutdown: skipping alarm setting\n"); + return -ESHUTDOWN; + } + + /* Set alarm for sec/min/hour/month/year/day */ + ret = max77660_reg_write(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_AE1, 0x77); + if (ret < 0) { + dev_err(rtc->dev, "RTC_AE1 write failed: %d\n", ret); + return ret; + } + + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", __func__, + alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday, + alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec); + max77660_time_to_register(&alrm->time, buf); + buf[RTC_WEEKDAY] = 0; + + ret = max77660_reg_writes(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_ALARM_SEC1, RTC_MAX_BUF, buf); + if (ret < 0) { + dev_err(rtc->dev, "RTC_ALARAM1 write failed: %d\n", ret); + return ret; + } + return max77660_rtc_alarm_irq_enable(dev, alrm->enabled); +} + +static irqreturn_t max77660_rtc_irq(int irq, void *data) +{ + struct max77660_rtc *rtc = (struct max77660_rtc *)data; + u8 status; + int ret; + + ret = max77660_reg_read(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_IRQ, &status); + if (ret < 0) { + dev_err(rtc->dev, "RTC_IRQ read failed: %d\n", ret); + goto out; + } + + if (!(status & MAX77660_RTC_IRQ_ALARM1_MASK)) { + dev_err(rtc->dev, "Unkknow RTC irq: status 0x%02x\n", status); + goto out; + } + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); +out: + return IRQ_HANDLED; +} + +static const struct rtc_class_ops max77660_rtc_ops = { + .read_time = max77660_rtc_read_time, + .set_time = max77660_rtc_set_time, + .read_alarm = max77660_rtc_read_alarm, + .set_alarm = max77660_rtc_set_alarm, + .alarm_irq_enable = max77660_rtc_alarm_irq_enable, +}; + +static int max77660_rtc_preinit(struct max77660_rtc *rtc) +{ + int ret; + + ret = max77660_reg_write(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_IRQ_MASK, 0xFF); + if (ret < 0) { + dev_err(rtc->dev, "RTC_IRQ_MASK write failed: %d\n", ret); + return ret; + } + + /* Configure Binary mode and 24hour mode */ + ret = max77660_reg_write(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_CTRL_MODE, MAX77660_RTCCNTLM_MASK); + if (ret < 0) { + dev_err(rtc->dev, "RTC_CTRL_MODE write failed: %d\n", ret); + return ret; + } + + ret = max77660_reg_write(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_CTRL, MAX77660_RTCCNTL_HRMODE_24); + if (ret < 0) { + dev_err(rtc->dev, "RTC_CTRL write failed: %d\n", ret); + return ret; + } + ret = max77660_reg_write(rtc->parent, MAX77660_RTC_SLAVE, + MAX77660_RTC_CTRL_MODE, 0x0); + if (ret < 0) { + dev_err(rtc->dev, "RTC_CTRL_MODE write failed: %d\n", ret); + return ret; + } + return 0; +} + +static int max77660_rtc_probe(struct platform_device *pdev) +{ + static struct max77660_rtc *rtc; + int ret; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) { + dev_err(&pdev->dev, "Memory allocation failed for rtc\n"); + return -ENOMEM; + } + + rtc->shutdown_ongoing = false; + dev_set_drvdata(&pdev->dev, rtc); + rtc->dev = &pdev->dev; + rtc->parent = pdev->dev.parent; + + ret = max77660_rtc_preinit(rtc); + if (ret < 0) { + dev_err(&pdev->dev, "RTC pre initilisation failed: %d\n", ret); + return ret; + } + + device_init_wakeup(&pdev->dev, 1); + + mutex_init(&rtc->rtc_reg_lock); + rtc->rtc = rtc_device_register("max77660-rtc", &pdev->dev, + &max77660_rtc_ops, THIS_MODULE); + if (IS_ERR_OR_NULL(rtc->rtc)) { + dev_err(&pdev->dev, "probe: Failed to register rtc\n"); + ret = PTR_ERR(rtc->rtc); + goto out; + } + + rtc->irq = platform_get_irq(pdev, 0); + ret = request_threaded_irq(rtc->irq, NULL, max77660_rtc_irq, + IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(&pdev->dev), rtc); + if (ret < 0) { + dev_err(rtc->dev, "request irq %d failed: %dn", rtc->irq, ret); + goto out_rtc_free; + } + + return 0; + +out_rtc_free: + rtc_device_unregister(rtc->rtc); +out: + mutex_destroy(&rtc->rtc_reg_lock); + return ret; +} + +static int max77660_rtc_remove(struct platform_device *pdev) +{ + struct max77660_rtc *rtc = dev_get_drvdata(&pdev->dev); + + free_irq(rtc->irq, rtc); + + rtc_device_unregister(rtc->rtc); + mutex_destroy(&rtc->rtc_reg_lock); + + return 0; +} + +static void max77660_rtc_shutdown(struct platform_device *pdev) +{ + struct max77660_rtc *rtc = dev_get_drvdata(&pdev->dev); + + rtc->shutdown_ongoing = true; +} + +#ifdef CONFIG_PM_SLEEP +static int max77660_rtc_suspend(struct device *dev) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + int ret; + + if (device_may_wakeup(dev)) { + struct rtc_wkalrm alm; + + enable_irq_wake(rtc->irq); + + /* Set RTC can generate the wakeup signal */ + ret = max77660_reg_set_bits(rtc->parent, MAX77660_PWR_SLAVE, + MAX77660_REG_GLOBAL_CFG2, MAX77660_GLBLCNFG2_RTCWKEN); + if (ret < 0) + dev_err(rtc->dev, "RTC wake enable failed: %d\n", ret); + + ret = max77660_rtc_read_alarm(dev, &alm); + if (!ret) { + dev_info(dev, "%s() alrm %d time %d %d %d %d %d %d\n", + __func__, alm.enabled, alm.time.tm_year, + alm.time.tm_mon, alm.time.tm_mday, + alm.time.tm_hour, alm.time.tm_min, + alm.time.tm_sec); + } + } + return 0; +} + +static int max77660_rtc_resume(struct device *dev) +{ + struct max77660_rtc *rtc = dev_get_drvdata(dev); + int ret; + + if (device_may_wakeup(dev)) { + struct rtc_time tm; + + disable_irq_wake(rtc->irq); + + /* Set RTC can generate the wakeup signal */ + ret = max77660_reg_clr_bits(rtc->parent, MAX77660_PWR_SLAVE, + MAX77660_REG_GLOBAL_CFG2, MAX77660_GLBLCNFG2_RTCWKEN); + if (ret < 0) + dev_err(rtc->dev, "RTC wake enable failed: %d\n", ret); + + ret = max77660_rtc_read_time(dev, &tm); + if (!ret) + dev_info(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } + return 0; +}; +#endif + +static const struct dev_pm_ops max77660_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(max77660_rtc_suspend, max77660_rtc_resume) +}; + +static struct platform_driver max77660_rtc_driver = { + .probe = max77660_rtc_probe, + .remove = max77660_rtc_remove, + .driver = { + .name = "max77660-rtc", + .owner = THIS_MODULE, + .pm = &max77660_pm_ops, + }, + .shutdown = max77660_rtc_shutdown, +}; + +module_platform_driver(max77660_rtc_driver); + +MODULE_DESCRIPTION("max77660 RTC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_AUTHOR("Maxim Integrated"); +MODULE_ALIAS("platform:max77660-rtc"); diff --git a/drivers/rtc/rtc-max77663.c b/drivers/rtc/rtc-max77663.c new file mode 100644 index 000000000000..0e14ccb7b7e9 --- /dev/null +++ b/drivers/rtc/rtc-max77663.c @@ -0,0 +1,643 @@ +/* + * drivers/rtc/rtc-max77663.c + * Max77663 RTC driver + * + * Copyright 2011-2012, Maxim Integrated Products, Inc. + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * + * 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/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/rtc.h> +#include <linux/mfd/max77663-core.h> + +/* RTC Registers */ +#define MAX77663_RTC_IRQ 0x00 +#define MAX77663_RTC_IRQ_MASK 0x01 +#define MAX77663_RTC_CTRL_MODE 0x02 +#define MAX77663_RTC_CTRL 0x03 +#define MAX77663_RTC_UPDATE0 0x04 +#define MAX77663_RTC_UPDATE1 0x05 +#define MAX77663_RTC_SEC 0x07 +#define MAX77663_RTC_MIN 0x08 +#define MAX77663_RTC_HOUR 0x09 +#define MAX77663_RTC_WEEKDAY 0x0A +#define MAX77663_RTC_MONTH 0x0B +#define MAX77663_RTC_YEAR 0x0C +#define MAX77663_RTC_MONTHDAY 0x0D +#define MAX77663_RTC_ALARM_SEC1 0x0E +#define MAX77663_RTC_ALARM_MIN1 0x0F +#define MAX77663_RTC_ALARM_HOUR1 0x10 +#define MAX77663_RTC_ALARM_WEEKDAY1 0x11 +#define MAX77663_RTC_ALARM_MONTH1 0x12 +#define MAX77663_RTC_ALARM_YEAR1 0x13 +#define MAX77663_RTC_ALARM_MONTHDAY1 0x14 + +#define RTC_IRQ_60SEC_MASK (1 << 0) +#define RTC_IRQ_ALARM1_MASK (1 << 1) +#define RTC_IRQ_ALARM2_MASK (1 << 2) +#define RTC_IRQ_SMPL_MASK (1 << 3) +#define RTC_IRQ_1SEC_MASK (1 << 4) +#define RTC_IRQ_MASK 0x1F + +#define BCD_MODE_MASK (1 << 0) +#define HR_MODE_MASK (1 << 1) + +#define WB_UPDATE_MASK (1 << 0) +#define FLAG_AUTO_CLEAR_MASK (1 << 1) +#define FREEZE_SEC_MASK (1 << 2) +#define RTC_WAKE_MASK (1 << 3) +#define RB_UPDATE_MASK (1 << 4) + +#define WB_UPDATE_FLAG_MASK (1 << 0) +#define RB_UPDATE_FLAG_MASK (1 << 1) + +#define SEC_MASK 0x7F +#define MIN_MASK 0x7F +#define HOUR_MASK 0x3F +#define WEEKDAY_MASK 0x7F +#define MONTH_MASK 0x1F +#define YEAR_MASK 0xFF +#define MONTHDAY_MASK 0x3F + +#define ALARM_EN_MASK 0x80 +#define ALARM_EN_SHIFT 7 + +#define RTC_YEAR_BASE 100 +#define RTC_YEAR_MAX 99 + +/* ON/OFF Registers */ +#define MAX77663_REG_ONOFF_CFG2 0x42 + +#define ONOFF_WK_ALARM1_MASK (1 << 2) + +enum { + RTC_SEC, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_MONTH, + RTC_YEAR, + RTC_MONTHDAY, + RTC_NR +}; + +struct max77663_rtc { + struct rtc_device *rtc; + struct device *dev; + + struct mutex io_lock; + int irq; + u8 irq_mask; + bool shutdown_ongoing; +}; + +static inline struct device *_to_parent(struct max77663_rtc *rtc) +{ + return rtc->dev->parent; +} + +static inline int max77663_rtc_update_buffer(struct max77663_rtc *rtc, + int write) +{ + struct device *parent = _to_parent(rtc); + u8 val = FLAG_AUTO_CLEAR_MASK | RTC_WAKE_MASK; + int ret; + + if (write) + val |= WB_UPDATE_MASK; + else + val |= RB_UPDATE_MASK; + + dev_dbg(rtc->dev, "rtc_update_buffer: write=%d, addr=0x%x, val=0x%x\n", + write, MAX77663_RTC_UPDATE0, val); + ret = max77663_write(parent, MAX77663_RTC_UPDATE0, &val, 1, 1); + if (ret < 0) { + dev_err(rtc->dev, "rtc_update_buffer: " + "Failed to get rtc update0\n"); + return ret; + } + + /* + * Must wait 14ms for buffer update. + * If the sleeping time is 10us - 20ms, usleep_range() is recommended. + * Please refer Documentation/timers/timers-howto.txt. + */ + usleep_range(14000, 14000); + + return 0; +} + +static inline int max77663_rtc_write(struct max77663_rtc *rtc, u8 addr, + void *values, u32 len, int update_buffer) +{ + struct device *parent = _to_parent(rtc); + int ret; + + mutex_lock(&rtc->io_lock); + + dev_dbg(rtc->dev, "rtc_write: addr=0x%x, values=0x%x, len=%u, " + "update_buffer=%d\n", + addr, *((u8 *)values), len, update_buffer); + ret = max77663_write(parent, addr, values, len, 1); + if (ret < 0) + goto out; + + if (update_buffer) + ret = max77663_rtc_update_buffer(rtc, 1); + +out: + mutex_unlock(&rtc->io_lock); + return ret; +} + +static inline int max77663_rtc_read(struct max77663_rtc *rtc, u8 addr, + void *values, u32 len, int update_buffer) +{ + struct device *parent = _to_parent(rtc); + int ret; + + mutex_lock(&rtc->io_lock); + + if (update_buffer) { + ret = max77663_rtc_update_buffer(rtc, 0); + if (ret < 0) + goto out; + } + + ret = max77663_read(parent, addr, values, len, 1); + dev_dbg(rtc->dev, "rtc_read: addr=0x%x, values=0x%x, len=%u, " + "update_buffer=%d\n", + addr, *((u8 *)values), len, update_buffer); + +out: + mutex_unlock(&rtc->io_lock); + return ret; +} + +static inline int max77663_rtc_reg_to_tm(struct max77663_rtc *rtc, u8 *buf, + struct rtc_time *tm) +{ + int wday = buf[RTC_WEEKDAY] & WEEKDAY_MASK; + + if (unlikely(!wday)) { + dev_err(rtc->dev, + "rtc_reg_to_tm: Invalid day of week, %d\n", wday); + return -EINVAL; + } + + tm->tm_sec = (int)(buf[RTC_SEC] & SEC_MASK); + tm->tm_min = (int)(buf[RTC_MIN] & MIN_MASK); + tm->tm_hour = (int)(buf[RTC_HOUR] & HOUR_MASK); + tm->tm_mday = (int)(buf[RTC_MONTHDAY] & MONTHDAY_MASK); + tm->tm_mon = (int)(buf[RTC_MONTH] & MONTH_MASK) - 1; + tm->tm_year = (int)(buf[RTC_YEAR] & YEAR_MASK) + RTC_YEAR_BASE; + tm->tm_wday = ffs(wday) - 1; + + return 0; +} + +static inline int max77663_rtc_tm_to_reg(struct max77663_rtc *rtc, u8 *buf, + struct rtc_time *tm, int alarm) +{ + u8 alarm_mask = alarm ? ALARM_EN_MASK : 0; + + if (unlikely((tm->tm_year < RTC_YEAR_BASE) || + (tm->tm_year > RTC_YEAR_BASE + RTC_YEAR_MAX))) { + dev_err(rtc->dev, + "rtc_tm_to_reg: Invalid year, %d\n", tm->tm_year); + return -EINVAL; + } + + buf[RTC_SEC] = tm->tm_sec | alarm_mask; + buf[RTC_MIN] = tm->tm_min | alarm_mask; + buf[RTC_HOUR] = tm->tm_hour | alarm_mask; + buf[RTC_MONTHDAY] = tm->tm_mday | alarm_mask; + buf[RTC_MONTH] = (tm->tm_mon + 1) | alarm_mask; + buf[RTC_YEAR] = (tm->tm_year - RTC_YEAR_BASE) | alarm_mask; + + /* The wday is configured only when disabled alarm. */ + if (!alarm) + buf[RTC_WEEKDAY] = (1 << tm->tm_wday); + else { + /* Configure its default reset value 0x01, and not enable it. */ + buf[RTC_WEEKDAY] = 0x01; + } + return 0; +} + +static inline int max77663_rtc_irq_mask(struct max77663_rtc *rtc, u8 irq) +{ + struct device *parent = _to_parent(rtc); + u8 irq_mask = rtc->irq_mask | irq; + int ret = 0; + + ret = max77663_write(parent, MAX77663_RTC_IRQ_MASK, &irq_mask, 1, 1); + if (ret < 0) { + dev_err(rtc->dev, "rtc_irq_mask: Failed to set rtc irq mask\n"); + goto out; + } + rtc->irq_mask = irq_mask; + +out: + return ret; +} + +static inline int max77663_rtc_irq_unmask(struct max77663_rtc *rtc, u8 irq) +{ + struct device *parent = _to_parent(rtc); + u8 irq_mask = rtc->irq_mask & ~irq; + int ret = 0; + + ret = max77663_write(parent, MAX77663_RTC_IRQ_MASK, &irq_mask, 1, 1); + if (ret < 0) { + dev_err(rtc->dev, + "rtc_irq_unmask: Failed to set rtc irq mask\n"); + goto out; + } + rtc->irq_mask = irq_mask; + +out: + return ret; +} + +static inline int max77663_rtc_do_irq(struct max77663_rtc *rtc) +{ + struct device *parent = _to_parent(rtc); + u8 irq_status; + int ret; + + ret = max77663_rtc_update_buffer(rtc, 0); + if (ret < 0) { + dev_err(rtc->dev, "rtc_irq: Failed to get rtc update buffer\n"); + return ret; + } + + ret = max77663_read(parent, MAX77663_RTC_IRQ, &irq_status, 1, 1); + if (ret < 0) { + dev_err(rtc->dev, "rtc_irq: Failed to get rtc irq status\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_do_irq: irq_mask=0x%02x, irq_status=0x%02x\n", + rtc->irq_mask, irq_status); + + if (!(rtc->irq_mask & RTC_IRQ_ALARM1_MASK) && + (irq_status & RTC_IRQ_ALARM1_MASK)) + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + + if (!(rtc->irq_mask & RTC_IRQ_1SEC_MASK) && + (irq_status & RTC_IRQ_1SEC_MASK)) + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_UF); + + return ret; +} + +static irqreturn_t max77663_rtc_irq(int irq, void *data) +{ + struct max77663_rtc *rtc = (struct max77663_rtc *)data; + + max77663_rtc_do_irq(rtc); + + return IRQ_HANDLED; +} + +static int max77663_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct max77663_rtc *rtc = dev_get_drvdata(dev); + int ret = 0; + + if (rtc->irq < 0) + return -ENXIO; + + mutex_lock(&rtc->io_lock); + + /* Handle pending interrupt */ + ret = max77663_rtc_do_irq(rtc); + if (ret < 0) + goto out; + + /* Config alarm interrupt */ + if (enabled) { + ret = max77663_rtc_irq_unmask(rtc, RTC_IRQ_ALARM1_MASK); + if (ret < 0) + goto out; + } else { + ret = max77663_rtc_irq_mask(rtc, RTC_IRQ_ALARM1_MASK); + if (ret < 0) + goto out; + } +out: + mutex_unlock(&rtc->io_lock); + return ret; +} + +static int max77663_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max77663_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_NR]; + int ret; + + ret = max77663_rtc_read(rtc, MAX77663_RTC_SEC, buf, sizeof(buf), 1); + if (ret < 0) { + dev_err(rtc->dev, "rtc_read_time: Failed to read rtc time\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_read_time: " + "buf: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + buf[RTC_SEC], buf[RTC_MIN], buf[RTC_HOUR], buf[RTC_WEEKDAY], + buf[RTC_MONTH], buf[RTC_YEAR], buf[RTC_MONTHDAY]); + + ret = max77663_rtc_reg_to_tm(rtc, buf, tm); + if (ret < 0) { + dev_err(rtc->dev, "rtc_read_time: " + "Failed to convert register format into time format\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_read_time: " + "tm: %d-%02d-%02d %02d:%02d:%02d, wday=%d\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, + tm->tm_sec, tm->tm_wday); + + return ret; +} + +static int max77663_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max77663_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_NR]; + int ret; + + dev_dbg(rtc->dev, "rtc_set_time: " + "tm: %d-%02d-%02d %02d:%02d:%02d, wday=%d\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, + tm->tm_sec, tm->tm_wday); + + ret = max77663_rtc_tm_to_reg(rtc, buf, tm, 0); + if (ret < 0) { + dev_err(rtc->dev, "rtc_set_time: " + "Failed to convert time format into register format\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_set_time: " + "buf: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + buf[RTC_SEC], buf[RTC_MIN], buf[RTC_HOUR], buf[RTC_WEEKDAY], + buf[RTC_MONTH], buf[RTC_YEAR], buf[RTC_MONTHDAY]); + + return max77663_rtc_write(rtc, MAX77663_RTC_SEC, buf, sizeof(buf), 1); +} + +static int max77663_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77663_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_NR]; + int ret; + + ret = max77663_rtc_read(rtc, MAX77663_RTC_ALARM_SEC1, buf, sizeof(buf), + 1); + if (ret < 0) { + dev_err(rtc->dev, + "rtc_read_alarm: Failed to read rtc alarm time\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_read_alarm: " + "buf: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + buf[RTC_SEC], buf[RTC_MIN], buf[RTC_HOUR], buf[RTC_WEEKDAY], + buf[RTC_MONTH], buf[RTC_YEAR], buf[RTC_MONTHDAY]); + + ret = max77663_rtc_reg_to_tm(rtc, buf, &alrm->time); + if (ret < 0) { + dev_err(rtc->dev, "rtc_read_alarm: " + "Failed to convert register format into time format\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_read_alarm: " + "tm: %d-%02d-%02d %02d:%02d:%02d, wday=%d\n", + alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday, + alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec, + alrm->time.tm_wday); + + if (rtc->irq_mask & RTC_IRQ_ALARM1_MASK) + alrm->enabled = 0; + else + alrm->enabled = 1; + + return 0; +} + +static int max77663_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77663_rtc *rtc = dev_get_drvdata(dev); + u8 buf[RTC_NR]; + int ret; + + if (rtc->shutdown_ongoing) { + dev_warn(rtc->dev, "rtc_set_alarm: " + "Device shutdown on-going, skip alarm setting.\n"); + return -ESHUTDOWN; + } + dev_dbg(rtc->dev, "rtc_set_alarm: " + "tm: %d-%02d-%02d %02d:%02d:%02d, wday=%d [%s]\n", + alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday, + alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec, + alrm->time.tm_wday, alrm->enabled?"enable":"disable"); + + ret = max77663_rtc_tm_to_reg(rtc, buf, &alrm->time, 1); + if (ret < 0) { + dev_err(rtc->dev, "rtc_set_alarm: " + "Failed to convert time format into register format\n"); + return ret; + } + + dev_dbg(rtc->dev, "rtc_set_alarm: " + "buf: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + buf[RTC_SEC], buf[RTC_MIN], buf[RTC_HOUR], buf[RTC_WEEKDAY], + buf[RTC_MONTH], buf[RTC_YEAR], buf[RTC_MONTHDAY]); + + ret = max77663_rtc_write(rtc, MAX77663_RTC_ALARM_SEC1, buf, sizeof(buf), + 1); + if (ret < 0) { + dev_err(rtc->dev, + "rtc_set_alarm: Failed to write rtc alarm time\n"); + return ret; + } + + ret = max77663_rtc_alarm_irq_enable(dev, alrm->enabled); + if (ret < 0) { + dev_err(rtc->dev, + "rtc_set_alarm: Failed to enable rtc alarm\n"); + return ret; + } + + return ret; +} + +static const struct rtc_class_ops max77663_rtc_ops = { + .read_time = max77663_rtc_read_time, + .set_time = max77663_rtc_set_time, + .read_alarm = max77663_rtc_read_alarm, + .set_alarm = max77663_rtc_set_alarm, + .alarm_irq_enable = max77663_rtc_alarm_irq_enable, +}; + +static int max77663_rtc_preinit(struct max77663_rtc *rtc) +{ + struct device *parent = _to_parent(rtc); + u8 val; + int ret; + + /* Mask all interrupts */ + rtc->irq_mask = 0xFF; + ret = max77663_rtc_write(rtc, MAX77663_RTC_IRQ_MASK, &rtc->irq_mask, 1, + 0); + if (ret < 0) { + dev_err(rtc->dev, "preinit: Failed to set rtc irq mask\n"); + return ret; + } + + /* Configure Binary mode and 24hour mode */ + val = HR_MODE_MASK; + ret = max77663_rtc_write(rtc, MAX77663_RTC_CTRL, &val, 1, 0); + if (ret < 0) { + dev_err(rtc->dev, "preinit: Failed to set rtc control\n"); + return ret; + } + + /* It should be disabled alarm wakeup to wakeup from sleep + * by EN1 input signal */ + ret = max77663_set_bits(parent, MAX77663_REG_ONOFF_CFG2, + ONOFF_WK_ALARM1_MASK, 0, 0); + if (ret < 0) { + dev_err(rtc->dev, "preinit: Failed to set onoff cfg2\n"); + return ret; + } + + return 0; +} + +static int max77663_rtc_probe(struct platform_device *pdev) +{ + struct max77663_platform_data *parent_pdata = + pdev->dev.parent->platform_data; + static struct max77663_rtc *rtc; + int ret = 0; + + rtc = kzalloc(sizeof(struct max77663_rtc), GFP_KERNEL); + if (!rtc) { + dev_err(&pdev->dev, "probe: kzalloc() failed\n"); + return -ENOMEM; + } + rtc->shutdown_ongoing = false; + dev_set_drvdata(&pdev->dev, rtc); + rtc->dev = &pdev->dev; + mutex_init(&rtc->io_lock); + + ret = max77663_rtc_preinit(rtc); + if (ret) { + dev_err(&pdev->dev, "probe: Failed to rtc preinit\n"); + goto out_kfree; + } + + /* + * RTC should be a wakeup source, or alarm dev can't link to + * this devices. that cause Android time change not set into + * RTC register. + */ + device_init_wakeup(&pdev->dev, true); + + rtc->rtc = rtc_device_register("max77663-rtc", &pdev->dev, + &max77663_rtc_ops, THIS_MODULE); + if (IS_ERR_OR_NULL(rtc->rtc)) { + dev_err(&pdev->dev, "probe: Failed to register rtc\n"); + ret = PTR_ERR(rtc->rtc); + goto out_kfree; + } + + if (parent_pdata->irq_base < 0) + goto out; + + rtc->irq = parent_pdata->irq_base + MAX77663_IRQ_RTC; + ret = request_threaded_irq(rtc->irq, NULL, max77663_rtc_irq, + IRQF_ONESHOT, "max77663-rtc", rtc); + if (ret < 0) { + dev_err(rtc->dev, "probe: Failed to request irq %d\n", + rtc->irq); + rtc->irq = -1; + } else { + device_init_wakeup(rtc->dev, 1); + enable_irq_wake(rtc->irq); + } + + return 0; + +out_kfree: + mutex_destroy(&rtc->io_lock); + kfree(rtc->rtc); +out: + return ret; +} + +static int max77663_rtc_remove(struct platform_device *pdev) +{ + struct max77663_rtc *rtc = dev_get_drvdata(&pdev->dev); + + if (rtc->irq != -1) + free_irq(rtc->irq, rtc); + + rtc_device_unregister(rtc->rtc); + mutex_destroy(&rtc->io_lock); + kfree(rtc); + + return 0; +} + +static void max77663_rtc_shutdown(struct platform_device *pdev) +{ + struct max77663_rtc *rtc = dev_get_drvdata(&pdev->dev); + u8 buf[RTC_NR] = { 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x1 }; + + rtc->shutdown_ongoing = true; + dev_info(rtc->dev, "rtc_shutdown: clean alarm\n"); + max77663_rtc_write(rtc, MAX77663_RTC_ALARM_SEC1, buf, sizeof(buf), 1); + max77663_rtc_alarm_irq_enable(&pdev->dev, 0); +} + +static struct platform_driver max77663_rtc_driver = { + .probe = max77663_rtc_probe, + .remove = max77663_rtc_remove, + .driver = { + .name = "max77663-rtc", + .owner = THIS_MODULE, + }, + .shutdown = max77663_rtc_shutdown, +}; + +static int __init max77663_rtc_init(void) +{ + return platform_driver_register(&max77663_rtc_driver); +} +module_init(max77663_rtc_init); + +static void __exit max77663_rtc_exit(void) +{ + platform_driver_unregister(&max77663_rtc_driver); +} +module_exit(max77663_rtc_exit); + +MODULE_DESCRIPTION("max77663 RTC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); diff --git a/drivers/rtc/rtc-max8907c.c b/drivers/rtc/rtc-max8907c.c new file mode 100644 index 000000000000..732976195dae --- /dev/null +++ b/drivers/rtc/rtc-max8907c.c @@ -0,0 +1,323 @@ +/* + * RTC driver for Maxim MAX8907c + * + * Copyright (c) 2011, NVIDIA Corporation. + * Based on drivers/rtc/rtc-max8925.c, Copyright (C) 2009-2010 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/mfd/max8907c.h> + +enum { + RTC_SEC = 0, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_DATE, + RTC_MONTH, + RTC_YEAR1, + RTC_YEAR2, +}; + +#define TIME_NUM 8 +#define ALARM_1SEC (1 << 7) +#define HOUR_12 (1 << 7) +#define HOUR_AM_PM (1 << 5) +#define ALARM0_IRQ (1 << 3) +#define ALARM1_IRQ (1 << 2) +#define ALARM0_STATUS (1 << 2) +#define ALARM1_STATUS (1 << 1) + +struct max8907c_rtc_info { + struct rtc_device *rtc_dev; + struct i2c_client *i2c; + struct max8907c *chip; +}; + +static irqreturn_t rtc_update_handler(int irq, void *data) +{ + struct max8907c_rtc_info *info = (struct max8907c_rtc_info *)data; + + /* disable ALARM0 except for 1SEC alarm */ + max8907c_set_bits(info->i2c, MAX8907C_REG_ALARM0_CNTL, 0x7f, 0); + rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int tm_calc(struct rtc_time *tm, u8 *buf, int len) +{ + if (len < TIME_NUM) + return -EINVAL; + tm->tm_year = (buf[RTC_YEAR2] >> 4) * 1000 + + (buf[RTC_YEAR2] & 0xf) * 100 + + (buf[RTC_YEAR1] >> 4) * 10 + + (buf[RTC_YEAR1] & 0xf); + tm->tm_year -= 1900; + /* RTC month index issue in max8907c + : January index is 1 but kernel assumes it as 0 */ + tm->tm_mon = ((buf[RTC_MONTH] >> 4) & 0x01) * 10 + + (buf[RTC_MONTH] & 0x0f) - 1; + tm->tm_mday = ((buf[RTC_DATE] >> 4) & 0x03) * 10 + + (buf[RTC_DATE] & 0x0f); + tm->tm_wday = buf[RTC_WEEKDAY] & 0x07; + if (buf[RTC_HOUR] & HOUR_12) { + tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x1) * 10 + + (buf[RTC_HOUR] & 0x0f); + if (buf[RTC_HOUR] & HOUR_AM_PM) + tm->tm_hour += 12; + } else { + tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x03) * 10 + + (buf[RTC_HOUR] & 0x0f); + } + tm->tm_min = ((buf[RTC_MIN] >> 4) & 0x7) * 10 + + (buf[RTC_MIN] & 0x0f); + tm->tm_sec = ((buf[RTC_SEC] >> 4) & 0x7) * 10 + + (buf[RTC_SEC] & 0x0f); + return 0; +} + +static int data_calc(u8 *buf, struct rtc_time *tm, int len) +{ + u8 high, low; + + if (len < TIME_NUM) + return -EINVAL; + + high = (tm->tm_year + 1900) / 1000; + low = (tm->tm_year + 1900) / 100; + low = low - high * 10; + buf[RTC_YEAR2] = (high << 4) + low; + high = (tm->tm_year + 1900) / 10; + low = tm->tm_year + 1900; + low = low - high * 10; + high = high - (high / 10) * 10; + buf[RTC_YEAR1] = (high << 4) + low; + + /* RTC month index issue in max8907c + : January index is 1 but kernel assumes it as 0 */ + high = (tm->tm_mon + 1) / 10; + low = (tm->tm_mon + 1) % 10; + buf[RTC_MONTH] = (high << 4) + low; + + high = tm->tm_mday / 10; + low = tm->tm_mday; + low = low - high * 10; + buf[RTC_DATE] = (high << 4) + low; + buf[RTC_WEEKDAY] = tm->tm_wday; + high = tm->tm_hour / 10; + low = tm->tm_hour; + low = low - high * 10; + buf[RTC_HOUR] = (high << 4) + low; + high = tm->tm_min / 10; + low = tm->tm_min; + low = low - high * 10; + buf[RTC_MIN] = (high << 4) + low; + high = tm->tm_sec / 10; + low = tm->tm_sec; + low = low - high * 10; + buf[RTC_SEC] = (high << 4) + low; + return 0; +} + +static int max8907c_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max8907c_rtc_info *info = dev_get_drvdata(dev); + u8 buf[TIME_NUM]; + int ret; + + ret = max8907c_reg_bulk_read(info->i2c, MAX8907C_REG_RTC_SEC, TIME_NUM, buf); + + if (ret < 0) + return ret; + ret = tm_calc(tm, buf, TIME_NUM); + + return ret; +} + +static int max8907c_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max8907c_rtc_info *info = dev_get_drvdata(dev); + u8 buf[TIME_NUM]; + int ret; + + ret = data_calc(buf, tm, TIME_NUM); + + if (ret < 0) + return ret; + ret = max8907c_reg_bulk_write(info->i2c, MAX8907C_REG_RTC_SEC, TIME_NUM, buf); + + return ret; +} + +static int max8907c_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8907c_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = max8907c_reg_bulk_read(info->i2c, MAX8907C_REG_ALARM0_SEC, TIME_NUM, buf); + if (ret < 0) + return ret; + ret = tm_calc(&alrm->time, buf, TIME_NUM); + if (ret < 0) + return ret; + ret = max8907c_reg_read(info->i2c, MAX8907C_REG_RTC_IRQ_MASK); + if (ret < 0) + return ret; + if ((ret & ALARM0_IRQ) == 0) + alrm->enabled = 1; + else + alrm->enabled = 0; + ret = max8907c_reg_read(info->i2c, MAX8907C_REG_RTC_STATUS); + if (ret < 0) + return ret; + if (ret & ALARM0_STATUS) + alrm->pending = 1; + else + alrm->pending = 0; + + return ret; +} + +static int max8907c_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8907c_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[TIME_NUM]; + int ret; + + ret = data_calc(buf, &alrm->time, TIME_NUM); + if (ret < 0) + return ret; + ret = max8907c_reg_bulk_write(info->i2c, MAX8907C_REG_ALARM0_SEC, TIME_NUM, buf); + if (ret < 0) + return ret; + /* only enable alarm on year/month/day/hour/min/sec */ + ret = max8907c_reg_write(info->i2c, MAX8907C_REG_ALARM0_CNTL, 0x77); + + return ret; +} + +static const struct rtc_class_ops max8907c_rtc_ops = { + .read_time = max8907c_rtc_read_time, + .set_time = max8907c_rtc_set_time, + .read_alarm = max8907c_rtc_read_alarm, + .set_alarm = max8907c_rtc_set_alarm, +}; + +static int max8907c_rtc_probe(struct platform_device *pdev) +{ + struct max8907c *chip = dev_get_drvdata(pdev->dev.parent); + struct max8907c_rtc_info *info; + int irq, ret; + + info = kzalloc(sizeof(struct max8907c_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->i2c = chip->i2c_rtc; + info->chip = chip; + + irq = chip->irq_base + MAX8907C_IRQ_RTC_ALARM0; + + ret = request_threaded_irq(irq, NULL, rtc_update_handler, + IRQF_ONESHOT, "rtc-alarm0", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + irq, ret); + goto out_irq; + } + + dev_set_drvdata(&pdev->dev, info); + info->rtc_dev = rtc_device_register("max8907c-rtc", &pdev->dev, + &max8907c_rtc_ops, THIS_MODULE); + ret = PTR_ERR(info->rtc_dev); + if (IS_ERR(info->rtc_dev)) { + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + goto out_rtc; + } + + max8907c_set_bits(chip->i2c_power, MAX8907C_REG_SYSENSEL, 0x2, 0x2); + + platform_set_drvdata(pdev, info); + + device_init_wakeup(&pdev->dev, 1); + + return 0; +out_rtc: + free_irq(chip->irq_base + MAX8907C_IRQ_RTC_ALARM0, info); + +out_irq: + kfree(info); + return ret; +} + +static int max8907c_rtc_remove(struct platform_device *pdev) +{ + struct max8907c_rtc_info *info = platform_get_drvdata(pdev); + + if (info) { + free_irq(info->chip->irq_base + MAX8907C_IRQ_RTC_ALARM0, info); + + rtc_device_unregister(info->rtc_dev); + kfree(info); + } + return 0; +} + +#ifdef CONFIG_PM +static int max8907c_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev=&pdev->dev; + struct max8907c_rtc_info *info = platform_get_drvdata(pdev); + + if (device_may_wakeup(dev)) + enable_irq_wake(info->chip->irq_base + MAX8907C_IRQ_RTC_ALARM0); + return 0; +} + +static int max8907c_rtc_resume(struct platform_device *pdev) +{ + struct device *dev=&pdev->dev; + struct max8907c_rtc_info *info = platform_get_drvdata(pdev); + + if (device_may_wakeup(dev)) + disable_irq_wake(info->chip->irq_base + MAX8907C_IRQ_RTC_ALARM0); + return 0; +} +#endif + +static struct platform_driver max8907c_rtc_driver = { + .driver = { + .name = "max8907c-rtc", + .owner = THIS_MODULE, + }, + .probe = max8907c_rtc_probe, + .remove = max8907c_rtc_remove, +#ifdef CONFIG_PM + .suspend = max8907c_rtc_suspend, + .resume = max8907c_rtc_resume, +#endif +}; + +static int __init max8907c_rtc_init(void) +{ + return platform_driver_register(&max8907c_rtc_driver); +} +module_init(max8907c_rtc_init); + +static void __exit max8907c_rtc_exit(void) +{ + platform_driver_unregister(&max8907c_rtc_driver); +} +module_exit(max8907c_rtc_exit); + +MODULE_DESCRIPTION("Maxim MAX8907C RTC driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/rtc/rtc-palmas.c b/drivers/rtc/rtc-palmas.c index 50204d474eb7..ae747543450e 100644 --- a/drivers/rtc/rtc-palmas.c +++ b/drivers/rtc/rtc-palmas.c @@ -4,8 +4,9 @@ * RTC driver for TI Palma series devices like TPS65913, * TPS65914 power management IC. * - * Copyright (c) 2012, NVIDIA Corporation. + * Copyright (c) 2012 - 2013, NVIDIA CORPORATION. All rights reserved. * + * Author: Kasoju Mallikarjun <mkasoju@nvidia.com> * Author: Laxman Dewangan <ldewangan@nvidia.com> * * This program is free software; you can redistribute it and/or @@ -73,6 +74,9 @@ static int palmas_rtc_read_time(struct device *dev, struct rtc_time *tm) tm->tm_mon = bcd2bin(rtc_data[4]) - 1; tm->tm_year = bcd2bin(rtc_data[5]) + 100; + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); return ret; } @@ -89,6 +93,10 @@ static int palmas_rtc_set_time(struct device *dev, struct rtc_time *tm) rtc_data[4] = bin2bcd(tm->tm_mon + 1); rtc_data[5] = bin2bcd(tm->tm_year - 100); + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + /* Stop RTC while updating the RTC time registers */ ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG, PALMAS_RTC_CTRL_REG_STOP_RTC, 0); @@ -144,6 +152,10 @@ static int palmas_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) alm->time.tm_mon = bcd2bin(alarm_data[4]) - 1; alm->time.tm_year = bcd2bin(alarm_data[5]) + 100; + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", __func__, + alm->time.tm_year, alm->time.tm_mon, alm->time.tm_mday, + alm->time.tm_hour, alm->time.tm_min, alm->time.tm_sec); + ret = palmas_read(palmas, PALMAS_RTC_BASE, PALMAS_RTC_INTERRUPTS_REG, &int_val); if (ret < 0) { @@ -175,6 +187,10 @@ static int palmas_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) alarm_data[4] = bin2bcd(alm->time.tm_mon + 1); alarm_data[5] = bin2bcd(alm->time.tm_year - 100); + dev_dbg(dev, "%s() %d %d %d %d %d %d\n", __func__, + alm->time.tm_year, alm->time.tm_mon, alm->time.tm_mday, + alm->time.tm_hour, alm->time.tm_min, alm->time.tm_sec); + ret = palmas_bulk_write(palmas, PALMAS_RTC_BASE, PALMAS_ALARM_SECONDS_REG, alarm_data, PALMAS_NUM_TIME_REGS); if (ret < 0) { @@ -237,12 +253,33 @@ static int palmas_rtc_probe(struct platform_device *pdev) { struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); struct palmas_rtc *palmas_rtc = NULL; + struct palmas_platform_data *palmas_pdata; + struct palmas_rtc_platform_data *rtc_pdata = NULL; int ret; + bool enable_bb_charging = false; + bool high_bb_charging; + + + palmas_pdata = dev_get_platdata(pdev->dev.parent); + if (palmas_pdata) + rtc_pdata = palmas_pdata->rtc_pdata; + + if (rtc_pdata) { + enable_bb_charging = rtc_pdata->backup_battery_chargeable; + high_bb_charging = rtc_pdata->backup_battery_charge_high_current; + } else if (pdev->dev.of_node) { + enable_bb_charging = of_property_read_bool(pdev->dev.of_node, + "ti,backup-battery-chargeable"); + high_bb_charging = of_property_read_bool(pdev->dev.of_node, + "ti,backup-battery-charge-high-current"); + } palmas_rtc = devm_kzalloc(&pdev->dev, sizeof(struct palmas_rtc), GFP_KERNEL); - if (!palmas_rtc) + if (!palmas_rtc) { + dev_err(&pdev->dev, "Memory allocation failed.\n"); return -ENOMEM; + } /* Clear pending interrupts */ ret = palmas_clear_interrupts(&pdev->dev); @@ -251,9 +288,44 @@ static int palmas_rtc_probe(struct platform_device *pdev) return ret; } + palmas->rtc = palmas_rtc; palmas_rtc->dev = &pdev->dev; platform_set_drvdata(pdev, palmas_rtc); + if (enable_bb_charging) { + unsigned reg = PALMAS_BACKUP_BATTERY_CTRL_BBS_BBC_LOW_ICHRG; + + if (high_bb_charging) + reg = 0; + + ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE, + PALMAS_BACKUP_BATTERY_CTRL, + PALMAS_BACKUP_BATTERY_CTRL_BBS_BBC_LOW_ICHRG, reg); + if (ret < 0) { + dev_err(&pdev->dev, + "BACKUP_BATTERY_CTRL update failed, %d\n", ret); + return ret; + } + + ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE, + PALMAS_BACKUP_BATTERY_CTRL, + PALMAS_BACKUP_BATTERY_CTRL_BB_CHG_EN, + PALMAS_BACKUP_BATTERY_CTRL_BB_CHG_EN); + if (ret < 0) { + dev_err(&pdev->dev, + "BACKUP_BATTERY_CTRL update failed, %d\n", ret); + return ret; + } + } + + ret = palmas_write(palmas, PALMAS_RTC_BASE, + PALMAS_RTC_INTERRUPTS_REG, 0); + if (ret < 0) { + dev_err(&pdev->dev, "RTC_INTERRUPTS_REG write failed: %d\n", + ret); + return ret; + } + /* Start RTC */ ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG, PALMAS_RTC_CTRL_REG_STOP_RTC, @@ -263,33 +335,39 @@ static int palmas_rtc_probe(struct platform_device *pdev) return ret; } - palmas_rtc->irq = platform_get_irq(pdev, 0); + palmas_rtc->irq = palmas_irq_get_virq(palmas, PALMAS_RTC_ALARM_IRQ); + dev_dbg(&pdev->dev, "RTC interrupt %d\n", palmas_rtc->irq); - palmas_rtc->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, - &palmas_rtc_ops, THIS_MODULE); + device_init_wakeup(&pdev->dev, 1); + palmas_rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, + &palmas_rtc_ops, THIS_MODULE); if (IS_ERR(palmas_rtc->rtc)) { ret = PTR_ERR(palmas_rtc->rtc); dev_err(&pdev->dev, "RTC register failed, err = %d\n", ret); return ret; } - ret = devm_request_threaded_irq(&pdev->dev, palmas_rtc->irq, NULL, + ret = request_threaded_irq(palmas_rtc->irq, NULL, palmas_rtc_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(&pdev->dev), palmas_rtc); if (ret < 0) { dev_err(&pdev->dev, "IRQ request failed, err = %d\n", ret); + rtc_device_unregister(palmas_rtc->rtc); return ret; } - device_set_wakeup_capable(&pdev->dev, 1); return 0; } static int palmas_rtc_remove(struct platform_device *pdev) { + struct palmas_rtc *palmas_rtc = platform_get_drvdata(pdev); + palmas_rtc_alarm_irq_enable(&pdev->dev, 0); + free_irq(palmas_rtc->irq, palmas_rtc); + rtc_device_unregister(palmas_rtc->rtc); return 0; } @@ -298,8 +376,20 @@ static int palmas_rtc_suspend(struct device *dev) { struct palmas_rtc *palmas_rtc = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { + int ret; + struct rtc_wkalrm alm; + enable_irq_wake(palmas_rtc->irq); + ret = palmas_rtc_read_alarm(dev, &alm); + if (!ret) + dev_info(dev, "%s() alrm %d time %d %d %d %d %d %d\n", + __func__, alm.enabled, + alm.time.tm_year, alm.time.tm_mon, + alm.time.tm_mday, alm.time.tm_hour, + alm.time.tm_min, alm.time.tm_sec); + } + return 0; } @@ -307,8 +397,18 @@ static int palmas_rtc_resume(struct device *dev) { struct palmas_rtc *palmas_rtc = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { + struct rtc_time tm; + int ret; + disable_irq_wake(palmas_rtc->irq); + ret = palmas_rtc_read_time(dev, &tm); + if (!ret) + dev_info(dev, "%s() %d %d %d %d %d %d\n", + __func__, tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } + return 0; } #endif @@ -340,5 +440,6 @@ module_platform_driver(palmas_rtc_driver); MODULE_ALIAS("platform:palmas_rtc"); MODULE_DESCRIPTION("TI PALMAS series RTC driver"); +MODULE_AUTHOR("Kasoju Mallikarjun <mkasoju@nvidia.com>"); MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-ricoh583.c b/drivers/rtc/rtc-ricoh583.c new file mode 100644 index 000000000000..f960ffa4cbe5 --- /dev/null +++ b/drivers/rtc/rtc-ricoh583.c @@ -0,0 +1,404 @@ +/* + * drivers/rtc/rtc_ricoh583.c + * + * rtc driver for ricoh rc5t583 pmu + * + * copyright (c) 2011, nvidia corporation. + * + * 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. + * + * this program is distributed in the hope that it will be useful, but without + * any warranty; without even the implied warranty of merchantability or + * fitness for a particular purpose. see the gnu general public license for + * more details. + * + * you should have received a copy of the gnu general public license along + * with this program; if not, write to the free software foundation, inc., + * 51 franklin street, fifth floor, boston, ma 02110-1301, usa. + */ + +/* #define debug 1 */ +/* #define verbose_debug 1 */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mfd/ricoh583.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define rtc_ctrl1 0xED +#define rtc_ctrl2 0xEE +#define rtc_seconds_reg 0xE0 +#define rtc_alarm_y 0xF0 +#define rtc_adjust 0xE7 + +/* +linux rtc driver refers 1900 as base year in many calculations. +(e.g. refer drivers/rtc/rtc-lib.c) +*/ +#define os_ref_year 1900 + +/* + pmu rtc have only 2 nibbles to store year information, so using an + offset of 100 to set the base year as 2000 for our driver. +*/ +#define rtc_year_offset 100 + +struct ricoh583_rtc { + unsigned long epoch_start; + int irq; + struct rtc_device *rtc; + bool irq_en; +}; + +static int ricoh583_read_regs(struct device *dev, int reg, int len, + uint8_t *val) +{ + int ret; + + ret = ricoh583_bulk_reads(dev->parent, reg, len, val); + if (ret < 0) { + dev_err(dev->parent, "\n %s failed reading from 0x%02x\n", + __func__, reg); + WARN_ON(1); + } + return ret; +} + +static int ricoh583_write_regs(struct device *dev, int reg, int len, + uint8_t *val) +{ + int ret; + ret = ricoh583_bulk_writes(dev->parent, reg, len, val); + if (ret < 0) { + dev_err(dev->parent, "\n %s failed writing\n", __func__); + WARN_ON(1); + } + + return ret; +} + +static int ricoh583_rtc_valid_tm(struct device *dev, struct rtc_time *tm) +{ + if (tm->tm_year >= (rtc_year_offset + 99) + || tm->tm_mon > 12 + || tm->tm_mday < 1 + || tm->tm_mday > rtc_month_days(tm->tm_mon, + tm->tm_year + os_ref_year) + || tm->tm_hour >= 24 + || tm->tm_min >= 60 + || tm->tm_sec >= 60) { + dev_err(dev->parent, "\n returning error due to time" + "%d/%d/%d %d:%d:%d", tm->tm_mon, tm->tm_mday, + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec); + return -EINVAL; + } + return 0; +} + +static u8 dec2bcd(u8 dec) +{ + return ((dec/10)<<4)+(dec%10); +} + +static u8 bcd2dec(u8 bcd) +{ + return (bcd >> 4)*10+(bcd & 0xf); +} + +static void convert_bcd_to_decimal(u8 *buf, u8 len) +{ + int i = 0; + for (i = 0; i < len; i++) + buf[i] = bcd2dec(buf[i]); +} + +static void convert_decimal_to_bcd(u8 *buf, u8 len) +{ + int i = 0; + for (i = 0; i < len; i++) + buf[i] = dec2bcd(buf[i]); +} + +static void print_time(struct device *dev, struct rtc_time *tm) +{ + dev_info(dev, "rtc-time : %d/%d/%d %d:%d\n", + (tm->tm_mon + 1), tm->tm_mday, (tm->tm_year + os_ref_year), + tm->tm_hour, tm->tm_min); +} + +static int ricoh583_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + u8 buff[7]; + int err; + err = ricoh583_read_regs(dev, rtc_seconds_reg, sizeof(buff), buff); + if (err < 0) { + dev_err(dev, "\n %s :: failed to read time\n", __FILE__); + return err; + } + convert_bcd_to_decimal(buff, sizeof(buff)); + tm->tm_sec = buff[0]; + tm->tm_min = buff[1]; + tm->tm_hour = buff[2]; + tm->tm_wday = buff[3]; + tm->tm_mday = buff[4]; + tm->tm_mon = buff[5] - 1; + tm->tm_year = buff[6] + rtc_year_offset; + print_time(dev, tm); + return ricoh583_rtc_valid_tm(dev, tm); +} + +static int ricoh583_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + u8 buff[7]; + int err; + + print_time(dev, tm); + buff[0] = tm->tm_sec; + buff[1] = tm->tm_min; + buff[2] = tm->tm_hour; + buff[3] = tm->tm_wday; + buff[4] = tm->tm_mday; + buff[5] = tm->tm_mon + 1; + buff[6] = tm->tm_year - rtc_year_offset; + + convert_decimal_to_bcd(buff, sizeof(buff)); + err = ricoh583_write_regs(dev, rtc_seconds_reg, sizeof(buff), buff); + if (err < 0) { + dev_err(dev->parent, "\n failed to program new time\n"); + return err; + } + + return 0; +} +static int ricoh583_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm); + +static int ricoh583_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct ricoh583_rtc *rtc = dev_get_drvdata(dev); + unsigned long seconds; + u8 buff[5]; + int err; + struct rtc_time tm; + + if (rtc->irq == -1) + return -EIO; + + rtc_tm_to_time(&alrm->time, &seconds); + ricoh583_rtc_read_time(dev, &tm); + rtc_tm_to_time(&tm, &rtc->epoch_start); + /* + work around: As YAL does not provide the seconds register, + program minute register to next minute, in cases when alarm + is requested within a minute from the current time. + */ + if (seconds - rtc->epoch_start < 60) + alrm->time.tm_min += 1; + dev_info(dev->parent, "\n setting alarm to requested time::\n"); + print_time(dev->parent, &alrm->time); + + if (WARN_ON(alrm->enabled && (seconds < rtc->epoch_start))) { + dev_err(dev->parent, "\n can't set alarm to requested time\n"); + return -EINVAL; + } + + if (alrm->enabled && !rtc->irq_en) + rtc->irq_en = true; + else if (!alrm->enabled && rtc->irq_en) + rtc->irq_en = false; + + buff[0] = alrm->time.tm_min; + buff[1] = alrm->time.tm_hour; + buff[2] = alrm->time.tm_mday; + buff[3] = alrm->time.tm_mon + 1; + buff[4] = alrm->time.tm_year - rtc_year_offset; + convert_decimal_to_bcd(buff, sizeof(buff)); + err = ricoh583_write_regs(dev, rtc_alarm_y, sizeof(buff), buff); + if (err) { + dev_err(dev->parent, "\n unable to set alarm\n"); + return -EBUSY; + } + buff[0] = 0x20; /* to enable alarm_y */ + buff[1] = 0x20; /* to enable 24-hour format */ + err = ricoh583_write_regs(dev, rtc_ctrl1, 2, buff); + if (err) { + dev_err(dev, "failed programming rtc ctrl regs\n"); + return -EBUSY; + } +return err; +} + +static int ricoh583_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + u8 buff[5]; + int err; + + err = ricoh583_read_regs(dev, rtc_alarm_y, sizeof(buff), buff); + if (err) + return err; + convert_bcd_to_decimal(buff, sizeof(buff)); + + alrm->time.tm_min = buff[0]; + alrm->time.tm_hour = buff[1]; + alrm->time.tm_mday = buff[2]; + alrm->time.tm_mon = buff[3] - 1; + alrm->time.tm_year = buff[4] + rtc_year_offset; + + dev_info(dev->parent, "\n getting alarm time::\n"); + print_time(dev, &alrm->time); + + return 0; +} + +static const struct rtc_class_ops ricoh583_rtc_ops = { + .read_time = ricoh583_rtc_read_time, + .set_time = ricoh583_rtc_set_time, + .set_alarm = ricoh583_rtc_set_alarm, + .read_alarm = ricoh583_rtc_read_alarm, +}; + +static irqreturn_t ricoh583_rtc_irq(int irq, void *data) +{ + struct device *dev = data; + struct ricoh583_rtc *rtc = dev_get_drvdata(dev); + u8 reg; + int err; + + /* clear alarm-Y status bits.*/ + err = ricoh583_read_regs(dev, rtc_ctrl2, 1, ®); + if (err) { + dev_err(dev->parent, "unable to read rtc_ctrl2 reg\n"); + return -EBUSY; + } + reg &= ~0x8; + err = ricoh583_write_regs(dev, rtc_ctrl2, 1, ®); + if (err) { + dev_err(dev->parent, "unable to program rtc_status reg\n"); + return -EBUSY; + } + + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int ricoh583_rtc_probe(struct platform_device *pdev) +{ + struct ricoh583_rtc_platform_data *pdata = pdev->dev.platform_data; + struct ricoh583_rtc *rtc; + struct rtc_time tm; + int err; + u8 reg[2]; + rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); + + if (!rtc) + return -ENOMEM; + + rtc->irq = -1; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data specified\n"); + return -EINVAL; + } + + if (pdata->irq < 0) + dev_err(&pdev->dev, "\n no irq specified, wakeup is disabled\n"); + + dev_set_drvdata(&pdev->dev, rtc); + device_init_wakeup(&pdev->dev, 1); + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, + &ricoh583_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc->rtc)) { + err = PTR_ERR(rtc->rtc); + goto fail; + } + reg[0] = 0; /* clearing RTC Adjust register */ + err = ricoh583_write_regs(&pdev->dev, rtc_adjust, 1, reg); + if (err) { + dev_err(&pdev->dev, "unable to program rtc_adjust reg\n"); + return -EBUSY; + } + + reg[0] = 0x20; /* to enable alarm_y */ + reg[1] = 0x20; /* to enable 24-hour format */ + err = ricoh583_write_regs(&pdev->dev, rtc_ctrl1, 2, reg); + if (err) { + dev_err(&pdev->dev, "failed rtc setup\n"); + return -EBUSY; + } + + ricoh583_rtc_read_time(&pdev->dev, &tm); + if (ricoh583_rtc_valid_tm(&pdev->dev, &tm)) { + if (pdata->time.tm_year < 2000 || pdata->time.tm_year > 2100) { + memset(&pdata->time, 0, sizeof(pdata->time)); + pdata->time.tm_year = rtc_year_offset; + pdata->time.tm_mday = 1; + } else + pdata->time.tm_year -= os_ref_year; + ricoh583_rtc_set_time(&pdev->dev, &pdata->time); + } + if (pdata && (pdata->irq >= 0)) { + rtc->irq = pdata->irq; + err = request_threaded_irq(pdata->irq, NULL, ricoh583_rtc_irq, + IRQF_ONESHOT, "rtc_ricoh583", + &pdev->dev); + if (err) { + dev_err(&pdev->dev, "request IRQ:%d fail\n", rtc->irq); + rtc->irq = -1; + } else { + device_init_wakeup(&pdev->dev, 1); + enable_irq_wake(rtc->irq); + } + } + return 0; + +fail: + if (!IS_ERR_OR_NULL(rtc->rtc)) + rtc_device_unregister(rtc->rtc); + kfree(rtc); + return err; +} + +static int ricoh583_rtc_remove(struct platform_device *pdev) +{ + struct ricoh583_rtc *rtc = dev_get_drvdata(&pdev->dev); + + if (rtc->irq != -1) + free_irq(rtc->irq, rtc); + rtc_device_unregister(rtc->rtc); + kfree(rtc); + return 0; +} + +static struct platform_driver ricoh583_rtc_driver = { + .driver = { + .name = "rtc_ricoh583", + .owner = THIS_MODULE, + }, + .probe = ricoh583_rtc_probe, + .remove = ricoh583_rtc_remove, +}; + +static int __init ricoh583_rtc_init(void) +{ + return platform_driver_register(&ricoh583_rtc_driver); +} +module_init(ricoh583_rtc_init); + +static void __exit ricoh583_rtc_exit(void) +{ + platform_driver_unregister(&ricoh583_rtc_driver); +} +module_exit(ricoh583_rtc_exit); + +MODULE_DESCRIPTION("RICOH PMU ricoh583 RTC driver"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc_ricoh583"); diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c index 76af92ad5a8a..9168a6b83f36 100644 --- a/drivers/rtc/rtc-tegra.c +++ b/drivers/rtc/rtc-tegra.c @@ -1,7 +1,8 @@ /* * An RTC driver for the NVIDIA Tegra 200 series internal RTC. * - * Copyright (c) 2010, NVIDIA Corporation. + * Copyright (c) 2010-2011, NVIDIA Corporation. + * Copyright (c) 2010 Jon Mayo <jmayo@nvidia.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 diff --git a/drivers/rtc/rtc-tps6591x.c b/drivers/rtc/rtc-tps6591x.c new file mode 100644 index 000000000000..1897be34730b --- /dev/null +++ b/drivers/rtc/rtc-tps6591x.c @@ -0,0 +1,567 @@ +/* + * drivers/rtc/rtc_tps6591x.c + * + * RTC driver for TI TPS6591x + * + * Copyright (c) 2011-2012, NVIDIA Corporation. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* #define DEBUG 1 */ +/* #define VERBOSE_DEBUG 1 */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mfd/tps6591x.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define RTC_CTRL 0x10 +#define RTC_STATUS 0x11 +#define RTC_SECONDS_REG 0x0 +#define RTC_ALARM 0x8 +#define RTC_INT 0x12 +#define RTC_RESET_STATUS 0x16 +#define RTC_BBCH_REG 0x39 + +#define RTC_BBCH_SEL 0x02 +#define RTC_BBCH_EN 0x01 +#define ENABLE_ALARM_INT 0x8 +#define RTC_RESET_VALUE 0x80 +#define ALARM_INT_STATUS 0x40 + +/* +Linux RTC driver refers 1900 as base year in many calculations. +(e.g. refer drivers/rtc/rtc-lib.c) +*/ +#define OS_REF_YEAR 1900 + +/* + PMU RTC have only 2 nibbles to store year information, so using an offset + of 100 to set the base year as 2000 for our driver. +*/ +#define RTC_YEAR_OFFSET 100 + +struct tps6591x_rtc { + unsigned long epoch_start; + int irq; + struct rtc_device *rtc; + bool irq_en; +}; + +static int tps6591x_read_regs(struct device *dev, int reg, int len, + uint8_t *val) +{ + int ret; + + /* dummy read of STATUS_REG as per data sheet */ + ret = tps6591x_reads(dev->parent, RTC_STATUS, 1, val); + if (ret < 0) { + dev_err(dev->parent, "\n %s failed reading from RTC_STATUS\n", + __func__); + WARN_ON(1); + return ret; + } + + ret = tps6591x_reads(dev->parent, reg, len, val); + if (ret < 0) { + dev_err(dev->parent, "\n %s failed reading from 0x%02x\n", + __func__, reg); + WARN_ON(1); + return ret; + } + return 0; +} + +static int tps6591x_write_regs(struct device *dev, int reg, int len, + uint8_t *val) +{ + int ret; + ret = tps6591x_writes(dev->parent, reg, len, val); + if (ret < 0) { + dev_err(dev->parent, "\n %s failed writing\n", __func__); + WARN_ON(1); + return ret; + } + + return 0; +} + +static int tps6591x_rtc_valid_tm(struct rtc_time *tm) +{ + if (tm->tm_year >= (RTC_YEAR_OFFSET + 99) + || tm->tm_year < (RTC_YEAR_OFFSET) + || tm->tm_mon >= 12 + || tm->tm_mday < 1 + || tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + OS_REF_YEAR) + || tm->tm_hour >= 24 + || tm->tm_min >= 60 + || tm->tm_sec >= 60) + return -EINVAL; + return 0; +} + +static u8 dec2bcd(u8 dec) +{ + return ((dec/10)<<4)+(dec%10); +} + +static u8 bcd2dec(u8 bcd) +{ + return (bcd >> 4)*10+(bcd & 0xF); +} + +static void convert_bcd_to_decimal(u8 *buf, u8 len) +{ + int i = 0; + for (i = 0; i < len; i++) + buf[i] = bcd2dec(buf[i]); +} + +static void convert_decimal_to_bcd(u8 *buf, u8 len) +{ + int i = 0; + for (i = 0; i < len; i++) + buf[i] = dec2bcd(buf[i]); +} + +static void print_time(struct device *dev, struct rtc_time *tm) +{ + dev_info(dev, "RTC Time : %d/%d/%d %d:%d:%d\n", + (tm->tm_mon + 1), tm->tm_mday, (tm->tm_year + OS_REF_YEAR), + tm->tm_hour, tm->tm_min , tm->tm_sec); +} + +static int tps6591x_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + u8 buff[7]; + int err; + err = tps6591x_read_regs(dev, RTC_SECONDS_REG, sizeof(buff), buff); + if (err < 0) { + dev_err(dev, "\n %s :: failed to read time\n", __FILE__); + return err; + } + convert_bcd_to_decimal(buff, sizeof(buff)); + tm->tm_sec = buff[0]; + tm->tm_min = buff[1]; + tm->tm_hour = buff[2]; + tm->tm_mday = buff[3]; + tm->tm_mon = buff[4] - 1; + tm->tm_year = buff[5] + RTC_YEAR_OFFSET; + tm->tm_wday = buff[6]; + print_time(dev, tm); + return tps6591x_rtc_valid_tm(tm); +} + +static int tps6591x_rtc_stop(struct device *dev) +{ + u8 reg = 0; + u8 retries = 0; + int err; + do { + err = tps6591x_read_regs(dev, RTC_CTRL, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to read RTC_CTRL reg\n"); + return err; + } + + /* clear STOP bit alone */ + reg &= ~0x1; + + err = tps6591x_write_regs(dev, RTC_CTRL, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to program RTC_CTRL reg\n"); + return err; + } + + err = tps6591x_read_regs(dev, RTC_STATUS, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to read RTC_CTRL reg\n"); + return err; + } + /* FixMe: Is allowing up to 5 retries sufficient?? */ + if (retries++ == 5) { + dev_err(dev->parent, "\n failed to stop RTC\n"); + return -EBUSY; + } + } while (reg & 2); + return 0; +} + +static int tps6591x_rtc_start(struct device *dev) +{ + u8 reg = 0; + u8 retries = 0; + int err; + + do { + err = tps6591x_read_regs(dev, RTC_CTRL, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to read RTC_CTRL reg\n"); + return err; + } + + /* set STOP bit alone */ + reg |= 0x1; + + err = tps6591x_write_regs(dev, RTC_CTRL, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to program RTC_CTRL reg\n"); + return err; + } + + err = tps6591x_read_regs(dev, RTC_STATUS, 1, ®); + if (err < 0) { + dev_err(dev->parent, "\n failed to read RTC_CTRL reg\n"); + return err; + } + /* FixMe: Is allowing up to 5 retries sufficient?? */ + if (retries++ == 5) { + dev_err(dev->parent, "\n failed to stop RTC\n"); + return -EBUSY; + } + } while (!(reg & 2)); + return 0; +} + + +static int tps6591x_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + u8 buff[7]; + int err; + + err = tps6591x_rtc_valid_tm(tm); + if (err < 0) { + dev_err(dev->parent, "\n Invalid Time\n"); + return err; + } + + buff[0] = tm->tm_sec; + buff[1] = tm->tm_min; + buff[2] = tm->tm_hour; + buff[3] = tm->tm_mday; + buff[4] = tm->tm_mon + 1; + buff[5] = tm->tm_year % RTC_YEAR_OFFSET; + buff[6] = tm->tm_wday; + + print_time(dev, tm); + convert_decimal_to_bcd(buff, sizeof(buff)); + err = tps6591x_rtc_stop(dev); + if (err < 0) { + dev_err(dev->parent, "\n failed to clear RTC_ENABLE\n"); + return err; + } + + err = tps6591x_write_regs(dev, RTC_SECONDS_REG, sizeof(buff), buff); + if (err < 0) { + dev_err(dev->parent, "\n failed to program new time\n"); + return err; + } + + err = tps6591x_rtc_start(dev); + if (err < 0) { + dev_err(dev->parent, "\n failed to set RTC_ENABLE\n"); + return err; + } + + return 0; +} + +static int tps6591x_rtc_alarm_irq_enable(struct device *dev, + unsigned int enable) +{ + struct tps6591x_rtc *rtc = dev_get_drvdata(dev); + u8 reg; + int err; + + if (rtc->irq == -1) + return -EIO; + + if (enable) { + if (rtc->irq_en == true) + return 0; + err = tps6591x_read_regs(dev, RTC_INT, 1, ®); + if (err) + return err; + reg |= 0x8; + err = tps6591x_write_regs(dev, RTC_INT, 1, ®); + if (err) + return err; + rtc->irq_en = true; + } else { + if (rtc->irq_en == false) + return 0; + err = tps6591x_read_regs(dev, RTC_INT, 1, ®); + if (err) + return err; + reg &= ~0x8; + err = tps6591x_write_regs(dev, RTC_INT, 1, ®); + if (err) + return err; + rtc->irq_en = false; + } + return 0; +} + +static int tps6591x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct tps6591x_rtc *rtc = dev_get_drvdata(dev); + unsigned long seconds; + u8 buff[6]; + int err; + struct rtc_time tm; + + if (rtc->irq == -1) + return -EIO; + + err = tps6591x_rtc_valid_tm(&alrm->time); + if (err < 0) { + dev_err(dev->parent, "\n Invalid alarm time\n"); + return err; + } + + dev_info(dev->parent, "\n setting alarm to requested time::\n"); + print_time(dev->parent, &alrm->time); + rtc_tm_to_time(&alrm->time, &seconds); + tps6591x_rtc_read_time(dev, &tm); + rtc_tm_to_time(&tm, &rtc->epoch_start); + + if (WARN_ON(alrm->enabled && (seconds < rtc->epoch_start))) { + dev_err(dev->parent, "\n can't set alarm to requested time\n"); + return -EINVAL; + } + + err = tps6591x_rtc_alarm_irq_enable(dev, alrm->enabled); + if(err) { + dev_err(dev->parent, "\n can't set alarm irq\n"); + return err; + } + + buff[0] = alrm->time.tm_sec; + buff[1] = alrm->time.tm_min; + buff[2] = alrm->time.tm_hour; + buff[3] = alrm->time.tm_mday; + buff[4] = alrm->time.tm_mon + 1; + buff[5] = alrm->time.tm_year % RTC_YEAR_OFFSET; + convert_decimal_to_bcd(buff, sizeof(buff)); + err = tps6591x_write_regs(dev, RTC_ALARM, sizeof(buff), buff); + if (err) + dev_err(dev->parent, "\n unable to program alarm\n"); + + return err; +} + +static int tps6591x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + u8 buff[6]; + int err; + + err = tps6591x_read_regs(dev, RTC_ALARM, sizeof(buff), buff); + if (err) + return err; + convert_bcd_to_decimal(buff, sizeof(buff)); + + alrm->time.tm_sec = buff[0]; + alrm->time.tm_min = buff[1]; + alrm->time.tm_hour = buff[2]; + alrm->time.tm_mday = buff[3]; + alrm->time.tm_mon = buff[4] - 1; + alrm->time.tm_year = buff[5] + RTC_YEAR_OFFSET; + + dev_info(dev->parent, "\n getting alarm time::\n"); + print_time(dev, &alrm->time); + + return 0; +} + +static const struct rtc_class_ops tps6591x_rtc_ops = { + .read_time = tps6591x_rtc_read_time, + .set_time = tps6591x_rtc_set_time, + .set_alarm = tps6591x_rtc_set_alarm, + .read_alarm = tps6591x_rtc_read_alarm, + .alarm_irq_enable = tps6591x_rtc_alarm_irq_enable, +}; + +static irqreturn_t tps6591x_rtc_irq(int irq, void *data) +{ + struct device *dev = data; + struct tps6591x_rtc *rtc = dev_get_drvdata(dev); + u8 reg; + int err; + + /* clear Alarm status bits.*/ + err = tps6591x_read_regs(dev, RTC_STATUS, 1, ®); + if (err) { + dev_err(dev->parent, "unable to read RTC_STATUS reg\n"); + return -EBUSY; + } + + reg = ALARM_INT_STATUS; + err = tps6591x_write_regs(dev, RTC_STATUS, 1, ®); + if (err) { + dev_err(dev->parent, "unable to program RTC_STATUS reg\n"); + return -EBUSY; + } + + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int tps6591x_rtc_probe(struct platform_device *pdev) +{ + struct tps6591x_rtc_platform_data *pdata = pdev->dev.platform_data; + struct tps6591x_rtc *rtc; + struct rtc_time tm; + int err; + u8 reg; + + rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); + + if (!rtc) + return -ENOMEM; + + rtc->irq = -1; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data specified\n"); + return -EINVAL; + } + + if (pdata->irq < 0) + dev_err(&pdev->dev, "\n no IRQ specified, wakeup is disabled\n"); + + dev_set_drvdata(&pdev->dev, rtc); + device_init_wakeup(&pdev->dev, 1); + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, + &tps6591x_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc->rtc)) { + err = PTR_ERR(rtc->rtc); + goto fail; + } + + if ((int)pdev && (int)&pdev->dev) + err = tps6591x_read_regs(&pdev->dev, RTC_STATUS, 1, ®); + else { + dev_err(&pdev->dev, "\n %s Input params incorrect\n", __func__); + return -EBUSY; + } + if (err) { + dev_err(&pdev->dev, "\n %s unable to read status\n", __func__); + return -EBUSY; + } + + reg = RTC_BBCH_SEL | RTC_BBCH_EN; + tps6591x_write_regs(&pdev->dev, RTC_BBCH_REG, 1, ®); + if (err) { + dev_err(&pdev->dev, "unable to program Charger reg\n"); + return -EBUSY; + } + + err = tps6591x_rtc_start(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "unable to start RTC\n"); + return -EBUSY; + } + + tps6591x_rtc_read_time(&pdev->dev, &tm); + + if (tps6591x_rtc_valid_tm(&tm) < 0) { + if (pdata->time.tm_year < 2000 || pdata->time.tm_year >= 2100) { + memset(&pdata->time, 0, sizeof(pdata->time)); + pdata->time.tm_year = 2000; + pdata->time.tm_mday = 1; + } + pdata->time.tm_year -= OS_REF_YEAR; + tps6591x_rtc_set_time(&pdev->dev, &pdata->time); + } + + reg = ALARM_INT_STATUS; + err = tps6591x_write_regs(&pdev->dev, RTC_STATUS, 1, ®); + if (err) { + dev_err(&pdev->dev, "unable to program RTC_STATUS reg\n"); + return -EBUSY; + } + + reg = ENABLE_ALARM_INT; + tps6591x_write_regs(&pdev->dev, RTC_INT, 1, ®); + if (err) { + dev_err(&pdev->dev, "unable to program Interrupt Mask reg\n"); + return -EBUSY; + } + + if (pdata && (pdata->irq >= 0)) { + rtc->irq = pdata->irq; + err = request_threaded_irq(pdata->irq, NULL, tps6591x_rtc_irq, + IRQF_ONESHOT, "rtc_tps6591x", + &pdev->dev); + if (err) { + dev_err(&pdev->dev, "request IRQ:%d fail\n", rtc->irq); + rtc->irq = -1; + } else { + device_init_wakeup(&pdev->dev, 1); + enable_irq_wake(rtc->irq); + } + } + return 0; + +fail: + if (!IS_ERR_OR_NULL(rtc->rtc)) + rtc_device_unregister(rtc->rtc); + kfree(rtc); + return err; +} + +static int tps6591x_rtc_remove(struct platform_device *pdev) +{ + struct tps6591x_rtc *rtc = dev_get_drvdata(&pdev->dev); + + if (rtc->irq != -1) + free_irq(rtc->irq, rtc); + rtc_device_unregister(rtc->rtc); + kfree(rtc); + return 0; +} + +static struct platform_driver tps6591x_rtc_driver = { + .driver = { + .name = "rtc_tps6591x", + .owner = THIS_MODULE, + }, + .probe = tps6591x_rtc_probe, + .remove = tps6591x_rtc_remove, +}; + +static int __init tps6591x_rtc_init(void) +{ + return platform_driver_register(&tps6591x_rtc_driver); +} +module_init(tps6591x_rtc_init); + +static void __exit tps6591x_rtc_exit(void) +{ + platform_driver_unregister(&tps6591x_rtc_driver); +} +module_exit(tps6591x_rtc_exit); + +MODULE_DESCRIPTION("TI TPS6591x RTC driver"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc_tps6591x"); |