Commit cf044f0ed526752b8c2aaae748220759608b3fc8
Committed by
Linus Torvalds
1 parent
bc96ba7414
Exists in
master
and in
39 other branches
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>
Showing 1 changed file with 171 additions and 5 deletions Side-by-side Diff
drivers/rtc/rtc-isl1208.c
... | ... | @@ -39,6 +39,8 @@ |
39 | 39 | #define ISL1208_REG_SR_BAT (1<<1) /* battery */ |
40 | 40 | #define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */ |
41 | 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 | 44 | #define ISL1208_REG_09 0x09 /* reserved */ |
43 | 45 | #define ISL1208_REG_ATR 0x0a |
44 | 46 | #define ISL1208_REG_DTR 0x0b |
... | ... | @@ -202,6 +204,30 @@ |
202 | 204 | } |
203 | 205 | |
204 | 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 | 231 | isl1208_rtc_proc(struct device *dev, struct seq_file *seq) |
206 | 232 | { |
207 | 233 | struct i2c_client *const client = to_i2c_client(dev); |
208 | 234 | |
... | ... | @@ -288,9 +314,8 @@ |
288 | 314 | { |
289 | 315 | struct rtc_time *const tm = &alarm->time; |
290 | 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 | 319 | if (sr < 0) { |
295 | 320 | dev_err(&client->dev, "%s: reading SR failed\n", __func__); |
296 | 321 | return sr; |
297 | 322 | |
... | ... | @@ -313,10 +338,77 @@ |
313 | 338 | bcd2bin(regs[ISL1208_REG_MOA - ISL1208_REG_SCA] & 0x1f) - 1; |
314 | 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 | + | |
316 | 356 | return 0; |
317 | 357 | } |
318 | 358 | |
319 | 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 | + | |
408 | + return 0; | |
409 | +} | |
410 | + | |
411 | +static int | |
320 | 412 | isl1208_rtc_read_time(struct device *dev, struct rtc_time *tm) |
321 | 413 | { |
322 | 414 | return isl1208_i2c_read_time(to_i2c_client(dev), tm); |
323 | 415 | |
... | ... | @@ -391,12 +483,63 @@ |
391 | 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 | 537 | static const struct rtc_class_ops isl1208_rtc_ops = { |
395 | 538 | .proc = isl1208_rtc_proc, |
396 | 539 | .read_time = isl1208_rtc_read_time, |
397 | 540 | .set_time = isl1208_rtc_set_time, |
398 | 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 | 545 | /* sysfs interface */ |
403 | 546 | |
... | ... | @@ -488,11 +631,29 @@ |
488 | 631 | dev_info(&client->dev, |
489 | 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 | 650 | rtc = rtc_device_register(isl1208_driver.driver.name, |
492 | 651 | &client->dev, &isl1208_rtc_ops, |
493 | 652 | THIS_MODULE); |
494 | - if (IS_ERR(rtc)) | |
495 | - return PTR_ERR(rtc); | |
653 | + if (IS_ERR(rtc)) { | |
654 | + rc = PTR_ERR(rtc); | |
655 | + goto exit_free_irq; | |
656 | + } | |
496 | 657 | |
497 | 658 | i2c_set_clientdata(client, rtc); |
498 | 659 | |
... | ... | @@ -514,6 +675,9 @@ |
514 | 675 | |
515 | 676 | exit_unregister: |
516 | 677 | rtc_device_unregister(rtc); |
678 | +exit_free_irq: | |
679 | + if (client->irq) | |
680 | + free_irq(client->irq, client); | |
517 | 681 | |
518 | 682 | return rc; |
519 | 683 | } |
... | ... | @@ -525,6 +689,8 @@ |
525 | 689 | |
526 | 690 | sysfs_remove_group(&client->dev.kobj, &isl1208_rtc_sysfs_files); |
527 | 691 | rtc_device_unregister(rtc); |
692 | + if (client->irq) | |
693 | + free_irq(client->irq, client); | |
528 | 694 | |
529 | 695 | return 0; |
530 | 696 | } |