diff options
Diffstat (limited to 'drivers/rtc/rtc-as3722.c')
| -rw-r--r-- | drivers/rtc/rtc-as3722.c | 261 | 
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-as3722.c b/drivers/rtc/rtc-as3722.c new file mode 100644 index 00000000000..9f38eda6915 --- /dev/null +++ b/drivers/rtc/rtc-as3722.c @@ -0,0 +1,261 @@ +/* + * 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> + * Author: Laxman Dewangan <ldewangan@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 + * 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. + */ + +#include <linux/bcd.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/as3722.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/time.h> + +#define AS3722_RTC_START_YEAR	  2000 +struct as3722_rtc { +	struct rtc_device	*rtc; +	struct device		*dev; +	struct as3722		*as3722; +	int			alarm_irq; +	bool			irq_enable; +}; + +static void as3722_time_to_reg(u8 *rbuff, struct rtc_time *tm) +{ +	rbuff[0] = bin2bcd(tm->tm_sec); +	rbuff[1] = bin2bcd(tm->tm_min); +	rbuff[2] = bin2bcd(tm->tm_hour); +	rbuff[3] = bin2bcd(tm->tm_mday); +	rbuff[4] = bin2bcd(tm->tm_mon); +	rbuff[5] = bin2bcd(tm->tm_year - (AS3722_RTC_START_YEAR - 1900)); +} + +static void as3722_reg_to_time(u8 *rbuff, struct rtc_time *tm) +{ +	tm->tm_sec = bcd2bin(rbuff[0] & 0x7F); +	tm->tm_min = bcd2bin(rbuff[1] & 0x7F); +	tm->tm_hour = bcd2bin(rbuff[2] & 0x3F); +	tm->tm_mday = bcd2bin(rbuff[3] & 0x3F); +	tm->tm_mon = bcd2bin(rbuff[4] & 0x1F); +	tm->tm_year = (AS3722_RTC_START_YEAR - 1900) + bcd2bin(rbuff[5] & 0x7F); +	return; +} + +static int as3722_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); +	struct as3722 *as3722 = as3722_rtc->as3722; +	u8 as_time_array[6]; +	int ret; + +	ret = as3722_block_read(as3722, AS3722_RTC_SECOND_REG, +			6, as_time_array); +	if (ret < 0) { +		dev_err(dev, "RTC_SECOND reg block read failed %d\n", ret); +		return ret; +	} +	as3722_reg_to_time(as_time_array, tm); +	return 0; +} + +static int as3722_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); +	struct as3722 *as3722 = as3722_rtc->as3722; +	u8 as_time_array[6]; +	int ret; + +	if (tm->tm_year < (AS3722_RTC_START_YEAR - 1900)) +		return -EINVAL; + +	as3722_time_to_reg(as_time_array, tm); +	ret = as3722_block_write(as3722, AS3722_RTC_SECOND_REG, 6, +			as_time_array); +	if (ret < 0) +		dev_err(dev, "RTC_SECOND reg block write failed %d\n", ret); +	return ret; +} + +static int as3722_rtc_alarm_irq_enable(struct device *dev, +		unsigned int enabled) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); + +	if (enabled && !as3722_rtc->irq_enable) { +		enable_irq(as3722_rtc->alarm_irq); +		as3722_rtc->irq_enable = true; +	} else if (!enabled && as3722_rtc->irq_enable)  { +		disable_irq(as3722_rtc->alarm_irq); +		as3722_rtc->irq_enable = false; +	} +	return 0; +} + +static int as3722_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); +	struct as3722 *as3722 = as3722_rtc->as3722; +	u8 as_time_array[6]; +	int ret; + +	ret = as3722_block_read(as3722, AS3722_RTC_ALARM_SECOND_REG, 6, +			as_time_array); +	if (ret < 0) { +		dev_err(dev, "RTC_ALARM_SECOND block read failed %d\n", ret); +		return ret; +	} + +	as3722_reg_to_time(as_time_array, &alrm->time); +	return 0; +} + +static int as3722_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); +	struct as3722 *as3722 = as3722_rtc->as3722; +	u8 as_time_array[6]; +	int ret; + +	if (alrm->time.tm_year < (AS3722_RTC_START_YEAR - 1900)) +		return -EINVAL; + +	ret = as3722_rtc_alarm_irq_enable(dev, 0); +	if (ret < 0) { +		dev_err(dev, "Disable RTC alarm failed\n"); +		return ret; +	} + +	as3722_time_to_reg(as_time_array, &alrm->time); +	ret = as3722_block_write(as3722, AS3722_RTC_ALARM_SECOND_REG, 6, +			as_time_array); +	if (ret < 0) { +		dev_err(dev, "RTC_ALARM_SECOND block write failed %d\n", ret); +		return ret; +	} + +	if (alrm->enabled) +		ret = as3722_rtc_alarm_irq_enable(dev, alrm->enabled); +	return ret; +} + +static irqreturn_t as3722_alarm_irq(int irq, void *data) +{ +	struct as3722_rtc *as3722_rtc = data; + +	rtc_update_irq(as3722_rtc->rtc, 1, RTC_IRQF | RTC_AF); +	return IRQ_HANDLED; +} + +static const struct rtc_class_ops as3722_rtc_ops = { +	.read_time = as3722_rtc_read_time, +	.set_time = as3722_rtc_set_time, +	.read_alarm = as3722_rtc_read_alarm, +	.set_alarm = as3722_rtc_set_alarm, +	.alarm_irq_enable = as3722_rtc_alarm_irq_enable, +}; + +static int as3722_rtc_probe(struct platform_device *pdev) +{ +	struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); +	struct as3722_rtc *as3722_rtc; +	int ret; + +	as3722_rtc = devm_kzalloc(&pdev->dev, sizeof(*as3722_rtc), GFP_KERNEL); +	if (!as3722_rtc) +		return -ENOMEM; + +	as3722_rtc->as3722 = as3722; +	as3722_rtc->dev = &pdev->dev; +	platform_set_drvdata(pdev, as3722_rtc); + +	/* Enable the RTC to make sure it is running. */ +	ret = as3722_update_bits(as3722, AS3722_RTC_CONTROL_REG, +			AS3722_RTC_ON | AS3722_RTC_ALARM_WAKEUP_EN, +			AS3722_RTC_ON | AS3722_RTC_ALARM_WAKEUP_EN); +	if (ret < 0) { +		dev_err(&pdev->dev, "RTC_CONTROL reg write failed: %d\n", ret); +		return ret; +	} + +	device_init_wakeup(&pdev->dev, 1); + +	as3722_rtc->rtc = devm_rtc_device_register(&pdev->dev, "as3722-rtc", +				&as3722_rtc_ops, THIS_MODULE); +	if (IS_ERR(as3722_rtc->rtc)) { +		ret = PTR_ERR(as3722_rtc->rtc); +		dev_err(&pdev->dev, "RTC register failed: %d\n", ret); +		return ret; +	} + +	as3722_rtc->alarm_irq = platform_get_irq(pdev, 0); +	dev_info(&pdev->dev, "RTC interrupt %d\n", as3722_rtc->alarm_irq); + +	ret = devm_request_threaded_irq(&pdev->dev, as3722_rtc->alarm_irq, NULL, +			as3722_alarm_irq, IRQF_ONESHOT | IRQF_EARLY_RESUME, +			"rtc-alarm", as3722_rtc); +	if (ret < 0) { +		dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n", +				as3722_rtc->alarm_irq, ret); +		return ret; +	} +	disable_irq(as3722_rtc->alarm_irq); +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int as3722_rtc_suspend(struct device *dev) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		enable_irq_wake(as3722_rtc->alarm_irq); + +	return 0; +} + +static int as3722_rtc_resume(struct device *dev) +{ +	struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		disable_irq_wake(as3722_rtc->alarm_irq); +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(as3722_rtc_pm_ops, as3722_rtc_suspend, +			 as3722_rtc_resume); + +static struct platform_driver as3722_rtc_driver = { +	.probe = as3722_rtc_probe, +	.driver = { +		.name = "as3722-rtc", +		.pm = &as3722_rtc_pm_ops, +	}, +}; +module_platform_driver(as3722_rtc_driver); + +MODULE_DESCRIPTION("RTC driver for AS3722 PMICs"); +MODULE_ALIAS("platform:as3722-rtc"); +MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL");  | 
