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:
parent
bc96ba7414
commit
cf044f0ed5
1 changed files with 171 additions and 5 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue