clocksource: tegra: Add userspace alarm control
Juha-Matti Tilli [Wed, 6 Aug 2014 12:35:48 +0000 (15:35 +0300)]
Add code to control rtc alarms from userspace.

Change-Id: I6e218d4d4fd6263f0e896e1827bad5385e9626fb
Signed-off-by: Juha-Matti Tilli <jtilli@nvidia.com>
Reviewed-on: http://git-master/r/494217
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Prashant Gaikwad <pgaikwad@nvidia.com>
Tested-by: Prashant Gaikwad <pgaikwad@nvidia.com>
Reviewed-by: Sivaram Nair <sivaramn@nvidia.com>

drivers/clocksource/tegra-rtc.c

index ae497ab..eb8f1c6 100644 (file)
 
 #include <linux/clk.h>
 #include <linux/clocksource.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
+#include <linux/syscore_ops.h>
 #include <linux/tegra-timer.h>
 #include <linux/tick.h>
+#include <linux/rtc.h>
 #include <asm/mach/time.h>
 
 static void __iomem *rtc_base;
@@ -33,6 +36,11 @@ 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_SHADOW_SECONDS            0x00c
+#define TEGRA_RTC_REG_MILLI_SECONDS             0x010
+#define TEGRA_RTC_REG_SECONDS_ALARM0            0x014
+#define TEGRA_RTC_REG_SECONDS_ALARM1            0x018
+#define TEGRA_RTC_REG_MILLI_SECONDS_ALARM0      0x01c
 #define TEGRA_RTC_REG_MSEC_CDN_ALARM0          0x024
 #define TEGRA_RTC_REG_INTR_MASK                        0x028
 /* write 1 bits to clear status bits */
@@ -40,9 +48,17 @@ static void __iomem *rtc_base;
 
 /* bits in INTR_MASK */
 #define TEGRA_RTC_INTR_MASK_MSEC_CDN_ALARM     (1<<4)
+#define TEGRA_RTC_INTR_MASK_SEC_CDN_ALARM       (1<<3)
+#define TEGRA_RTC_INTR_MASK_MSEC_ALARM          (1<<2)
+#define TEGRA_RTC_INTR_MASK_SEC_ALARM1          (1<<1)
+#define TEGRA_RTC_INTR_MASK_SEC_ALARM0          (1<<0)
 
 /* bits in INTR_STATUS */
 #define TEGRA_RTC_INTR_STATUS_MSEC_CDN_ALARM   (1<<4)
+#define TEGRA_RTC_INTR_STATUS_SEC_CDN_ALARM     (1<<3)
+#define TEGRA_RTC_INTR_STATUS_MSEC_ALARM        (1<<2)
+#define TEGRA_RTC_INTR_STATUS_SEC_ALARM1        (1<<1)
+#define TEGRA_RTC_INTR_STATUS_SEC_ALARM0        (1<<0)
 
 static u64 persistent_ms, last_persistent_ms;
 static struct timespec persistent_ts;
@@ -60,7 +76,6 @@ 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.
@@ -100,25 +115,52 @@ retry_failed:
        return -ETIMEDOUT;
 }
 
-static int tegra_rtc_alarm_irq_enable(unsigned int enable)
+static int tegra_rtc_sec_alarm0_irq_enable(unsigned int enabled)
 {
-       u32 status;
+       unsigned int mask;
 
-       /* 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 */
+       tegra_rtc_wait_while_busy();
+       mask = readl(rtc_base + TEGRA_RTC_REG_INTR_MASK);
+       if (enabled)
+               mask |= TEGRA_RTC_INTR_MASK_SEC_ALARM0;
        else
-               status &= ~TEGRA_RTC_INTR_MASK_MSEC_CDN_ALARM; /* clear it */
-
-       writel(status, rtc_base + TEGRA_RTC_REG_INTR_MASK);
+               mask &= ~TEGRA_RTC_INTR_MASK_SEC_ALARM0;
+       writel(mask, rtc_base + TEGRA_RTC_REG_INTR_MASK);
 
        return 0;
 }
 
