Patchwork [PATCHv0,RESEND] Add support for Intersil ISL12057 I2C RTC/Alarm chip

login
register
mail settings
Submitter Arnaud Ebalard
Date Oct. 1, 2013, 8:21 p.m.
Message ID <87ob787nf6.fsf@natisbad.org>
Download mbox | patch
Permalink /patch/279571/
State New
Headers show

Comments

Arnaud Ebalard - Oct. 1, 2013, 8:21 p.m.
Hi,

Netgear ReadyNAS 102 (Armada 370-base) NAS includes an Intersil ISL12057
I2C RTC/Alarm chip. As part of an effort to get full support for the NAS
in mainline kernel, I wrote a driver for the chip based on ISL1208 one
developed by Hebert Valerio Riedel. The patch below is an updated resend
of the one I submitted on August 3 (i.e. holidays), but got no reply
for. See

  https://groups.google.com/forum/#!topic/rtc-linux/ypqMloOezj0

If possible, I would like to be able to get it mainline in 3.13, and
pushed required DT bindings for ReadyNAS 102 to avoid the NAS to wake up
in the 70s.

Setting and retrieving time works as expected at startup and shutdown.
Also, alarm bit is set in status register when the alarm is triggered.

Some things worth mentioning:
 - on my platform, the Alarm IRQ is not connected to the SoC but to a
   power chip, so I cannot test the alarm IRQ handling code of the
   patch. I'd be happy to get feedback if someone has the same hardware.
 - nonetheless, when the alarm is set and the NAS is powered off, it
   gets woken up, i.e. the alarm setting code does work.
 - trying to interact w/ the clock using userland tools (Debian jessie)
   makes me wonder what I missed:

   root@humble:/sys/class/rtc/rtc0# hwclock -r
   hwclock: select() to /dev/rtc0 to wait for clock tick timed out: Success
  
   root@humble:/tmp# gcc rtctest.c 
   root@humble:/tmp# ./a.out 
  
                          RTC Driver Test Example.
  
    Counting 5 update (1/sec) interrupts from reading /dev/rtc0:
  
    [ nothing happens ]
  
    I tried and find some documentation on the topic to understand where
    the issue comes from but found nothing obvious.

I intend to provide some documentation in next round and fix reported
issues. Additionally, I wonder why ISL1208 code has no specific mutex
protection for access and changes. I also followed that path in attached
driver but I guess this is something that should be improved.

Comments welcome,

Cheers,

a+

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 drivers/rtc/Kconfig        |   9 +
 drivers/rtc/Makefile       |   1 +
 drivers/rtc/rtc-isl12057.c | 641 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 651 insertions(+)
 create mode 100644 drivers/rtc/rtc-isl12057.c
Mark Rutland - Oct. 2, 2013, 11:14 a.m.
On Tue, Oct 01, 2013 at 09:21:01PM +0100, Arnaud Ebalard wrote:
> Hi,
> 
> Netgear ReadyNAS 102 (Armada 370-base) NAS includes an Intersil ISL12057
> I2C RTC/Alarm chip. As part of an effort to get full support for the NAS
> in mainline kernel, I wrote a driver for the chip based on ISL1208 one
> developed by Hebert Valerio Riedel. The patch below is an updated resend
> of the one I submitted on August 3 (i.e. holidays), but got no reply
> for. See
> 
>   https://groups.google.com/forum/#!topic/rtc-linux/ypqMloOezj0
> 
> If possible, I would like to be able to get it mainline in 3.13, and
> pushed required DT bindings for ReadyNAS 102 to avoid the NAS to wake up
> in the 70s.
> 
> Setting and retrieving time works as expected at startup and shutdown.
> Also, alarm bit is set in status register when the alarm is triggered.
> 
> Some things worth mentioning:
>  - on my platform, the Alarm IRQ is not connected to the SoC but to a
>    power chip, so I cannot test the alarm IRQ handling code of the
>    patch. I'd be happy to get feedback if someone has the same hardware.
>  - nonetheless, when the alarm is set and the NAS is powered off, it
>    gets woken up, i.e. the alarm setting code does work.
>  - trying to interact w/ the clock using userland tools (Debian jessie)
>    makes me wonder what I missed:
> 
>    root@humble:/sys/class/rtc/rtc0# hwclock -r
>    hwclock: select() to /dev/rtc0 to wait for clock tick timed out: Success
> 
>    root@humble:/tmp# gcc rtctest.c
>    root@humble:/tmp# ./a.out
> 
>                           RTC Driver Test Example.
> 
>     Counting 5 update (1/sec) interrupts from reading /dev/rtc0:
> 
>     [ nothing happens ]
> 
>     I tried and find some documentation on the topic to understand where
>     the issue comes from but found nothing obvious.

Forgive me if I'm mistaken, but you mentioned above that interrupts
aren't wired up on your platform, and judging by the output the test
program seems to be relying on them.

> 
> I intend to provide some documentation in next round and fix reported
> issues. Additionally, I wonder why ISL1208 code has no specific mutex
> protection for access and changes. I also followed that path in attached
> driver but I guess this is something that should be improved.

The devicetree binding must go in when the driver is added, rather than
later.

Given the above known issue, I think that should be fixed up before the
driver gets merged -- 
If this driver has known issues then should those not be fixed before
merging it?

> 
> Comments welcome,
> 
> Cheers,
> 
> a+
> 
> Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
> ---
>  drivers/rtc/Kconfig        |   9 +
>  drivers/rtc/Makefile       |   1 +
>  drivers/rtc/rtc-isl12057.c | 641 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 651 insertions(+)
>  create mode 100644 drivers/rtc/rtc-isl12057.c
 
[...]

> +#ifdef CONFIG_OF
> +static struct of_device_id isl12057_dt_match[] = {
> +       { .compatible = "isil,isl12057" },
> +       { },
> +};
> +#endif

It looks like this is a simple device with only an interrupt (no clocks,
regulators, other elements), so that can go under
Documentation/devicetree/bindings/i2c/trivial-devices.txt.

Also, the "isil" vendor prefix doesn't seem to be documented. Could you
please create a patch adding it to
Documentation/devicetree/bindings/vendor-prefixes.txt?

Cheers,
Mark.
Arnaud Ebalard - Oct. 7, 2013, 6:04 p.m.
Hi,

Mark Rutland <mark.rutland@arm.com> writes:

> On Tue, Oct 01, 2013 at 09:21:01PM +0100, Arnaud Ebalard wrote:
>> Hi,
>> 
>> Netgear ReadyNAS 102 (Armada 370-base) NAS includes an Intersil ISL12057
>> I2C RTC/Alarm chip. As part of an effort to get full support for the NAS
>> in mainline kernel, I wrote a driver for the chip based on ISL1208 one
>> developed by Hebert Valerio Riedel. The patch below is an updated resend
>> of the one I submitted on August 3 (i.e. holidays), but got no reply
>> for. See
>> 
>>   https://groups.google.com/forum/#!topic/rtc-linux/ypqMloOezj0
>> 
>> If possible, I would like to be able to get it mainline in 3.13, and
>> pushed required DT bindings for ReadyNAS 102 to avoid the NAS to wake up
>> in the 70s.
>> 
>> Setting and retrieving time works as expected at startup and shutdown.
>> Also, alarm bit is set in status register when the alarm is triggered.
>> 
>> Some things worth mentioning:
>>  - on my platform, the Alarm IRQ is not connected to the SoC but to a
>>    power chip, so I cannot test the alarm IRQ handling code of the
>>    patch. I'd be happy to get feedback if someone has the same hardware.
>>  - nonetheless, when the alarm is set and the NAS is powered off, it
>>    gets woken up, i.e. the alarm setting code does work.
>>  - trying to interact w/ the clock using userland tools (Debian jessie)
>>    makes me wonder what I missed:
>> 
>>    root@humble:/sys/class/rtc/rtc0# hwclock -r
>>    hwclock: select() to /dev/rtc0 to wait for clock tick timed out: Success
>> 
>>    root@humble:/tmp# gcc rtctest.c
>>    root@humble:/tmp# ./a.out
>> 
>>                           RTC Driver Test Example.
>> 
>>     Counting 5 update (1/sec) interrupts from reading /dev/rtc0:
>> 
>>     [ nothing happens ]
>> 
>>     I tried and find some documentation on the topic to understand where
>>     the issue comes from but found nothing obvious.
>
> Forgive me if I'm mistaken, but you mentioned above that interrupts
> aren't wired up on your platform, and judging by the output the test
> program seems to be relying on them.

