diff mbox

[NEW,DRIVER,V3,5/8] DA9058 RTC driver

Message ID 201208151518.q7FFINX1011619@latitude.olech.com
State Superseded
Headers show

Commit Message

Anthony Olech Aug. 15, 2012, 3:05 p.m. UTC
This is the RTC component driver of the Dialog DA9058 PMIC.
This driver is just one component of the whole DA9058 PMIC driver.
It depends on the CORE component driver of the DA9058 MFD.

Signed-off-by: Anthony Olech <anthony.olech.opensource@diasemi.com>
Signed-off-by: David Dajun Chen <david.chen@diasemi.com>
---
 drivers/rtc/Kconfig      |   10 +
 drivers/rtc/Makefile     |    1 +
 drivers/rtc/rtc-da9058.c |  458 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 469 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-da9058.c

Comments

Lars-Peter Clausen Aug. 15, 2012, 6:36 p.m. UTC | #1
On 08/15/2012 05:05 PM, Anthony Olech wrote:
> This is the RTC component driver of the Dialog DA9058 PMIC.
> This driver is just one component of the whole DA9058 PMIC driver.
> It depends on the CORE component driver of the DA9058 MFD.

How much is this one actually different from the da9052 rtc core? I just had a
quick glance at them, but they seem rather similar.