+void tegra_rtc_set_alarm(unsigned int period)
+{
+       unsigned int sec;
+       unsigned int msec;
+       unsigned int target;
+
+       tegra_rtc_wait_while_busy();
+       msec = readl(rtc_base + TEGRA_RTC_REG_MILLI_SECONDS);
+       sec = readl(rtc_base + TEGRA_RTC_REG_SHADOW_SECONDS);
+
+       target = sec + period;
+       writel(target, rtc_base + TEGRA_RTC_REG_SECONDS_ALARM0);
+
+       trace_printk("%s: now %lu, target %lu\n", __func__,
+                       sec * MSEC_PER_SEC + msec, target * MSEC_PER_SEC);
+       pr_info("%s: alarm set to fire after %u sec\n", __func__, period);
+
+       tegra_rtc_sec_alarm0_irq_enable(1);
+}
+
+
 static irqreturn_t tegra_rtc_interrupt(int irq, void *dev_id)
 {
        u32 status;
+       unsigned int sec;
+       unsigned int msec;
+
+       tegra_rtc_sec_alarm0_irq_enable(0);
+       msec = readl(rtc_base + TEGRA_RTC_REG_MILLI_SECONDS);
+       sec = readl(rtc_base + TEGRA_RTC_REG_SHADOW_SECONDS);
+       trace_printk("%s: irq time %lu\n", __func__, sec * MSEC_PER_SEC + msec);
 
        status = readl(rtc_base + TEGRA_RTC_REG_INTR_STATUS);
        if (status) {
@@ -138,6 +180,22 @@ static struct irqaction tegra_rtc_irq = {
        .irq            = INT_RTC,
 };
 
+#ifdef CONFIG_TEGRA_LP0_IN_IDLE
+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;
+}
 void tegra_rtc_set_trigger(unsigned long cycles)
 {
        unsigned long msec;
@@ -161,6 +219,53 @@ void tegra_rtc_set_trigger(unsigned long cycles)
 }
 #endif
 
+static unsigned int alarm_period;
+
+static int tegra_debug_pm_suspend(void)
+{
+       if (alarm_period)
+               tegra_rtc_set_alarm(alarm_period);
+       return 0;
+}
+
+static void tegra_debug_pm_resume(void)
+{
+}
+
+static struct syscore_ops tegra_debug_pm_syscore_ops = {
+       .suspend = tegra_debug_pm_suspend,
+       .resume = tegra_debug_pm_resume,
+};
+
+static int alarm_set(void *data, u64 val)
+{
+       alarm_period = val;
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(alarm_fops, NULL, alarm_set, "%llu\n");
+
+static struct dentry *pm_dentry;
+
+static int debugfs_init(void)
+{
+       struct dentry *root = NULL;
+       root = debugfs_create_dir("tegra-rtc", NULL);
+
+       if (!root)
+               return -ENOMEM;
+
+       if (!debugfs_create_file("alarm", S_IWUSR, root, NULL, &alarm_fops))
+               goto err_out;
+
+       pm_dentry = root;
+       return 0;
+
+err_out:
+       debugfs_remove_recursive(root);
+       return -ENOMEM;
+}
+
 /*
  * tegra_read_persistent_clock -  Return time from a persistent clock.
  *
@@ -187,9 +292,7 @@ void tegra_read_persistent_clock(struct timespec *ts)
 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
@@ -205,7 +308,6 @@ 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);
@@ -218,7 +320,6 @@ static void __init tegra_init_rtc(struct device_node *np)
        }
 
        enable_irq_wake(tegra_rtc_irq.irq);
-#endif
 
        of_node_put(np);
 
@@ -229,4 +330,25 @@ static void __init tegra_init_rtc(struct device_node *np)
 
        register_persistent_clock(NULL, tegra_read_persistent_clock);
 }
+
+static int __init tegra_rtc_debugfs_init(void)
+{
+       int dfs_init_ret;
+       register_syscore_ops(&tegra_debug_pm_syscore_ops);
+       dfs_init_ret = debugfs_init();
+       if (dfs_init_ret) {
+               pr_err("%s: Can't init debugfs", __func__);
+               BUG();
+       }
+       return 0;
+}
+static void __init tegra_rtc_debugfs_exit(void)
+{
+       unregister_syscore_ops(&tegra_debug_pm_syscore_ops);
+       debugfs_remove_recursive(pm_dentry);
+}
+
+module_init(tegra_rtc_debugfs_init);
+module_exit(tegra_rtc_debugfs_exit);
+
 CLOCKSOURCE_OF_DECLARE(tegra_rtc, "nvidia,tegra-rtc", tegra_init_rtc);