Message ID | 4D6183E6.1030706@bluewatersys.com |
---|---|
State | Accepted |
Headers | show |
On Mon, 21 Feb 2011, Ryan Mallon wrote: > Add alarm/wakeup support to rtc isl1208 driver > A few (very minor) comments below. > Signed-off-by: Ryan Mallon <ryan@bluewatersys.com> > --- > > diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c > index 468200c..94aa4f5 100644 > --- a/drivers/rtc/rtc-isl1208.c > +++ b/drivers/rtc/rtc-isl1208.c > @@ -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 > @@ -202,6 +204,31 @@ isl1208_i2c_set_usr(struct i2c_client *client, u16 usr) > } > > static int > +isl1208_rtc_toggle_alarm(struct i2c_client *client, int enable) > +{ > + int icr; > + > + icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT); int icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT); ?? No functional change, but cuts down on source size. > + 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) > { > struct i2c_client *const client = to_i2c_client(dev); > @@ -288,7 +315,7 @@ 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; > > sr = isl1208_i2c_get_sr(client); Maybe it's just me, but I'd have written this as int icr, yr; int sr = isl1208_i2c_get_sr(client); > if (sr < 0) { > @@ -313,6 +340,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 +485,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_dbg(&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 +633,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 +677,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); I didn't dig deep into this, but from a cursory look it seems that free_irq() should be fine being handed NULL. If that's right, then the condifional "if" is not needed here.. > > return rc; > } > @@ -525,6 +691,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; > } >
diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c index 468200c..94aa4f5 100644 --- a/drivers/rtc/rtc-isl1208.c +++ b/drivers/rtc/rtc-isl1208.c @@ -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 @@ -202,6 +204,31 @@ isl1208_i2c_set_usr(struct i2c_client *client, u16 usr) } static int +isl1208_rtc_toggle_alarm(struct i2c_client *client, int enable) +{ + int icr; + + 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) { struct i2c_client *const client = to_i2c_client(dev); @@ -288,7 +315,7 @@ 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; sr = isl1208_i2c_get_sr(client); if (sr < 0) { @@ -313,6 +340,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 +485,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_dbg(&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 +633,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 +677,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 +691,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; }
Add alarm/wakeup support to rtc isl1208 driver Signed-off-by: Ryan Mallon <ryan@bluewatersys.com> ---