diff options
author | Prashant Gaikwad <pgaikwad@nvidia.com> | 2013-10-23 17:16:56 +0530 |
---|---|---|
committer | Bharat Nihalani <bnihalani@nvidia.com> | 2013-11-04 16:54:36 -0800 |
commit | ee6426e675d62ad29348eb8a84c2b59dc47f4515 (patch) | |
tree | 633c453faab93aac09f9476eda39fbf1fc4f4dd4 | |
parent | 8a2d60965962ddbab4c41dcf3b9015d7e4e21f7f (diff) |
arm: tegra: add rtc timer to wake from lp0
When device is in LP0 all timers are off except RTC.
Use this timer when we enter LP0 from cpuidle.
Bug 1254633
Change-Id: I6887aae221b27be095ea213bac70a110081d3d64
Signed-off-by: Prashant Gaikwad <pgaikwad@nvidia.com>
Reviewed-on: http://git-master/r/303230
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r-- | drivers/clocksource/tegra-nvtimers.c | 140 | ||||
-rw-r--r-- | include/linux/tegra-timer.h | 6 |
2 files changed, 146 insertions, 0 deletions
diff --git a/drivers/clocksource/tegra-nvtimers.c b/drivers/clocksource/tegra-nvtimers.c index a23dbf43da76..c7c4a1a61686 100644 --- a/drivers/clocksource/tegra-nvtimers.c +++ b/drivers/clocksource/tegra-nvtimers.c @@ -35,6 +35,7 @@ #include <linux/of_address.h> #include <linux/tegra-soc.h> #include <linux/tegra-timer.h> +#include <linux/delay.h> #include <asm/mach/time.h> #include <asm/delay.h> @@ -46,6 +47,20 @@ void __iomem *timer_reg_base; static void __iomem *rtc_base; +/* set to 1 = busy every eight 32kHz clocks during copy of sec+msec to AHB */ +#define TEGRA_RTC_REG_BUSY 0x004 +#define TEGRA_RTC_REG_SECONDS 0x008 +#define TEGRA_RTC_REG_MSEC_CDN_ALARM0 0x024 +#define TEGRA_RTC_REG_INTR_MASK 0x028 +/* write 1 bits to clear status bits */ +#define TEGRA_RTC_REG_INTR_STATUS 0x02c + +/* bits in INTR_MASK */ +#define TEGRA_RTC_INTR_MASK_MSEC_CDN_ALARM (1<<4) + +/* bits in INTR_STATUS */ +#define TEGRA_RTC_INTR_STATUS_MSEC_CDN_ALARM (1<<4) + static u64 persistent_ms, last_persistent_ms; static struct timespec persistent_ts; @@ -215,6 +230,112 @@ u64 tegra_rtc_read_ms(void) return (u64)s * MSEC_PER_SEC + ms; } +#ifdef CONFIG_TEGRA_LP0_IN_IDLE +/* RTC hardware is busy when it is updating its values over AHB once + * every eight 32kHz clocks (~250uS). + * outside of these updates the CPU is free to write. + * CPU is always free to read. + */ +static inline u32 tegra_rtc_check_busy(void) +{ + return readl(rtc_base + TEGRA_RTC_REG_BUSY) & 1; +} + +/* Wait for hardware to be ready for writing. + * This function tries to maximize the amount of time before the next update. + * It does this by waiting for the RTC to become busy with its periodic update, + * then returning once the RTC first becomes not busy. + * This periodic update (where the seconds and milliseconds are copied to the + * AHB side) occurs every eight 32kHz clocks (~250uS). + * The behavior of this function allows us to make some assumptions without + * introducing a race, because 250uS is plenty of time to read/write a value. + */ +static int tegra_rtc_wait_while_busy(void) +{ + int retries = 500; /* ~490 us is the worst case, ~250 us is best. */ + + /* first wait for the RTC to become busy. this is when it + * posts its updated seconds+msec registers to AHB side. */ + while (tegra_rtc_check_busy()) { + if (!retries--) + goto retry_failed; + udelay(1); + } + + /* now we have about 250 us to manipulate registers */ + return 0; + +retry_failed: + pr_err("RTC: write failed:retry count exceeded.\n"); + return -ETIMEDOUT; +} + +static int tegra_rtc_alarm_irq_enable(unsigned int enable) +{ + u32 status; + + /* read the original value, and OR in the flag. */ + status = readl(rtc_base + TEGRA_RTC_REG_INTR_MASK); + if (enable) + status |= TEGRA_RTC_INTR_MASK_MSEC_CDN_ALARM; /* set it */ + else + status &= ~TEGRA_RTC_INTR_MASK_MSEC_CDN_ALARM; /* clear it */ + + writel(status, rtc_base + TEGRA_RTC_REG_INTR_MASK); + + return 0; +} + +static irqreturn_t tegra_rtc_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = (struct clock_event_device *)dev_id; + u32 status; + + status = readl(rtc_base + TEGRA_RTC_REG_INTR_STATUS); + if (status) { + /* clear the interrupt masks and status on any irq. */ + tegra_rtc_wait_while_busy(); + writel(0, rtc_base + TEGRA_RTC_REG_INTR_MASK); + writel(status, rtc_base + TEGRA_RTC_REG_INTR_STATUS); + } + + if ((status & TEGRA_RTC_INTR_STATUS_MSEC_CDN_ALARM)) + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction tegra_rtc_irq = { + .name = "tegra_rtc", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_HIGH, + .handler = tegra_rtc_interrupt, + .dev_id = &tegra_clockevent, + .irq = INT_RTC, +}; + +void tegra_rtc_set_trigger(unsigned long cycles) +{ + unsigned long msec; + + /* Convert to msec */ + msec = cycles / 1000; + + if (msec) + msec = 0x80000000 | (0x0fffffff & msec); + + tegra_rtc_wait_while_busy(); + + writel(msec, rtc_base + TEGRA_RTC_REG_MSEC_CDN_ALARM0); + + tegra_rtc_wait_while_busy(); + + if (msec) + tegra_rtc_alarm_irq_enable(1); + else + tegra_rtc_alarm_irq_enable(0); +} +#endif + /* * tegra_read_persistent_clock - Return time from a persistent clock. * @@ -514,6 +635,10 @@ CLOCKSOURCE_OF_DECLARE(tegra_timer, "nvidia,tegra-nvtimer", tegra_init_timer); static void __init tegra_init_rtc(struct device_node *np) { struct clk *clk; +#if defined(CONFIG_TEGRA_LP0_IN_IDLE) + int ret = 0; +#endif + /* * rtc registers are used by read_persistent_clock, keep the rtc clock * enabled @@ -528,6 +653,21 @@ static void __init tegra_init_rtc(struct device_node *np) if (IS_ERR(clk)) clk = clk_get_sys("rtc-tegra", NULL); +#if defined(CONFIG_TEGRA_LP0_IN_IDLE) + /* clear out the hardware. */ + writel(0, rtc_base + TEGRA_RTC_REG_MSEC_CDN_ALARM0); + writel(0xffffffff, rtc_base + TEGRA_RTC_REG_INTR_STATUS); + writel(0, rtc_base + TEGRA_RTC_REG_INTR_MASK); + + ret = setup_irq(tegra_rtc_irq.irq, &tegra_rtc_irq); + if (ret) { + pr_err("Failed to register RTC IRQ: %d\n", ret); + BUG(); + } + + enable_irq_wake(tegra_rtc_irq.irq); +#endif + of_node_put(np); if (IS_ERR(clk)) diff --git a/include/linux/tegra-timer.h b/include/linux/tegra-timer.h index f580a1263530..937b5d807e52 100644 --- a/include/linux/tegra-timer.h +++ b/include/linux/tegra-timer.h @@ -45,6 +45,12 @@ void __init tegra20_init_timer(void); void __init tegra30_init_timer(void); #endif +#ifdef CONFIG_TEGRA_LP0_IN_IDLE +void tegra_rtc_set_trigger(unsigned long cycles); +#else +static inline void tegra_rtc_set_trigger(unsigned long cycles) {} +#endif + struct tegra_twd_context { u32 twd_ctrl; u32 twd_load; |