summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrashant Gaikwad <pgaikwad@nvidia.com>2013-10-23 17:16:56 +0530
committerBharat Nihalani <bnihalani@nvidia.com>2013-11-04 16:54:36 -0800
commitee6426e675d62ad29348eb8a84c2b59dc47f4515 (patch)
tree633c453faab93aac09f9476eda39fbf1fc4f4dd4
parent8a2d60965962ddbab4c41dcf3b9015d7e4e21f7f (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.c140
-rw-r--r--include/linux/tegra-timer.h6
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;