rtc: ds1307: fix ds1307_native_smbus_read_block_data function
[deliverable/linux.git] / drivers / rtc / rtc-m41t80.c
index 1a769275ab9d87ca39d22d53325ebae84876b910..d1bf93a8720027e529b6e51b8dbab304c4155c58 100644 (file)
 #define M41T80_ALARM_REG_SIZE  \
        (M41T80_REG_ALARM_SEC + 1 - M41T80_REG_ALARM_MON)
 
-#define M41T80_SEC_ST          (1 << 7)        /* ST: Stop Bit */
-#define M41T80_ALMON_AFE       (1 << 7)        /* AFE: AF Enable Bit */
-#define M41T80_ALMON_SQWE      (1 << 6)        /* SQWE: SQW Enable Bit */
-#define M41T80_ALHOUR_HT       (1 << 6)        /* HT: Halt Update Bit */
-#define M41T80_FLAGS_AF                (1 << 6)        /* AF: Alarm Flag Bit */
-#define M41T80_FLAGS_BATT_LOW  (1 << 4)        /* BL: Battery Low Bit */
-#define M41T80_WATCHDOG_RB2    (1 << 7)        /* RB: Watchdog resolution */
-#define M41T80_WATCHDOG_RB1    (1 << 1)        /* RB: Watchdog resolution */
-#define M41T80_WATCHDOG_RB0    (1 << 0)        /* RB: Watchdog resolution */
-
-#define M41T80_FEATURE_HT      (1 << 0)        /* Halt feature */
-#define M41T80_FEATURE_BL      (1 << 1)        /* Battery low indicator */
-#define M41T80_FEATURE_SQ      (1 << 2)        /* Squarewave feature */
-#define M41T80_FEATURE_WD      (1 << 3)        /* Extra watchdog resolution */
-#define M41T80_FEATURE_SQ_ALT  (1 << 4)        /* RSx bits are in reg 4 */
+#define M41T80_SEC_ST          BIT(7)  /* ST: Stop Bit */
+#define M41T80_ALMON_AFE       BIT(7)  /* AFE: AF Enable Bit */
+#define M41T80_ALMON_SQWE      BIT(6)  /* SQWE: SQW Enable Bit */
+#define M41T80_ALHOUR_HT       BIT(6)  /* HT: Halt Update Bit */
+#define M41T80_FLAGS_OF                BIT(2)  /* OF: Oscillator Failure Bit */
+#define M41T80_FLAGS_AF                BIT(6)  /* AF: Alarm Flag Bit */
+#define M41T80_FLAGS_BATT_LOW  BIT(4)  /* BL: Battery Low Bit */
+#define M41T80_WATCHDOG_RB2    BIT(7)  /* RB: Watchdog resolution */
+#define M41T80_WATCHDOG_RB1    BIT(1)  /* RB: Watchdog resolution */
+#define M41T80_WATCHDOG_RB0    BIT(0)  /* RB: Watchdog resolution */
+
+#define M41T80_FEATURE_HT      BIT(0)  /* Halt feature */
+#define M41T80_FEATURE_BL      BIT(1)  /* Battery low indicator */
+#define M41T80_FEATURE_SQ      BIT(2)  /* Squarewave feature */
+#define M41T80_FEATURE_WD      BIT(3)  /* Extra watchdog resolution */
+#define M41T80_FEATURE_SQ_ALT  BIT(4)  /* RSx bits are in reg 4 */
 
 static DEFINE_MUTEX(m41t80_rtc_mutex);
 static const struct i2c_device_id m41t80_id[] = {
@@ -90,11 +91,60 @@ struct m41t80_data {
        struct rtc_device *rtc;
 };
 
+static irqreturn_t m41t80_handle_irq(int irq, void *dev_id)
+{
+       struct i2c_client *client = dev_id;
+       struct m41t80_data *m41t80 = i2c_get_clientdata(client);
+       struct mutex *lock = &m41t80->rtc->ops_lock;
+       unsigned long events = 0;
+       int flags, flags_afe;
+
+       mutex_lock(lock);
+
+       flags_afe = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+       if (flags_afe < 0) {
+               mutex_unlock(lock);
+               return IRQ_NONE;
+       }
+
+       flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
+       if (flags <= 0) {
+               mutex_unlock(lock);
+               return IRQ_NONE;
+       }
+
+       if (flags & M41T80_FLAGS_AF) {
+               flags &= ~M41T80_FLAGS_AF;
+               flags_afe &= ~M41T80_ALMON_AFE;
+               events |= RTC_AF;
+       }
+
+       if (events) {
+               rtc_update_irq(m41t80->rtc, 1, events);
+               i2c_smbus_write_byte_data(client, M41T80_REG_FLAGS, flags);
+               i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
+                                         flags_afe);
+       }
+
+       mutex_unlock(lock);
+
+       return IRQ_HANDLED;
+}
+
 static int m41t80_get_datetime(struct i2c_client *client,
                               struct rtc_time *tm)
 {
        unsigned char buf[8];
-       int err;
+       int err, flags;
+
+       flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
+       if (flags < 0)
+               return flags;
+
+       if (flags & M41T80_FLAGS_OF) {
+               dev_err(&client->dev, "Oscillator failure, data is invalid.\n");
+               return -EINVAL;
+       }
 
        err = i2c_smbus_read_i2c_block_data(client, M41T80_REG_SSEC,
                                            sizeof(buf), buf);
@@ -119,7 +169,7 @@ static int m41t80_get_datetime(struct i2c_client *client,
 static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm)
 {
        unsigned char buf[8];
-       int err;
+       int err, flags;
 
        if (tm->tm_year < 100 || tm->tm_year > 199)
                return -EINVAL;
@@ -140,6 +190,17 @@ static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm)
                return err;
        }
 
