diff options
author | Amit Kamath <akamath@nvidia.com> | 2010-12-20 13:00:59 +0530 |
---|---|---|
committer | Bharat Nihalani <bnihalani@nvidia.com> | 2010-12-21 00:19:39 -0800 |
commit | f7b12b99b022aad05cfff36d7d2932c93c5a3f97 (patch) | |
tree | a8b3f8cd9aefe1edf0ec41b7ecaae0f1993d3aea /drivers/rtc | |
parent | 1d46b71b62739a2f4154918c0055f85c29d8e6d5 (diff) |
ARM: tegra: Driver for internal RTC and alarm
Implementation using internal RTC(real time clock).
Alarm wake up from lp0/lp1 is supported.
Use option CONFIG_RTC_DRV_TEGRA to enable.
Change-Id: Iadb767b35423463186eca32b8f47694a59aee7f4
Reviewed-on: http://git-master/r/13780
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 11 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-tegra.c | 472 |
3 files changed, 484 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a86401ea8e71..326e8b7193b2 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -249,6 +249,17 @@ 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_TEGRA + tristate "NVIDIA Tegra Internal RTC driver" + depends on ARCH_TEGRA + default RTC_CLASS + help + If you say yes here you get support for the + Tegra 200 series internal RTC module. + + This drive can also be built as a module. If so, the module + will be called rtc-tegra. + config RTC_DRV_PCF8563 tristate "Philips PCF8563/Epson RTC8564" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 5520733748d2..cc4a28918637 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -69,6 +69,7 @@ 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_PCAP) += rtc-pcap.o +obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c new file mode 100644 index 000000000000..68eeaeca5228 --- /dev/null +++ b/drivers/rtc/rtc-tegra.c @@ -0,0 +1,472 @@ +/* + * drivers/rtc/rtc-tegra.c + * + * An RTC driver for the NVIDIA Tegra 200 series internal RTC. + * + * Copyright (c) 2010, 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 + * 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. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> + +/* how many attempts to wait in tegra_rtc_wait_while_busy(). */ +#define RTC_TEGRA_RETRIES 15 + +#define tegra_rtc_read(ofs) readl(rtc_base + (ofs)) +#define tegra_rtc_write(ofs, val) writel((val), rtc_base + (ofs)) + +/* STATUS: This bit is set when a write is initiated on the APB side. It is + * cleared once the write completes in RTC 32KHz clock domain which could be + * several thousands of APB clocks. This must be IDLE before a write is + * initiated. Note that this bit is only for writes. + * 0 = IDLE + * 1 = BUSY + */ +#define RTC_TEGRA_REG_BUSY 0x004 +#define RTC_TEGRA_REG_SECONDS 0x008 +#define RTC_TEGRA_REG_SHADOW_SECONDS 0x00c +#define RTC_TEGRA_REG_MILLI_SECONDS 0x010 +#define RTC_TEGRA_REG_SECONDS_ALARM0 0x014 +#define RTC_TEGRA_REG_SECONDS_ALARM1 0x018 +#define RTC_TEGRA_REG_MILLI_SECONDS_ALARM0 0x01c +#define RTC_TEGRA_REG_INTR_MASK 0x028 +/* a write to this register performs a clear. reg=reg&(~x) */ +#define RTC_TEGRA_REG_INTR_STATUS 0x02c + +/* bits in INTR_MASK */ +#define RTC_TEGRA_INTR_MASK_MSEC_CDN_ALARM (1<<4) +#define RTC_TEGRA_INTR_MASK_SEC_CDN_ALARM (1<<3) +#define RTC_TEGRA_INTR_MASK_MSEC_ALARM (1<<2) +#define RTC_TEGRA_INTR_MASK_SEC_ALARM1 (1<<1) +#define RTC_TEGRA_INTR_MASK_SEC_ALARM0 (1<<0) + +/* bits in INTR_STATUS */ +#define RTC_TEGRA_INTR_STATUS_MSEC_CDN_ALARM (1<<4) +#define RTC_TEGRA_INTR_STATUS_SEC_CDN_ALARM (1<<3) +#define RTC_TEGRA_INTR_STATUS_MSEC_ALARM (1<<2) +#define RTC_TEGRA_INTR_STATUS_SEC_ALARM1 (1<<1) +#define RTC_TEGRA_INTR_STATUS_SEC_ALARM0 (1<<0) + +static struct rtc_device *rtc_dev; +static DEFINE_SPINLOCK(tegra_rtc_lock); +static void __iomem *rtc_base; /* NULL if not initialized. */ +static int tegra_rtc_irq; /* alarm and periodic interrupt */ + +/* check is hardware is accessing APB. */ +static inline u32 tegra_rtc_check_busy(void) +{ + return tegra_rtc_read(RTC_TEGRA_REG_BUSY); +} + +/* wait for hardware to be ready for writing. + * do not call this inside the spin lock because it sleeps. + */ +static int tegra_rtc_wait_while_busy(struct device *dev) +{ + /* TODO: wait for busy then not busy to catch a leading edge. */ + int retries = RTC_TEGRA_RETRIES; + while (tegra_rtc_check_busy()) { + if (!retries--) { + dev_err(dev, "write failed:retry count exceeded.\n"); + return -ETIMEDOUT; + } + msleep(1); + } + return 0; +} + +/* waits for the RTC to not be busy accessing APB, then write a single value. */ +static int tegra_rtc_write_not_busy(struct device *dev, unsigned ofs, u32 value) +{ + unsigned long sl_irq_flags; + int ret; + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + if(tegra_rtc_check_busy()) { + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + ret = tegra_rtc_wait_while_busy(dev); + if (ret) + return ret; + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + } + tegra_rtc_write(ofs, value); + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + return 0; +} + + +static int tegra_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long sec, msec; + unsigned long sl_irq_flags; + + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + + msec = tegra_rtc_read(RTC_TEGRA_REG_MILLI_SECONDS); + sec = tegra_rtc_read(RTC_TEGRA_REG_SHADOW_SECONDS); + + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + + rtc_time_to_tm(sec, tm); + + dev_vdbg(dev, "time read as %lu. %d/%d/%d %d:%02u:%02u\n", + sec, + tm->tm_mon+1, + tm->tm_mday, + tm->tm_year+1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec + ); + + return 0; +} + +static int tegra_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long sec; + int ret; + + /* convert tm to seconds. */ + ret = rtc_valid_tm(tm); + if (ret) return ret; + rtc_tm_to_time(tm, &sec); + + dev_vdbg(dev, "time set to %lu. %d/%d/%d %d:%02u:%02u\n", + sec, + tm->tm_mon+1, + tm->tm_mday, + tm->tm_year+1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec + ); + + /* seconds only written if wait succeeded. */ + ret = tegra_rtc_write_not_busy(dev, RTC_TEGRA_REG_SECONDS, sec); + + dev_vdbg( + dev, "time read back as %d\n", + tegra_rtc_read(RTC_TEGRA_REG_SECONDS) + ); + return ret; +} + +static int tegra_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + unsigned long sec; + unsigned long sl_irq_flags; + unsigned tmp; + + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + + sec = tegra_rtc_read(RTC_TEGRA_REG_SECONDS_ALARM0); + + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + + if (sec == 0) { + /* alarm is disabled. */ + t->enabled = 0; + t->time.tm_mon = -1; + t->time.tm_mday = -1; + t->time.tm_year = -1; + t->time.tm_hour = -1; + t->time.tm_min = -1; + t->time.tm_sec = -1; + } else { + /* alarm is enabled. */ + t->enabled = 1; + rtc_time_to_tm(sec, &t->time); + } + + tmp = tegra_rtc_read(RTC_TEGRA_REG_INTR_STATUS); + t->pending = (tmp & RTC_TEGRA_INTR_STATUS_SEC_ALARM0) != 0; + + return 0; +} + +static int tegra_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + unsigned status; + unsigned long sl_irq_flags; + int ret; + + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + if(tegra_rtc_check_busy()) { /* wait for the busy bit to clear. */ + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + ret = tegra_rtc_wait_while_busy(dev); + if (ret) + return ret; + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + } + /* read the original value, and OR in the flag. */ + status = tegra_rtc_read(RTC_TEGRA_REG_INTR_MASK); + if (enabled) + status |= RTC_TEGRA_INTR_MASK_SEC_ALARM0; /* set it */ + else + status &= ~RTC_TEGRA_INTR_MASK_SEC_ALARM0; /* clear it */ + tegra_rtc_write(RTC_TEGRA_REG_INTR_MASK, status); + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + + return 0; +} + +static int tegra_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + int ret; + unsigned long sec; + + if (t->enabled) + rtc_tm_to_time(&t->time, &sec); + else + sec = 0; + + ret = tegra_rtc_write_not_busy(dev, RTC_TEGRA_REG_SECONDS_ALARM0, sec); + dev_vdbg( + dev, "alarm read back as %d\n", + tegra_rtc_read(RTC_TEGRA_REG_SECONDS_ALARM0) + ); + + /* if successfully written and alarm is enabled ... */ + if (ret == 0 && sec) { + tegra_rtc_alarm_irq_enable(dev, 1); + + dev_vdbg(dev, "alarm set as %lu. %d/%d/%d %d:%02u:%02u\n", + sec, + t->time.tm_mon+1, + t->time.tm_mday, + t->time.tm_year+1900, + t->time.tm_hour, + t->time.tm_min, + t->time.tm_sec + ); + } else { + /* disable alarm if 0 or write error. */ + dev_vdbg(dev, "alarm disabled\n"); + tegra_rtc_alarm_irq_enable(dev, 0); + } + + return ret; +} + +static int tegra_rtc_ioctl( + struct device *dev, unsigned int cmd, unsigned long arg) +{ + /* use default ioctl handlers for: + * RTC_RD_TIME, RTC_SET_TIME, RTC_ALM_SET, RTC_ALM_READ, RTC_WKALM_SET, + * RTC_WKALM_RD, RTC_IRQP_SET, RTC_IRQP_READ, RTC_PIE_ON, RTC_PIE_OFF + */ + return -ENOIOCTLCMD; +} + +/* additional proc lines. */ +static int tegra_rtc_proc(struct device *dev, struct seq_file *seq) +{ + if (!dev || !dev->driver) + return 0; + return seq_printf(seq, "name\t\t: %s\n", dev_name(dev)); +} + +static irqreturn_t tegra_rtc_irq_handler(int irq, void *dev_id) +{ + unsigned long events = 0; + unsigned status; + unsigned long sl_irq_flags; + + status = tegra_rtc_read(RTC_TEGRA_REG_INTR_STATUS); + + if (status) { + /* clear the interrupt masks and status on any irq. */ + spin_lock_irqsave(&tegra_rtc_lock, sl_irq_flags); + tegra_rtc_write(RTC_TEGRA_REG_INTR_MASK, 0); + tegra_rtc_write(RTC_TEGRA_REG_INTR_STATUS, -1); + spin_unlock_irqrestore(&tegra_rtc_lock, sl_irq_flags); + } + + /* check if Alarm */ + if ((status & RTC_TEGRA_INTR_STATUS_SEC_ALARM0)) + events |= RTC_IRQF | RTC_AF; + + /* check if Periodic */ + if ((status & RTC_TEGRA_INTR_STATUS_SEC_CDN_ALARM)) + events |= RTC_IRQF | RTC_PF; + + rtc_update_irq(rtc_dev, 1, events); + return IRQ_HANDLED; +} + +static struct rtc_class_ops tegra_rtc_ops = { + .ioctl = tegra_rtc_ioctl, + .read_time = tegra_rtc_read_time, + .set_time = tegra_rtc_set_time, + .read_alarm = tegra_rtc_read_alarm, + .set_alarm = tegra_rtc_set_alarm, + .proc = tegra_rtc_proc, + .alarm_irq_enable = tegra_rtc_alarm_irq_enable, +}; + +static int __init tegra_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + /* get resources from arch/arm/mach-tegra/board-nvodm.c */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err( + &pdev->dev, "Unable to allocate resources for device.\n" + ); + return -EBUSY; + } + + tegra_rtc_irq = platform_get_irq(pdev, 0); + if (tegra_rtc_irq <= 0) { + tegra_rtc_irq = 0; + return -EBUSY; + } + + rtc_base = ioremap(res->start, res->end - res->start + 1); + if (!rtc_base) { + dev_err(&pdev->dev, "Unable to grab IOs for device.\n"); + return -EBUSY; + } + + /* clear out the hardware. */ + tegra_rtc_write_not_busy(&pdev->dev, RTC_TEGRA_REG_SECONDS_ALARM0, 0); + tegra_rtc_write_not_busy(&pdev->dev, RTC_TEGRA_REG_INTR_STATUS, -1); + tegra_rtc_write_not_busy(&pdev->dev, RTC_TEGRA_REG_INTR_MASK, 0); + + device_init_wakeup(&pdev->dev, 1); + + rtc_dev = rtc_device_register( + pdev->name, &pdev->dev, &tegra_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc_dev)) { + ret = PTR_ERR(rtc_dev); + rtc_dev = NULL; + dev_err(&pdev->dev, "Unable to register device (err=%d).\n", ret); + goto err_iounmap; + } + + ret = request_irq(tegra_rtc_irq, tegra_rtc_irq_handler, + IRQF_DISABLED, "rtc alarm", &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Unable to request interrupt for device (err=%d).\n", ret); + goto err_dev_unreg; + } + + dev_notice(&pdev->dev, "Tegra internal Real Time Clock\n"); + + return 0; + +err_dev_unreg: + rtc_device_unregister(rtc_dev); + rtc_dev = NULL; +err_iounmap: + iounmap(rtc_base); + rtc_base = NULL; + + return ret; +} + +static int __devexit tegra_rtc_remove(struct platform_device *pdev) +{ + if (rtc_dev) { + rtc_device_unregister(rtc_dev); + rtc_dev = NULL; + } + return 0; +} + +#ifdef CONFIG_PM +static int tegra_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev=&pdev->dev; + + /* only use ALARM0 as a wake source. */ + tegra_rtc_write(RTC_TEGRA_REG_INTR_STATUS, -1); + tegra_rtc_write(RTC_TEGRA_REG_INTR_MASK, + RTC_TEGRA_INTR_STATUS_SEC_ALARM0); + dev_vdbg(dev, "alarm sec = %d\n", + tegra_rtc_read(RTC_TEGRA_REG_SECONDS_ALARM0)); + + dev_vdbg(dev, "Suspend (device_may_wakeup=%d) irq:%d\n", + device_may_wakeup(dev), tegra_rtc_irq); + /* leave the alarms on as a wake source. */ + if (device_may_wakeup(dev)) + enable_irq_wake(tegra_rtc_irq); + return 0; +} + +static int tegra_rtc_resume(struct platform_device *pdev) +{ + struct device *dev=&pdev->dev; + unsigned int intr_status; + + /* clear */ + intr_status = tegra_rtc_read(RTC_TEGRA_REG_INTR_STATUS); + if (intr_status & RTC_TEGRA_INTR_STATUS_SEC_ALARM0) { + tegra_rtc_write_not_busy(dev, RTC_TEGRA_REG_INTR_MASK, 0); + tegra_rtc_write_not_busy(dev, RTC_TEGRA_REG_INTR_STATUS, -1); + } + + dev_vdbg(dev, "Resume (device_may_wakeup=%d)\n", device_may_wakeup(dev)); + /* alarms were left on as a wake source, turn them off. */ + if (device_may_wakeup(dev)) + disable_irq_wake(tegra_rtc_irq); + return 0; +} +#endif + +static void tegra_rtc_shutdown(struct platform_device *pdev) +{ + dev_vdbg(&pdev->dev, "disabling interrupts.\n"); + tegra_rtc_alarm_irq_enable(&pdev->dev, 0); +} + +MODULE_ALIAS("platform:tegra_rtc"); +static struct platform_driver tegra_rtc_driver = { + .remove = __devexit_p(tegra_rtc_remove), + .shutdown = tegra_rtc_shutdown, + .driver = { + .name = "tegra_rtc", + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tegra_rtc_suspend, + .resume = tegra_rtc_resume, +#endif +}; + +static int __init tegra_rtc_init(void) +{ + return platform_driver_probe(&tegra_rtc_driver, tegra_rtc_probe); +} +module_init(tegra_rtc_init); + +static void __exit tegra_rtc_exit(void) +{ + platform_driver_unregister(&tegra_rtc_driver); +} +module_exit(tegra_rtc_exit); + +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_DESCRIPTION("driver for Tegra internal RTC"); +MODULE_LICENSE("GPL"); |