diff options
Diffstat (limited to 'drivers/rtc/rtc-snvs.c')
| -rw-r--r-- | drivers/rtc/rtc-snvs.c | 350 | 
1 files changed, 350 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-snvs.c b/drivers/rtc/rtc-snvs.c new file mode 100644 index 00000000000..3c0da333f46 --- /dev/null +++ b/drivers/rtc/rtc-snvs.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> + +/* These register offsets are relative to LP (Low Power) range */ +#define SNVS_LPCR		0x04 +#define SNVS_LPSR		0x18 +#define SNVS_LPSRTCMR		0x1c +#define SNVS_LPSRTCLR		0x20 +#define SNVS_LPTAR		0x24 +#define SNVS_LPPGDR		0x30 + +#define SNVS_LPCR_SRTC_ENV	(1 << 0) +#define SNVS_LPCR_LPTA_EN	(1 << 1) +#define SNVS_LPCR_LPWUI_EN	(1 << 3) +#define SNVS_LPSR_LPTA		(1 << 0) + +#define SNVS_LPPGDR_INIT	0x41736166 +#define CNTR_TO_SECS_SH		15 + +struct snvs_rtc_data { +	struct rtc_device *rtc; +	void __iomem *ioaddr; +	int irq; +	spinlock_t lock; +}; + +static u32 rtc_read_lp_counter(void __iomem *ioaddr) +{ +	u64 read1, read2; + +	do { +		read1 = readl(ioaddr + SNVS_LPSRTCMR); +		read1 <<= 32; +		read1 |= readl(ioaddr + SNVS_LPSRTCLR); + +		read2 = readl(ioaddr + SNVS_LPSRTCMR); +		read2 <<= 32; +		read2 |= readl(ioaddr + SNVS_LPSRTCLR); +	} while (read1 != read2); + +	/* Convert 47-bit counter to 32-bit raw second count */ +	return (u32) (read1 >> CNTR_TO_SECS_SH); +} + +static void rtc_write_sync_lp(void __iomem *ioaddr) +{ +	u32 count1, count2, count3; +	int i; + +	/* Wait for 3 CKIL cycles */ +	for (i = 0; i < 3; i++) { +		do { +			count1 = readl(ioaddr + SNVS_LPSRTCLR); +			count2 = readl(ioaddr + SNVS_LPSRTCLR); +		} while (count1 != count2); + +		/* Now wait until counter value changes */ +		do { +			do { +				count2 = readl(ioaddr + SNVS_LPSRTCLR); +				count3 = readl(ioaddr + SNVS_LPSRTCLR); +			} while (count2 != count3); +		} while (count3 == count1); +	} +} + +static int snvs_rtc_enable(struct snvs_rtc_data *data, bool enable) +{ +	unsigned long flags; +	int timeout = 1000; +	u32 lpcr; + +	spin_lock_irqsave(&data->lock, flags); + +	lpcr = readl(data->ioaddr + SNVS_LPCR); +	if (enable) +		lpcr |= SNVS_LPCR_SRTC_ENV; +	else +		lpcr &= ~SNVS_LPCR_SRTC_ENV; +	writel(lpcr, data->ioaddr + SNVS_LPCR); + +	spin_unlock_irqrestore(&data->lock, flags); + +	while (--timeout) { +		lpcr = readl(data->ioaddr + SNVS_LPCR); + +		if (enable) { +			if (lpcr & SNVS_LPCR_SRTC_ENV) +				break; +		} else { +			if (!(lpcr & SNVS_LPCR_SRTC_ENV)) +				break; +		} +	} + +	if (!timeout) +		return -ETIMEDOUT; + +	return 0; +} + +static int snvs_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	unsigned long time = rtc_read_lp_counter(data->ioaddr); + +	rtc_time_to_tm(time, tm); + +	return 0; +} + +static int snvs_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	unsigned long time; + +	rtc_tm_to_time(tm, &time); + +	/* Disable RTC first */ +	snvs_rtc_enable(data, false); + +	/* Write 32-bit time to 47-bit timer, leaving 15 LSBs blank */ +	writel(time << CNTR_TO_SECS_SH, data->ioaddr + SNVS_LPSRTCLR); +	writel(time >> (32 - CNTR_TO_SECS_SH), data->ioaddr + SNVS_LPSRTCMR); + +	/* Enable RTC again */ +	snvs_rtc_enable(data, true); + +	return 0; +} + +static int snvs_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	u32 lptar, lpsr; + +	lptar = readl(data->ioaddr + SNVS_LPTAR); +	rtc_time_to_tm(lptar, &alrm->time); + +	lpsr = readl(data->ioaddr + SNVS_LPSR); +	alrm->pending = (lpsr & SNVS_LPSR_LPTA) ? 1 : 0; + +	return 0; +} + +static int snvs_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	u32 lpcr; +	unsigned long flags; + +	spin_lock_irqsave(&data->lock, flags); + +	lpcr = readl(data->ioaddr + SNVS_LPCR); +	if (enable) +		lpcr |= (SNVS_LPCR_LPTA_EN | SNVS_LPCR_LPWUI_EN); +	else +		lpcr &= ~(SNVS_LPCR_LPTA_EN | SNVS_LPCR_LPWUI_EN); +	writel(lpcr, data->ioaddr + SNVS_LPCR); + +	spin_unlock_irqrestore(&data->lock, flags); + +	rtc_write_sync_lp(data->ioaddr); + +	return 0; +} + +static int snvs_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	struct rtc_time *alrm_tm = &alrm->time; +	unsigned long time; +	unsigned long flags; +	u32 lpcr; + +	rtc_tm_to_time(alrm_tm, &time); + +	spin_lock_irqsave(&data->lock, flags); + +	/* Have to clear LPTA_EN before programming new alarm time in LPTAR */ +	lpcr = readl(data->ioaddr + SNVS_LPCR); +	lpcr &= ~SNVS_LPCR_LPTA_EN; +	writel(lpcr, data->ioaddr + SNVS_LPCR); + +	spin_unlock_irqrestore(&data->lock, flags); + +	writel(time, data->ioaddr + SNVS_LPTAR); + +	/* Clear alarm interrupt status bit */ +	writel(SNVS_LPSR_LPTA, data->ioaddr + SNVS_LPSR); + +	return snvs_rtc_alarm_irq_enable(dev, alrm->enabled); +} + +static const struct rtc_class_ops snvs_rtc_ops = { +	.read_time = snvs_rtc_read_time, +	.set_time = snvs_rtc_set_time, +	.read_alarm = snvs_rtc_read_alarm, +	.set_alarm = snvs_rtc_set_alarm, +	.alarm_irq_enable = snvs_rtc_alarm_irq_enable, +}; + +static irqreturn_t snvs_rtc_irq_handler(int irq, void *dev_id) +{ +	struct device *dev = dev_id; +	struct snvs_rtc_data *data = dev_get_drvdata(dev); +	u32 lpsr; +	u32 events = 0; + +	lpsr = readl(data->ioaddr + SNVS_LPSR); + +	if (lpsr & SNVS_LPSR_LPTA) { +		events |= (RTC_AF | RTC_IRQF); + +		/* RTC alarm should be one-shot */ +		snvs_rtc_alarm_irq_enable(dev, 0); + +		rtc_update_irq(data->rtc, 1, events); +	} + +	/* clear interrupt status */ +	writel(lpsr, data->ioaddr + SNVS_LPSR); + +	return events ? IRQ_HANDLED : IRQ_NONE; +} + +static int __devinit snvs_rtc_probe(struct platform_device *pdev) +{ +	struct snvs_rtc_data *data; +	struct resource *res; +	int ret; + +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	data->ioaddr = devm_request_and_ioremap(&pdev->dev, res); +	if (!data->ioaddr) +		return -EADDRNOTAVAIL; + +	data->irq = platform_get_irq(pdev, 0); +	if (data->irq < 0) +		return data->irq; + +	platform_set_drvdata(pdev, data); + +	spin_lock_init(&data->lock); + +	/* Initialize glitch detect */ +	writel(SNVS_LPPGDR_INIT, data->ioaddr + SNVS_LPPGDR); + +	/* Clear interrupt status */ +	writel(0xffffffff, data->ioaddr + SNVS_LPSR); + +	/* Enable RTC */ +	snvs_rtc_enable(data, true); + +	device_init_wakeup(&pdev->dev, true); + +	ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler, +			       IRQF_SHARED, "rtc alarm", &pdev->dev); +	if (ret) { +		dev_err(&pdev->dev, "failed to request irq %d: %d\n", +			data->irq, ret); +		return ret; +	} + +	data->rtc = rtc_device_register(pdev->name, &pdev->dev, +					&snvs_rtc_ops, THIS_MODULE); +	if (IS_ERR(data->rtc)) { +		ret = PTR_ERR(data->rtc); +		dev_err(&pdev->dev, "failed to register rtc: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static int __devexit snvs_rtc_remove(struct platform_device *pdev) +{ +	struct snvs_rtc_data *data = platform_get_drvdata(pdev); + +	rtc_device_unregister(data->rtc); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snvs_rtc_suspend(struct device *dev) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		enable_irq_wake(data->irq); + +	return 0; +} + +static int snvs_rtc_resume(struct device *dev) +{ +	struct snvs_rtc_data *data = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		disable_irq_wake(data->irq); + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(snvs_rtc_pm_ops, snvs_rtc_suspend, snvs_rtc_resume); + +static const struct of_device_id __devinitconst snvs_dt_ids[] = { +	{ .compatible = "fsl,sec-v4.0-mon-rtc-lp", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, snvs_dt_ids); + +static struct platform_driver snvs_rtc_driver = { +	.driver = { +		.name	= "snvs_rtc", +		.owner	= THIS_MODULE, +		.pm	= &snvs_rtc_pm_ops, +		.of_match_table = snvs_dt_ids, +	}, +	.probe		= snvs_rtc_probe, +	.remove		= __devexit_p(snvs_rtc_remove), +}; +module_platform_driver(snvs_rtc_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale SNVS RTC Driver"); +MODULE_LICENSE("GPL");  |