From patchwork Thu Oct 14 20:56:31 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: achew@nvidia.com X-Patchwork-Id: 67868 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-pw0-f56.google.com (mail-pw0-f56.google.com [209.85.160.56]) by ozlabs.org (Postfix) with ESMTP id B5A9AB70CB for ; Fri, 15 Oct 2010 07:56:46 +1100 (EST) Received: by pwi10 with SMTP id 10sf144065pwi.11 for ; Thu, 14 Oct 2010 13:56:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=beta; h=domainkey-signature:received:mime-version:x-beenthere:received :received:received:received:received-spf:received:received :x-pgp-universal:received:from:to:cc:subject:date:message-id :x-mailer:x-originalarrivaltime:x-original-sender :x-original-authentication-results:reply-to:precedence:mailing-list :list-id:list-post:list-help:list-archive:sender:list-subscribe :list-unsubscribe:content-type; bh=cMIRK9G9mjtG1H9Tf3LfwBFMl2dLCMNeI5au4E76rIM=; b=3fPA2cakkv49ncSeOAoXs7SEMx5wD7qZEa4c0lZFUuJtnrD0aoEQ7t9GJ6EIknUf+w z2Hz5/3iU7nPI2CC/TZ3ObuUjsCY6dgf0la/t4GyPig1lQ+5+9JV6uL2iCOMvpAt46nE LfmcSksFrdIzD5D3M/7hflneRrmvbD5zc5abA= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=mime-version:x-beenthere:received-spf:x-pgp-universal:from:to:cc :subject:date:message-id:x-mailer:x-originalarrivaltime :x-original-sender:x-original-authentication-results:reply-to :precedence:mailing-list:list-id:list-post:list-help:list-archive :sender:list-subscribe:list-unsubscribe:content-type; b=sG8Sig4IYoT4cLQ27IMZbYxgFWOF5FPZUKCvB/imcIAqHKHG/sEw1JY5bMzqgw0Miy hoPL3z2MD0tAezQTU+kL1kiz0cIYU0OXSMO6yTGAM5qpNIUPJZ52i+TBXbZCuh2WEA+6 DfRqOhHJeuyHWno+sjACUIK6jB03mIf1p3wyc= Received: by 10.142.65.4 with SMTP id n4mr545317wfa.19.1287089804152; Thu, 14 Oct 2010 13:56:44 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: rtc-linux@googlegroups.com Received: by 10.142.70.10 with SMTP id s10ls1481067wfa.1.p; Thu, 14 Oct 2010 13:56:43 -0700 (PDT) Received: by 10.142.221.10 with SMTP id t10mr4247086wfg.63.1287089803761; Thu, 14 Oct 2010 13:56:43 -0700 (PDT) Received: by 10.142.221.10 with SMTP id t10mr4247083wfg.63.1287089803732; Thu, 14 Oct 2010 13:56:43 -0700 (PDT) Received: from hqemgate03.nvidia.com (hqemgate03.nvidia.com [216.228.121.140]) by gmr-mx.google.com with ESMTP id h6si15609089wfj.1.2010.10.14.13.56.43; Thu, 14 Oct 2010 13:56:43 -0700 (PDT) Received-SPF: pass (google.com: domain of AChew@nvidia.com designates 216.228.121.140 as permitted sender) client-ip=216.228.121.140; Received: from hqnvupgp03.nvidia.com (Not Verified[172.17.102.18]) by hqemgate03.nvidia.com id ; Thu, 14 Oct 2010 14:01:26 -0700 Received: from hqemfe03.nvidia.com ([172.17.108.22]) by hqnvupgp03.nvidia.com (PGP Universal service); Thu, 14 Oct 2010 13:56:42 -0700 X-PGP-Universal: processed; by hqnvupgp03.nvidia.com on Thu, 14 Oct 2010 13:56:42 -0700 Received: from localhost.localdomain ([172.20.144.16]) by hqemfe03.nvidia.com with Microsoft SMTPSVC(6.0.3790.4675); Thu, 14 Oct 2010 13:56:42 -0700 From: achew@nvidia.com To: jmayo@nvidia.com, a.zummo@towertech.it, olof@lixom.net, greg@kroah.com, mcuos.com@gmail.com Cc: linux-kernel@vger.kernel.org, linux-tegra@vger.kernel.org, rtc-linux@googlegroups.com, Andrew Chew Subject: [rtc-linux] [PATCH 1/1 V2] rtc: Add real-time clock driver for Tegra. Date: Thu, 14 Oct 2010 13:56:31 -0700 Message-Id: <1287089791-25673-1-git-send-email-achew@nvidia.com> X-Mailer: git-send-email 1.7.0.4 X-OriginalArrivalTime: 14 Oct 2010 20:56:42.0712 (UTC) FILETIME=[4E174580:01CB6BE2] X-Original-Sender: achew@nvidia.com X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: domain of AChew@nvidia.com designates 216.228.121.140 as permitted sender) smtp.mail=AChew@nvidia.com Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: List-Post: , List-Help: , List-Archive: Sender: rtc-linux@googlegroups.com List-Subscribe: , List-Unsubscribe: , From: Andrew Chew This is a platform driver that supports the built-in real-time clock on Tegra SOCs. Signed-off-by: Andrew Chew --- Applied Wan ZongShun's corrections. drivers/rtc/Kconfig | 10 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-tegra.c | 509 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 520 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-tegra.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2432541..ca0da79 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -963,4 +963,14 @@ config RTC_DRV_JZ4740 This driver can also be buillt as a module. If so, the module will be called rtc-jz4740. +config RTC_DRV_TEGRA + tristate "NVIDIA Tegra Internal RTC driver" + depends on RTC_CLASS && ARCH_TEGRA + 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. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 4ff4b88..a7f3617 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -91,6 +91,7 @@ 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 +obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c new file mode 100644 index 0000000..c3d23e6 --- /dev/null +++ b/drivers/rtc/rtc-tegra.c @@ -0,0 +1,509 @@ +/* + * An RTC driver for the NVIDIA Tegra 200 series internal RTC. + * + * Copyright (c) 2010, NVIDIA Corporation. + * Copyright (c) 2010 Jon Mayo + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* how many attempts to wait in tegra_rtc_wait_while_busy(). */ +#define RTC_TEGRA_RETRIES 15 + +/* 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) + +struct tegra_rtc_info { + struct platform_device *pdev; + struct rtc_device *rtc_dev; + void __iomem *rtc_base; /* NULL if not initialized. */ + int tegra_rtc_irq; /* alarm and periodic irq */ + spinlock_t tegra_rtc_lock; +}; + +/* check is hardware is accessing APB. */ +static inline u32 tegra_rtc_check_busy(struct tegra_rtc_info *info) +{ + return readl(info->rtc_base + 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) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + + /* TODO: wait for busy then not busy to catch a leading edge. */ + int retries = RTC_TEGRA_RETRIES; + + while (tegra_rtc_check_busy(info)) { + 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) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned long sl_irq_flags; + int ret; + + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + + if (tegra_rtc_check_busy(info)) { + spin_unlock_irqrestore(&info->tegra_rtc_lock, sl_irq_flags); + ret = tegra_rtc_wait_while_busy(dev); + if (ret) + return ret; + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + } + + writel(value, info->rtc_base + ofs); + + spin_unlock_irqrestore(&info->tegra_rtc_lock, sl_irq_flags); + + return 0; +} + + +static int tegra_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned long sec, msec; + unsigned long sl_irq_flags; + + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + + msec = readl(info->rtc_base + RTC_TEGRA_REG_MILLI_SECONDS); + sec = readl(info->rtc_base + RTC_TEGRA_REG_SHADOW_SECONDS); + + spin_unlock_irqrestore(&info->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) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned long sec; + int ret; + + /* convert tm to seconds. */ + ret = rtc_valid_tm(tm); + if (ret < 0) + 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", + readl(info->rtc_base + RTC_TEGRA_REG_SECONDS)); + + return ret; +} + +static int tegra_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned long sec; + unsigned long sl_irq_flags; + unsigned tmp; + + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + + sec = readl(info->rtc_base + RTC_TEGRA_REG_SECONDS_ALARM0); + + spin_unlock_irqrestore(&info->tegra_rtc_lock, sl_irq_flags); + + if (sec == 0) { + /* alarm is disabled. */ + alarm->enabled = 0; + alarm->time.tm_mon = -1; + alarm->time.tm_mday = -1; + alarm->time.tm_year = -1; + alarm->time.tm_hour = -1; + alarm->time.tm_min = -1; + alarm->time.tm_sec = -1; + } else { + /* alarm is enabled. */ + alarm->enabled = 1; + rtc_time_to_tm(sec, &alarm->time); + } + + tmp = readl(info->rtc_base + RTC_TEGRA_REG_INTR_STATUS); + alarm->pending = (tmp & RTC_TEGRA_INTR_STATUS_SEC_ALARM0) != 0; + + return 0; +} + +static int tegra_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned status; + unsigned long sl_irq_flags; + int ret; + + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + + if (tegra_rtc_check_busy(info)) { /* wait for the busy bit to clear. */ + spin_unlock_irqrestore(&info->tegra_rtc_lock, sl_irq_flags); + ret = tegra_rtc_wait_while_busy(dev); + if (ret) + return ret; + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + } + + /* read the original value, and OR in the flag. */ + status = readl(info->rtc_base + 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 */ + + writel(status, info->rtc_base + RTC_TEGRA_REG_INTR_MASK); + + spin_unlock_irqrestore(&info->tegra_rtc_lock, sl_irq_flags); + + return 0; +} + +static int tegra_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct tegra_rtc_info *info = dev_get_drvdata(dev); + int ret; + unsigned long sec; + + if (alarm->enabled) + rtc_tm_to_time(&alarm->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", + readl(info->rtc_base + 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, + alarm->time.tm_mon+1, + alarm->time.tm_mday, + alarm->time.tm_year+1900, + alarm->time.tm_hour, + alarm->time.tm_min, + alarm->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; +} + +/* 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 *data) +{ + struct device *dev = data; + struct tegra_rtc_info *info = dev_get_drvdata(dev); + unsigned long events = 0; + unsigned status; + unsigned long sl_irq_flags; + + status = readl(info->rtc_base + RTC_TEGRA_REG_INTR_STATUS); + if (status) { + /* clear the interrupt masks and status on any irq. */ + spin_lock_irqsave(&info->tegra_rtc_lock, sl_irq_flags); + writel(0, info->rtc_base + RTC_TEGRA_REG_INTR_MASK); + writel(0xffffffff, info->rtc_base + RTC_TEGRA_REG_INTR_STATUS); + spin_unlock_irqrestore(&info->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(info->rtc_dev, 1, events); + + return IRQ_HANDLED; +} + +static struct rtc_class_ops tegra_rtc_ops = { + .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 __devinit tegra_rtc_probe(struct platform_device *pdev) +{ + struct tegra_rtc_info *info; + struct resource *res; + int ret; + + info = kzalloc(sizeof(struct tegra_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Unable to allocate resources for device.\n"); + ret = -EBUSY; + goto err_free_info; + } + + info->tegra_rtc_irq = platform_get_irq(pdev, 0); + if (info->tegra_rtc_irq <= 0) { + ret = -EBUSY; + goto err_free_info; + } + + info->rtc_base = ioremap(res->start, res->end - res->start + 1); + if (!info->rtc_base) { + dev_err(&pdev->dev, "Unable to grab IOs for device.\n"); + ret = -EBUSY; + goto err_free_info; + } + + /* set context info. */ + info->pdev = pdev; + info->tegra_rtc_lock = __SPIN_LOCK_UNLOCKED(info->tegra_rtc_lock); + + platform_set_drvdata(pdev, info); + + /* 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, + 0xffffffff); + tegra_rtc_write_not_busy(&pdev->dev, RTC_TEGRA_REG_INTR_MASK, 0); + + device_init_wakeup(&pdev->dev, 1); + + info->rtc_dev = rtc_device_register( + pdev->name, &pdev->dev, &tegra_rtc_ops, THIS_MODULE); + if (IS_ERR(info->rtc_dev)) { + ret = PTR_ERR(info->rtc_dev); + info->rtc_dev = NULL; + dev_err(&pdev->dev, + "Unable to register device (err=%d).\n", + ret); + goto err_iounmap; + } + + ret = request_irq(info->tegra_rtc_irq, tegra_rtc_irq_handler, + IRQF_TRIGGER_HIGH, "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(info->rtc_dev); +err_iounmap: + iounmap(info->rtc_base); +err_free_info: + kfree(info); + + return ret; +} + +static int __devexit tegra_rtc_remove(struct platform_device *pdev) +{ + struct tegra_rtc_info *info = platform_get_drvdata(pdev); + + free_irq(info->tegra_rtc_irq, &pdev->dev); + rtc_device_unregister(info->rtc_dev); + iounmap(info->rtc_base); + kfree(info); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int tegra_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev = &pdev->dev; + struct tegra_rtc_info *info = platform_get_drvdata(pdev); + + /* only use ALARM0 as a wake source. */ + writel(0xffffffff, info->rtc_base + RTC_TEGRA_REG_INTR_STATUS); + writel(RTC_TEGRA_INTR_STATUS_SEC_ALARM0, + info->rtc_base + RTC_TEGRA_REG_INTR_MASK); + + dev_vdbg(dev, "alarm sec = %d\n", + readl(info->rtc_base + RTC_TEGRA_REG_SECONDS_ALARM0)); + + dev_vdbg(dev, "Suspend (device_may_wakeup=%d) irq:%d\n", + device_may_wakeup(dev), info->tegra_rtc_irq); + + /* leave the alarms on as a wake source. */ + if (device_may_wakeup(dev)) + enable_irq_wake(info->tegra_rtc_irq); + + return 0; +} + +static int tegra_rtc_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra_rtc_info *info = platform_get_drvdata(pdev); + unsigned int intr_status; + + /* clear */ + intr_status = readl(info->rtc_base + 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, + 0xffffffff); + } + + 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(info->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_DESCRIPTION("driver for Tegra internal RTC"); +MODULE_LICENSE("GPL");