> 
> Signed-off-by: Anthony Olech <anthony.olech.opensource@diasemi.com>
> Signed-off-by: David Dajun Chen <david.chen@diasemi.com>
> ---
>  drivers/rtc/Kconfig      |   10 +
>  drivers/rtc/Makefile     |    1 +
>  drivers/rtc/rtc-da9058.c |  458 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 469 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/rtc/rtc-da9058.c
> 
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 08cbdb9..21f5630 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -135,6 +135,16 @@ config RTC_DRV_88PM860X
>  	  This driver can also be built as a module. If so, the module
>  	  will be called rtc-88pm860x.
>  
> +config RTC_DRV_DA9058
> +	tristate "Dialog DA9058"
> +	depends on MFD_DA9058
> +	help
> +	  If you say yes here you will get support for the
> +	  RTC of the Dialog DA9058 PMIC.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called rtc-da9058.
> +
>  config RTC_DRV_DS1307
>  	tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025"
>  	help
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 2973921..b772f05 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -29,6 +29,7 @@ obj-$(CONFIG_RTC_DRV_CMOS)	+= rtc-cmos.o
>  obj-$(CONFIG_RTC_DRV_COH901331)	+= rtc-coh901331.o
>  obj-$(CONFIG_RTC_DRV_DA9052)	+= rtc-da9052.o
>  obj-$(CONFIG_RTC_DRV_DAVINCI)	+= rtc-davinci.o
> +obj-$(CONFIG_RTC_DRV_DA9058)	+= rtc-da9058.o
>  obj-$(CONFIG_RTC_DRV_DM355EVM)	+= rtc-dm355evm.o
>  obj-$(CONFIG_RTC_DRV_VRTC)	+= rtc-mrst.o
>  obj-$(CONFIG_RTC_DRV_DS1216)	+= rtc-ds1216.o
> diff --git a/drivers/rtc/rtc-da9058.c b/drivers/rtc/rtc-da9058.c
> new file mode 100644
> index 0000000..1b4e05b
> --- /dev/null
> +++ b/drivers/rtc/rtc-da9058.c
> @@ -0,0 +1,458 @@
> +/*
> + *  Copyright (C) 2012 Dialog Semiconductor Ltd.
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; either version 2 of the License, or
> + *  (at your option) any later version.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/rtc.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/core.h>
> +
> +#include <linux/mfd/da9058/version.h>
> +#include <linux/mfd/da9058/registers.h>
> +#include <linux/mfd/da9058/core.h>
> +#include <linux/mfd/da9058/irq.h>
> +#include <linux/mfd/da9058/rtc.h>
> +
> +/*
> + *  Limit values
> + */
> +#define DA9058_RTC_SECONDS_LIMIT		59
> +#define DA9058_RTC_MINUTES_LIMIT		59
> +#define DA9058_RTC_HOURS_LIMIT			23
> +#define DA9058_RTC_DAYS_LIMIT			31
> +#define DA9058_RTC_MONTHS_LIMIT			12
> +#define DA9058_RTC_YEARS_LIMIT			63
> +
> +struct da9058_rtc {
> +	struct da9058 *da9058;
> +	struct platform_device *pdev;
> +	struct rtc_device *rtc_dev;
> +	int alarm_irq;
> +	int tick_irq;
> +	int alarm_enabled;	/* used over suspend/resume */
> +};
> +
> +static int da9058_rtc_check_param(struct rtc_time *rtc_tm)
> +{
> +	if ((rtc_tm->tm_sec > DA9058_RTC_SECONDS_LIMIT) || (rtc_tm->tm_sec < 0))
> +		return -EIO;
> +
> +	if ((rtc_tm->tm_min > DA9058_RTC_MINUTES_LIMIT) || (rtc_tm->tm_min < 0))
> +		return -EIO;
> +
> +	if ((rtc_tm->tm_hour > DA9058_RTC_HOURS_LIMIT) || (rtc_tm->tm_hour < 0))
> +		return -EIO;
> +
> +	if ((rtc_tm->tm_mday > DA9058_RTC_DAYS_LIMIT) || (rtc_tm->tm_mday <= 0))
> +		return -EIO;
> +
> +	if ((rtc_tm->tm_mon > DA9058_RTC_MONTHS_LIMIT) || (rtc_tm->tm_mon <= 0))
> +		return -EIO;
> +
> +	if ((rtc_tm->tm_year > DA9058_RTC_YEARS_LIMIT) || (rtc_tm->tm_year < 0))
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int da9058_rtc_readtime(struct device *dev, struct rtc_time *tm)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +	struct da9058 *da9058 = rtc->da9058;
> +	u8 rtc_time[6];
> +	int ret;
> +
> +	ret = da9058_bulk_read(da9058, DA9058_COUNTS_REG, rtc_time, 6);
> +	if (ret)
> +		return ret;
> +
> +	tm->tm_sec = rtc_time[0] & DA9058_RTC_SECS_MASK;
> +
> +	tm->tm_min = rtc_time[1] & DA9058_RTC_MINS_MASK;
> +
> +	tm->tm_hour = rtc_time[2] & DA9058_RTC_HRS_MASK;
> +
> +	tm->tm_mday = (rtc_time[3] & DA9058_RTC_DAY_MASK);
> +
> +	tm->tm_mon = (rtc_time[4] & DA9058_RTC_MTH_MASK);
> +
> +	tm->tm_year = (rtc_time[5] & DA9058_RTC_YRS_MASK);
> +
> +	ret = da9058_rtc_check_param(tm);
> +
> +	if (ret)
> +		return ret;
> +
> +	tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon,
> +						tm->tm_year);
> +	tm->tm_year += 100;
> +	tm->tm_mon -= 1;
> +
> +	return 0;
> +}
> +
> +static int da9058_rtc_settime(struct device *dev, struct rtc_time *tm)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +	struct da9058 *da9058 = rtc->da9058;
> +	unsigned int rtc_ctrl, val;
> +	u8 rtc_time[6];
> +	int ret;
> +
> +	tm->tm_year -= 100;
> +	tm->tm_mon += 1;
> +
> +	ret = da9058_rtc_check_param(tm);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = da9058_reg_read(da9058, DA9058_COUNTS_REG, &rtc_ctrl);
> +	if (ret)
> +		return ret;
> +	rtc_ctrl &= ~DA9058_RTC_SECS_MASK;
> +
> +	rtc_time[0] = rtc_ctrl | tm->tm_sec;
> +	rtc_time[1] = tm->tm_min;
> +	rtc_time[2] = tm->tm_hour;
> +	rtc_time[3] = tm->tm_mday;
> +	rtc_time[4] = tm->tm_mon;
> +	rtc_time[5] = tm->tm_year;
> +
> +	ret = da9058_bulk_write(da9058, DA9058_COUNTS_REG, rtc_time, 6);
> +	if (ret) {
> +		dev_dbg(dev, "failed %d to write to RTC\n", ret);
> +		return ret;
> +	}
> +	ret = da9058_reg_read(da9058, DA9058_COUNTY_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	val &= DA9058_COUNTY_MONITOR;
> +	if (val)
> +		return 0;
> +
> +	ret = da9058_set_bits(da9058, DA9058_COUNTY_REG, DA9058_COUNTY_MONITOR);
> +
> +	return ret;
> +}
> +
> +static int da9058_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +	struct da9058 *da9058 = rtc->da9058;
> +	struct rtc_time *tm = &alrm->time;
> +	unsigned int val;
> +	u8 alm_time[6];
> +	int ret;
> +
> +	ret = da9058_bulk_read(da9058, DA9058_ALARMS_REG, alm_time, 6);
> +	if (ret)
> +		return ret;
> +
> +	tm->tm_min = alm_time[0] & DA9058_RTC_ALMSECS_MASK;
> +
> +	tm->tm_min = alm_time[1] & DA9058_RTC_ALMMINS_MASK;
> +
> +	tm->tm_hour = alm_time[2] & DA9058_RTC_ALMHRS_MASK;
> +
> +	tm->tm_mday = alm_time[3] & DA9058_RTC_ALMDAY_MASK;
> +
> +	tm->tm_mon = alm_time[4] & DA9058_RTC_ALMMTH_MASK;
> +
> +	tm->tm_year = alm_time[5] & DA9058_RTC_ALMYRS_MASK;
> +
> +	ret = da9058_rtc_check_param(tm);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	alrm->enabled = val & DA9058_ALARMY_ALARMON;
> +
> +	tm->tm_year += 100;
> +	tm->tm_mon -= 1;
> +
> +	return 0;
> +}
> +
> +static int da9058_rtc_stop_alarm(struct da9058_rtc *rtc)
> +{
> +	return da9058_clear_bits(rtc->da9058, DA9058_ALARMY_REG,
> +						DA9058_ALARMY_ALARMON);
> +}
> +
> +static int da9058_rtc_start_alarm(struct da9058_rtc *rtc)
> +{
> +	return da9058_set_bits(rtc->da9058, DA9058_ALARMY_REG,
> +						DA9058_ALARMY_ALARMON);
> +}
> +
> +static int da9058_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +	struct da9058 *da9058 = rtc->da9058;
> +	struct rtc_time *tm = &alrm->time;
> +	unsigned int rtc_ctrl;
> +	u8 alm_time[6];
> +	int ret;
> +
> +	tm->tm_year -= 100;
> +	tm->tm_mon += 1;
> +
> +	ret = da9058_rtc_check_param(tm);
> +	if (ret < 0)
> +		return ret;
> +
> +	memset(alm_time, 0, sizeof(alm_time));
> +
> +	if (tm->tm_sec != -1)
> +		alm_time[0] |= tm->tm_sec;
> +	else
> +		alm_time[0] |= DA9058_RTC_ALMSECS_MASK;
> +
> +	ret = da9058_reg_read(da9058, DA9058_ALARMMI_REG, &rtc_ctrl);
> +	if (ret)
> +		return ret;
> +	rtc_ctrl &= ~DA9058_RTC_ALMMINS_MASK;
> +
> +	if (tm->tm_min != -1)
> +		alm_time[1] = rtc_ctrl | tm->tm_min;
> +	else
> +		alm_time[1] = rtc_ctrl | DA9058_RTC_ALMMINS_MASK;
> +
> +	if (tm->tm_hour != -1)
> +		alm_time[2] |= tm->tm_hour;
> +	else
> +		alm_time[2] |= DA9058_RTC_ALMHRS_MASK;
> +
> +	if (tm->tm_mday != -1)
> +		alm_time[3] |= tm->tm_mday;
> +	else
> +		alm_time[3] |= DA9058_RTC_ALMDAY_MASK;
> +
> +	if (tm->tm_mon != -1)
> +		alm_time[4] |= tm->tm_mon;
> +	else
> +		alm_time[4] |= DA9058_RTC_ALMMTH_MASK;
> +
> +	ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &rtc_ctrl);
> +	if (ret)
> +		return ret;
> +
> +	rtc_ctrl &= ~DA9058_RTC_ALMYRS_MASK;
> +
> +	if (tm->tm_year != -1)
> +		alm_time[5] = rtc_ctrl | tm->tm_year;
> +	else
> +		alm_time[5] = rtc_ctrl | DA9058_RTC_ALMYRS_MASK;
> +
> +	ret = da9058_rtc_stop_alarm(rtc);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = da9058_bulk_write(da9058, DA9058_ALARMS_REG, alm_time, 6);
> +	if (ret)
> +		return ret;
> +
> +	if (alrm->enabled)
> +		ret = da9058_rtc_start_alarm(rtc);
> +
> +	return ret;
> +}
> +
> +static int da9058_rtc_alarm_irq_enable(struct device *dev,
> +						unsigned int enabled)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +
> +	if (enabled)
> +		return da9058_rtc_start_alarm(rtc);
> +	else
> +		return da9058_rtc_stop_alarm(rtc);
> +}
> +
> +static irqreturn_t da9058_rtc_timer_alarm_handler(int irq, void *data)
> +{
> +	struct da9058_rtc *rtc = data;
> +
> +	da9058_rtc_stop_alarm(rtc);
> +	rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t da9058_rtc_tick_alarm_handler(int irq, void *data)
> +{
> +	struct da9058_rtc *rtc = data;
> +
> +	rtc_update_irq(rtc->rtc_dev, 1, RTC_PF | RTC_IRQF);
> +
> +	return IRQ_HANDLED;
> +}
> +static int da9058_rtc_proc(struct device *dev, struct seq_file *seq)
> +{
> +	struct da9058_rtc *rtc = dev_get_drvdata(dev);
> +	struct da9058 *da9058 = rtc->da9058;
> +	unsigned int rtc_ctrl;
> +	int ret;
> +
> +	ret = da9058_reg_read(da9058, DA9058_ALARMY_REG, &rtc_ctrl);
> +
> +	seq_printf(seq, rtc_ctrl & DA9058_ALARMY_ALARMON ?
> +		"ALRM is running\n" : "ALRM is not running\n");
> +
> +	return 0;
> +}
> +
> +static const struct rtc_class_ops da9058_rtc_ops = {
> +	.read_time = da9058_rtc_readtime,
> +	.set_time = da9058_rtc_settime,
> +	.read_alarm = da9058_rtc_readalarm,
> +	.set_alarm = da9058_rtc_setalarm,
> +	.proc = da9058_rtc_proc,
> +	.alarm_irq_enable = da9058_rtc_alarm_irq_enable,
> +};
> +
> +static int da9058_rtc_probe(struct platform_device *pdev)
> +{
> +	struct da9058 *da9058 = dev_get_drvdata(pdev->dev.parent);
> +	const struct mfd_cell *cell = mfd_get_cell(pdev);
> +	struct da9058_rtc_pdata *rtc_pdata;
> +	struct da9058_rtc *rtc;
> +	int ret;
> +
> +	if (cell == NULL) {
> +		ret = -ENODEV;
> +		goto exit;
> +	}
> +
> +	rtc_pdata = cell->platform_data;
> +
> +	if (rtc_pdata == NULL) {
> +		ret = -EINVAL;
> +		goto exit;
> +	}
> +
> +	rtc = devm_kzalloc(&pdev->dev, sizeof(struct da9058_rtc), GFP_KERNEL);
> +	if (!rtc) {
> +		ret = -ENOMEM;
> +		goto exit;
> +	}
> +
> +	platform_set_drvdata(pdev, rtc);
> +
> +	rtc->da9058 = da9058;
> +	rtc->pdev = pdev;
> +	ret = da9058_clear_bits(da9058, DA9058_WAITCONT_REG,
> +						DA9058_WAITCONT_RTCCLOCK);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to set RTC running: %d\n", ret);
> +		goto unable_to_init_device;
> +	}
> +
> +	ret = da9058_set_bits(da9058, DA9058_COUNTY_REG, DA9058_COUNTY_MONITOR);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed No gating RTC: %d\n", ret);
> +		goto unable_to_init_device;
> +	}
> +
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	rtc->rtc_dev = rtc_device_register("da9058", &pdev->dev,
> +						&da9058_rtc_ops, THIS_MODULE);
> +	if (IS_ERR(rtc->rtc_dev)) {
> +		ret = PTR_ERR(rtc->rtc_dev);
> +		dev_err(&pdev->dev, "failed to register RTC: %d\n", ret);
> +		goto unable_to_register_device;
> +	}
> +
> +	rtc->alarm_irq = platform_get_irq(pdev, 0);
> +	if (rtc->alarm_irq < 0) {
> +		dev_err(&pdev->dev, "can not get RTC ALARM IRQ error=%d\n",
> +				rtc->alarm_irq);
> +		ret = -ENODEV;
> +		goto failed_to_get_alarm_irq;
> +	}
> +
> +	ret = request_threaded_irq(da9058_to_virt_irq_num(da9058,
> +							rtc->alarm_irq),
> +				NULL, da9058_rtc_timer_alarm_handler,
> +				IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +				"DA9058 RTC Timer Alarm", rtc);
> +
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"Failed to get rtc timer alarm IRQ %d: %d\n",
> +			rtc->alarm_irq, ret);
> +		goto unable_to_setup_timer_irq;
> +	}
> +
> +	rtc->tick_irq = platform_get_irq(pdev, 1);
> +	if (rtc->tick_irq < 0) {
> +		dev_err(&pdev->dev, "can not get RTC TICK IRQ error=%d\n",
> +				rtc->tick_irq);
> +		ret = -ENODEV;
> +		goto failed_to_get_tick_irq;
> +	}
> +	ret = request_threaded_irq(da9058_to_virt_irq_num(da9058,
> +							rtc->tick_irq),
> +				NULL, da9058_rtc_tick_alarm_handler,
> +				IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +				"DA9058 RTC Tick Alarm", rtc);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"Failed to get rtc timer alarm IRQ %d: %d\n",
> +			DA9058_IRQ_ETICK, ret);
> +		goto unable_to_setup_alarm_irq;
> +	}
> +	goto exit;
> +
> +failed_to_get_tick_irq:
> +unable_to_setup_alarm_irq:
> +	free_irq(da9058_to_virt_irq_num(da9058, rtc->alarm_irq), rtc);
> +failed_to_get_alarm_irq:
> +unable_to_setup_timer_irq:
> +	rtc_device_unregister(rtc->rtc_dev);
> +unable_to_register_device:
> +unable_to_init_device:
> +	platform_set_drvdata(pdev, NULL);
> +exit:
> +	return ret;
> +}
> +
> +static int __devexit da9058_rtc_remove(struct platform_device *pdev)
> +{
> +	struct da9058_rtc *rtc = platform_get_drvdata(pdev);
> +	struct da9058 *da9058 = rtc->da9058;
> +
> +	free_irq(da9058_to_virt_irq_num(da9058, rtc->alarm_irq), rtc);
> +	free_irq(da9058_to_virt_irq_num(da9058, rtc->tick_irq), rtc);
> +
> +	rtc_device_unregister(rtc->rtc_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver da9058_rtc_driver = {
> +	.probe = da9058_rtc_probe,
> +	.remove = __devexit_p(da9058_rtc_remove),
> +	.driver = {
> +		.name = "da9058-rtc",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(da9058_rtc_driver);
> +
> +MODULE_DESCRIPTION("Dialog DA9058 PMIC Real Time Clock Driver");
> +MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:da9058-rtc");
diff mbox

Patch

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