diff options
Diffstat (limited to 'drivers/rtc/rtc-bfin.c')
| -rw-r--r-- | drivers/rtc/rtc-bfin.c | 259 |
1 files changed, 118 insertions, 141 deletions
diff --git a/drivers/rtc/rtc-bfin.c b/drivers/rtc/rtc-bfin.c index d90ba860d21..fe4bdb06a55 100644 --- a/drivers/rtc/rtc-bfin.c +++ b/drivers/rtc/rtc-bfin.c @@ -1,8 +1,8 @@ /* * Blackfin On-Chip Real Time Clock Driver - * Supports BF52[257]/BF53[123]/BF53[467]/BF54[24789] + * Supports BF51x/BF52x/BF53[123]/BF53[467]/BF54x * - * Copyright 2004-2007 Analog Devices Inc. + * Copyright 2004-2010 Analog Devices Inc. * * Enter bugs at http://blackfin.uclinux.org/ * @@ -20,9 +20,9 @@ * write would be discarded and things quickly fall apart. * * To keep this delay from significantly degrading performance (we, in theory, - * would have to sleep for up to 1 second everytime we wanted to write a + * would have to sleep for up to 1 second every time we wanted to write a * register), we only check the write pending status before we start to issue - * a new write. We bank on the idea that it doesnt matter when the sync + * a new write. We bank on the idea that it doesn't matter when the sync * happens so long as we don't attempt another write before it does. The only * time userspace would take this penalty is when they try and do multiple * operations right after another ... but in this case, they need to take the @@ -32,6 +32,15 @@ * writes to clear status registers complete immediately. */ +/* It may seem odd that there is no SWCNT code in here (which would be exposed + * via the periodic interrupt event, or PIE). Since the Blackfin RTC peripheral + * runs in units of seconds (N/HZ) but the Linux framework runs in units of HZ + * (2^N HZ), there is no point in keeping code that only provides 1 HZ PIEs. + * The same exact behavior can be accomplished by using the update interrupt + * event (UIE). Maybe down the line the RTC peripheral will suck less in which + * case we can re-introduce PIE support. + */ + #include <linux/bcd.h> #include <linux/completion.h> #include <linux/delay.h> @@ -42,6 +51,7 @@ #include <linux/platform_device.h> #include <linux/rtc.h> #include <linux/seq_file.h> +#include <linux/slab.h> #include <asm/blackfin.h> @@ -144,14 +154,13 @@ static void bfin_rtc_sync_pending(struct device *dev) * Initialize the RTC. Enable pre-scaler to scale RTC clock * to 1Hz and clear interrupt/status registers. */ -static void bfin_rtc_reset(struct device *dev) +static void bfin_rtc_reset(struct device *dev, u16 rtc_ictl) { struct bfin_rtc *rtc = dev_get_drvdata(dev); dev_dbg_stamp(dev); bfin_rtc_sync_pending(dev); bfin_write_RTC_PREN(0x1); - bfin_write_RTC_ICTL(RTC_ISTAT_WRITE_COMPLETE); - bfin_write_RTC_SWCNT(0); + bfin_write_RTC_ICTL(rtc_ictl); bfin_write_RTC_ALARM(0); bfin_write_RTC_ISTAT(0xFFFF); rtc->rtc_wrote_regs = 0; @@ -174,37 +183,33 @@ static irqreturn_t bfin_rtc_interrupt(int irq, void *dev_id) struct bfin_rtc *rtc = dev_get_drvdata(dev); unsigned long events = 0; bool write_complete = false; - u16 rtc_istat, rtc_ictl; + u16 rtc_istat, rtc_istat_clear, rtc_ictl, bits; dev_dbg_stamp(dev); rtc_istat = bfin_read_RTC_ISTAT(); rtc_ictl = bfin_read_RTC_ICTL(); + rtc_istat_clear = 0; - if (rtc_istat & RTC_ISTAT_WRITE_COMPLETE) { - bfin_write_RTC_ISTAT(RTC_ISTAT_WRITE_COMPLETE); + bits = RTC_ISTAT_WRITE_COMPLETE; + if (rtc_istat & bits) { + rtc_istat_clear |= bits; write_complete = true; complete(&bfin_write_complete); } - if (rtc_ictl & (RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)) { - if (rtc_istat & (RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)) { - bfin_write_RTC_ISTAT(RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY); + bits = (RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY); + if (rtc_ictl & bits) { + if (rtc_istat & bits) { + rtc_istat_clear |= bits; events |= RTC_AF | RTC_IRQF; } } - if (rtc_ictl & RTC_ISTAT_STOPWATCH) { - if (rtc_istat & RTC_ISTAT_STOPWATCH) { - bfin_write_RTC_ISTAT(RTC_ISTAT_STOPWATCH); - events |= RTC_PF | RTC_IRQF; - bfin_write_RTC_SWCNT(rtc->rtc_dev->irq_freq); - } - } - - if (rtc_ictl & RTC_ISTAT_SEC) { - if (rtc_istat & RTC_ISTAT_SEC) { - bfin_write_RTC_ISTAT(RTC_ISTAT_SEC); + bits = RTC_ISTAT_SEC; + if (rtc_ictl & bits) { + if (rtc_istat & bits) { + rtc_istat_clear |= bits; events |= RTC_UF | RTC_IRQF; } } @@ -212,38 +217,19 @@ static irqreturn_t bfin_rtc_interrupt(int irq, void *dev_id) if (events) rtc_update_irq(rtc->rtc_dev, 1, events); - if (write_complete || events) + if (write_complete || events) { + bfin_write_RTC_ISTAT(rtc_istat_clear); return IRQ_HANDLED; - else + } else return IRQ_NONE; } -static int bfin_rtc_open(struct device *dev) -{ - int ret; - - dev_dbg_stamp(dev); - - ret = request_irq(IRQ_RTC, bfin_rtc_interrupt, IRQF_SHARED, to_platform_device(dev)->name, dev); - if (!ret) - bfin_rtc_reset(dev); - - return ret; -} - -static void bfin_rtc_release(struct device *dev) -{ - dev_dbg_stamp(dev); - bfin_rtc_reset(dev); - free_irq(IRQ_RTC, dev); -} - -static void bfin_rtc_int_set(struct bfin_rtc *rtc, u16 rtc_int) +static void bfin_rtc_int_set(u16 rtc_int) { bfin_write_RTC_ISTAT(rtc_int); bfin_write_RTC_ICTL(bfin_read_RTC_ICTL() | rtc_int); } -static void bfin_rtc_int_clear(struct bfin_rtc *rtc, u16 rtc_int) +static void bfin_rtc_int_clear(u16 rtc_int) { bfin_write_RTC_ICTL(bfin_read_RTC_ICTL() & rtc_int); } @@ -252,52 +238,20 @@ static void bfin_rtc_int_set_alarm(struct bfin_rtc *rtc) /* Blackfin has different bits for whether the alarm is * more than 24 hours away. */ - bfin_rtc_int_set(rtc, (rtc->rtc_alarm.tm_yday == -1 ? RTC_ISTAT_ALARM : RTC_ISTAT_ALARM_DAY)); + bfin_rtc_int_set(rtc->rtc_alarm.tm_yday == -1 ? RTC_ISTAT_ALARM : RTC_ISTAT_ALARM_DAY); } -static int bfin_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) + +static int bfin_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct bfin_rtc *rtc = dev_get_drvdata(dev); - int ret = 0; dev_dbg_stamp(dev); - - bfin_rtc_sync_pending(dev); - - switch (cmd) { - case RTC_PIE_ON: - dev_dbg_stamp(dev); - bfin_rtc_int_set(rtc, RTC_ISTAT_STOPWATCH); - bfin_write_RTC_SWCNT(rtc->rtc_dev->irq_freq); - break; - case RTC_PIE_OFF: - dev_dbg_stamp(dev); - bfin_rtc_int_clear(rtc, ~RTC_ISTAT_STOPWATCH); - break; - - case RTC_UIE_ON: - dev_dbg_stamp(dev); - bfin_rtc_int_set(rtc, RTC_ISTAT_SEC); - break; - case RTC_UIE_OFF: - dev_dbg_stamp(dev); - bfin_rtc_int_clear(rtc, ~RTC_ISTAT_SEC); - break; - - case RTC_AIE_ON: - dev_dbg_stamp(dev); + if (enabled) bfin_rtc_int_set_alarm(rtc); - break; - case RTC_AIE_OFF: - dev_dbg_stamp(dev); - bfin_rtc_int_clear(rtc, ~(RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)); - break; - - default: - dev_dbg_stamp(dev); - ret = -ENOIOCTLCMD; - } + else + bfin_rtc_int_clear(~(RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)); - return ret; + return 0; } static int bfin_rtc_read_time(struct device *dev, struct rtc_time *tm) @@ -371,102 +325,125 @@ static int bfin_rtc_proc(struct device *dev, struct seq_file *seq) seq_printf(seq, "alarm_IRQ\t: %s\n" "wkalarm_IRQ\t: %s\n" - "seconds_IRQ\t: %s\n" - "periodic_IRQ\t: %s\n", + "seconds_IRQ\t: %s\n", yesno(ictl & RTC_ISTAT_ALARM), yesno(ictl & RTC_ISTAT_ALARM_DAY), - yesno(ictl & RTC_ISTAT_SEC), - yesno(ictl & RTC_ISTAT_STOPWATCH)); + yesno(ictl & RTC_ISTAT_SEC)); return 0; #undef yesno } -/** - * bfin_irq_set_freq - make sure hardware supports requested freq - * @dev: pointer to RTC device structure - * @freq: requested frequency rate - * - * The Blackfin RTC can only generate periodic events at 1 per - * second (1 Hz), so reject any attempt at changing it. - */ -static int bfin_irq_set_freq(struct device *dev, int freq) -{ - dev_dbg_stamp(dev); - return -ENOTTY; -} - static struct rtc_class_ops bfin_rtc_ops = { - .open = bfin_rtc_open, - .release = bfin_rtc_release, - .ioctl = bfin_rtc_ioctl, .read_time = bfin_rtc_read_time, .set_time = bfin_rtc_set_time, .read_alarm = bfin_rtc_read_alarm, .set_alarm = bfin_rtc_set_alarm, .proc = bfin_rtc_proc, - .irq_set_freq = bfin_irq_set_freq, + .alarm_irq_enable = bfin_rtc_alarm_irq_enable, }; -static int __devinit bfin_rtc_probe(struct platform_device *pdev) +static int bfin_rtc_probe(struct platform_device *pdev) { struct bfin_rtc *rtc; - int ret = 0; + struct device *dev = &pdev->dev; + int ret; + unsigned long timeout = jiffies + HZ; - dev_dbg_stamp(&pdev->dev); + dev_dbg_stamp(dev); - rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); + /* Allocate memory for our RTC struct */ + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (unlikely(!rtc)) return -ENOMEM; + platform_set_drvdata(pdev, rtc); + device_init_wakeup(dev, 1); + + /* Register our RTC with the RTC framework */ + rtc->rtc_dev = devm_rtc_device_register(dev, pdev->name, &bfin_rtc_ops, + THIS_MODULE); + if (unlikely(IS_ERR(rtc->rtc_dev))) + return PTR_ERR(rtc->rtc_dev); + + /* Grab the IRQ and init the hardware */ + ret = devm_request_irq(dev, IRQ_RTC, bfin_rtc_interrupt, 0, + pdev->name, dev); + if (unlikely(ret)) + dev_err(&pdev->dev, + "unable to request IRQ; alarm won't work, " + "and writes will be delayed\n"); + + /* sometimes the bootloader touched things, but the write complete was not + * enabled, so let's just do a quick timeout here since the IRQ will not fire ... + */ + while (bfin_read_RTC_ISTAT() & RTC_ISTAT_WRITE_PENDING) + if (time_after(jiffies, timeout)) + break; + bfin_rtc_reset(dev, RTC_ISTAT_WRITE_COMPLETE); + bfin_write_RTC_SWCNT(0); - rtc->rtc_dev = rtc_device_register(pdev->name, &pdev->dev, &bfin_rtc_ops, THIS_MODULE); - if (unlikely(IS_ERR(rtc))) { - ret = PTR_ERR(rtc->rtc_dev); - goto err; - } - rtc->rtc_dev->irq_freq = 1; + return 0; +} - platform_set_drvdata(pdev, rtc); +static int bfin_rtc_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + bfin_rtc_reset(dev, 0); return 0; +} - err: - kfree(rtc); - return ret; +#ifdef CONFIG_PM_SLEEP +static int bfin_rtc_suspend(struct device *dev) +{ + dev_dbg_stamp(dev); + + if (device_may_wakeup(dev)) { + enable_irq_wake(IRQ_RTC); + bfin_rtc_sync_pending(dev); + } else + bfin_rtc_int_clear(0); + + return 0; } -static int __devexit bfin_rtc_remove(struct platform_device *pdev) +static int bfin_rtc_resume(struct device *dev) { - struct bfin_rtc *rtc = platform_get_drvdata(pdev); + dev_dbg_stamp(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(IRQ_RTC); - rtc_device_unregister(rtc->rtc_dev); - platform_set_drvdata(pdev, NULL); - kfree(rtc); + /* + * Since only some of the RTC bits are maintained externally in the + * Vbat domain, we need to wait for the RTC MMRs to be synced into + * the core after waking up. This happens every RTC 1HZ. Once that + * has happened, we can go ahead and re-enable the important write + * complete interrupt event. + */ + while (!(bfin_read_RTC_ISTAT() & RTC_ISTAT_SEC)) + continue; + bfin_rtc_int_set(RTC_ISTAT_WRITE_COMPLETE); return 0; } +#endif + +static SIMPLE_DEV_PM_OPS(bfin_rtc_pm_ops, bfin_rtc_suspend, bfin_rtc_resume); static struct platform_driver bfin_rtc_driver = { .driver = { .name = "rtc-bfin", .owner = THIS_MODULE, + .pm = &bfin_rtc_pm_ops, }, .probe = bfin_rtc_probe, - .remove = __devexit_p(bfin_rtc_remove), + .remove = bfin_rtc_remove, }; -static int __init bfin_rtc_init(void) -{ - return platform_driver_register(&bfin_rtc_driver); -} - -static void __exit bfin_rtc_exit(void) -{ - platform_driver_unregister(&bfin_rtc_driver); -} - -module_init(bfin_rtc_init); -module_exit(bfin_rtc_exit); +module_platform_driver(bfin_rtc_driver); MODULE_DESCRIPTION("Blackfin On-Chip Real Time Clock Driver"); MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc-bfin"); |
