From patchwork Tue May 21 22:21:07 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Douglas Gilbert X-Patchwork-Id: 2599311 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) by patchwork2.kernel.org (Postfix) with ESMTP id E98D3DFB79 for ; Tue, 21 May 2013 22:21:46 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ueuw9-0001Tg-Mm; Tue, 21 May 2013 22:21:41 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ueuw6-0001iD-TQ; Tue, 21 May 2013 22:21:38 +0000 Received: from smtp.infotech.no ([82.134.31.41]) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Ueuw2-0001hs-Le for linux-arm-kernel@lists.infradead.org; Tue, 21 May 2013 22:21:37 +0000 Received: from localhost (localhost [127.0.0.1]) by smtp.infotech.no (Postfix) with ESMTP id ABC2E2041CE; Wed, 22 May 2013 00:21:09 +0200 (CEST) X-Virus-Scanned: by amavisd-new-2.6.6 (20110518) (Debian) at infotech.no Received: from smtp.infotech.no ([127.0.0.1]) by localhost (smtp.infotech.no [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id lg6+ObwLSPvp; Wed, 22 May 2013 00:21:09 +0200 (CEST) Received: from [10.7.0.30] (unknown [10.7.0.30]) by smtp.infotech.no (Postfix) with ESMTPA id 4CAEA2041CD; Wed, 22 May 2013 00:21:08 +0200 (CEST) Message-ID: <519BF353.2060800@interlog.com> Date: Tue, 21 May 2013 18:21:07 -0400 From: Douglas Gilbert User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 MIME-Version: 1.0 To: linux-arm-kernel@lists.infradead.org Subject: [PATCH] v2 rtc-at91rm9200: add support for at91sam9x5 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130521_182135_007891_AFB3D504 X-CRM114-Status: GOOD ( 17.87 ) X-Spam-Score: -1.9 (-) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-1.9 points) pts rule name description ---- ---------------------- -------------------------------------------------- -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Jean-Christophe PLAGNIOL-VILLARD , Johan Hovold , "Ferre, Nicolas" , Robert Nelson X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: dgilbert@interlog.com List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org An unspecified number of SoCs from the Atmel's at91sam9x5 family including the at91sam9g25 have a broken AT91_RTC_IMR (interrupt mask) register which always returns zero. If this bug can be neutralized, then the existing rtc-at91rm9200 driver will work. On the other hand, leaving this bug in place, and starting the RTC causes unhandled interrupts which result in the "SYS" interrupt being disabled. And that takes down several other interrupts wired or-ed through the SYS interrupt including the DBG port. This is the second version of this patch, and is against lk 3.10.0-rc2 . ChangeLog: - checks in probe() function if AT91_RTC_IMR is broken. If so, uses a shadow imr - apart from that check, SoCs with a good IMR register take the same paths through rtc-at91rm9200.c as before - SoCs with a broken IMR take a spinlock while changing the state of IER or IDR (and the shadow), mainly to disable interrupts **. ** similar technique used in arch/arm/mach-at91/clock.c Tested on an Aria G25 (at91sam9g25 based). Signed-off-by: Douglas Gilbert diff --git a/drivers/rtc/rtc-at91rm9200.c b/drivers/rtc/rtc-at91rm9200.c index 0eab77b..5f722c0 100644 --- a/drivers/rtc/rtc-at91rm9200.c +++ b/drivers/rtc/rtc-at91rm9200.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -43,10 +44,67 @@ #define AT91_RTC_EPOCH 1900UL /* just like arch/arm/common/rtctime.c */ static DECLARE_COMPLETION(at91_rtc_updated); + +static DEFINE_SPINLOCK(at91_rtc_broken_imr); + static unsigned int at91_alarm_year = AT91_RTC_EPOCH; static void __iomem *at91_rtc_regs; static int irq; +static bool has_broken_imr; +static int shadow_rtc_imr; + + + +static u32 at91_rtc_read_imr(void) +{ + if (has_broken_imr) + return shadow_rtc_imr; + else + return at91_rtc_read(AT91_RTC_IMR); +} + +static void at91_rtc_write_ier(u32 val) +{ + if (has_broken_imr) { + unsigned long flags; + + spin_lock_irqsave(&at91_rtc_broken_imr, flags); + shadow_rtc_imr |= val; + at91_rtc_write(AT91_RTC_IER, val); + spin_unlock_irqrestore(&at91_rtc_broken_imr, flags); + } else + at91_rtc_write(AT91_RTC_IER, val); +} + +static void at91_rtc_write_idr(u32 val) +{ + if (has_broken_imr) { + unsigned long flags; + + spin_lock_irqsave(&at91_rtc_broken_imr, flags); + shadow_rtc_imr &= ~val; + at91_rtc_write(AT91_RTC_IDR, val); + spin_unlock_irqrestore(&at91_rtc_broken_imr, flags); + } else + at91_rtc_write(AT91_RTC_IDR, val); +} + +/* + * Check if AT91_RTC_IMR correctly reports AT91_RTC_CALEV enabled. Then + * disable all known rtc interrupts. + */ +static void at91_rtc_check_imr_and_disable(void) +{ + at91_rtc_write(AT91_RTC_IER, AT91_RTC_ACKUPD); + has_broken_imr = ! (AT91_RTC_ACKUPD & at91_rtc_read(AT91_RTC_IMR)); + at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM | + AT91_RTC_SECEV | AT91_RTC_TIMEV | + AT91_RTC_CALEV); + if (has_broken_imr) + shadow_rtc_imr = 0; +} + /* * Decode time/date into rtc_time structure */ @@ -110,9 +168,9 @@ static int at91_rtc_settime(struct device *dev, struct rtc_time *tm) cr = at91_rtc_read(AT91_RTC_CR); at91_rtc_write(AT91_RTC_CR, cr | AT91_RTC_UPDCAL | AT91_RTC_UPDTIM); - at91_rtc_write(AT91_RTC_IER, AT91_RTC_ACKUPD); + at91_rtc_write_ier(AT91_RTC_ACKUPD); wait_for_completion(&at91_rtc_updated); /* wait for ACKUPD interrupt */ - at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD); + at91_rtc_write_idr(AT91_RTC_ACKUPD); at91_rtc_write(AT91_RTC_TIMR, bin2bcd(tm->tm_sec) << 0 @@ -169,7 +227,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) tm.tm_min = alrm->time.tm_min; tm.tm_sec = alrm->time.tm_sec; - at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM); + at91_rtc_write_idr(AT91_RTC_ALARM); at91_rtc_write(AT91_RTC_TIMALR, bin2bcd(tm.tm_sec) << 0 | bin2bcd(tm.tm_min) << 8 @@ -182,7 +240,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) if (alrm->enabled) { at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM); - at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM); + at91_rtc_write_ier(AT91_RTC_ALARM); } dev_dbg(dev, "%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __func__, @@ -198,9 +256,9 @@ static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) if (enabled) { at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM); - at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM); + at91_rtc_write_ier(AT91_RTC_ALARM); } else - at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM); + at91_rtc_write_idr(AT91_RTC_ALARM); return 0; } @@ -229,7 +287,7 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id) unsigned int rtsr; unsigned long events = 0; - rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read(AT91_RTC_IMR); + rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read_imr(); if (rtsr) { /* this interrupt is shared! Is it ours? */ if (rtsr & AT91_RTC_ALARM) events |= (RTC_AF | RTC_IRQF); @@ -289,10 +347,9 @@ static int __init at91_rtc_probe(struct platform_device *pdev) at91_rtc_write(AT91_RTC_CR, 0); at91_rtc_write(AT91_RTC_MR, 0); /* 24 hour mode */ + /* AT91_RTC_IMR register is broken on some SoCs using this driver */ /* Disable all interrupts */ - at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM | - AT91_RTC_SECEV | AT91_RTC_TIMEV | - AT91_RTC_CALEV); + at91_rtc_check_imr_and_disable(); ret = request_irq(irq, at91_rtc_interrupt, IRQF_SHARED, @@ -316,7 +373,8 @@ static int __init at91_rtc_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, rtc); - dev_info(&pdev->dev, "AT91 Real Time Clock driver.\n"); + dev_info(&pdev->dev, "AT91 Real Time Clock driver.%s\n", + (has_broken_imr ? " [AT91_RTC_IMR broken]" : "")); return 0; err_free_irq: @@ -335,9 +393,9 @@ static int __exit at91_rtc_remove(struct platform_device *pdev) struct rtc_device *rtc = platform_get_drvdata(pdev); /* Disable all interrupts */ - at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM | - AT91_RTC_SECEV | AT91_RTC_TIMEV | - AT91_RTC_CALEV); + at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM | + AT91_RTC_SECEV | AT91_RTC_TIMEV | + AT91_RTC_CALEV); free_irq(irq, pdev); rtc_device_unregister(rtc); @@ -364,7 +422,7 @@ static int at91_rtc_suspend(struct device *dev) if (device_may_wakeup(dev)) enable_irq_wake(irq); else - at91_rtc_write(AT91_RTC_IDR, at91_rtc_imr); + at91_rtc_write_idr(at91_rtc_imr); } return 0; } @@ -375,7 +433,7 @@ static int at91_rtc_resume(struct device *dev) if (device_may_wakeup(dev)) disable_irq_wake(irq); else - at91_rtc_write(AT91_RTC_IER, at91_rtc_imr); + at91_rtc_write_ier(at91_rtc_imr); } return 0; } -- 1.7.10.4