drivers/rtc/rtc-isl1208.c: add alarm support
authorRyan Mallon <ryan@bluewatersys.com>
Tue, 22 Mar 2011 23:34:53 +0000 (16:34 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 23 Mar 2011 00:44:16 +0000 (17:44 -0700)
Add alarm/wakeup support to rtc isl1208 driver

Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/rtc/rtc-isl1208.c

index 468200c38ecbf4116e0abf1873aec8f0e65e85fd..da8beb8cae51931162aebe3802cddc45067e8f96 100644 (file)
@@ -39,6 +39,8 @@
 #define ISL1208_REG_SR_BAT     (1<<1)  /* battery */
 #define ISL1208_REG_SR_RTCF    (1<<0)  /* rtc fail */
 #define ISL1208_REG_INT 0x08
+#define ISL1208_REG_INT_ALME   (1<<6)   /* alarm enable */
+#define ISL1208_REG_INT_IM     (1<<7)   /* interrupt/alarm mode */
 #define ISL1208_REG_09  0x09   /* reserved */
 #define ISL1208_REG_ATR 0x0a
 #define ISL1208_REG_DTR 0x0b
@@ -201,6 +203,30 @@ isl1208_i2c_set_usr(struct i2c_client *client, u16 usr)
                                    ISL1208_USR_SECTION_LEN);
 }
 
+static int
+isl1208_rtc_toggle_alarm(struct i2c_client *client, int enable)
+{
+       int icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT);
+
+       if (icr < 0) {
+               dev_err(&client->dev, "%s: reading INT failed\n", __func__);
+               return icr;
+       }
+
+       if (enable)
+               icr |= ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM;
+       else
+               icr &= ~(ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM);
+
+       icr = i2c_smbus_write_byte_data(client, ISL1208_REG_INT, icr);
+       if (icr < 0) {
+               dev_err(&client->dev, "%s: writing INT failed\n", __func__);
+               return icr;
+       }
+
+       return 0;
+}
+
 static int
 isl1208_rtc_proc(struct device *dev, struct seq_file *seq)
 {
@@ -288,9 +314,8 @@ isl1208_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm)
 {
        struct rtc_time *const tm = &alarm->time;
        u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, };
-       int sr;
+       int icr, yr, sr = isl1208_i2c_get_sr(client);
 
-       sr = isl1208_i2c_get_sr(client);
        if (sr < 0) {
                dev_err(&client->dev, "%s: reading SR failed\n", __func__);
                return sr;
@@ -313,6 +338,73 @@ isl1208_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm)
                bcd2bin(regs[ISL1208_REG_MOA - ISL1208_REG_SCA] & 0x1f) - 1;
        tm->tm_wday = bcd2bin(regs[ISL1208_REG_DWA - ISL1208_REG_SCA] & 0x03);
 
+       /* The alarm doesn't store the year so get it from the rtc section */
+       yr = i2c_smbus_read_byte_data(client, ISL1208_REG_YR);
+       if (yr < 0) {
+               dev_err(&client->dev, "%s: reading RTC YR failed\n", __func__);
+               return yr;
+       }
+       tm->tm_year = bcd2bin(yr) + 100;
+
+       icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT);
+       if (icr < 0) {
+               dev_err(&client->dev, "%s: reading INT failed\n", __func__);
+               return icr;
+       }
+       alarm->enabled = !!(icr & ISL1208_REG_INT_ALME);
+
+       return 0;
+}
+
+static int
+isl1208_i2c_set_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm)
+{
+       struct rtc_time *alarm_tm = &alarm->time;
+       u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, };
+       const int offs = ISL1208_REG_SCA;
+       unsigned long rtc_secs, alarm_secs;
+       struct rtc_time rtc_tm;
+       int err, enable;
+
+       err = isl1208_i2c_read_time(client, &rtc_tm);
+       if (err)
+               return err;
+       err = rtc_tm_to_time(&rtc_tm, &rtc_secs);
+       if (err)
+               return err;
+       err = rtc_tm_to_time(alarm_tm, &alarm_secs);
+       if (err)
+               return err;
+
+       /* If the alarm time is before the current time disable the alarm */
+       if (!alarm->enabled || alarm_secs <= rtc_secs)
+               enable = 0x00;
+       else
+               enable = 0x80;
+
+       /* Program the alarm and enable it for each setting */
+       regs[ISL1208_REG_SCA - offs] = bin2bcd(alarm_tm->tm_sec) | enable;
+       regs[ISL1208_REG_MNA - offs] = bin2bcd(alarm_tm->tm_min) | enable;
+       regs[ISL1208_REG_HRA - offs] = bin2bcd(alarm_tm->tm_hour) |
+               ISL1208_REG_HR_MIL | enable;
+
+       regs[ISL1208_REG_DTA - offs] = bin2bcd(alarm_tm->tm_mday) | enable;
+       regs[ISL1208_REG_MOA - offs] = bin2bcd(alarm_tm->tm_mon + 1) | enable;
+       regs[ISL1208_REG_DWA - offs] = bin2bcd(alarm_tm->tm_wday & 7) | enable;
+
+       /* write ALARM registers */
+       err = isl1208_i2c_set_regs(client, offs, regs,
+                                 ISL1208_ALARM_SECTION_LEN);
+       if (err < 0) {
+               dev_err(&client->dev, "%s: writing ALARM section failed\n",
+                       __func__);
+               return err;
+       }
+
+       err = isl1208_rtc_toggle_alarm(client, enable);
+       if (err)
+               return err;
+
        return 0;
 }
 
@@ -391,12 +483,63 @@ isl1208_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
        return isl1208_i2c_read_alarm(to_i2c_client(dev), alarm);
 }
 
+static int
+isl1208_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+       return isl1208_i2c_set_alarm(to_i2c_client(dev), alarm);
+}
+
+static irqreturn_t
+isl1208_rtc_interrupt(int irq, void *data)
+{
+       unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+       struct i2c_client *client = data;
+       int handled = 0, sr, 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 = isl1208_i2c_get_sr(client);
+               if (sr >= 0)
+                       break;
+
+               if (time_after(jiffies, timeout)) {
+                       dev_err(&client->dev, "%s: reading SR failed\n",
+                               __func__);
+                       return sr;
+               }
+       }
+
+       if (sr & ISL1208_REG_SR_ALM) {
+               dev_dbg(&client->dev, "alarm!\n");
+
+               /* Clear the alarm */
+               sr &= ~ISL1208_REG_SR_ALM;
+               sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR, sr);
+               if (sr < 0)
+                       dev_err(&client->dev, "%s: writing SR failed\n",
+                               __func__);
+               else
+                       handled = 1;
+
+               /* Disable the alarm */
+               err = isl1208_rtc_toggle_alarm(client, 0);
+               if (err)
+                       return err;
+       }
+
+       return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
 static const struct rtc_class_ops isl1208_rtc_ops = {
        .proc = isl1208_rtc_proc,
        .read_time = isl1208_rtc_read_time,
        .set_time = isl1208_rtc_set_time,
        .read_alarm = isl1208_rtc_read_alarm,
-       /*.set_alarm    = isl1208_rtc_set_alarm, */
+       .set_alarm = isl1208_rtc_set_alarm,
 };
 
 /* sysfs interface */
@@ -488,11 +631,29 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
        dev_info(&client->dev,
                 "chip found, driver version " DRV_VERSION "\n");
 
+       if (client->irq > 0) {
+               rc = request_threaded_irq(client->irq, NULL,
+                                         isl1208_rtc_interrupt,
+                                         IRQF_SHARED,
+                                         isl1208_driver.driver.name, client);
+               if (!rc) {
+                       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 = rtc_device_register(isl1208_driver.driver.name,
                                  &client->dev, &isl1208_rtc_ops,
                                  THIS_MODULE);
-       if (IS_ERR(rtc))
-               return PTR_ERR(rtc);
+       if (IS_ERR(rtc)) {
+               rc = PTR_ERR(rtc);
+               goto exit_free_irq;
+       }
 
        i2c_set_clientdata(client, rtc);
 
@@ -514,6 +675,9 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
 
 exit_unregister:
        rtc_device_unregister(rtc);
+exit_free_irq:
+       if (client->irq)
+               free_irq(client->irq, client);
 
        return rc;
 }
@@ -525,6 +689,8 @@ isl1208_remove(struct i2c_client *client)
 
        sysfs_remove_group(&client->dev.kobj, &isl1208_rtc_sysfs_files);
        rtc_device_unregister(rtc);
+       if (client->irq)
+               free_irq(client->irq, client);
 
        return 0;
 }