From patchwork Thu Jul 24 16:43:08 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Fuzzey X-Patchwork-Id: 373469 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-we0-x23b.google.com (mail-we0-x23b.google.com [IPv6:2a00:1450:400c:c03::23b]) (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 34F7A140216 for ; Fri, 25 Jul 2014 02:43:11 +1000 (EST) Received: by mail-we0-f187.google.com with SMTP id u57sf328420wes.14 for ; Thu, 24 Jul 2014 09:43:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20120806; h=subject:to:from:date:message-id:in-reply-to:references:user-agent :mime-version: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=pIrYra2h/fpgWqkn9PIHq/+8a9FeEohzHrHSpnbOel8=; b=fshgb0FCGAH7qq5K9LIeTHfAWTO0O9MB8xk4PFLKv47JoWwXSR7zfPMntdAqTngLJU ah7EgvMEKRoPdzYh1h4hPSViveDznvjfoWP1AByBgodOBHLY4ncV2PYHooRyCiOdK4QX m+SshWpXO3DqzTkElcTAniCyVatfoNmrp36cATV9h6d0X0gQuNnHDXgXB5XI59RIFrEp o6Np1e9L6gQvqrBd9oEToQne88rOlZvZGnutZVSG8sx0Fd4CJK+vYGOaviLLBxAxmDlT u8vrJdCtcSHXAV0yoVCnVc9oPvd6Cvd/u4x5luZruzKObZedi1tc0SDgSod2KzmYmjHX n2KQ== X-Received: by 10.180.210.237 with SMTP id mx13mr21883wic.2.1406220188864; Thu, 24 Jul 2014 09:43:08 -0700 (PDT) X-BeenThere: rtc-linux@googlegroups.com Received: by 10.180.101.134 with SMTP id fg6ls490330wib.21.canary; Thu, 24 Jul 2014 09:43:08 -0700 (PDT) X-Received: by 10.180.13.235 with SMTP id k11mr1733291wic.0.1406220188531; Thu, 24 Jul 2014 09:43:08 -0700 (PDT) Received: from mta1.parkeon.com (mta1.parkeon.com. [91.121.43.66]) by gmr-mx.google.com with ESMTPS id m6si471167wik.2.2014.07.24.09.43.08 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Thu, 24 Jul 2014 09:43:08 -0700 (PDT) Received-SPF: none (google.com: mfuzzey@parkeon.com does not designate permitted sender hosts) client-ip=91.121.43.66; Received: from ip71.parkeon.com ([213.152.31.71] helo=mta2.parkeon.com) by mta1.parkeon.com with esmtp (Exim 4.76) (envelope-from ) id 1XAM6m-0006NT-BQ; Thu, 24 Jul 2014 18:43:08 +0200 Received: from mail.besancon.parkeon.com ([10.32.16.23]) by mta2.parkeon.com with esmtp (Exim 4.77) (envelope-from ) id 1XAM6j-0007vc-VP; Thu, 24 Jul 2014 18:43:05 +0200 Received: from [10.32.51.161] (port=53379 helo=[127.0.0.1]) by mail.besancon.parkeon.com with esmtp (Exim 4.71) (envelope-from ) id 1XAM6m-0004pJ-7V; Thu, 24 Jul 2014 18:43:08 +0200 Subject: [rtc-linux] [PATCH 2/2] rtc: pcf8563: Add alarm support To: Alessandro Zummo , rtc-linux@googlegroups.com From: Martin Fuzzey Date: Thu, 24 Jul 2014 18:43:08 +0200 Message-ID: <20140724164308.23803.86301.stgit@localhost> In-Reply-To: <20140724164303.23803.85387.stgit@localhost> References: <20140724164303.23803.85387.stgit@localhost> User-Agent: StGit/0.16 MIME-Version: 1.0 X-Virus-Scanned: by ClamAV at mta2.parkeon.com X-Original-Sender: mfuzzey@parkeon.com X-Original-Authentication-Results: gmr-mx.google.com; spf=neutral (google.com: mfuzzey@parkeon.com does not designate permitted sender hosts) smtp.mail=mfuzzey@parkeon.com Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: X-Google-Group-Id: 712029733259 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Signed-off-by: Martin Fuzzey --- drivers/rtc/rtc-pcf8563.c | 244 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 88b5c99..8581954 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -26,6 +26,10 @@ #define PCF8563_REG_ST1 0x00 /* status */ #define PCF8563_REG_ST2 0x01 +#define PCF8563_BIT_ST2_TIE (1 << 0) +#define PCF8563_BIT_ST2_AIE (1 << 1) +#define PCF8563_BIT_ST2_TF (1 << 2) +#define PCF8563_BIT_ST2_AF (1 << 3) #define PCF8563_REG_SC 0x02 /* datetime */ #define PCF8563_REG_MN 0x03 @@ -181,6 +185,182 @@ static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm) &buf[PCF8563_REG_SC]); } +static int pcf8563_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rtc_time *tm = &alrm->time; + uint8_t alarm_buf[3]; + s32 status; + int ret; + + ret = pcf8563_read_regs(client, PCF8563_REG_AMN, + sizeof(alarm_buf), alarm_buf); + if (ret) + return ret; + + status = i2c_smbus_read_byte_data(client, PCF8563_REG_ST2); + if (status < 0) + return status; + + tm->tm_sec = -1; + tm->tm_min = bcd2bin(alarm_buf[0] & 0x7f); + tm->tm_hour = bcd2bin(alarm_buf[1] & 0x3f); + tm->tm_mday = bcd2bin(alarm_buf[2] & 0x3f); + tm->tm_mon = -1; + tm->tm_year = -1; + alrm->pending = status & PCF8563_BIT_ST2_AF ? 1 : 0; + + dev_dbg(dev, "%s: get alarm day=%d %02d:%02d pending=%d\n", __func__, + tm->tm_mday, tm->tm_hour, tm->tm_min, alrm->pending); + + return 0; +} + + +static int pcf8563_rtc_enable_alarm_irq(struct i2c_client *client, bool enable) +{ + uint8_t st2; + + dev_dbg(&client->dev, "%s %d\n", __func__, enable); + + st2 = PCF8563_BIT_ST2_AF; /* Wired AND => leave as is */ + if (enable) + st2 |= PCF8563_BIT_ST2_AIE; + + return i2c_smbus_write_byte_data(client, PCF8563_REG_ST2, st2); +} + + +static int pcf8563_rtc_check_alarm_valid(struct i2c_client *client, + struct rtc_time *alrm) +{ + unsigned long alrmsecs, rtcsecs; + struct rtc_time rtctm; + int ret; + + ret = pcf8563_get_datetime(client, &rtctm); + if (ret) + return ret; + + rtc_tm_to_time(&rtctm, &rtcsecs); + rtc_tm_to_time(alrm, &alrmsecs); + + /* Hardware doesn't have a seconds register for alarm time */ + if ((alrmsecs - rtcsecs) < 60) { + dev_dbg(&client->dev, "Alarm < 1 minute not supported\n"); + return -EINVAL; + } + + /* Hardware doesn't have month or year registers for alarm time */ + rtctm.tm_mon++; + if (rtctm.tm_mon >= 12) { + rtctm.tm_mon = 0; + rtctm.tm_year++; + } + + rtctm.tm_mday = min(rtctm.tm_mday, + rtc_month_days(rtctm.tm_mon, rtctm.tm_year)); + rtc_tm_to_time(&rtctm, &rtcsecs); + + if (alrm->tm_sec > 0) + rtcsecs -= 60; + + if (alrmsecs > rtcsecs) { + dev_dbg(&client->dev, "Alarm too far in future\n"); + return -EINVAL; + } + + return 0; +} + +static int pcf8563_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rtc_time *tm = &alrm->time; + int min = tm->tm_min; + int hour = tm->tm_hour; + int mday = tm->tm_mday; + unsigned char data[3]; + uint8_t enable; + int ret; + + dev_dbg(dev, "%s: day=%d %02d:%02d:%02d enabled=%d\n", __func__, + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, + alrm->enabled); + + ret = pcf8563_rtc_check_alarm_valid(client, tm); + if (ret) + return ret; + + if (tm->tm_sec > 0) { + /* As hardware has no seconds alarm register + * add an extra minute otherwise alarm interrupt + * will occur before requested time causing the rtc core + * to reschedule (which will be refused since < 1 min) */ + min++; + if (min > 59) { + min = 0; + hour++; + if (hour > 23) { + hour = 0; + mday++; + if (mday > rtc_month_days( + tm->tm_mon, + tm->tm_year + 1900)) + mday = 1; + } + } + dev_dbg(dev, "round up alarm to day=%d %02d:%02d\n", + mday, hour, min); + } + + /* Disable interrupt and clear any pending alarm */ + ret = i2c_smbus_write_byte_data(client, PCF8563_REG_ST2, 0); + if (ret) + return ret; + + enable = alrm->enabled ? 0 : 0x80; /* set to disable */ + data[0] = bin2bcd(min) | enable; + data[1] = bin2bcd(hour) | enable; + data[2] = bin2bcd(mday) | enable; + ret = i2c_smbus_write_i2c_block_data(client, PCF8563_REG_AMN, + sizeof(data), data); + if (ret) + return ret; + + return pcf8563_rtc_enable_alarm_irq(client, alrm->enabled); +} + +static int pcf8563_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct i2c_client *client = to_i2c_client(dev); + + dev_dbg(dev, "%s %d\n", __func__, enabled); + + return pcf8563_rtc_enable_alarm_irq(client, enabled); +} + + +static irqreturn_t pcf8563_irq_handler(int irq, void *data) +{ + struct i2c_client *client = data; + struct pcf8563 *pcf8563 = i2c_get_clientdata(client); + int ret; + + dev_dbg(&client->dev, "%s\n", __func__); + + ret = pcf8563_rtc_enable_alarm_irq(client, false); + if (ret) { + dev_err(&client->dev, "Failed to disable irq: %d", ret); + return IRQ_NONE; + } + + rtc_update_irq(pcf8563->rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + #ifdef CONFIG_RTC_INTF_DEV static int pcf8563_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { @@ -229,20 +409,54 @@ static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm) return pcf8563_set_datetime(to_i2c_client(dev), tm); } + +#ifdef CONFIG_PM_SLEEP + +static int pcf8563_rtc_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + + if (device_may_wakeup(dev)) + ret = enable_irq_wake(client->irq); + + return ret; +} + +static int pcf8563_rtc_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + + if (device_may_wakeup(dev)) + ret = disable_irq_wake(client->irq); + + return ret; +} + +#endif /* CONFIG_PM_SLEEP */ + + static const struct rtc_class_ops pcf8563_rtc_ops = { .ioctl = pcf8563_rtc_ioctl, .read_time = pcf8563_rtc_read_time, .set_time = pcf8563_rtc_set_time, + .read_alarm = pcf8563_rtc_read_alarm, + .set_alarm = pcf8563_rtc_set_alarm, + .alarm_irq_enable = pcf8563_rtc_alarm_irq_enable, }; static int pcf8563_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct pcf8563 *pcf8563; + int ret; dev_dbg(&client->dev, "%s\n", __func__); - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) return -ENODEV; pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563), @@ -254,11 +468,33 @@ static int pcf8563_probe(struct i2c_client *client, i2c_set_clientdata(client, pcf8563); + ret = pcf8563_rtc_enable_alarm_irq(client, false); + if (ret) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, pcf8563_irq_handler, + IRQF_ONESHOT, + pcf8563_driver.driver.name, + client); + if (ret) { + dev_err(&client->dev, "unable to request IRQ\n"); + return ret; + } + + device_init_wakeup(&client->dev, 1); + } + pcf8563->rtc = devm_rtc_device_register(&client->dev, pcf8563_driver.driver.name, &pcf8563_rtc_ops, THIS_MODULE); - return PTR_ERR_OR_ZERO(pcf8563->rtc); + if (IS_ERR(pcf8563->rtc)) + return PTR_ERR(pcf8563->rtc); + + + return 0; } static const struct i2c_device_id pcf8563_id[] = { @@ -276,11 +512,15 @@ static const struct of_device_id pcf8563_of_match[] = { MODULE_DEVICE_TABLE(of, pcf8563_of_match); #endif +static SIMPLE_DEV_PM_OPS(pcf8563_pm_ops, + pcf8563_rtc_suspend, pcf8563_rtc_resume); + static struct i2c_driver pcf8563_driver = { .driver = { .name = "rtc-pcf8563", .owner = THIS_MODULE, .of_match_table = of_match_ptr(pcf8563_of_match), + .pm = &pcf8563_pm_ops, }, .probe = pcf8563_probe, .id_table = pcf8563_id,