diff options
author | Kevin | 2014-11-15 09:58:27 +0800 |
---|---|---|
committer | Kevin | 2014-11-15 09:58:27 +0800 |
commit | 392e8802486cb573b916e746010e141a75f507e6 (patch) | |
tree | 50029aca02c81f087b90336e670b44e510782330 /ANDROID_3.4.5/drivers/rtc/rtc-bfin.c | |
download | FOSSEE-netbook-kernel-source-392e8802486cb573b916e746010e141a75f507e6.tar.gz FOSSEE-netbook-kernel-source-392e8802486cb573b916e746010e141a75f507e6.tar.bz2 FOSSEE-netbook-kernel-source-392e8802486cb573b916e746010e141a75f507e6.zip |
init android origin source code
Diffstat (limited to 'ANDROID_3.4.5/drivers/rtc/rtc-bfin.c')
-rw-r--r-- | ANDROID_3.4.5/drivers/rtc/rtc-bfin.c | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/ANDROID_3.4.5/drivers/rtc/rtc-bfin.c b/ANDROID_3.4.5/drivers/rtc/rtc-bfin.c new file mode 100644 index 00000000..abfc1a0c --- /dev/null +++ b/ANDROID_3.4.5/drivers/rtc/rtc-bfin.c @@ -0,0 +1,464 @@ +/* + * Blackfin On-Chip Real Time Clock Driver + * Supports BF51x/BF52x/BF53[123]/BF53[467]/BF54x + * + * Copyright 2004-2010 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +/* The biggest issue we deal with in this driver is that register writes are + * synced to the RTC frequency of 1Hz. So if you write to a register and + * attempt to write again before the first write has completed, the new write + * is simply discarded. This can easily be troublesome if userspace disables + * one event (say periodic) and then right after enables an event (say alarm). + * Since all events are maintained in the same interrupt mask register, if + * we wrote to it to disable the first event and then wrote to it again to + * enable the second event, that second event would not be enabled as the + * 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 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 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 + * sync penalty, so we should be OK. + * + * Also note that the RTC_ISTAT register does not suffer this penalty; its + * 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> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/seq_file.h> +#include <linux/slab.h> + +#include <asm/blackfin.h> + +#define dev_dbg_stamp(dev) dev_dbg(dev, "%s:%i: here i am\n", __func__, __LINE__) + +struct bfin_rtc { + struct rtc_device *rtc_dev; + struct rtc_time rtc_alarm; + u16 rtc_wrote_regs; +}; + +/* Bit values for the ISTAT / ICTL registers */ +#define RTC_ISTAT_WRITE_COMPLETE 0x8000 +#define RTC_ISTAT_WRITE_PENDING 0x4000 +#define RTC_ISTAT_ALARM_DAY 0x0040 +#define RTC_ISTAT_24HR 0x0020 +#define RTC_ISTAT_HOUR 0x0010 +#define RTC_ISTAT_MIN 0x0008 +#define RTC_ISTAT_SEC 0x0004 +#define RTC_ISTAT_ALARM 0x0002 +#define RTC_ISTAT_STOPWATCH 0x0001 + +/* Shift values for RTC_STAT register */ +#define DAY_BITS_OFF 17 +#define HOUR_BITS_OFF 12 +#define MIN_BITS_OFF 6 +#define SEC_BITS_OFF 0 + +/* Some helper functions to convert between the common RTC notion of time + * and the internal Blackfin notion that is encoded in 32bits. + */ +static inline u32 rtc_time_to_bfin(unsigned long now) +{ + u32 sec = (now % 60); + u32 min = (now % (60 * 60)) / 60; + u32 hour = (now % (60 * 60 * 24)) / (60 * 60); + u32 days = (now / (60 * 60 * 24)); + return (sec << SEC_BITS_OFF) + + (min << MIN_BITS_OFF) + + (hour << HOUR_BITS_OFF) + + (days << DAY_BITS_OFF); +} +static inline unsigned long rtc_bfin_to_time(u32 rtc_bfin) +{ + return (((rtc_bfin >> SEC_BITS_OFF) & 0x003F)) + + (((rtc_bfin >> MIN_BITS_OFF) & 0x003F) * 60) + + (((rtc_bfin >> HOUR_BITS_OFF) & 0x001F) * 60 * 60) + + (((rtc_bfin >> DAY_BITS_OFF) & 0x7FFF) * 60 * 60 * 24); +} +static inline void rtc_bfin_to_tm(u32 rtc_bfin, struct rtc_time *tm) +{ + rtc_time_to_tm(rtc_bfin_to_time(rtc_bfin), tm); +} + +/** + * bfin_rtc_sync_pending - make sure pending writes have complete + * + * Wait for the previous write to a RTC register to complete. + * Unfortunately, we can't sleep here as that introduces a race condition when + * turning on interrupt events. Consider this: + * - process sets alarm + * - process enables alarm + * - process sleeps while waiting for rtc write to sync + * - interrupt fires while process is sleeping + * - interrupt acks the event by writing to ISTAT + * - interrupt sets the WRITE PENDING bit + * - interrupt handler finishes + * - process wakes up, sees WRITE PENDING bit set, goes to sleep + * - interrupt fires while process is sleeping + * If anyone can point out the obvious solution here, i'm listening :). This + * shouldn't be an issue on an SMP or preempt system as this function should + * only be called with the rtc lock held. + * + * Other options: + * - disable PREN so the sync happens at 32.768kHZ ... but this changes the + * inc rate for all RTC registers from 1HZ to 32.768kHZ ... + * - use the write complete IRQ + */ +/* +static void bfin_rtc_sync_pending_polled(void) +{ + while (!(bfin_read_RTC_ISTAT() & RTC_ISTAT_WRITE_COMPLETE)) + if (!(bfin_read_RTC_ISTAT() & RTC_ISTAT_WRITE_PENDING)) + break; + bfin_write_RTC_ISTAT(RTC_ISTAT_WRITE_COMPLETE); +} +*/ +static DECLARE_COMPLETION(bfin_write_complete); +static void bfin_rtc_sync_pending(struct device *dev) +{ + dev_dbg_stamp(dev); + while (bfin_read_RTC_ISTAT() & RTC_ISTAT_WRITE_PENDING) + wait_for_completion_timeout(&bfin_write_complete, HZ * 5); + dev_dbg_stamp(dev); +} + +/** + * bfin_rtc_reset - set RTC to sane/known state + * + * 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, 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_ictl); + bfin_write_RTC_ALARM(0); + bfin_write_RTC_ISTAT(0xFFFF); + rtc->rtc_wrote_regs = 0; +} + +/** + * bfin_rtc_interrupt - handle interrupt from RTC + * + * Since we handle all RTC events here, we have to make sure the requested + * interrupt is enabled (in RTC_ICTL) as the event status register (RTC_ISTAT) + * always gets updated regardless of the interrupt being enabled. So when one + * even we care about (e.g. stopwatch) goes off, we don't want to turn around + * and say that other events have happened as well (e.g. second). We do not + * have to worry about pending writes to the RTC_ICTL register as interrupts + * only fire if they are enabled in the RTC_ICTL register. + */ +static irqreturn_t bfin_rtc_interrupt(int irq, void *dev_id) +{ + struct device *dev = dev_id; + struct bfin_rtc *rtc = dev_get_drvdata(dev); + unsigned long events = 0; + bool write_complete = false; + 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; + + bits = RTC_ISTAT_WRITE_COMPLETE; + if (rtc_istat & bits) { + rtc_istat_clear |= bits; + write_complete = true; + complete(&bfin_write_complete); + } + + 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; + } + } + + bits = RTC_ISTAT_SEC; + if (rtc_ictl & bits) { + if (rtc_istat & bits) { + rtc_istat_clear |= bits; + events |= RTC_UF | RTC_IRQF; + } + } + + if (events) + rtc_update_irq(rtc->rtc_dev, 1, events); + + if (write_complete || events) { + bfin_write_RTC_ISTAT(rtc_istat_clear); + return IRQ_HANDLED; + } else + return IRQ_NONE; +} + +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(u16 rtc_int) +{ + bfin_write_RTC_ICTL(bfin_read_RTC_ICTL() & rtc_int); +} +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_alarm.tm_yday == -1 ? RTC_ISTAT_ALARM : RTC_ISTAT_ALARM_DAY); +} + +static int bfin_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct bfin_rtc *rtc = dev_get_drvdata(dev); + + dev_dbg_stamp(dev); + if (enabled) + bfin_rtc_int_set_alarm(rtc); + else + bfin_rtc_int_clear(~(RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)); + + return 0; +} + +static int bfin_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct bfin_rtc *rtc = dev_get_drvdata(dev); + + dev_dbg_stamp(dev); + + if (rtc->rtc_wrote_regs & 0x1) + bfin_rtc_sync_pending(dev); + + rtc_bfin_to_tm(bfin_read_RTC_STAT(), tm); + + return 0; +} + +static int bfin_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct bfin_rtc *rtc = dev_get_drvdata(dev); + int ret; + unsigned long now; + + dev_dbg_stamp(dev); + + ret = rtc_tm_to_time(tm, &now); + if (ret == 0) { + if (rtc->rtc_wrote_regs & 0x1) + bfin_rtc_sync_pending(dev); + bfin_write_RTC_STAT(rtc_time_to_bfin(now)); + rtc->rtc_wrote_regs = 0x1; + } + + return ret; +} + +static int bfin_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct bfin_rtc *rtc = dev_get_drvdata(dev); + dev_dbg_stamp(dev); + alrm->time = rtc->rtc_alarm; + bfin_rtc_sync_pending(dev); + alrm->enabled = !!(bfin_read_RTC_ICTL() & (RTC_ISTAT_ALARM | RTC_ISTAT_ALARM_DAY)); + return 0; +} + +static int bfin_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct bfin_rtc *rtc = dev_get_drvdata(dev); + unsigned long rtc_alarm; + + dev_dbg_stamp(dev); + + if (rtc_tm_to_time(&alrm->time, &rtc_alarm)) + return -EINVAL; + + rtc->rtc_alarm = alrm->time; + + bfin_rtc_sync_pending(dev); + bfin_write_RTC_ALARM(rtc_time_to_bfin(rtc_alarm)); + if (alrm->enabled) + bfin_rtc_int_set_alarm(rtc); + + return 0; +} + +static int bfin_rtc_proc(struct device *dev, struct seq_file *seq) +{ +#define yesno(x) ((x) ? "yes" : "no") + u16 ictl = bfin_read_RTC_ICTL(); + dev_dbg_stamp(dev); + seq_printf(seq, + "alarm_IRQ\t: %s\n" + "wkalarm_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)); + return 0; +#undef yesno +} + +static struct rtc_class_ops bfin_rtc_ops = { + .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, + .alarm_irq_enable = bfin_rtc_alarm_irq_enable, +}; + +static int __devinit bfin_rtc_probe(struct platform_device *pdev) +{ + struct bfin_rtc *rtc; + struct device *dev = &pdev->dev; + int ret = 0; + unsigned long timeout = jiffies + HZ; + + dev_dbg_stamp(dev); + + /* Allocate memory for our RTC struct */ + rtc = kzalloc(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 = rtc_device_register(pdev->name, dev, &bfin_rtc_ops, + THIS_MODULE); + if (unlikely(IS_ERR(rtc->rtc_dev))) { + ret = PTR_ERR(rtc->rtc_dev); + goto err; + } + + /* Grab the IRQ and init the hardware */ + ret = request_irq(IRQ_RTC, bfin_rtc_interrupt, 0, pdev->name, dev); + if (unlikely(ret)) + goto err_reg; + /* 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); + + return 0; + +err_reg: + rtc_device_unregister(rtc->rtc_dev); +err: + kfree(rtc); + return ret; +} + +static int __devexit bfin_rtc_remove(struct platform_device *pdev) +{ + struct bfin_rtc *rtc = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + bfin_rtc_reset(dev, 0); + free_irq(IRQ_RTC, dev); + rtc_device_unregister(rtc->rtc_dev); + platform_set_drvdata(pdev, NULL); + kfree(rtc); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev = &pdev->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 bfin_rtc_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg_stamp(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(IRQ_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; +} +#else +# define bfin_rtc_suspend NULL +# define bfin_rtc_resume NULL +#endif + +static struct platform_driver bfin_rtc_driver = { + .driver = { + .name = "rtc-bfin", + .owner = THIS_MODULE, + }, + .probe = bfin_rtc_probe, + .remove = __devexit_p(bfin_rtc_remove), + .suspend = bfin_rtc_suspend, + .resume = bfin_rtc_resume, +}; + +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"); |