From patchwork Mon Aug 9 16:17:29 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: wellsk40@gmail.com X-Patchwork-Id: 61324 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-yx0-f184.google.com (mail-yx0-f184.google.com [209.85.213.184]) by ozlabs.org (Postfix) with ESMTP id 4A155B70B8 for ; Tue, 10 Aug 2010 09:17:05 +1000 (EST) Received: by yxl31 with SMTP id 31sf29794121yxl.11 for ; Mon, 09 Aug 2010 16:17:03 -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:received :from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :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=7HeiwbDKBF1zqr9+q8teq3MMeT5T+GMcGRlJfc+ob60=; b=wuOhRq929rZIFSoL57HsrYWej6UAQQ3EmLncQTA8Jz6NvfLK0yR8il3pb6tpIXVDDL YkO3FR7zvSuu9XpPQCmcSKP8TSsACALCyFeG+BnVz/A9qXS1a0ojRFdLINy1r3AVILrf 3WRI5waWKmWbaQaPpXEABL/7YuLYvJ1hFEA9w= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=mime-version:x-beenthere:received-spf:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references: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=ccM6i6DZTOaeBIR2GAvYhCEm+cCKYbHQkoUKVbucvsDmCbob698rRvsOP/sa009K5O MqWFzyL9wDpNwvZ2a/cWlTUD6nWGV0vdK/XTrF+IaIT+JDtsSSlyt+AfMKZONn0+qagg c+6u/1qpxaGD8WH62nUU6tT0aYKJ0u2O1P/o0= Received: by 10.101.114.12 with SMTP id r12mr1734102anm.25.1281395823040; Mon, 09 Aug 2010 16:17:03 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: rtc-linux@googlegroups.com Received: by 10.101.194.29 with SMTP id w29ls2310581anp.1.p; Mon, 09 Aug 2010 16:17:02 -0700 (PDT) Received: by 10.101.175.36 with SMTP id c36mr10278982anp.58.1281395822570; Mon, 09 Aug 2010 16:17:02 -0700 (PDT) Received: by 10.101.175.36 with SMTP id c36mr10278981anp.58.1281395822536; Mon, 09 Aug 2010 16:17:02 -0700 (PDT) Received: from mail-yw0-f51.google.com (mail-yw0-f51.google.com [209.85.213.51]) by gmr-mx.google.com with ESMTP id q14si4628902anq.4.2010.08.09.16.17.01; Mon, 09 Aug 2010 16:17:01 -0700 (PDT) Received-SPF: pass (google.com: domain of wellsk40@gmail.com designates 209.85.213.51 as permitted sender) client-ip=209.85.213.51; Received: by ywa6 with SMTP id 6so5678755ywa.10 for ; Mon, 09 Aug 2010 16:17:01 -0700 (PDT) Received: by 10.150.13.15 with SMTP id 15mr18738789ybm.40.1281395821402; Mon, 09 Aug 2010 16:17:01 -0700 (PDT) Received: from localhost.localdomain (66-193-100-242.static.twtelecom.net [66.193.100.242]) by mx.google.com with ESMTPS id q21sm5248219ybk.11.2010.08.09.16.16.59 (version=SSLv3 cipher=RC4-MD5); Mon, 09 Aug 2010 16:17:00 -0700 (PDT) From: wellsk40@gmail.com To: rtc-linux@googlegroups.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org Cc: Kevin Wells , Durgesh Pattamatta Subject: [rtc-linux] [PATCH 1/2] rtc: rtc-lpc32xx: Introduce RTC driver for the LPC32XX SoC Date: Mon, 9 Aug 2010 09:17:29 -0700 Message-Id: <1281370650-29520-2-git-send-email-wellsk40@gmail.com> X-Mailer: git-send-email 1.7.1.1 In-Reply-To: <1281370650-29520-1-git-send-email-wellsk40@gmail.com> References: <1281370650-29520-1-git-send-email-wellsk40@gmail.com> X-Original-Sender: wellsk40@gmail.com X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: domain of wellsk40@gmail.com designates 209.85.213.51 as permitted sender) smtp.mail=wellsk40@gmail.com; dkim=pass (test mode) header.i=@gmail.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: Kevin Wells This patch contains the RTC driver for the built-in RTC in the LPC32XX SoC. Signed-off-by: Kevin Wells Signed-off-by: Durgesh Pattamatta --- drivers/rtc/rtc-lpc32xx.c | 391 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 391 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-lpc32xx.c diff --git a/drivers/rtc/rtc-lpc32xx.c b/drivers/rtc/rtc-lpc32xx.c new file mode 100644 index 0000000..7803c68 --- /dev/null +++ b/drivers/rtc/rtc-lpc32xx.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2010 NXP Semiconductors + * + * 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Clock and Power control register offsets + */ +#define RTC_UCOUNT 0x00 +#define RTC_DCOUNT 0x04 +#define RTC_MATCH0 0x08 +#define RTC_MATCH1 0x0C +#define RTC_CTRL 0x10 +#define RTC_INTSTAT 0x14 +#define RTC_KEY 0x18 +#define RTC_SRAM 0x80 + +#define RTC_MATCH0_EN (1 << 0) +#define RTC_MATCH1_EN (1 << 1) +#define RTC_ONSW_MATCH0_EN (1 << 2) +#define RTC_ONSW_MATCH1_EN (1 << 3) +#define RTC_SW_RESET (1 << 4) +#define RTC_CNTR_DIS (1 << 6) +#define RTC_ONSW_FORCE_HIGH (1 << 7) + +#define RTC_MATCH0_INT_STS (1 << 0) +#define RTC_MATCH1_INT_STS (1 << 1) +#define RTC_ONSW_INT_STS (1 << 2) + +#define RTC_KEY_ONSW_LOADVAL 0xB5C13F27 + +#define RTC_NAME "rtc-lpc32xx" + +#define rtc_readl(dev, reg) \ + __raw_readl((dev)->rtc_base + reg) +#define rtc_writel(dev, reg, val) \ + __raw_writel((val), (dev)->rtc_base + reg) + +struct lpc32xx_rtc { + void __iomem *rtc_base; + unsigned int irq; + int alarm_enabled; + struct rtc_device *rtc; + spinlock_t lock; +}; + +static int lpc32xx_rtc_read_time(struct device *dev, struct rtc_time *time) +{ + unsigned long elapsed_sec; + struct lpc32xx_rtc *rtc = dev_get_drvdata(dev); + + elapsed_sec = rtc_readl(rtc, RTC_UCOUNT); + rtc_time_to_tm(elapsed_sec, time); + + return rtc_valid_tm(time); +} + +static int lpc32xx_rtc_set_mmss(struct device *dev, unsigned long secs) +{ + struct lpc32xx_rtc *rtc = dev_get_drvdata(dev); + u32 tmp; + + spin_lock_irq(&rtc->lock); + + /* RTC must be disabled during count update */ + tmp = rtc_readl(rtc, RTC_CTRL); + rtc_writel(rtc, RTC_CTRL, tmp | RTC_CNTR_DIS); + rtc_writel(rtc, RTC_UCOUNT, secs); + rtc_writel(rtc, RTC_DCOUNT, 0xFFFFFFFF - secs); + rtc_writel(rtc, RTC_CTRL, tmp &= ~RTC_CNTR_DIS); + + spin_unlock_irq(&rtc->lock); + + return 0; +} + +static int lpc32xx_rtc_read_alarm(struct device *dev, + struct rtc_wkalrm *wkalrm) +{ + struct lpc32xx_rtc *rtc = dev_get_drvdata(dev); + + rtc_time_to_tm(rtc_readl(rtc, RTC_MATCH0), &wkalrm->time); + wkalrm->enabled = rtc->alarm_enabled; + + return rtc_valid_tm(&wkalrm->time); +} + +static int lpc32xx_rtc_set_alarm(struct device *dev, + struct rtc_wkalrm *wkalrm) +{ + struct lpc32xx_rtc *rtc = dev_get_drvdata(dev); + unsigned long alarmsecs; + u32 tmp; + int ret; + ret = rtc_tm_to_time(&wkalrm->time, &alarmsecs); + if (ret < 0) { + dev_err(dev, "Failed to convert time: %d\n", ret); + return ret; + } + + spin_lock_irq(&rtc->lock); + + /* Disable alarm during update */ + tmp = rtc_readl(rtc, RTC_CTRL); + rtc_writel(rtc, RTC_CTRL, tmp & ~RTC_MATCH0_EN); + + rtc->alarm_enabled = wkalrm->enabled = 1; + if (wkalrm->enabled) { + rtc_writel(rtc, RTC_MATCH0, alarmsecs); + rtc_writel(rtc, RTC_INTSTAT, RTC_MATCH0_INT_STS); + rtc_writel(rtc, RTC_CTRL, tmp | RTC_MATCH0_EN); + } + + spin_unlock_irq(&rtc->lock); + + return 0; +} + +static int lpc32xx_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct lpc32xx_rtc *rtc = dev_get_drvdata(dev); + u32 tmp; + + spin_lock_irq(&rtc->lock); + rtc->alarm_enabled = (int) enabled; + tmp = rtc_readl(rtc, RTC_CTRL); + + if (enabled) + tmp |= RTC_MATCH0_EN; + else + tmp &= ~RTC_MATCH0_EN; + + rtc_writel(rtc, RTC_CTRL, tmp); + spin_unlock_irq(&rtc->lock); + + return 0; +} + +static irqreturn_t lpc32xx_rtc_alarm_interrupt(int irq, void *dev) +{ + struct lpc32xx_rtc *rtc = (struct lpc32xx_rtc *) dev; + + spin_lock(&rtc->lock); + + /* Disable alarm interrupt */ + rtc_writel(rtc, RTC_CTRL, + rtc_readl(rtc, RTC_CTRL) & ~RTC_MATCH0_EN); + rtc->alarm_enabled = 0; + + /* + * Write a large value to the match value so the RTC won't + * keep firing the match status + */ + rtc_writel(rtc, RTC_MATCH0, 0xFFFFFFFF); + rtc_writel(rtc, RTC_INTSTAT, RTC_MATCH0_INT_STS); + + spin_unlock(&rtc->lock); + + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops lpc32xx_rtc_ops = { + .read_time = lpc32xx_rtc_read_time, + .set_mmss = lpc32xx_rtc_set_mmss, + .read_alarm = lpc32xx_rtc_read_alarm, + .set_alarm = lpc32xx_rtc_set_alarm, + .alarm_irq_enable = lpc32xx_rtc_alarm_irq_enable, +}; + +static int __devinit lpc32xx_rtc_probe(struct platform_device *pdev) +{ + struct resource *res, *mem = NULL; + struct lpc32xx_rtc *rtc = NULL; + int rtcirq, retval; + u32 tmp; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Can't get memory resource\n"); + return -ENOENT; + } + + rtcirq = platform_get_irq(pdev, 0); + if ((rtcirq < 0) || (rtcirq >= NR_IRQS)) { + dev_err(&pdev->dev, "Can't get interrupt resource\n"); + return -ENOENT; + } + + rtc = kzalloc(sizeof(struct lpc32xx_rtc), GFP_KERNEL); + if (unlikely(!rtc)) { + dev_err(&pdev->dev, "Can't allocate memory\n"); + return -ENOMEM; + } + rtc->irq = rtcirq; + + mem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!mem) { + dev_err(&pdev->dev, "RTC registers are not free\n"); + retval = -EBUSY; + goto err_reqmem; + } + + rtc->rtc_base = ioremap(res->start, res->end - res->start + 1); + if (!rtc->rtc_base) { + dev_err(&pdev->dev, "Can't map memory\n"); + retval = -EIO; + goto err_noremap; + } + + spin_lock_init(&rtc->lock); + + /* + * The RTC is on a seperate power domain and can keep it's state + * across a chip power cycle. If the RTC has never been previously + * setup, then set it up now for the first time. + */ + if (rtc_readl(rtc, RTC_KEY) == RTC_KEY_ONSW_LOADVAL) { + tmp = rtc_readl(rtc, RTC_CTRL); + tmp &= ~(RTC_SW_RESET | RTC_CNTR_DIS | RTC_MATCH0_EN | + RTC_MATCH1_EN | RTC_ONSW_MATCH0_EN | + RTC_ONSW_MATCH1_EN | RTC_ONSW_FORCE_HIGH); + rtc_writel(rtc, RTC_CTRL, tmp); + + /* Clear latched interrupt states */ + rtc_writel(rtc, RTC_MATCH0, 0xFFFFFFFF); + rtc_writel(rtc, RTC_INTSTAT, RTC_MATCH0_INT_STS | + RTC_MATCH1_INT_STS | RTC_ONSW_INT_STS); + + /* Write key value to RTC so it won't reload on reset */ + rtc_writel(rtc, RTC_KEY, RTC_KEY_ONSW_LOADVAL); + } else if (rtc_readl(rtc, RTC_CTRL) & RTC_MATCH0_EN) + rtc->alarm_enabled = 1; + + platform_set_drvdata(pdev, rtc); + + device_init_wakeup(&pdev->dev, 1); + rtc->rtc = rtc_device_register(RTC_NAME, &pdev->dev, &lpc32xx_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc->rtc)) { + dev_err(&pdev->dev, "Can't get RTC\n"); + retval = PTR_ERR(rtc->rtc); + goto err_noreg; + } + + retval = request_irq(rtc->irq, lpc32xx_rtc_alarm_interrupt, + IRQF_DISABLED, "rtcalarm", rtc); + if (retval < 0) { + dev_err(&pdev->dev, "Can't request interrupt\n"); + goto err_free_irq; + } + + return 0; + +err_free_irq: + rtc_device_unregister(rtc->rtc); +err_noreg: + iounmap(rtc->rtc_base); +err_noremap: + release_resource(mem); +err_reqmem: + kfree(rtc); + + return retval; +} + +static int __devexit lpc32xx_rtc_remove(struct platform_device *pdev) +{ + struct lpc32xx_rtc *rtc = platform_get_drvdata(pdev); + + free_irq(rtc->irq, pdev); + rtc_device_unregister(rtc->rtc); + iounmap(rtc->rtc_base); + release_resource(dev_get_drvdata(&rtc->rtc->dev)); + kfree(rtc); + + return 0; +} + +#ifdef CONFIG_PM +static int lpc32xx_rtc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc32xx_rtc *rtc = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(rtc->irq); + else + disable_irq_wake(rtc->irq); + + return 0; +} + +static int lpc32xx_rtc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc32xx_rtc *rtc = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(rtc->irq); + + return 0; +} + +/* Unconditionally disable the alarm */ +static int lpc32xx_rtc_freeze(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc32xx_rtc *rtc = platform_get_drvdata(pdev); + + spin_lock_irq(&rtc->lock); + + rtc_writel(rtc, RTC_CTRL, + rtc_readl(rtc, RTC_CTRL) & ~RTC_MATCH0_EN); + + spin_unlock_irq(&rtc->lock); + + return 0; +} + +static int lpc32xx_rtc_thaw(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpc32xx_rtc *rtc = platform_get_drvdata(pdev); + + if (rtc->alarm_enabled) + rtc_writel(rtc, RTC_CTRL, + rtc_readl(rtc, RTC_CTRL) | RTC_MATCH0_EN); + + return 0; +} + +#else +#define lpc32xx_rtc_suspend NULL +#define lpc32xx_rtc_resume NULL +#define lpc32xx_rtc_freeze NULL +#define lpc32xx_rtc_thaw NULL +#endif + +static const struct dev_pm_ops lpc32xx_rtc_pm_ops = { + .suspend = lpc32xx_rtc_suspend, + .resume = lpc32xx_rtc_resume, + .freeze = lpc32xx_rtc_freeze, + .thaw = lpc32xx_rtc_thaw, + .restore = lpc32xx_rtc_resume +}; + +static struct platform_driver lpc32xx_rtc_driver = { + .probe = lpc32xx_rtc_probe, + .remove = __devexit_p(lpc32xx_rtc_remove), + .driver = { + .name = RTC_NAME, + .pm = &lpc32xx_rtc_pm_ops, + }, +}; + +static int __init lpc32xx_rtc_init(void) +{ + return platform_driver_register(&lpc32xx_rtc_driver); +} + +static void __exit lpc32xx_rtc_exit(void) +{ + platform_driver_unregister(&lpc32xx_rtc_driver); +} + +module_init(lpc32xx_rtc_init); +module_exit(lpc32xx_rtc_exit); + +MODULE_AUTHOR("Kevin Wells