+       /* Clear the OF bit of Flags Register */
+       flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
+       if (flags < 0)
+               return flags;
+
+       if (i2c_smbus_write_byte_data(client, M41T80_REG_FLAGS,
+                                     flags & ~M41T80_FLAGS_OF)) {
+               dev_err(&client->dev, "Unable to write flags register\n");
+               return -EIO;
+       }
+
        return err;
 }
 
@@ -167,10 +228,109 @@ static int m41t80_rtc_set_time(struct device *dev, struct rtc_time *tm)
        return m41t80_set_datetime(to_i2c_client(dev), tm);
 }
 
-/*
- * XXX - m41t80 alarm functionality is reported broken.
- * until it is fixed, don't register alarm functions.
- */
+static int m41t80_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       int flags, retval;
+
+       flags = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+       if (flags < 0)
+               return flags;
+
+       if (enabled)
+               flags |= M41T80_ALMON_AFE;
+       else
+               flags &= ~M41T80_ALMON_AFE;
+
+       retval = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, flags);
+       if (retval < 0) {
+               dev_info(dev, "Unable to enable alarm IRQ %d\n", retval);
+               return retval;
+       }
+       return 0;
+}
+
+static int m41t80_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       u8 alarmvals[5];
+       int ret, err;
+
+       alarmvals[0] = bin2bcd(alrm->time.tm_mon + 1);
+       alarmvals[1] = bin2bcd(alrm->time.tm_mday);
+       alarmvals[2] = bin2bcd(alrm->time.tm_hour);
+       alarmvals[3] = bin2bcd(alrm->time.tm_min);
+       alarmvals[4] = bin2bcd(alrm->time.tm_sec);
+
+       /* Clear AF and AFE flags */
+       ret = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
+       if (ret < 0)
+               return ret;
+       err = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
+                                       ret & ~(M41T80_ALMON_AFE));
+       if (err < 0) {
+               dev_err(dev, "Unable to clear AFE bit\n");
+               return err;
+       }
+
+       ret = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
+       if (ret < 0)
+               return ret;
+
+       err = i2c_smbus_write_byte_data(client, M41T80_REG_FLAGS,
+                                       ret & ~(M41T80_FLAGS_AF));
+       if (err < 0) {
+               dev_err(dev, "Unable to clear AF bit\n");
+               return err;
+       }
+
+       /* Write the alarm */
+       err = i2c_smbus_write_i2c_block_data(client, M41T80_REG_ALARM_MON,
+                                            5, alarmvals);
+       if (err)
+               return err;
+
+       /* Enable the alarm interrupt */
+       if (alrm->enabled) {
+               alarmvals[0] |= M41T80_ALMON_AFE;
+               err = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
+                                               alarmvals[0]);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int m41t80_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       u8 alarmvals[5];
+       int flags, ret;
+
+       ret = i2c_smbus_read_i2c_block_data(client, M41T80_REG_ALARM_MON,
+                                           5, alarmvals);
+       if (ret != 5)
+               return ret < 0 ? ret : -EIO;
+
+       flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
+       if (flags < 0)
+               return flags;
+
+       alrm->time.tm_sec  = bcd2bin(alarmvals[4] & 0x7f);
+       alrm->time.tm_min  = bcd2bin(alarmvals[3] & 0x7f);
+       alrm->time.tm_hour = bcd2bin(alarmvals[2] & 0x3f);
+       alrm->time.tm_wday = -1;
+       alrm->time.tm_mday = bcd2bin(alarmvals[1] & 0x3f);
+       alrm->time.tm_mon  = bcd2bin(alarmvals[0] & 0x3f);
+       alrm->time.tm_year = -1;
+
+       alrm->enabled = !!(alarmvals[0] & M41T80_ALMON_AFE);
+       alrm->pending = (flags & M41T80_FLAGS_AF) && alrm->enabled;
+
+       return 0;
+}
+
 static struct rtc_class_ops m41t80_rtc_ops = {
        .read_time = m41t80_rtc_read_time,
        .set_time = m41t80_rtc_set_time,
@@ -226,7 +386,11 @@ static ssize_t sqwfreq_store(struct device *dev,
        struct i2c_client *client = to_i2c_client(dev);
        struct m41t80_data *clientdata = i2c_get_clientdata(client);
        int almon, sqw, reg_sqw, rc;
-       int val = simple_strtoul(buf, NULL, 0);
+       unsigned long val;
+
+       rc = kstrtoul(buf, 0, &val);
+       if (rc < 0)
+               return rc;
 
        if (!(clientdata->features & M41T80_FEATURE_SQ))
                return -EINVAL;
@@ -255,7 +419,7 @@ static ssize_t sqwfreq_store(struct device *dev,
        sqw = (sqw & 0x0f) | (val << 4);
 
        rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
-                                     almon & ~M41T80_ALMON_SQWE);
+                                      almon & ~M41T80_ALMON_SQWE);
        if (rc < 0)
                return rc;
 
@@ -265,8 +429,8 @@ static ssize_t sqwfreq_store(struct device *dev,
                        return rc;
 
                rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
-                                            almon | M41T80_ALMON_SQWE);
-               if (rc <0)
+                                              almon | M41T80_ALMON_SQWE);
+               if (rc < 0)
                        return rc;
        }
        return count;
