From patchwork Mon Dec 15 23:54:25 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnaud Ebalard X-Patchwork-Id: 5498451 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 895709F326 for ; Mon, 15 Dec 2014 23:54:18 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 3AE2F209EB for ; Mon, 15 Dec 2014 23:54:17 +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 DFF2D209E1 for ; Mon, 15 Dec 2014 23:54:15 +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 1Y0fRC-0000up-QJ; Mon, 15 Dec 2014 23:52:26 +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 1Y0fR7-0000kG-Rk for linux-arm-kernel@lists.infradead.org; Mon, 15 Dec 2014 23:52:23 +0000 Received: by smtp.natisbad.org (Postfix, from userid 109) id 62B431700508; Tue, 16 Dec 2014 00:51:59 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=natisbad.org; s=mail; t=1418687519; bh=krheJwSGJfhjFtJrOu4bp+7C0QWUkU7TGN6KH9D3NWg=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=M5ckkn57HTo00rqa7g1sDca0NzQZl85WvVxdmTfpZARA3qnvZv7z+ObZhs3upYerJ f3kFXeNyIW3GPq5EdgTLTSXPebwEhaSzKkp1mtozWK4FGorA5gMdZRKH/s8G0YlEYl 9uOc5LH0jMlrR3fAV44BkfM28IMeAaTyi/vI0sYY= X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from small (localhost [IPv6:::1]) by smtp.natisbad.org (Postfix) with ESMTP id CD1581700458; Tue, 16 Dec 2014 00:50:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=natisbad.org; s=mail; t=1418687436; bh=krheJwSGJfhjFtJrOu4bp+7C0QWUkU7TGN6KH9D3NWg=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=eOobTFlEKqfaAAo8j9+3XrUT9oZNonMK4thfWyUL4NSvnEBiKTZesWAF4z0OcO4Z6 VJm8RPHC1t8YVssESGkHmPsjWNiHvBYTA2U3ihlhCs+snOSgci3diJ/phLvy+hVhQ/ yd8Okq23pWI1kHNySGRQbvqAqV9VEzAyMrjc4Q/4= X-Hashcash: 1:20:141215:akpm@linux-foundation.org::x8oYnthlDksGcqbw:00000000000000000000000000000000000053bz X-Hashcash: 1:20:141215:uwe@kleine-koenig.org::6yXFHZ5awe8xRPw3:000000000000000000000000000000000000000018Es X-Hashcash: 1:20:141215:mark.rutland@arm.com::m8QikggJIgpELbVF:000000000000000000000000000000000000000000NGB X-Hashcash: 1:20:141215:a.zummo@towertech.it::/YeMlDoPjfbrkopJ:000000000000000000000000000000000000000000TMD X-Hashcash: 1:20:141215:peter.huewe@infineon.com::O8Wz1z/YaDH4sUph:000000000000000000000000000000000000044eg X-Hashcash: 1:20:141215:linus.walleij@linaro.org::0XgyQY97r/gEMP6o:00000000000000000000000000000000000002m6g X-Hashcash: 1:20:141215:treding@nvidia.com::Oq9C/X2Rwl0IpB6Q:00000000000000000000000000000000000000000002Zcd X-Hashcash: 1:20:141215:broonie@kernel.org::RfInGp40TE+/S5vZ:00000000000000000000000000000000000000000000M7K X-Hashcash: 1:20:141215:arnd@arndb.de::9BamO2gSX7fxxgkA:0000FedQ X-Hashcash: 1:20:141215:darshanapadmadas@gmail.com::YTJAMaTm4S3tfiOR:0000000000000000000000000000000000026oy X-Hashcash: 1:20:141215:rob.herring@calxeda.com::SJgFno3xdCRLWs7i:000000000000000000000000000000000000000YCf X-Hashcash: 1:20:141215:pawel.moll@arm.com::0cg0uek3ipCpi4F3:00000000000000000000000000000000000000000000BO9 X-Hashcash: 1:20:141215:swarren@wwwdotorg.org::nGPGLDL4/zNOeDrL:000000000000000000000000000000000000000001cX X-Hashcash: 1:20:141215:ijc+devicetree@hellion.org.uk::+hC9xcWgBmpH7xKn:000000000000000000000000000000002S0T X-Hashcash: 1:20:141215:grant.likely@linaro.org::laNuNa9f59XkWGAu:00000000000000000000000000000000000000ACVg X-Hashcash: 1:20:141215:devicetree@vger.kernel.org::5vLAOKGetQC/kIWy:000000000000000000000000000000000001cto X-Hashcash: 1:20:141215:linux-doc@vger.kernel.org::E5iLNYvHYBG1wvvg:0000000000000000000000000000000000008QfU X-Hashcash: 1:20:141215:rob@landley.net::sp9b9flF/w6R4THA:000b85 X-Hashcash: 1:20:141215:rtc-linux@googlegroups.com::YlO9Els03rnGtrGY:000000000000000000000000000000000005qU2 X-Hashcash: 1:20:141215:jason@lakedaemon.net::rbSc+ZP3jg9CQxA9:000000000000000000000000000000000000000000rpB X-Hashcash: 1:20:141215:linux@roeck-us.net::bohAs9hj+Ch7cKgI:00000000000000000000000000000000000000000000l3P X-Hashcash: 1:20:141215:jgunthorpe@obsidianresearch.com::zaZlm6PsIx9garHc:0000000000000000000000000000003u+b X-Hashcash: 1:20:141215:galak@codeaurora.org::81E73/cjCliw6uEj:000000000000000000000000000000000000000006jJC X-Hashcash: 1:20:141215:linux-arm-kernel@lists.infradead.org::DWig0Qq74E8y4KE0:000000000000000000000000090HO From: Arnaud Ebalard To: Andrew Morton , Uwe =?utf-8?Q?Kleine-K=C3=B6nig?= Subject: [PATCHv1 1/3] rtc: rtc-isl12057: add alarm support to Intersil ISL12057 RTC driver In-Reply-To: References: Message-Id: Date: Tue, 16 Dec 2014 00:54:25 +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-20141215_155222_263712_2F3A9627 X-CRM114-Status: GOOD ( 30.32 ) X-Spam-Score: 0.3 (/) Cc: Mark Rutland , linux-doc@vger.kernel.org, Linus Walleij , Arnd Bergmann , Ian Campbell , Jason Gunthorpe , rtc-linux@googlegroups.com, Thierry Reding , Guenter Roeck , devicetree@vger.kernel.org, Jason Cooper , Pawel Moll , Stephen Warren , Rob Herring , Grant Likely , Mark Brown , Peter Huewe , Darshana Padmadas , linux-arm-kernel@lists.infradead.org, Alessandro Zummo , Rob Landley , Kumar Gala 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. The chip supports two separate alarms: - Alarm1: works up to one month in the future and accurate to the second, associated w/ IRQ#2 pin - Alarm2: works up to one month in the future and accurate to the minute, associated w/ IRQ#1 or IRQ#2 pin (configuable). This patch only adds support for Alarm1 which allows to configure the chip to generate an interrupt on IRQ#2 pin when current time matches the alarm. This 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). The test was performed using a modified .dts file reflecting this change (see below) and rtc-test.c program available in Documentation/rtc.txt. This test program ran as expected, which validates alarm supports, including interrupt support. 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 works as expected (if the primary function of your interrupt pin is not GPIO, you will need some additional pinctrl properties): isl12057: isl12057@68 { compatible = "isil,isl12057"; interrupt-parent = <&gpio0>; interrupts = <6 IRQ_TYPE_EDGE_FALLING>; reg = <0x68>; }; FWIW, if someone is looking for a way to test alarm support this can be done in the following way: # echo `date '+%s' -d '+ 1 minutes'` > /sys/class/rtc/rtc0/wakealarm # shutdown -h now With the commands above, after a minute, the system comes back to life. Signed-off-by: Arnaud Ebalard --- drivers/rtc/rtc-isl12057.c | 313 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 305 insertions(+), 8 deletions(-) diff --git a/drivers/rtc/rtc-isl12057.c b/drivers/rtc/rtc-isl12057.c index 6e1fcfb5d7e6..3ec73ad7f2d8 100644 --- a/drivers/rtc/rtc-isl12057.c +++ b/drivers/rtc/rtc-isl12057.c @@ -79,8 +79,10 @@ #define ISL12057_MEM_MAP_LEN 0x10 struct isl12057_rtc_data { + struct rtc_device *rtc; struct regmap *regmap; struct mutex lock; + int irq; }; static void isl12057_rtc_regs_to_tm(struct rtc_time *tm, u8 *regs) @@ -160,14 +162,47 @@ 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 (%d)\n", __func__, ret); + + return ret; +} + +static int _isl12057_rtc_update_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: changing alarm interrupt flag failed (%d)\n", + __func__, ret); + + return ret; +} + +/* + * Note: as we only read from device and do not perform any update, there is + * no need for an equivalent function which would try and get driver's main + * lock. Here, it is safe for everyone if we just use regmap internal lock + * on the device when reading. + */ +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]; unsigned int sr; int ret; - mutex_lock(&data->lock); ret = regmap_read(data->regmap, ISL12057_REG_SR, &sr); if (ret) { dev_err(dev, "%s: unable to read oscillator status flag (%d)\n", @@ -187,8 +222,6 @@ static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm) __func__, ret); out: - mutex_unlock(&data->lock); - if (ret) return ret; @@ -197,6 +230,168 @@ out: return rtc_valid_tm(tm); } +static int isl12057_rtc_update_alarm(struct device *dev, int enable) +{ + struct isl12057_rtc_data *data = dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->lock); + ret = _isl12057_rtc_update_alarm(dev, enable); + 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 (%d)\n", + __func__, ret); + 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 alarm interrupt flag failed (%d)\n", + __func__, ret); + 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 for alarm is one month (%d)\n", + __func__, ret); + ret = -EINVAL; + goto err_unlock; + } + } + + /* Disable the alarm before modifying it */ + ret = _isl12057_rtc_update_alarm(dev, 0); + if (ret < 0) { + dev_err(dev, "%s: unable to disable the alarm (%d)\n", + __func__, ret); + 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 (%d)\n", + __func__, ret); + goto err_unlock; + } + + /* Enable or disable alarm */ + ret = _isl12057_rtc_update_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); @@ -262,9 +457,48 @@ 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) +{ + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(dev); + int ret = -ENOTTY; + + if (rtc_data->irq) + ret = isl12057_rtc_update_alarm(dev, enable); + + return ret; +} + +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_update_alarm(&client->dev, 0); + + handled = IRQ_HANDLED; + } + + return handled; +} + static const struct rtc_class_ops rtc_ops = { - .read_time = isl12057_rtc_read_time, + .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 = { @@ -277,7 +511,6 @@ static int isl12057_probe(struct i2c_client *client, { struct device *dev = &client->dev; struct isl12057_rtc_data *data; - struct rtc_device *rtc; struct regmap *regmap; int ret; @@ -310,10 +543,72 @@ static int isl12057_probe(struct i2c_client *client, data->regmap = regmap; dev_set_drvdata(dev, data); - rtc = devm_rtc_device_register(dev, DRV_NAME, &rtc_ops, THIS_MODULE); - return PTR_ERR_OR_ZERO(rtc); + if (client->irq > 0) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + isl12057_rtc_interrupt, + IRQF_SHARED|IRQF_ONESHOT, + DRV_NAME, client); + if (!ret) + data->irq = client->irq; + else + dev_err(dev, "%s: irq %d unavailable (%d)\n", __func__, + client->irq, ret); + } + + device_init_wakeup(dev, !!data->irq); + + data->rtc = devm_rtc_device_register(dev, DRV_NAME, &rtc_ops, + THIS_MODULE); + ret = PTR_ERR_OR_ZERO(data->rtc); + if (ret) { + dev_err(dev, "%s: unable to register RTC device (%d)\n", + __func__, ret); + goto err; + } + + /* We cannot support UIE mode if we do not have an IRQ line */ + if (!data->irq) + data->rtc->uie_unsupported = 1; + +err: + return ret; +} + +static int isl12057_remove(struct i2c_client *client) +{ + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(&client->dev); + + if (rtc_data->irq) + device_init_wakeup(&client->dev, false); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int isl12057_rtc_suspend(struct device *dev) +{ + struct isl12057_rtc_data *rtc_data = dev_get_drvdata(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(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 = "isl,isl12057" }, @@ -331,9 +626,11 @@ 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);