drivers/rtc/rtc-isl1208.c: add alarm support

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>
This commit is contained in:
Ryan Mallon 2011-03-22 16:34:53 -07:00 committed by Linus Torvalds
parent bc96ba7414
commit cf044f0ed5

View 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;
}