@@ -278,6 +442,7 @@ static struct attribute *attrs[] = {
        &dev_attr_sqwfreq.attr,
        NULL,
 };
+
 static struct attribute_group attr_group = {
        .attrs = attrs,
 };
@@ -329,7 +494,7 @@ static void wdt_ping(void)
                /*
                 * WDS = 1 (0x80), mulitplier = WD_TIMO, resolution = 1s (0x02)
                 */
-               i2c_data[1] = wdt_margin<<2 | 0x82;
+               i2c_data[1] = wdt_margin << 2 | 0x82;
 
        /*
         * M41T65 has three bits for watchdog resolution.  Don't set bit 7, as
@@ -586,7 +751,7 @@ static int m41t80_probe(struct i2c_client *client,
        int rc = 0;
        struct rtc_device *rtc = NULL;
        struct rtc_time tm;
-       struct m41t80_data *clientdata = NULL;
+       struct m41t80_data *m41t80_data = NULL;
 
        if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK |
                                     I2C_FUNC_SMBUS_BYTE_DATA)) {
@@ -594,37 +759,53 @@ static int m41t80_probe(struct i2c_client *client,
                return -ENODEV;
        }
 
-       clientdata = devm_kzalloc(&client->dev, sizeof(*clientdata),
-                               GFP_KERNEL);
-       if (!clientdata)
+       m41t80_data = devm_kzalloc(&client->dev, sizeof(*m41t80_data),
+                                  GFP_KERNEL);
+       if (!m41t80_data)
                return -ENOMEM;
 
-       clientdata->features = id->driver_data;
-       i2c_set_clientdata(client, clientdata);
+       m41t80_data->features = id->driver_data;
+       i2c_set_clientdata(client, m41t80_data);
+
+       if (client->irq > 0) {
+               rc = devm_request_threaded_irq(&client->dev, client->irq,
+                                              NULL, m41t80_handle_irq,
+                                              IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                              "m41t80", client);
+               if (rc) {
+                       dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n");
+                       client->irq = 0;
+               } else {
+                       m41t80_rtc_ops.read_alarm = m41t80_read_alarm;
+                       m41t80_rtc_ops.set_alarm = m41t80_set_alarm;
+                       m41t80_rtc_ops.alarm_irq_enable = m41t80_alarm_irq_enable;
+                       /* Enable the wakealarm */
+                       device_init_wakeup(&client->dev, true);
+               }
+       }
 
        rtc = devm_rtc_device_register(&client->dev, client->name,
-                                       &m41t80_rtc_ops, THIS_MODULE);
+                                      &m41t80_rtc_ops, THIS_MODULE);
        if (IS_ERR(rtc))
                return PTR_ERR(rtc);
 
