diff options
| author | Ryan Mallon <ryan@bluewatersys.com> | 2011-03-22 19:34:53 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-22 20:44:16 -0400 |
| commit | cf044f0ed526752b8c2aaae748220759608b3fc8 (patch) | |
| tree | bafd4c7abaed6fa03c5314d59f6de79a639a5008 /drivers | |
| parent | bc96ba7414ca4456661d0873856e0f363d32ba67 (diff) | |
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>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/rtc/rtc-isl1208.c | 176 |
1 files changed, 171 insertions, 5 deletions
diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c index 468200c38ecb..da8beb8cae51 100644 --- a/drivers/rtc/rtc-isl1208.c +++ b/drivers/rtc/rtc-isl1208.c | |||
| @@ -39,6 +39,8 @@ | |||
| 39 | #define ISL1208_REG_SR_BAT (1<<1) /* battery */ | 39 | #define ISL1208_REG_SR_BAT (1<<1) /* battery */ |
| 40 | #define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */ | 40 | #define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */ |
| 41 | #define ISL1208_REG_INT 0x08 | 41 | #define ISL1208_REG_INT 0x08 |
| 42 | #define ISL1208_REG_INT_ALME (1<<6) /* alarm enable */ | ||
| 43 | #define ISL1208_REG_INT_IM (1<<7) /* interrupt/alarm mode */ | ||
| 42 | #define ISL1208_REG_09 0x09 /* reserved */ | 44 | #define ISL1208_REG_09 0x09 /* reserved */ |
| 43 | #define ISL1208_REG_ATR 0x0a | 45 | #define ISL1208_REG_ATR 0x0a |
| 44 | #define ISL1208_REG_DTR 0x0b | 46 | #define ISL1208_REG_DTR 0x0b |
| @@ -202,6 +204,30 @@ isl1208_i2c_set_usr(struct i2c_client *client, u16 usr) | |||
| 202 | } | 204 | } |
| 203 | 205 | ||
| 204 | static int | 206 | static int |
| 207 | isl1208_rtc_toggle_alarm(struct i2c_client *client, int enable) | ||
| 208 | { | ||
| 209 | int icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT); | ||
| 210 | |||
| 211 | if (icr < 0) { | ||
| 212 | dev_err(&client->dev, "%s: reading INT failed\n", __func__); | ||
| 213 | return icr; | ||
| 214 | } | ||
| 215 | |||
| 216 | if (enable) | ||
| 217 | icr |= ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM; | ||
| 218 | else | ||
| 219 | icr &= ~(ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM); | ||
| 220 | |||
| 221 | icr = i2c_smbus_write_byte_data(client, ISL1208_REG_INT, icr); | ||
| 222 | if (icr < 0) { | ||
| 223 | dev_err(&client->dev, "%s: writing INT failed\n", __func__); | ||
| 224 | return icr; | ||
| 225 | } | ||
| 226 | |||
| 227 | return 0; | ||
| 228 | } | ||
| 229 | |||
| 230 | static int | ||
| 205 | isl1208_rtc_proc(struct device *dev, struct seq_file *seq) | 231 | isl1208_rtc_proc(struct device *dev, struct seq_file *seq) |
| 206 | { | 232 | { |
| 207 | struct i2c_client *const client = to_i2c_client(dev); | 233 | struct i2c_client *const client = to_i2c_client(dev); |
| @@ -288,9 +314,8 @@ isl1208_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) | |||
| 288 | { | 314 | { |
| 289 | struct rtc_time *const tm = &alarm->time; | 315 | struct rtc_time *const tm = &alarm->time; |
| 290 | u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, }; | 316 | u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, }; |
| 291 | int sr; | 317 | int icr, yr, sr = isl1208_i2c_get_sr(client); |
| 292 | 318 | ||
| 293 | sr = isl1208_i2c_get_sr(client); | ||
| 294 | if (sr < 0) { | 319 | if (sr < 0) { |
| 295 | dev_err(&client->dev, "%s: reading SR failed\n", __func__); | 320 | dev_err(&client->dev, "%s: reading SR failed\n", __func__); |
| 296 | return sr; | 321 | return sr; |
| @@ -313,6 +338,73 @@ isl1208_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) | |||
| 313 | bcd2bin(regs[ISL1208_REG_MOA - ISL1208_REG_SCA] & 0x1f) - 1; | 338 | bcd2bin(regs[ISL1208_REG_MOA - ISL1208_REG_SCA] & 0x1f) - 1; |
| 314 | tm->tm_wday = bcd2bin(regs[ISL1208_REG_DWA - ISL1208_REG_SCA] & 0x03); | 339 | tm->tm_wday = bcd2bin(regs[ISL1208_REG_DWA - ISL1208_REG_SCA] & 0x03); |
| 315 | 340 | ||
| 341 | /* The alarm doesn't store the year so get it from the rtc section */ | ||
| 342 | yr = i2c_smbus_read_byte_data(client, ISL1208_REG_YR); | ||
| 343 | if (yr < 0) { | ||
| 344 | dev_err(&client->dev, "%s: reading RTC YR failed\n", __func__); | ||
| 345 | return yr; | ||
| 346 | } | ||
| 347 | tm->tm_year = bcd2bin(yr) + 100; | ||
| 348 | |||
| 349 | icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT); | ||
| 350 | if (icr < 0) { | ||
| 351 | dev_err(&client->dev, "%s: reading INT failed\n", __func__); | ||
| 352 | return icr; | ||
| 353 | } | ||
| 354 | alarm->enabled = !!(icr & ISL1208_REG_INT_ALME); | ||
| 355 | |||
| 356 | return 0; | ||
| 357 | } | ||
| 358 | |||
| 359 | static int | ||
| 360 | isl1208_i2c_set_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) | ||
| 361 | { | ||
| 362 | struct rtc_time *alarm_tm = &alarm->time; | ||
| 363 | u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, }; | ||
| 364 | const int offs = ISL1208_REG_SCA; | ||
| 365 | unsigned long rtc_secs, alarm_secs; | ||
| 366 | struct rtc_time rtc_tm; | ||
| 367 | int err, enable; | ||
| 368 | |||
| 369 | err = isl1208_i2c_read_time(client, &rtc_tm); | ||
| 370 | if (err) | ||
| 371 | return err; | ||
| 372 | err = rtc_tm_to_time(&rtc_tm, &rtc_secs); | ||
| 373 | if (err) | ||
| 374 | return err; | ||
| 375 | err = rtc_tm_to_time(alarm_tm, &alarm_secs); | ||
| 376 | if (err) | ||
| 377 | return err; | ||
| 378 | |||
| 379 | /* If the alarm time is before the current time disable the alarm */ | ||
| 380 | if (!alarm->enabled || alarm_secs <= rtc_secs) | ||
| 381 | enable = 0x00; | ||
| 382 | else | ||
| 383 | enable = 0x80; | ||
| 384 | |||
| 385 | /* Program the alarm and enable it for each setting */ | ||
| 386 | regs[ISL1208_REG_SCA - offs] = bin2bcd(alarm_tm->tm_sec) | enable; | ||
| 387 | regs[ISL1208_REG_MNA - offs] = bin2bcd(alarm_tm->tm_min) | enable; | ||
| 388 | regs[ISL1208_REG_HRA - offs] = bin2bcd(alarm_tm->tm_hour) | | ||
| 389 | ISL1208_REG_HR_MIL | enable; | ||
| 390 | |||
| 391 | regs[ISL1208_REG_DTA - offs] = bin2bcd(alarm_tm->tm_mday) | enable; | ||
| 392 | regs[ISL1208_REG_MOA - offs] = bin2bcd(alarm_tm->tm_mon + 1) | enable; | ||
| 393 | regs[ISL1208_REG_DWA - offs] = bin2bcd(alarm_tm->tm_wday & 7) | enable; | ||
| 394 | |||
| 395 | /* write ALARM registers */ | ||
| 396 | err = isl1208_i2c_set_regs(client, offs, regs, | ||
| 397 | ISL1208_ALARM_SECTION_LEN); | ||
| 398 | if (err < 0) { | ||
| 399 | dev_err(&client->dev, "%s: writing ALARM section failed\n", | ||
| 400 | __func__); | ||
| 401 | return err; | ||
| 402 | } | ||
| 403 | |||
| 404 | err = isl1208_rtc_toggle_alarm(client, enable); | ||
| 405 | if (err) | ||
| 406 | return err; | ||
| 407 | |||
| 316 | return 0; | 408 | return 0; |
| 317 | } | 409 | } |
| 318 | 410 | ||
| @@ -391,12 +483,63 @@ isl1208_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) | |||
| 391 | return isl1208_i2c_read_alarm(to_i2c_client(dev), alarm); | 483 | return isl1208_i2c_read_alarm(to_i2c_client(dev), alarm); |
| 392 | } | 484 | } |
| 393 | 485 | ||
| 486 | static int | ||
| 487 | isl1208_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
| 488 | { | ||
| 489 | return isl1208_i2c_set_alarm(to_i2c_client(dev), alarm); | ||
| 490 | } | ||
| 491 | |||
| 492 | static irqreturn_t | ||
| 493 | isl1208_rtc_interrupt(int irq, void *data) | ||
| 494 | { | ||
| 495 | unsigned long timeout = jiffies + msecs_to_jiffies(1000); | ||
| 496 | struct i2c_client *client = data; | ||
| 497 | int handled = 0, sr, err; | ||
| 498 | |||
| 499 | /* | ||
| 500 | * I2C reads get NAK'ed if we read straight away after an interrupt? | ||
| 501 | * Using a mdelay/msleep didn't seem to help either, so we work around | ||
| 502 | * this by continually trying to read the register for a short time. | ||
| 503 | */ | ||
| 504 | while (1) { | ||
| 505 | sr = isl1208_i2c_get_sr(client); | ||
| 506 | if (sr >= 0) | ||
| 507 | break; | ||
| 508 | |||
| 509 | if (time_after(jiffies, timeout)) { | ||
| 510 | dev_err(&client->dev, "%s: reading SR failed\n", | ||
| 511 | __func__); | ||
| 512 | return sr; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | |||
| 516 | if (sr & ISL1208_REG_SR_ALM) { | ||
| 517 | dev_dbg(&client->dev, "alarm!\n"); | ||
| 518 | |||
| 519 | /* Clear the alarm */ | ||
| 520 | sr &= ~ISL1208_REG_SR_ALM; | ||
| 521 | sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR, sr); | ||
| 522 | if (sr < 0) | ||
| 523 | dev_err(&client->dev, "%s: writing SR failed\n", | ||
| 524 | __func__); | ||
| 525 | else | ||
| 526 | handled = 1; | ||
| 527 | |||
| 528 | /* Disable the alarm */ | ||
| 529 | err = isl1208_rtc_toggle_alarm(client, 0); | ||
| 530 | if (err) | ||
| 531 | return err; | ||
| 532 | } | ||
| 533 | |||
| 534 | return handled ? IRQ_HANDLED : IRQ_NONE; | ||
| 535 | } | ||
| 536 | |||
| 394 | static const struct rtc_class_ops isl1208_rtc_ops = { | 537 | static const struct rtc_class_ops isl1208_rtc_ops = { |
| 395 | .proc = isl1208_rtc_proc, | 538 | .proc = isl1208_rtc_proc, |
| 396 | .read_time = isl1208_rtc_read_time, | 539 | .read_time = isl1208_rtc_read_time, |
| 397 | .set_time = isl1208_rtc_set_time, | 540 | .set_time = isl1208_rtc_set_time, |
| 398 | .read_alarm = isl1208_rtc_read_alarm, | 541 | .read_alarm = isl1208_rtc_read_alarm, |
| 399 | /*.set_alarm = isl1208_rtc_set_alarm, */ | 542 | .set_alarm = isl1208_rtc_set_alarm, |
| 400 | }; | 543 | }; |
| 401 | 544 | ||
| 402 | /* sysfs interface */ | 545 | /* sysfs interface */ |
| @@ -488,11 +631,29 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id) | |||
| 488 | dev_info(&client->dev, | 631 | dev_info(&client->dev, |
| 489 | "chip found, driver version " DRV_VERSION "\n"); | 632 | "chip found, driver version " DRV_VERSION "\n"); |
| 490 | 633 | ||
| 634 | if (client->irq > 0) { | ||
| 635 | rc = request_threaded_irq(client->irq, NULL, | ||
| 636 | isl1208_rtc_interrupt, | ||
| 637 | IRQF_SHARED, | ||
| 638 | isl1208_driver.driver.name, client); | ||
| 639 | if (!rc) { | ||
| 640 | device_init_wakeup(&client->dev, 1); | ||
| 641 | enable_irq_wake(client->irq); | ||
| 642 | } else { | ||
| 643 | dev_err(&client->dev, | ||
| 644 | "Unable to request irq %d, no alarm support\n", | ||
| 645 | client->irq); | ||
| 646 | client->irq = 0; | ||
| 647 | } | ||
| 648 | } | ||
| 649 | |||
| 491 | rtc = rtc_device_register(isl1208_driver.driver.name, | 650 | rtc = rtc_device_register(isl1208_driver.driver.name, |
| 492 | &client->dev, &isl1208_rtc_ops, | 651 | &client->dev, &isl1208_rtc_ops, |
| 493 | THIS_MODULE); | 652 | THIS_MODULE); |
| 494 | if (IS_ERR(rtc)) | 653 | if (IS_ERR(rtc)) { |
| 495 | return PTR_ERR(rtc); | 654 | rc = PTR_ERR(rtc); |
| 655 | goto exit_free_irq; | ||
| 656 | } | ||
| 496 | 657 | ||
| 497 | i2c_set_clientdata(client, rtc); | 658 | i2c_set_clientdata(client, rtc); |
| 498 | 659 | ||
| @@ -514,6 +675,9 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id) | |||
| 514 | 675 | ||
| 515 | exit_unregister: | 676 | exit_unregister: |
| 516 | rtc_device_unregister(rtc); | 677 | rtc_device_unregister(rtc); |
| 678 | exit_free_irq: | ||
| 679 | if (client->irq) | ||
| 680 | free_irq(client->irq, client); | ||
| 517 | 681 | ||
| 518 | return rc; | 682 | return rc; |
| 519 | } | 683 | } |
| @@ -525,6 +689,8 @@ isl1208_remove(struct i2c_client *client) | |||
| 525 | 689 | ||
| 526 | sysfs_remove_group(&client->dev.kobj, &isl1208_rtc_sysfs_files); | 690 | sysfs_remove_group(&client->dev.kobj, &isl1208_rtc_sysfs_files); |
| 527 | rtc_device_unregister(rtc); | 691 | rtc_device_unregister(rtc); |
| 692 | if (client->irq) | ||
| 693 | free_irq(client->irq, client); | ||
| 528 | 694 | ||
| 529 | return 0; | 695 | return 0; |
| 530 | } | 696 | } |
