]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/rtc/rtc-ds3232.c
Merge branch 'master' into tk71
[mv-sheeva.git] / drivers / rtc / rtc-ds3232.c
index 9de8516e3531e70bad818747f41de4b8052486bd..950735415a7c9cf934057fd465b22ddbdd5d6650 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * RTC client/driver for the Maxim/Dallas DS3232 Real-Time Clock over I2C
  *
- * Copyright (C) 2009-2010 Freescale Semiconductor.
+ * Copyright (C) 2009-2011 Freescale Semiconductor.
+ * Author: Jack Lan <jack.lan@freescale.com>
  *
  * 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
@@ -140,9 +141,11 @@ static int ds3232_read_time(struct device *dev, struct rtc_time *time)
                time->tm_hour = bcd2bin(hour);
        }
 
-       time->tm_wday = bcd2bin(week);
+       /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
+       time->tm_wday = bcd2bin(week) - 1;
        time->tm_mday = bcd2bin(day);
-       time->tm_mon = bcd2bin(month & 0x7F);
+       /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
+       time->tm_mon = bcd2bin(month & 0x7F) - 1;
        if (century)
                add_century = 100;
 
@@ -161,9 +164,11 @@ static int ds3232_set_time(struct device *dev, struct rtc_time *time)
        buf[0] = bin2bcd(time->tm_sec);
        buf[1] = bin2bcd(time->tm_min);
        buf[2] = bin2bcd(time->tm_hour);
-       buf[3] = bin2bcd(time->tm_wday); /* Day of the week */
+       /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
+       buf[3] = bin2bcd(time->tm_wday + 1);
        buf[4] = bin2bcd(time->tm_mday); /* Date */
-       buf[5] = bin2bcd(time->tm_mon);
+       /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
+       buf[5] = bin2bcd(time->tm_mon + 1);
        if (time->tm_year >= 100) {
                buf[5] |= 0x80;
                buf[6] = bin2bcd(time->tm_year - 100);
@@ -175,6 +180,182 @@ static int ds3232_set_time(struct device *dev, struct rtc_time *time)
                                              DS3232_REG_SECONDS, 7, buf);
 }
 
+/*
+ * DS3232 has two alarm, we only use alarm1
+ * According to linux specification, only support one-shot alarm
+ * no periodic alarm mode
+ */
+static int ds3232_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds3232 *ds3232 = i2c_get_clientdata(client);
+       int control, stat;
+       int ret;
+       u8 buf[4];
+
+       mutex_lock(&ds3232->mutex);
+
+       ret = i2c_smbus_read_byte_data(client, DS3232_REG_SR);
+       if (ret < 0)
+               goto out;
+       stat = ret;
+       ret = i2c_smbus_read_byte_data(client, DS3232_REG_CR);
+       if (ret < 0)
+               goto out;
+       control = ret;
+       ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf);
+       if (ret < 0)
+               goto out;
+
+       alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F);
+       alarm->time.tm_min = bcd2bin(buf[1] & 0x7F);
+       alarm->time.tm_hour = bcd2bin(buf[2] & 0x7F);
+       alarm->time.tm_mday = bcd2bin(buf[3] & 0x7F);
+
+       alarm->time.tm_mon = -1;
+       alarm->time.tm_year = -1;
+       alarm->time.tm_wday = -1;
+       alarm->time.tm_yday = -1;
+       alarm->time.tm_isdst = -1;
+
+       alarm->enabled = !!(control & DS3232_REG_CR_A1IE);
+       alarm->pending = !!(stat & DS3232_REG_SR_A1F);
+
+       ret = 0;
+out:
+       mutex_unlock(&ds3232->mutex);
+       return ret;
+}
+
+/*
+ * linux rtc-module does not support wday alarm
+ * and only 24h time mode supported indeed
+ */
+static int ds3232_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds3232 *ds3232 = i2c_get_clientdata(client);
+       int control, stat;
+       int ret;
+       u8 buf[4];
+
+       if (client->irq <= 0)
+               return -EINVAL;
+
+       mutex_lock(&ds3232->mutex);
+
+       buf[0] = bin2bcd(alarm->time.tm_sec);
+       buf[1] = bin2bcd(alarm->time.tm_min);
+       buf[2] = bin2bcd(alarm->time.tm_hour);
+       buf[3] = bin2bcd(alarm->time.tm_mday);
+
+       /* clear alarm interrupt enable bit */
+       ret = i2c_smbus_read_byte_data(client, DS3232_REG_CR);
+       if (ret < 0)
+               goto out;
+       control = ret;
+       control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE);
+       ret = i2c_smbus_write_byte_data(client, DS3232_REG_CR, control);
+       if (ret < 0)
+               goto out;
+
+       /* clear any pending alarm flag */
+       ret = i2c_smbus_read_byte_data(client, DS3232_REG_SR);
+       if (ret < 0)
+               goto out;
+       stat = ret;
+       stat &= ~(DS3232_REG_SR_A1F | DS3232_REG_SR_A2F);
+       ret = i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat);
+       if (ret < 0)
+               goto out;
+
+       ret = i2c_smbus_write_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf);
+
+       if (alarm->enabled) {
+               control |= DS3232_REG_CR_A1IE;
+               ret = i2c_smbus_write_byte_data(client, DS3232_REG_CR, control);
+       }
+out:
+       mutex_unlock(&ds3232->mutex);
+       return ret;
+}
+
+static void ds3232_update_alarm(struct i2c_client *client)
+{
+       struct ds3232 *ds3232 = i2c_get_clientdata(client);
+       int control;
+       int ret;
+       u8 buf[4];
+
+       mutex_lock(&ds3232->mutex);
+
+       ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf);
+       if (ret < 0)
+               goto unlock;
+
+       buf[0] = bcd2bin(buf[0]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ?
+                                                               0x80 : buf[0];
+       buf[1] = bcd2bin(buf[1]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ?
+                                                               0x80 : buf[1];
+       buf[2] = bcd2bin(buf[2]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ?
+                                                               0x80 : buf[2];
+       buf[3] = bcd2bin(buf[3]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ?
+                                                               0x80 : buf[3];
+
+       ret = i2c_smbus_write_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf);
+       if (ret < 0)
+               goto unlock;
+
+       control = i2c_smbus_read_byte_data(client, DS3232_REG_CR);
+       if (control < 0)
+               goto unlock;
+
+       if (ds3232->rtc->irq_data & (RTC_AF | RTC_UF))
+               /* enable alarm1 interrupt */
+               control |= DS3232_REG_CR_A1IE;
+       else
+               /* disable alarm1 interrupt */
+               control &= ~(DS3232_REG_CR_A1IE);
+       i2c_smbus_write_byte_data(client, DS3232_REG_CR, control);
+
+unlock:
+       mutex_unlock(&ds3232->mutex);
+}
+
+static int ds3232_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds3232 *ds3232 = i2c_get_clientdata(client);
+
+       if (client->irq <= 0)
+               return -EINVAL;
+
+       if (enabled)
+               ds3232->rtc->irq_data |= RTC_AF;
+       else
+               ds3232->rtc->irq_data &= ~RTC_AF;
+
+       ds3232_update_alarm(client);
+       return 0;
+}
+
+static int ds3232_update_irq_enable(struct device *dev, unsigned int enabled)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds3232 *ds3232 = i2c_get_clientdata(client);
+
+       if (client->irq <= 0)
+               return -EINVAL;
+
+       if (enabled)
+               ds3232->rtc->irq_data |= RTC_UF;
+       else
+               ds3232->rtc->irq_data &= ~RTC_UF;
+
+       ds3232_update_alarm(client);
+       return 0;
+}
+
 static irqreturn_t ds3232_irq(int irq, void *dev_id)
 {
        struct i2c_client *client = dev_id;
@@ -222,6 +403,10 @@ unlock:
 static const struct rtc_class_ops ds3232_rtc_ops = {
        .read_time = ds3232_read_time,
        .set_time = ds3232_set_time,
+       .read_alarm = ds3232_read_alarm,
+       .set_alarm = ds3232_set_alarm,
+       .alarm_irq_enable = ds3232_alarm_irq_enable,
+       .update_irq_enable = ds3232_update_irq_enable,
 };
 
 static int __devinit ds3232_probe(struct i2c_client *client,
@@ -282,7 +467,7 @@ static int __devexit ds3232_remove(struct i2c_client *client)
                mutex_unlock(&ds3232->mutex);
 
                free_irq(client->irq, client);
-               flush_scheduled_work();
+               cancel_work_sync(&ds3232->work);
        }
 
        rtc_device_unregister(ds3232->rtc);