-       clientdata->rtc = rtc;
+       m41t80_data->rtc = rtc;
 
        /* Make sure HT (Halt Update) bit is cleared */
        rc = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_HOUR);
 
        if (rc >= 0 && rc & M41T80_ALHOUR_HT) {
-               if (clientdata->features & M41T80_FEATURE_HT) {
+               if (m41t80_data->features & M41T80_FEATURE_HT) {
                        m41t80_get_datetime(client, &tm);
                        dev_info(&client->dev, "HT bit was set!\n");
                        dev_info(&client->dev,
-                                "Power Down at "
-                                "%04i-%02i-%02i %02i:%02i:%02i\n",
+                                "Power Down at %04i-%02i-%02i %02i:%02i:%02i\n",
                                 tm.tm_year + 1900,
                                 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
                                 tm.tm_min, tm.tm_sec);
                }
                rc = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_HOUR,
-                                             rc & ~M41T80_ALHOUR_HT);
+                                              rc & ~M41T80_ALHOUR_HT);
        }
 
        if (rc < 0) {
@@ -637,7 +818,7 @@ static int m41t80_probe(struct i2c_client *client,
 
        if (rc >= 0 && rc & M41T80_SEC_ST)
                rc = i2c_smbus_write_byte_data(client, M41T80_REG_SEC,
-                                             rc & ~M41T80_SEC_ST);
+                                              rc & ~M41T80_SEC_ST);
        if (rc < 0) {
                dev_err(&client->dev, "Can't clear ST bit\n");
                return rc;
@@ -660,7 +841,7 @@ static int m41t80_probe(struct i2c_client *client,
        }
 
 #ifdef CONFIG_RTC_DRV_M41T80_WDT
-       if (clientdata->features & M41T80_FEATURE_HT) {
+       if (m41t80_data->features & M41T80_FEATURE_HT) {
                save_client = client;
                rc = misc_register(&wdt_dev);
                if (rc)
This page took 0.028307 seconds and 5 git commands to generate.