This is correct. I spent some more time digging about what I should do
an it's not very clear to me. I found some informative elements about
IRQ handling and RTC in

 commit c9f5c7e7a8 rtc: rtc-spear: Provide flag for no support of UIE mode
 commit 4a649903f9 rtc: Provide flag for rtc devices that don't support UIE

So I modified the driver to have uie_unsupported set to 1 for testing. This
makes hwclock happy but does not please rtctest.c, because commit
4a649903f9 modified the interface to have -EINVAL returned.

root@humble:/tmp# hwclock -r
Sun 06 Oct 2013 09:38:12 PM CEST  -0.107813 seconds
root@humble:/tmp# ./a.out 

                        RTC Driver Test Example.

RTC_UIE_ON ioctl: Invalid argument


Even if I modify the code to pass the UIE related test
(i.e. s/ENOTTY/EINVAL/) then I get stuck on RTC_AIE_ON
test. 


Anyway, to come back to my initial issue, I thinks my problem is that
the ReadyNAS 102 has the ISL 12057 Alarm IRQ pin connected to a power
chip instead of the SoC, i.e. there is no way for the SoC to get
interrupts from the chips and the RTC interface does not seem (at least
to me) to provide a trivial whay to handle that. As I am unable to
understand what I should do and not do from the documentation, I am
basically asking RTC experts for guidelines on what to do.

One solution^Wworkaround would be for me to completely remove the alarm
related code (at least initially) and provide a basic driver that only
support the clock function of the chip. Alarm support could then be
added later by someone with an IRQ pin routed to its SoC.

Another possible path could be to provide some DT (i.e. OF) knob in order
to make ISL 12057 code (or possibly generic RTC code) aware that alarm
IRQ are indeed supported by the chip but not routed to the processor
itself. Regarding the IRQ handling code, care should be taken in that
case to review it as I cannot test it myself.


>> I intend to provide some documentation in next round and fix reported
>> issues. Additionally, I wonder why ISL1208 code has no specific mutex
>> protection for access and changes. I also followed that path in attached
>> driver but I guess this is something that should be improved.
>
> The devicetree binding must go in when the driver is added, rather than
> later.

As soon as I know what should be fixed I will write some documentation
if needed. As you write below, if the driver remains as is (trivial
interface), it will not be needed. On the contrary, if some DT knob is
needed, I will provide documentation once the code in a next round of
the patch set.


> Given the above known issue, I think that should be fixed up before the
> driver gets merged -- 
> If this driver has known issues then should those not be fixed before
> merging it?

I don't think the driver has issues per se. All the problems come from
the fact that the platform I have does not have the chip alarm interrupt
line routed to the SoC:

 - someone with an ISL 12057 with a routed alarm interrupt pin could
   test the IRQ-related code of the driver; otherwise, that code - based
   on ISL 1208 one - should be carefully reviewed.
 - some direction is needed about RTC interface in order to be able to
   express the fact that the chip supports setting an alarm which can
   trigger an interrupt at a given time but that this interrupt is for
   someone else than the SoC.


>> +#ifdef CONFIG_OF
>> +static struct of_device_id isl12057_dt_match[] = {
>> +       { .compatible = "isil,isl12057" },
>> +       { },
>> +};
>> +#endif
>
> It looks like this is a simple device with only an interrupt (no clocks,
> regulators, other elements), so that can go under
> Documentation/devicetree/bindings/i2c/trivial-devices.txt.

Will do that if the interface remains as is.


> Also, the "isil" vendor prefix doesn't seem to be documented. Could you
> please create a patch adding it to
> Documentation/devicetree/bindings/vendor-prefixes.txt?

will do. I went for "isil" because it is the stock ticker symbol for
Intersil (FWIW, https://lkml.org/lkml/2013/7/11/170).

Cheers,

a+

ps: sorry if your received the mail twice but google smtp servers decide
to drop my first email.

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 9654aa3..cfdc45b 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -294,6 +294,15 @@  config RTC_DRV_ISL12022
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-isl12022.
 
+config RTC_DRV_ISL12057
+       tristate "Intersil ISL12057"
+       help
+	  If you say yes here you get support for the Intersil ISL12057
+	  I2C RTC/Calendar chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-isl12057.
+
 config RTC_DRV_X1205
 	tristate "Xicor/Intersil X1205"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2dff3d2..e3bc3d5 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -57,6 +57,7 @@  obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
 obj-$(CONFIG_RTC_DRV_IMXDI)	+= rtc-imxdi.o
 obj-$(CONFIG_RTC_DRV_ISL1208)	+= rtc-isl1208.o
 obj-$(CONFIG_RTC_DRV_ISL12022)	+= rtc-isl12022.o
+obj-$(CONFIG_RTC_DRV_ISL12057)  += rtc-isl12057.o
 obj-$(CONFIG_RTC_DRV_JZ4740)	+= rtc-jz4740.o
 obj-$(CONFIG_RTC_DRV_LP8788)	+= rtc-lp8788.o
 obj-$(CONFIG_RTC_DRV_LPC32XX)	+= rtc-lpc32xx.o
diff --git a/drivers/rtc/rtc-isl12057.c b/drivers/rtc/rtc-isl12057.c
new file mode 100644
index 0000000..7bc6465
--- /dev/null
+++ b/drivers/rtc/rtc-isl12057.c
@@ -0,0 +1,641 @@ 
+/*
+ * rtc-isl12057 - Driver for Intersil ISL12057 I2C Real Time Clock/Calendar
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * This work is largely based on Intersil ISL1208 driver developed by
+ * Hebert Valerio Riedel <hvr@gnu.org>.
+ *
+ * Detailed datasheet on which this development is based is available here:
+ *
+ *  http://natisbad.org/NAS2/refs/ISL12057.pdf
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#define DRV_NAME "rtc-isl12057"
+#define DRV_VERSION "0.1"
+
+/* RTC section */
+#define ISL12057_REG_RTC_SC     0x00 /* Seconds */
+#define ISL12057_REG_RTC_MN     0x01 /* Minutes */
+#define ISL12057_REG_RTC_HR     0x02 /* Hours */
+#define ISL12057_REG_RTC_HR_PM  (1<<5) /* AM/PM bit in 12h format */
+#define ISL12057_REG_RTC_HR_MIL (1<<6) /* 24h/12h format */
+#define ISL12057_REG_RTC_DW     0x03 /* Day of the Week */
+#define ISL12057_REG_RTC_DT     0x04 /* Date */
+#define ISL12057_REG_RTC_MO     0x05 /* Month */
+#define ISL12057_REG_RTC_YR     0x06 /* Year */
+#define ISL12057_RTC_SEC_LEN    7
+
+/* Alarm 1 section */
+#define ISL12057_REG_A1_SC      0x07 /* Alarm 1 Seconds */
+#define ISL12057_REG_A1_MN      0x08 /* Alarm 1 Minutes */
+#define ISL12057_REG_A1_HR      0x09 /* Alarm 1 Hours */
+#define ISL12057_REG_A1_HR_PM   (1<<5) /* AM/PM bit in 12h format */
+#define ISL12057_REG_A1_HR_MIL  (1<<6) /* 24h/12h format */
+#define ISL12057_REG_A1_DWDT    0x0A /* Alarm 1 Date / Day of the week */
+#define ISL12057_REG_A1_DWDT_B  (1<<6) /* DW / DT selection bit */
+#define ISL12057_A1_SEC_LEN     4
+
+/* Alarm 2 section */
+#define ISL12057_REG_A2_MN      0x0B /* Alarm 2 Minutes */
+#define ISL12057_REG_A2_HR      0x0C /* Alarm 2 Hours */
+#define ISL12057_REG_A2_DWDT    0x0D /* Alarm 2 Date / Day of the week */
+#define ISL12057_A2_SEC_LEN     3
+
+/* Control/Status registers */
+#define ISL12057_REG_INT        0x0E
+#define ISL12057_REG_INT_A1IE   (1<<0) /* Alarm 1 interrupt enable bit */
+#define ISL12057_REG_INT_A2IE   (1<<1) /* Alarm 2 interrupt enable bit */
+#define ISL12057_REG_INT_INTCN  (1<<2) /* Interrupt control enable bit */
+#define ISL12057_REG_INT_RS1    (1<<3) /* Freq out control bit 1 */
+#define ISL12057_REG_INT_RS2    (1<<4) /* Freq out control bit 2 */
+#define ISL12057_REG_INT_EOSC   (1<<7) /* Oscillator enable bit */
+
+#define ISL12057_REG_SR         0x0F
+#define ISL12057_REG_SR_A1F     (1<<0) /* Alarm 1 interrupt bit */
+#define ISL12057_REG_SR_A2F     (1<<1) /* Alarm 2 interrupt bit */
+#define ISL12057_REG_SR_OSF     (1<<7) /* Oscillator failure bit */
+
+/* Register memory map length */
+#define ISL12057_MEM_MAP_LEN    0x10
+
+static struct i2c_driver isl12057_driver;
+
+/* block read */
+static int isl12057_i2c_read_regs(struct i2c_client *client, u8 reg, u8 buf[],
+				  unsigned len)
+{
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = client->addr,
+			.len = sizeof(u8),
+			.buf = buf
+		},
+		{
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = len,
+			.buf = buf
+		}
+	};
+	int ret;
+
+	BUG_ON(reg > ISL12057_REG_SR);
+	BUG_ON(reg + len > ISL12057_REG_SR + 1);
+
+	ret = i2c_transfer(client->adapter, msgs, 2);
+	if (ret <= 0)
+		return ret;
+
+	return 0;
+}
+
+/* block write */
+static int isl12057_i2c_set_regs(struct i2c_client *client, u8 reg,
+				 u8 const buf[], unsigned len)
+{
+	u8 i2c_buf[ISL12057_MEM_MAP_LEN + 1];
+	struct i2c_msg msgs[1] = {
+		{
+			.addr = client->addr,
+			.len = len + 1,
+			.buf = i2c_buf
+		}
+	};
+	int ret;
+
+	BUG_ON(reg > ISL12057_REG_SR);
+	BUG_ON(reg + len > ISL12057_REG_SR + 1);
+
+	i2c_buf[0] = reg;
+	memcpy(&i2c_buf[1], &buf[0], len);
+
+	ret = i2c_transfer(client->adapter, msgs, 1);
+	if (ret <= 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Try and match register bits w/ fixed null values to see whether we
+ * are dealing with an ISL12057.
+ */
+static int isl12057_i2c_validate_client(struct i2c_client *client)
+{
+	u8 regs[ISL12057_MEM_MAP_LEN] = { 0, };
+	u8 mask[ISL12057_MEM_MAP_LEN] = { 0x80, 0x80, 0x80, 0xf8,
+					  0xc0, 0x60, 0x00, 0x00,
+					  0x00, 0x00, 0x00, 0x00,
+					  0x00, 0x00, 0x60, 0x7c };
+	int ret, i;
+
+	ret = isl12057_i2c_read_regs(client, 0, regs, ISL12057_MEM_MAP_LEN);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ISL12057_MEM_MAP_LEN; ++i) {
+		if (regs[i] & mask[i])	/* check if bits are cleared */
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int isl12057_rtc_toggle_alarm(struct i2c_client *client, int enable)
+{
+	int ir;
+
+	ir = i2c_smbus_read_byte_data(client, ISL12057_REG_INT);
+	if (ir < 0) {
+		dev_err(&client->dev, "%s: reading INT failed\n", __func__);
+		return ir;
+	}
+
+	if (enable)
+		ir |=  ISL12057_REG_INT_A1IE;
+	else
+		ir &= ~ISL12057_REG_INT_A1IE;
+
+	ir = i2c_smbus_write_byte_data(client, ISL12057_REG_INT, ir);
+	if (ir < 0) {
+		dev_err(&client->dev, "%s: writing INT failed\n", __func__);
+		return ir;
+	}
+
+	return 0;
+}
+
+static int isl12057_i2c_read_time(struct i2c_client *client,
+				  struct rtc_time *tm)
+{
+	u8 regs[ISL12057_RTC_SEC_LEN] = { 0, };
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, ISL12057_REG_SR);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: reading SR failed\n", __func__);
+		return ret;
+	}
+
+	ret = isl12057_i2c_read_regs(client, ISL12057_REG_RTC_SC, regs,
+				     ISL12057_RTC_SEC_LEN);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: reading RTC section failed\n",
+			__func__);
+		return ret;
+	}
+
+	tm->tm_sec  = bcd2bin(regs[ISL12057_REG_RTC_SC]);
+	tm->tm_min  = bcd2bin(regs[ISL12057_REG_RTC_MN]);
+	tm->tm_hour = bcd2bin(regs[ISL12057_REG_RTC_HR] & 0x3f);
+	tm->tm_mday = bcd2bin(regs[ISL12057_REG_RTC_DT]);
+	tm->tm_wday = bcd2bin(regs[ISL12057_REG_RTC_DW]) - 1; /* starts at 1 */
+	tm->tm_mon  = bcd2bin(regs[ISL12057_REG_RTC_MO]) - 1; /* starts at 1 */
+	tm->tm_year = bcd2bin(regs[ISL12057_REG_RTC_YR]) + 100;
+
+	return 0;
+}
+
+static int isl12057_i2c_read_alarm(struct i2c_client *client,
+				   struct rtc_wkalrm *alarm)
+{
+	struct rtc_time rtc_tm, *alarm_tm = &alarm->time;
+	u8 regs[ISL12057_A1_SEC_LEN] = { 0, };
+	unsigned long rtc_secs, alarm_secs;
+	int ir, ret;
+
+	ret = isl12057_i2c_read_regs(client, ISL12057_REG_A1_SC, regs,
+				     ISL12057_A1_SEC_LEN);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: reading alarm section failed\n",
+			__func__);
+		return ret;
+	}
+
+	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_i2c_read_time(client, &rtc_tm);
+	if (ret)
+		return ret;
+	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)
+		return ret;
+
+	ret = rtc_tm_to_time(alarm_tm, &alarm_secs);
+	if (ret)
+		return ret;
+
+	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;
+		}
+	}
+
+	ir = i2c_smbus_read_byte_data(client, ISL12057_REG_INT);
+	if (ir < 0) {
+		dev_err(&client->dev, "%s: reading INT failed\n", __func__);
+		return ir;
+	}
+	alarm->enabled = !!(ir & ISL12057_REG_INT_A1IE);
+
+	return 0;
+}
+
+static int isl12057_i2c_set_alarm(struct i2c_client *client,
+				  struct rtc_wkalrm *alarm)
+{
+	struct rtc_time *alarm_tm = &alarm->time;
+	u8 regs[ISL12057_A1_SEC_LEN] = { 0, };
+	unsigned long rtc_secs, alarm_secs;
+	struct rtc_time rtc_tm;
+	int ret, sr, enable = 1;
+
+	ret = isl12057_i2c_read_time(client, &rtc_tm);
+	if (ret)
+		return ret;
+
+	ret = rtc_tm_to_time(&rtc_tm, &rtc_secs);
+	if (ret)
+		return ret;
+
+	ret = rtc_tm_to_time(alarm_tm, &alarm_secs);
+	if (ret)
+		return ret;
+
+	/* 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)
+			return ret;
+
+		if (alarm_secs > rtc_secs) {
+			dev_err(&client->dev,
+				"%s: limit is one month in the future\n",
+				__func__);
+			return -EINVAL;
+		}
+	}
+
+	/* Disable the alarm before modifying it */
+	sr = i2c_smbus_read_byte_data(client, ISL12057_REG_SR);
+	if (sr < 0) {
+		dev_err(&client->dev, "%s: reading SR failed\n", __func__);
+		return sr;
+	}
+
+	if (sr & ISL12057_REG_SR_A1F) {
+		sr &= ~ISL12057_REG_SR_A1F;
+		sr = i2c_smbus_write_byte_data(client, ISL12057_REG_SR, sr);
+		if (sr < 0) {
+			dev_err(&client->dev, "%s: writing SR failed\n",
+				__func__);
+			return sr;
+		}
+	}
+
+	/* Program alarm registers */
+	regs[0] = bin2bcd(alarm_tm->tm_sec);
+	regs[1] = bin2bcd(alarm_tm->tm_min);
+	regs[2] = bin2bcd(alarm_tm->tm_hour);
+	regs[3] = bin2bcd(alarm_tm->tm_mday);
+
+	ret = isl12057_i2c_set_regs(client, ISL12057_REG_A1_SC, regs,
+				    ISL12057_A1_SEC_LEN);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: writing ALARM section failed\n",
+			__func__);
+		return ret;
+	}
+
+	/* Enable or disable alarm */
+	ret = isl12057_rtc_toggle_alarm(client, enable);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int isl12057_i2c_set_time(struct i2c_client *client,
+				 struct rtc_time const *tm)
+{
+	u8 regs[ISL12057_RTC_SEC_LEN] = { 0, };
+	int sr;
+
+	/*
+	 * The clock has an 8 bit wide bcd-coded register for the year.
+	 * tm_year is an offset from 1900 and we are interested in the
+	 * 2000-2099 range, so any value less than 100 is invalid.
+	 */
+	if (tm->tm_year < 100)
+		return -EINVAL;
+
+	regs[ISL12057_REG_RTC_SC] = bin2bcd(tm->tm_sec);
+	regs[ISL12057_REG_RTC_MN] = bin2bcd(tm->tm_min);
+	regs[ISL12057_REG_RTC_HR] = bin2bcd(tm->tm_hour); /* 24-hour format */
+	regs[ISL12057_REG_RTC_DT] = bin2bcd(tm->tm_mday);
+	regs[ISL12057_REG_RTC_MO] = bin2bcd(tm->tm_mon + 1);
+	regs[ISL12057_REG_RTC_YR] = bin2bcd(tm->tm_year - 100);
+	regs[ISL12057_REG_RTC_DW] = bin2bcd(tm->tm_wday + 1);
+
+	/* write RTC registers */
+	sr = isl12057_i2c_set_regs(client, 0, regs, ISL12057_RTC_SEC_LEN);
+	if (sr < 0) {
+		dev_err(&client->dev, "%s: writing RTC section failed\n",
+			__func__);
+		return sr;
+	}
+
+	return 0;
+}
+
+
+static int isl12057_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	return isl12057_i2c_read_time(to_i2c_client(dev), tm);
+}
+
+static int isl12057_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	return isl12057_i2c_set_time(to_i2c_client(dev), tm);
+}
+
+static int isl12057_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	return isl12057_i2c_read_alarm(to_i2c_client(dev), alarm);
+}
+
+static int isl12057_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	return isl12057_i2c_set_alarm(to_i2c_client(dev), alarm);
+}
+
+static int isl12057_rtc_proc(struct device *dev, struct seq_file *seq)
+{
+	struct i2c_client *const client = to_i2c_client(dev);
+	int sr, ir;
+
+	/* Status register */
+	sr = i2c_smbus_read_byte_data(client, ISL12057_REG_SR);
+	if (sr < 0) {
+		dev_err(&client->dev, "%s: reading SR failed\n", __func__);
+		return sr;
+	}
+
+	seq_printf(seq, "status_reg\t:%s%s%s (0x%.2x)\n",
+		   (sr & ISL12057_REG_SR_OSF) ? " OSF" : "",
+		   (sr & ISL12057_REG_SR_A1F) ? " A1F" : "",
+		   (sr & ISL12057_REG_SR_A2F) ? " A2F" : "", sr);
+
+	/* Control register */
+	ir = i2c_smbus_read_byte_data(client, ISL12057_REG_INT);
+	if (ir < 0) {
+		dev_err(&client->dev, "%s: reading INT failed\n", __func__);
+		return ir;
+	}
+
+	seq_printf(seq, "status_reg\t:%s%s%s%s%s%s (0x%.2x)\n",
+		   (ir & ISL12057_REG_INT_A1IE) ?  " A1IE"  : "",
+		   (ir & ISL12057_REG_INT_A2IE) ?  " A2IE"  : "",
+		   (ir & ISL12057_REG_INT_INTCN) ? " INTCN" : "",
+		   (ir & ISL12057_REG_INT_RS1) ?   " RS1"   : "",
+		   (ir & ISL12057_REG_INT_RS2) ?   " RS2"   : "",
+		   (ir & ISL12057_REG_INT_EOSC) ?  " EOSC"  : "", ir);
+
+	return 0;
+}
+
+static int isl12057_rtc_alarm_irq_enable(struct device *dev,
+					 unsigned int enabled)
+{
+	return isl12057_rtc_toggle_alarm(to_i2c_client(dev), enabled);
+}
+
+static irqreturn_t isl12057_rtc_interrupt(int irq, void *data)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+	struct i2c_client *client = data;
+	struct rtc_device *rtc = i2c_get_clientdata(client);
+	int handled = 0, sr = -1, err;
+
+	/*
+	 * I2C reads get NAK'ed if we read straight away after an interrupt?
+	 * Using a mdelay/msleep didn't seem to help either, so we work around
+	 * this by continually trying to read the register for a short time.
+	 */
+	while (1) {
+		sr = i2c_smbus_read_byte_data(client, ISL12057_REG_SR);
+		if (sr >= 0)
+			break;
+
+		if (time_after(jiffies, timeout)) {
+			dev_err(&client->dev, "%s: reading SR failed\n",
+				__func__);
+			return sr;
+		}
+	}
+
+	if (sr & ISL12057_REG_SR_A1F) {
+		dev_dbg(&client->dev, "alarm!\n");
+
+		rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF);
+
+		/* Clear the alarm */
+		sr &= ~ISL12057_REG_SR_A1F;
+		sr = i2c_smbus_write_byte_data(client, ISL12057_REG_SR, sr);
+		if (sr < 0)
+			dev_err(&client->dev, "%s: writing SR failed\n",
+				__func__);
+		else
+			handled = 1;
+
+		/* Disable the alarm */
+		err = isl12057_rtc_toggle_alarm(client, 0);
+		if (err)
+			return err;
+	}
+
+	return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static const struct rtc_class_ops isl12057_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,
+	.proc = isl12057_rtc_proc,
+};
+
+static int isl12057_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct rtc_device *rtc;
+	int ret = 0;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return -ENODEV;
+
+	if (isl12057_i2c_validate_client(client) < 0)
+		return -ENODEV;
+
+	dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+
+	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, 1);
+			enable_irq_wake(client->irq);
+		} else {
+			dev_err(&client->dev,
+				"Unable to request irq %d, no alarm support\n",
+				client->irq);
+			client->irq = 0;
+		}
+	}
+
+	rtc = devm_rtc_device_register(&client->dev, DRV_NAME,
+				       &isl12057_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		ret = PTR_ERR(rtc);
+		goto exit_free_irq;
+	}
+
+	i2c_set_clientdata(client, rtc);
+
+	/* Enable oscillator if not already running */
+	ret = i2c_smbus_read_byte_data(client, ISL12057_REG_INT);
+	if (ret < 0) {
+		dev_err(&client->dev, "reading control reg failed\n");
+		goto exit_free_irq;
+	}
+	if (ret & ISL12057_REG_INT_EOSC) {
+		ret &= ~ISL12057_REG_INT_EOSC; /* 0 means enabled */
+		ret = i2c_smbus_write_byte_data(client, ISL12057_REG_INT, ret);
+		if (ret < 0) {
+			dev_err(&client->dev, "writing control reg failed\n");
+			goto exit_free_irq;
+		}
+	}
+
+	/* Clear oscillator failure and alarm bit if needed */
+	ret = i2c_smbus_read_byte_data(client, ISL12057_REG_SR);
+	if (ret < 0) {
+		dev_err(&client->dev, "reading status reg failed\n");
+		goto exit_free_irq;
+	}
+	if (ret & (ISL12057_REG_SR_OSF | ISL12057_REG_SR_A1F)) {
+		ret &= ~(ISL12057_REG_SR_OSF | ISL12057_REG_SR_A1F);
+		ret = i2c_smbus_write_byte_data(client, ISL12057_REG_SR, ret);
+		if (ret < 0) {
+			dev_err(&client->dev, "writing status reg failed\n");
+			goto exit_free_irq;
+		}
+	}
+
+	return 0;
+
+exit_free_irq:
+	if (client->irq)
+		free_irq(client->irq, client);
+
+	return ret;
+}
+
+static int isl12057_remove(struct i2c_client *client)
+{
+	struct rtc_device *rtc = i2c_get_clientdata(client);
+
+	rtc_device_unregister(rtc);
+	if (client->irq)
+		free_irq(client->irq, client);
+
+	return 0;
+}
+
+
+#ifdef CONFIG_OF
+static struct of_device_id isl12057_dt_match[] = {
+	{ .compatible = "isil,isl12057" },
+	{ },
+};
+#endif
+
+static const struct i2c_device_id isl12057_id[] = {
+	{ "isl12057", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, isl12057_id);
+
+static struct i2c_driver isl12057_driver = {
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.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 <arno@natisbad.org>");
+MODULE_DESCRIPTION("Intersil ISL12057 RTC/Alarm driver");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");