From patchwork Wed Nov 5 21:42:52 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Arnaud Ebalard X-Patchwork-Id: 5238291 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id D79B59F472 for ; Wed, 5 Nov 2014 21:43:58 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 6DEEE2015A for ; Wed, 5 Nov 2014 21:43:57 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 095D220108 for ; Wed, 5 Nov 2014 21:43:56 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Xm8L7-0003II-JC; Wed, 05 Nov 2014 21:42:05 +0000 Received: from 36.223.133.77.rev.sfr.net ([77.133.223.36] helo=smtp.natisbad.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Xm8L2-00033b-P0 for linux-arm-kernel@lists.infradead.org; Wed, 05 Nov 2014 21:42:02 +0000 Received: by smtp.natisbad.org (Postfix, from userid 109) id F04661700DF9; Wed, 5 Nov 2014 22:41:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=natisbad.org; s=mail; t=1415223694; bh=I/P+DxKgM1np54GDhDr4+XgonAAJiu4pglOO2Xa4sVY=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=DSDfByC1Klt6vvQGTY+7D/ge1uJgZe/nQGVa6XHd0BzVRnTK5bWnBVxbCiqm4Aoqa wAcGbKD3jW7Xm0NWtF9dF7T9N0YCNtaL98Q9Gh048YJlp/EgThG3Fwu1JlInOFzUN/ wgDT3uvr8Cl5qMG7wh8NwgcbEn7s3qlnBRMzabbM= X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_NONE,RP_MATCHES_RCVD,T_DKIM_INVALID,UNPARSEABLE_RELAY autolearn=ham version=3.3.1 Received: from small (localhost [IPv6:::1]) by smtp.natisbad.org (Postfix) with ESMTP id ED1B51700DA8; Wed, 5 Nov 2014 22:39:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=natisbad.org; s=mail; t=1415223578; bh=I/P+DxKgM1np54GDhDr4+XgonAAJiu4pglOO2Xa4sVY=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=3uM/4P74NvEjOhtSnjPWrNS7VyLvZAgGBMB4QwuG/fEh49Vy+IOBFG2lLcmhqIT0R oQDeTcUfmPw4LLi1/t3+08qxROtehN5kRpILv8yXZXqDEeSjQUdj/DJxX8LHcVh9Nx 6gfBfB+b1h0qpyk9t7ZDorWmaYaCdpgO8kgJiKI8= X-Hashcash: 1:20:141105:mark.rutland@arm.com::tJyuB/HBH04JGi/l:0000000000000000000000000000000000000000049Tz X-Hashcash: 1:20:141105:a.zummo@towertech.it::TGKS7zQZ+lh+wIly:000000000000000000000000000000000000000000ili X-Hashcash: 1:20:141105:peter.huewe@infineon.com::6ddZLCwOPgnjFCwB:00000000000000000000000000000000000003oB5 X-Hashcash: 1:20:141105:linus.walleij@linaro.org::DIJfqSzY+tiqQ9Y6:00000000000000000000000000000000000002AXR X-Hashcash: 1:20:141105:treding@nvidia.com::BnUv0V43k84NLpkV:00000000000000000000000000000000000000000000kPP X-Hashcash: 1:20:141105:broonie@kernel.org::srkd3+Y55X815hUt:000000000000000000000000000000000000000000089Z0 X-Hashcash: 1:20:141105:rob.herring@calxeda.com::uEn9lrPNNci1lMZs:000000000000000000000000000000000000000KAF X-Hashcash: 1:20:141105:pawel.moll@arm.com::j8abCxKyfRGTQU+s:00000000000000000000000000000000000000000000oWr X-Hashcash: 1:20:141105:swarren@wwwdotorg.org::YFD1vjyvii2YvC4E:00000000000000000000000000000000000000005KCu X-Hashcash: 1:20:141105:ijc+devicetree@hellion.org.uk::ivFlw1jJztPVHLvC:0000000000000000000000000000000025gh X-Hashcash: 1:20:141105:grant.likely@linaro.org::yRKfmjIvFtutHlhJ:000000000000000000000000000000000000000IqE X-Hashcash: 1:20:141105:devicetree@vger.kernel.org::AzPY0K6jd8m8XTO2:000000000000000000000000000000000002NoL X-Hashcash: 1:20:141105:linux-doc@vger.kernel.org::D/uCrD5lMzad85cf:0000000000000000000000000000000000000Oyg X-Hashcash: 1:20:141105:rob@landley.net::2Gh2FJ4EeLyOO2qL:0021m7 X-Hashcash: 1:20:141105:rtc-linux@googlegroups.com::m4bQhYNoEzyHLNvp:000000000000000000000000000000000001P53 X-Hashcash: 1:20:141105:jason@lakedaemon.net::Q5h/wWX7V5CWldpu:000000000000000000000000000000000000000005ndy X-Hashcash: 1:20:141105:linux@roeck-us.net::uxxIgtZDX++6HQn6:00000000000000000000000000000000000000000001GLY X-Hashcash: 1:20:141105:jgunthorpe@obsidianresearch.com::5DVq1M1r+SEYbeZG:0000000000000000000000000000006yeZ X-Hashcash: 1:20:141105:galak@codeaurora.org::IWx8luswqnU33F5p:000000000000000000000000000000000000000003BrN X-Hashcash: 1:20:141105:linux-arm-kernel@lists.infradead.org::m8G6sag+u9QArZxQ:00000000000000000000000003m7q X-Hashcash: 1:20:141105:uwe@kleine-koenig.org::haCGZqCBYzliFoVy:00000000000000000000000000000000000000000lW6 From: Arnaud Ebalard To: Mark Rutland , Alessandro Zummo , Peter Huewe , Linus Walleij , Thierry Reding , Mark Brown Subject: [PATCHv0 3/3] rtc: rtc-isl12057: add alarm support to Intersil ISL12057 RTC driver In-Reply-To: References: Message-Id: <148403c0ab1d556cbb99d9242c65f714a77843e5.1415222752.git.arno@natisbad.org> Date: Wed, 05 Nov 2014 22:42:52 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux) MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20141105_134201_209355_DA86904A X-CRM114-Status: GOOD ( 27.38 ) X-Spam-Score: 0.3 (/) Cc: devicetree@vger.kernel.org, rtc-linux@googlegroups.com, Pawel Moll , Ian Campbell , Stephen Warren , linux-doc@vger.kernel.org, Rob Herring , Jason Gunthorpe , Uwe =?utf-8?Q?Kleine-K=C3=B6nig?= , linux-arm-kernel@lists.infradead.org, Rob Landley , Kumar Gala , Grant Likely , Guenter Roeck , Jason Cooper X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list 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 X-Virus-Scanned: ClamAV using ClamSMTP This patch adds alarm support to Intersil ISL12057 driver. This allows to configure the chip to generate an interrupt when the alarm matches current time value. Alarm can be programmed up to one month in the future. The patch was tested on a Netgear ReadyNAS 102 after some soldering of the IRQ#2 pin of the RTC chip to a MPP line of the SoC (the one used usually handles the reset button). By default, on this device, the interrupt line of the RTC chip is not directly connected to the SoC but to a PMIC which handles powering of the device upon alarm when it is off. The test was performed using a modified .dts file reflecting this change and rtc-test.c program available in Documentation/rtc.txt. This test program ran as expected, which validates alarm supports, including interrupt support. Additional tests were performed on an unmodified ReadyNAS 102 to validate the ability to wake up the device based on a configured alarm value. As a side note, the ISL12057 remains in the list of trivial devices, i.e. no specific DT binding being added by this patch: i2c core automatically handles extraction of IRQ line info from .dts file. For instance, if one wants to reference the interrupt line for the alarm in its .dts file, adding interrupt and interrupt-parent properties should work as expected: isl12057: isl12057@68 { compatible = "isil,isl12057"; interrupt-parent = <&gpio0>; interrupts = <6 IRQ_TYPE_EDGE_FALLING>; reg = <0x68>; }; Signed-off-by: Arnaud Ebalard --- .../devicetree/bindings/i2c/trivial-devices.txt | 2 +- drivers/rtc/rtc-isl12057.c | 311 ++++++++++++++++++++- 2 files changed, 304 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index edac97c0f756..f716e7da44a9 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -55,7 +55,7 @@ fsl,sgtl5000 SGTL5000: Ultra Low-Power Audio Codec gmt,g751 G751: Digital Temperature Sensor and Thermal Watchdog with Two-Wire Interface infineon,slb9635tt Infineon SLB9635 (Soft-) I2C TPM (old protocol, max 100khz) infineon,slb9645tt Infineon SLB9645 I2C TPM (new protocol, max 400khz) -isil,isl12057 Intersil ISL12057 I2C RTC Chip +isil,isl12057 Intersil ISL12057 I2C RTC/Alarm Chip maxim,ds1050 5 Bit Programmable, Pulse-Width Modulator maxim,max1237 Low-Power, 4-/12-Channel, 2-Wire Serial, 12-Bit ADCs maxim,max6625 9-Bit/12-Bit Temperature Sensors with I²C-Compatible Serial Interface diff --git a/drivers/rtc/rtc-isl12057.c b/drivers/rtc/rtc-isl12057.c index adb0646236b1..56d222f31baa 100644 --- a/drivers/rtc/rtc-isl12057.c +++ b/drivers/rtc/rtc-isl12057.c @@ -1,5 +1,5 @@ /* - * rtc-isl12057 - Driver for Intersil ISL12057 I2C Real Time Clock + * rtc-isl12057 - Driver for Intersil ISL12057 I2C Real Time Clock / Alarm * * Copyright (C) 2013, Arnaud EBALARD * @@ -27,7 +27,6 @@ #include #include #include -#include #include #define DRV_NAME "rtc-isl12057" @@ -78,6 +77,7 @@ #define ISL12057_MEM_MAP_LEN 0x10 struct isl12057_rtc_data { + struct rtc_device *rtc; struct regmap *regmap; struct mutex lock; }; @@ -148,17 +148,42 @@ static int isl12057_i2c_validate_chip(struct regmap *regmap) return 0; } -static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) +static int _isl12057_rtc_clear_alarm(struct device *dev) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(data->regmap, ISL12057_REG_SR, + ISL12057_REG_SR_A1F, 0); + + if (ret) + dev_err(dev, "%s: clearing alarm failed\n", __func__); + + return ret; +} + +static int _isl12057_rtc_toggle_alarm(struct device *dev, int enable) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(data->regmap, ISL12057_REG_INT, + ISL12057_REG_INT_A1IE, + enable ? ISL12057_REG_INT_A1IE : 0); + if (ret) + dev_err(dev, "%s: writing INT failed\n", __func__); + + return ret; +} + +static int _isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct isl12057_rtc_data *data = dev_get_drvdata(dev); u8 regs[ISL12057_RTC_SEC_LEN]; int ret; - mutex_lock(&data->lock); ret = regmap_bulk_read(data->regmap, ISL12057_REG_RTC_SC, regs, ISL12057_RTC_SEC_LEN); - mutex_unlock(&data->lock); - if (ret) { dev_err(dev, "%s: RTC read failed\n", __func__); return ret; @@ -169,6 +194,176 @@ static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) return rtc_valid_tm(tm); } +static int isl12057_rtc_toggle_alarm(struct device *dev, int enable) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->lock); + ret = _isl12057_rtc_toggle_alarm(dev, enable); + mutex_unlock(&data->lock); + + return ret; +} + +static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->lock); + ret = _isl12057_rtc_read_time(dev, tm); + mutex_unlock(&data->lock); + + return ret; +} + +static int isl12057_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + struct rtc_time rtc_tm, *alarm_tm = &alarm->time; + unsigned long rtc_secs, alarm_secs; + u8 regs[ISL12057_A1_SEC_LEN]; + unsigned int ir; + int ret; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, ISL12057_REG_A1_SC, regs, + ISL12057_A1_SEC_LEN); + if (ret) { + dev_err(dev, "%s: reading alarm section failed\n", __func__); + goto err_unlock; + } + + alarm_tm->tm_sec = bcd2bin(regs[0] & 0x7f); + alarm_tm->tm_min = bcd2bin(regs[1] & 0x7f); + alarm_tm->tm_hour = bcd2bin(regs[2] & 0x3f); + alarm_tm->tm_mday = bcd2bin(regs[3] & 0x3f); + alarm_tm->tm_wday = -1; + + /* + * The alarm section does not store year/month. We use the ones in rtc + * section as a basis and increment month and then year if needed to get + * alarm after current time. + */ + ret = _isl12057_rtc_read_time(dev, &rtc_tm); + if (ret) + goto err_unlock; + + alarm_tm->tm_year = rtc_tm.tm_year; + alarm_tm->tm_mon = rtc_tm.tm_mon; + + ret = rtc_tm_to_time(&rtc_tm, &rtc_secs); + if (ret) + goto err_unlock; + + ret = rtc_tm_to_time(alarm_tm, &alarm_secs); + if (ret) + goto err_unlock; + + if (alarm_secs < rtc_secs) { + if (alarm_tm->tm_mon == 11) { + alarm_tm->tm_mon = 0; + alarm_tm->tm_year += 1; + } else { + alarm_tm->tm_mon += 1; + } + } + + ret = regmap_read(data->regmap, ISL12057_REG_INT, &ir); + if (ret) { + dev_err(dev, "%s: reading INT failed\n", __func__); + goto err_unlock; + } + + alarm->enabled = !!(ir & ISL12057_REG_INT_A1IE); + +err_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int isl12057_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + struct rtc_time *alarm_tm = &alarm->time; + unsigned long rtc_secs, alarm_secs; + u8 regs[ISL12057_A1_SEC_LEN]; + struct rtc_time rtc_tm; + int ret, enable = 1; + + mutex_lock(&data->lock); + ret = _isl12057_rtc_read_time(dev, &rtc_tm); + if (ret) + goto err_unlock; + + ret = rtc_tm_to_time(&rtc_tm, &rtc_secs); + if (ret) + goto err_unlock; + + ret = rtc_tm_to_time(alarm_tm, &alarm_secs); + if (ret) + goto err_unlock; + + /* If alarm time is before current time, disable the alarm */ + if (!alarm->enabled || alarm_secs <= rtc_secs) { + enable = 0; + } else { + /* + * Chip only support alarms up to one month in the future. Let's + * return an error if we get something after that limit. + * Comparison is done by incrementing rtc_tm month field by one + * and checking alarm value is still below. + */ + if (rtc_tm.tm_mon == 11) { /* handle year wrapping */ + rtc_tm.tm_mon = 0; + rtc_tm.tm_year += 1; + } else { + rtc_tm.tm_mon += 1; + } + + ret = rtc_tm_to_time(&rtc_tm, &rtc_secs); + if (ret) + goto err_unlock; + + if (alarm_secs > rtc_secs) { + dev_err(dev, "%s: max one month in the future\n", + __func__); + ret = -EINVAL; + goto err_unlock; + } + } + + /* Disable the alarm before modifying it */ + ret = _isl12057_rtc_toggle_alarm(dev, 0); + if (ret < 0) { + dev_err(dev, "Unable to disable the alarm\n"); + goto err_unlock; + } + + /* Program alarm registers */ + regs[0] = bin2bcd(alarm_tm->tm_sec) & 0x7f; + regs[1] = bin2bcd(alarm_tm->tm_min) & 0x7f; + regs[2] = bin2bcd(alarm_tm->tm_hour) & 0x3f; + regs[3] = bin2bcd(alarm_tm->tm_mday) & 0x3f; + + ret = regmap_bulk_write(data->regmap, ISL12057_REG_A1_SC, regs, + ISL12057_A1_SEC_LEN); + if (ret < 0) { + dev_err(dev, "%s: writing ALARM section failed\n", __func__); + goto err_unlock; + } + + /* Enable or disable alarm */ + ret = _isl12057_rtc_toggle_alarm(dev, enable); + +err_unlock: + mutex_unlock(&data->lock); + + return ret; +} + static int isl12057_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct isl12057_rtc_data *data = dev_get_drvdata(dev); @@ -226,9 +421,42 @@ static int isl12057_check_rtc_status(struct device *dev, struct regmap *regmap) return 0; } +static int isl12057_rtc_alarm_irq_enable(struct device *dev, + unsigned int enable) +{ + return isl12057_rtc_toggle_alarm(dev, enable); +} + +static irqreturn_t isl12057_rtc_interrupt(int irq, void *data) +{ + struct i2c_client *client = data; + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(&client->dev); + struct rtc_device *rtc = rtc_data->rtc; + int ret, handled = IRQ_NONE; + unsigned int sr; + + ret = regmap_read(rtc_data->regmap, ISL12057_REG_SR, &sr); + if (!ret && (sr & ISL12057_REG_SR_A1F)) { + dev_dbg(&client->dev, "RTC alarm!\n"); + + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + + /* Acknowledge and disable the alarm */ + _isl12057_rtc_clear_alarm(&client->dev); + _isl12057_rtc_toggle_alarm(&client->dev, 0); + + handled = IRQ_HANDLED; + } + + return handled; +} + static const struct rtc_class_ops rtc_ops = { .read_time = isl12057_rtc_read_time, .set_time = isl12057_rtc_set_time, + .read_alarm = isl12057_rtc_read_alarm, + .set_alarm = isl12057_rtc_set_alarm, + .alarm_irq_enable = isl12057_rtc_alarm_irq_enable, }; static struct regmap_config isl12057_rtc_regmap_config = { @@ -273,10 +501,75 @@ static int isl12057_probe(struct i2c_client *client, data->regmap = regmap; dev_set_drvdata(dev, data); + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + isl12057_rtc_interrupt, + IRQF_SHARED|IRQF_ONESHOT, + DRV_NAME, client); + if (!ret) { + device_init_wakeup(&client->dev, true); + } else { + dev_err(dev, "irq %d unavailable, no alarm support\n", + client->irq); + client->irq = 0; + } + } + rtc = devm_rtc_device_register(dev, DRV_NAME, &rtc_ops, THIS_MODULE); - return PTR_ERR_OR_ZERO(rtc); + ret = PTR_ERR_OR_ZERO(rtc); + if (ret) { + dev_err(dev, "unable to register RTC device\n"); + goto err; + + /* + * XXX we may additionally need to unregister wakeup source + * on error: see https://lkml.org/lkml/2014/10/9/425 + */ + } + + data->rtc = rtc; + + return ret; + +err: + if (client->irq > 0) + free_irq(client->irq, client); + + return ret; +} + +static int isl12057_remove(struct i2c_client *client) +{ + if (client->irq > 0) + free_irq(client->irq, client); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int isl12057_rtc_suspend(struct device *dev) +{ + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(&client->dev); + + if (device_may_wakeup(dev)) + return enable_irq_wake(rtc_data->irq); + + return 0; } +static int isl12057_rtc_resume(struct device *dev) +{ + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(&client->dev); + + if (device_may_wakeup(dev)) + return disable_irq_wake(rtc_data->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(isl12057_rtc_pm_ops, isl12057_rtc_suspend, + isl12057_rtc_resume); + #ifdef CONFIG_OF static const struct of_device_id isl12057_dt_match[] = { { .compatible = "isil,isl12057" }, @@ -294,13 +587,15 @@ static struct i2c_driver isl12057_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, + .pm = &isl12057_rtc_pm_ops, .of_match_table = of_match_ptr(isl12057_dt_match), }, .probe = isl12057_probe, + .remove = isl12057_remove, .id_table = isl12057_id, }; module_i2c_driver(isl12057_driver); MODULE_AUTHOR("Arnaud EBALARD "); -MODULE_DESCRIPTION("Intersil ISL12057 RTC driver"); +MODULE_DESCRIPTION("Intersil ISL12057 RTC/Alarm driver"); MODULE_LICENSE("GPL");