OMAP: AM437X: Add rtc_only with ddr in self-refresh support
authorKeerthy <j-keerthy@ti.com>
Fri, 2 Nov 2018 10:28:10 +0000 (15:58 +0530)
committerTero Kristo <t-kristo@ti.com>
Tue, 6 Nov 2018 13:03:47 +0000 (15:03 +0200)
During RTC-only suspend, power is lost to the wkup domain, so we need to
save and restore the state of that domain. We also need to store some
information within the RTC registers so that u-boot can do the right thing
at powerup.

The state is entered by getting the RTC to bring the pmic_power_en line low
which will instruct the PMIC to disable the appropriate power rails after
putting DDR into self-refresh mode. To bring pmic_power_en low, we need to
get an ALARM2 event. Since we are running from SRAM at that point, it means
calculating what the next second is (via ASM) and programming that into the
RTC. This patch also adds support for wake up source detection.

Signed-off-by: Keerthy <j-keerthy@ti.com>
Signed-off-by: Dave Gerlach <d-gerlach@ti.com>
arch/arm/mach-omap2/pm33xx-core.c
drivers/soc/ti/Kconfig
drivers/soc/ti/pm33xx.c
include/linux/platform_data/pm33xx.h

index f4971e4a86b26893578badccb6c0828a4c8821ab..3665ac6aca76441b5af1cfc7824aab9d4041a0a4 100644 (file)
 #include <asm/suspend.h>
 #include <linux/errno.h>
 #include <linux/platform_data/pm33xx.h>
+#include <linux/clk.h>
+#include <linux/platform_data/gpio-omap.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/wkup_m3_ipc.h>
+#include <linux/of.h>
+#include <linux/rtc.h>
 
 #include "cm33xx.h"
 #include "common.h"
@@ -38,6 +44,29 @@ static int __init am43xx_map_scu(void)
        return 0;
 }
 
+static int am33xx_check_off_mode_enable(void)
+{
+       if (enable_off_mode)
+               pr_warn("WARNING: This platform does not support off-mode, entering DeepSleep suspend.\n");
+
+       /* off mode not supported on am335x so return 0 always */
+       return 0;
+}
+
+static int am43xx_check_off_mode_enable(void)
+{
+       /*
+        * Check for am437x-sk-evm which due to HW design cannot support
+        * this mode reliably.
+        */
+       if (of_machine_is_compatible("ti,am437x-sk-evm") && enable_off_mode) {
+               pr_warn("WARNING: This platform does not support off-mode, entering DeepSleep suspend.\n");
+               return 0;
+       }
+
+       return enable_off_mode;
+}
+
 static int amx3_common_init(void)
 {
        gfx_pwrdm = pwrdm_lookup("gfx_pwrdm");
@@ -139,7 +168,9 @@ static int am43xx_suspend(unsigned int state, int (*fn)(unsigned long),
        scu_power_mode(scu_base, SCU_PM_POWEROFF);
        ret = cpu_suspend(args, fn);
        scu_power_mode(scu_base, SCU_PM_NORMAL);
-       amx3_post_suspend_common();
+
+       if (!am43xx_check_off_mode_enable())
+               amx3_post_suspend_common();
 
        return ret;
 }
@@ -161,10 +192,48 @@ void __iomem *am43xx_get_rtc_base_addr(void)
        return omap_hwmod_get_mpu_rt_va(rtc_oh);
 }
 
+static void am43xx_save_context(void)
+{
+}
+
+static void am33xx_save_context(void)
+{
+       omap_intc_save_context();
+}
+
+static void am33xx_restore_context(void)
+{
+       omap_intc_restore_context();
+}
+
+static void am43xx_restore_context(void)
+{
+       /*
+        * HACK: restore dpll_per_clkdcoldo register contents, to avoid
+        * breaking suspend-resume
+        */
+       writel_relaxed(0x0, AM33XX_L4_WK_IO_ADDRESS(0x44df2e14));
+}
+
+static void am43xx_prepare_rtc_suspend(void)
+{
+       omap_hwmod_enable(rtc_oh);
+}
+
+static void am43xx_prepare_rtc_resume(void)
+{
+       omap_hwmod_idle(rtc_oh);
+}
+
 static struct am33xx_pm_platform_data am33xx_ops = {
        .init = am33xx_suspend_init,
        .soc_suspend = am33xx_suspend,
        .get_sram_addrs = amx3_get_sram_addrs,
+       .save_context = am33xx_save_context,
+       .restore_context = am33xx_restore_context,
+       .prepare_rtc_suspend = am43xx_prepare_rtc_suspend,
+       .prepare_rtc_resume = am43xx_prepare_rtc_resume,
+       .check_off_mode_enable = am33xx_check_off_mode_enable,
        .get_rtc_base_addr = am43xx_get_rtc_base_addr,
 };
 
@@ -172,6 +241,11 @@ static struct am33xx_pm_platform_data am43xx_ops = {
        .init = am43xx_suspend_init,
        .soc_suspend = am43xx_suspend,
        .get_sram_addrs = amx3_get_sram_addrs,
+       .save_context = am43xx_save_context,
+       .restore_context = am43xx_restore_context,
+       .prepare_rtc_suspend = am43xx_prepare_rtc_suspend,
+       .prepare_rtc_resume = am43xx_prepare_rtc_resume,
+       .check_off_mode_enable = am43xx_check_off_mode_enable,
        .get_rtc_base_addr = am43xx_get_rtc_base_addr,
 };
 
index be4570baad96c0c1a1bd5c1d9eb1d0d70e4da97d..57960e92ebe03bafa01ad04ba7ca95176640e677 100644 (file)
@@ -45,11 +45,12 @@ config KEYSTONE_NAVIGATOR_DMA
 config AMX3_PM
        tristate "AMx3 Power Management"
        depends on SOC_AM33XX || SOC_AM43XX
-       depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM
+       depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM && RTC_DRV_OMAP
        help
          Enable power management on AM335x and AM437x. Required for suspend to mem
          and standby states on both AM335x and AM437x platforms and for deeper cpuidle
-         c-states on AM335x.
+         c-states on AM335x. Also required for rtc and ddr in self-refresh low
+         power mode on AM437x platforms.
 
 config WKUP_M3_IPC
        tristate "TI AMx3 Wkup-M3 IPC Driver"
index d0dab323651fc3b6c4741898669318b54a32e588..0dc1e3ccb72b9ef5490d6c43f35e47880a2ef74a 100644 (file)
@@ -6,6 +6,7 @@
  *     Vaibhav Bedia, Dave Gerlach
  */
 
+#include <linux/clk.h>
 #include <linux/cpu.h>
 #include <linux/err.h>
 #include <linux/genalloc.h>
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/module.h>
+#include <linux/nvmem-consumer.h>
 #include <linux/of.h>
 #include <linux/platform_data/pm33xx.h>
 #include <linux/platform_device.h>
+#include <linux/rtc.h>
 #include <linux/sizes.h>
 #include <linux/sram.h>
 #include <linux/suspend.h>
 #define AMX3_PM_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \
                                         (unsigned long)pm_sram->do_wfi)
 
+#define RTC_SCRATCH_RESUME_REG 0
+#define RTC_SCRATCH_MAGIC_REG  1
+#define RTC_REG_BOOT_MAGIC     0x8cd0 /* RTC */
+#define GIC_INT_SET_PENDING_BASE 0x200
+#define AM43XX_GIC_DIST_BASE   0x48241000
+
+static u32 rtc_magic_val;
+
 static int (*am33xx_do_wfi_sram)(unsigned long unused);
 static phys_addr_t am33xx_do_wfi_sram_phys;
 
 static struct gen_pool *sram_pool, *sram_pool_data;
 static unsigned long ocmcram_location, ocmcram_location_data;
 
+static struct rtc_device *omap_rtc;
+static void __iomem *gic_dist_base;
+
 static struct am33xx_pm_platform_data *pm_ops;
 static struct am33xx_pm_sram_addr *pm_sram;
 
 static struct device *pm33xx_dev;
 static struct wkup_m3_ipc *m3_ipc;
 
+#ifdef CONFIG_SUSPEND
+
+static int rtc_only_idle;
+static int retrigger_irq;
+
 static unsigned long suspend_wfi_flags;
 
+static struct wkup_m3_wakeup_src wakeup_src = {.irq_nr = 0,
+       .src = "Unknown",
+};
+
+static struct wkup_m3_wakeup_src rtc_alarm_wakeup = {
+       .irq_nr = 108, .src = "RTC Alarm",
+};
+
+static struct wkup_m3_wakeup_src rtc_ext_wakeup = {
+       .irq_nr = 0, .src = "Ext wakeup",
+};
+
+#endif
+
 static u32 sram_suspend_address(unsigned long addr)
 {
        return ((unsigned long)am33xx_do_wfi_sram +
                AMX3_PM_SRAM_SYMBOL_OFFSET(addr));
 }
 
+static int am33xx_push_sram_idle(void)
+{
+       struct am33xx_pm_ro_sram_data ro_sram_data;
+       int ret;
+       u32 table_addr, ro_data_addr;
+       void *copy_addr;
+
+       ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data;
+       ro_sram_data.amx3_pm_sram_data_phys =
+               gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data);
+       ro_sram_data.rtc_base_virt = pm_ops->get_rtc_base_addr();
+
+       /* Save physical address to calculate resume offset during pm init */
+       am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool,
+                                                       ocmcram_location);
+
+       am33xx_do_wfi_sram = sram_exec_copy(sram_pool, (void *)ocmcram_location,
+                                           pm_sram->do_wfi,
+                                           *pm_sram->do_wfi_sz);
+       if (!am33xx_do_wfi_sram) {
+               dev_err(pm33xx_dev,
+                       "PM: %s: am33xx_do_wfi copy to sram failed\n",
+                       __func__);
+               return -ENODEV;
+       }
+
+       table_addr =
+               sram_suspend_address((unsigned long)pm_sram->emif_sram_table);
+       ret = ti_emif_copy_pm_function_table(sram_pool, (void *)table_addr);
+       if (ret) {
+               dev_dbg(pm33xx_dev,
+                       "PM: %s: EMIF function copy failed\n", __func__);
+               return -EPROBE_DEFER;
+       }
+
+       ro_data_addr =
+               sram_suspend_address((unsigned long)pm_sram->ro_sram_data);
+       copy_addr = sram_exec_copy(sram_pool, (void *)ro_data_addr,
+                                  &ro_sram_data,
+                                  sizeof(ro_sram_data));
+       if (!copy_addr) {
+               dev_err(pm33xx_dev,
+                       "PM: %s: ro_sram_data copy to sram failed\n",
+                       __func__);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int __init am43xx_map_gic(void)
+{
+       gic_dist_base = ioremap(AM43XX_GIC_DIST_BASE, SZ_4K);
+
+       if (!gic_dist_base)
+               return -ENOMEM;
+
+       return 0;
+}
+
 #ifdef CONFIG_SUSPEND
+
+static int rtc_only_idle;
+static int retrigger_irq;
+
+struct wkup_m3_wakeup_src rtc_wake_src(void)
+{
+       u32 i;
+
+       i = __raw_readl(pm_ops->get_rtc_base_addr() + 0x44) & 0x40;
+
+       if (i) {
+               retrigger_irq = rtc_alarm_wakeup.irq_nr;
+               return rtc_alarm_wakeup;
+       }
+
+       retrigger_irq = rtc_ext_wakeup.irq_nr;
+
+       return rtc_ext_wakeup;
+}
+
+int am33xx_rtc_only_idle(unsigned long wfi_flags)
+{
+       rtc_power_off_program(omap_rtc);
+       am33xx_do_wfi_sram(wfi_flags);
+       return 0;
+}
+
 static int am33xx_pm_suspend(suspend_state_t suspend_state)
 {
        int i, ret = 0;
 
-       ret = pm_ops->soc_suspend((unsigned long)suspend_state,
-                                 am33xx_do_wfi_sram, suspend_wfi_flags);
+       if (suspend_state == PM_SUSPEND_MEM &&
+           pm_ops->check_off_mode_enable()) {
+               pm_ops->prepare_rtc_suspend();
+               pm_ops->save_context();
+               suspend_wfi_flags |= WFI_FLAG_RTC_ONLY;
+               clk_save_context();
+               ret = pm_ops->soc_suspend(suspend_state, am33xx_rtc_only_idle,
+                                         suspend_wfi_flags);
+
+               suspend_wfi_flags &= ~WFI_FLAG_RTC_ONLY;
+
+               if (!ret) {
+                       clk_restore_context();
+                       pm_ops->restore_context();
+                       m3_ipc->ops->set_rtc_only(m3_ipc);
+                       am33xx_push_sram_idle();
+               }
+       } else {
+               ret = pm_ops->soc_suspend(suspend_state, am33xx_do_wfi_sram,
+                                         suspend_wfi_flags);
+       }
 
        if (ret) {
                dev_err(pm33xx_dev, "PM: Kernel suspend failure\n");
@@ -77,8 +216,20 @@ static int am33xx_pm_suspend(suspend_state_t suspend_state)
                                "PM: CM3 returned unknown result = %d\n", i);
                        ret = -1;
                }
+
+               /* print the wakeup reason */
+               if (rtc_only_idle) {
+                       wakeup_src = rtc_wake_src();
+                       pr_info("PM: Wakeup source %s\n", wakeup_src.src);
+               } else {
+                       pr_info("PM: Wakeup source %s\n",
+                               m3_ipc->ops->request_wake_src(m3_ipc));
+               }
        }
 
+       if (suspend_state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable())
+               pm_ops->prepare_rtc_resume();
+
        return ret;
 }
 
@@ -102,6 +253,14 @@ static int am33xx_pm_begin(suspend_state_t state)
 {
        int ret = -EINVAL;
 
+       if (state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) {
+               nvmem_device_write(omap_rtc->nvmem, RTC_SCRATCH_MAGIC_REG * 4,
+                                  4, (void *)&rtc_magic_val);
+               rtc_only_idle = 1;
+       } else {
+               rtc_only_idle = 0;
+       }
+
        switch (state) {
        case PM_SUSPEND_MEM:
                ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_DEEPSLEEP);
@@ -116,7 +275,27 @@ static int am33xx_pm_begin(suspend_state_t state)
 
 static void am33xx_pm_end(void)
 {
+       u32 val = 0;
+
        m3_ipc->ops->finish_low_power(m3_ipc);
+       if (rtc_only_idle) {
+               if (retrigger_irq)
+                       /*
+                        * 32 bits of Interrupt Set-Pending correspond to 32
+                        * 32 interupts. Compute the bit offset of the
+                        * Interrupt and set that particular bit
+                        * Compute the register offset by dividing interrupt
+                        * number by 32 and mutiplying by 4
+                        */
+                       writel_relaxed(1 << (retrigger_irq & 31),
+                                      gic_dist_base + GIC_INT_SET_PENDING_BASE
+                                      + retrigger_irq / 32 * 4);
+                       nvmem_device_write(omap_rtc->nvmem,
+                                          RTC_SCRATCH_MAGIC_REG * 4,
+                                          4, (void *)&val);
+       }
+
+       rtc_only_idle = 0;
 }
 
 static int am33xx_pm_valid(suspend_state_t state)
@@ -219,51 +398,32 @@ mpu_put_node:
        return ret;
 }
 
-static int am33xx_push_sram_idle(void)
+static int am33xx_pm_rtc_setup(void)
 {
-       struct am33xx_pm_ro_sram_data ro_sram_data;
-       int ret;
-       u32 table_addr, ro_data_addr;
-       void *copy_addr;
+       struct device_node *np;
+       unsigned long val = 0;
 
-       ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data;
-       ro_sram_data.amx3_pm_sram_data_phys =
-               gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data);
-       ro_sram_data.rtc_base_virt = pm_ops->get_rtc_base_addr();
+       np = of_find_node_by_name(NULL, "rtc");
 
-       /* Save physical address to calculate resume offset during pm init */
-       am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool,
-                                                       ocmcram_location);
-
-       am33xx_do_wfi_sram = sram_exec_copy(sram_pool, (void *)ocmcram_location,
-                                           pm_sram->do_wfi,
-                                           *pm_sram->do_wfi_sz);
-       if (!am33xx_do_wfi_sram) {
-               dev_err(pm33xx_dev,
-                       "PM: %s: am33xx_do_wfi copy to sram failed\n",
-                       __func__);
-               return -ENODEV;
-       }
+       if (of_device_is_available(np)) {
+               omap_rtc = rtc_class_open("rtc0");
+               if (!omap_rtc) {
+                       pr_warn("PM: rtc0 not available");
+                       return -EPROBE_DEFER;
+               }
 
-       table_addr =
-               sram_suspend_address((unsigned long)pm_sram->emif_sram_table);
-       ret = ti_emif_copy_pm_function_table(sram_pool, (void *)table_addr);
-       if (ret) {
-               dev_dbg(pm33xx_dev,
-                       "PM: %s: EMIF function copy failed\n", __func__);
-               return -EPROBE_DEFER;
-       }
+               nvmem_device_read(omap_rtc->nvmem, RTC_SCRATCH_MAGIC_REG * 4,
+                                 4, (void *)&rtc_magic_val);
+               if ((rtc_magic_val & 0xffff) != RTC_REG_BOOT_MAGIC)
+                       pr_warn("PM: bootloader does not support rtc-only!\n");
 
-       ro_data_addr =
-               sram_suspend_address((unsigned long)pm_sram->ro_sram_data);
-       copy_addr = sram_exec_copy(sram_pool, (void *)ro_data_addr,
-                                  &ro_sram_data,
-                                  sizeof(ro_sram_data));
-       if (!copy_addr) {
-               dev_err(pm33xx_dev,
-                       "PM: %s: ro_sram_data copy to sram failed\n",
-                       __func__);
-               return -ENODEV;
+               nvmem_device_write(omap_rtc->nvmem, RTC_SCRATCH_MAGIC_REG * 4,
+                                  4, (void *)&val);
+               val = pm_sram->resume_address;
+               nvmem_device_write(omap_rtc->nvmem, RTC_SCRATCH_RESUME_REG * 4,
+                                  4, (void *)&val);
+       } else {
+               pr_warn("PM: no-rtc available, rtc-only mode disabled.\n");
        }
 
        return 0;
@@ -284,34 +444,42 @@ static int am33xx_pm_probe(struct platform_device *pdev)
                return -ENODEV;
        }
 
+       ret = am43xx_map_gic();
+       if (ret) {
+               pr_err("PM: Could not ioremap GIC base\n");
+               return ret;
+       }
+
        pm_sram = pm_ops->get_sram_addrs();
        if (!pm_sram) {
                dev_err(dev, "PM: Cannot get PM asm function addresses!!\n");
                return -ENODEV;
        }
 
+       m3_ipc = wkup_m3_ipc_get();
+       if (!m3_ipc) {
+               pr_err("PM: Cannot get wkup_m3_ipc handle\n");
+               return -EPROBE_DEFER;
+       }
+
        pm33xx_dev = dev;
 
        ret = am33xx_pm_alloc_sram();
        if (ret)
                return ret;
 
-       ret = am33xx_push_sram_idle();
+       ret = am33xx_pm_rtc_setup();
        if (ret)
                goto err_free_sram;
 
-       m3_ipc = wkup_m3_ipc_get();
-       if (!m3_ipc) {
-               dev_dbg(dev, "PM: Cannot get wkup_m3_ipc handle\n");
-               ret = -EPROBE_DEFER;
+       ret = am33xx_push_sram_idle();
+       if (ret)
                goto err_free_sram;
-       }
 
        am33xx_pm_set_ipc_ops();
 
 #ifdef CONFIG_SUSPEND
        suspend_set_ops(&am33xx_pm_ops);
-#endif /* CONFIG_SUSPEND */
 
        /*
         * For a system suspend we must flush the caches, we want
@@ -323,6 +491,7 @@ static int am33xx_pm_probe(struct platform_device *pdev)
        suspend_wfi_flags |= WFI_FLAG_SELF_REFRESH;
        suspend_wfi_flags |= WFI_FLAG_SAVE_EMIF;
        suspend_wfi_flags |= WFI_FLAG_WAKE_M3;
+#endif /* CONFIG_SUSPEND */
 
        ret = pm_ops->init();
        if (ret) {
index fbf5ed73c7cca47c9afa3ff66134af8b6ec6429d..dd5971937a643c0703563a6ba4fa353237b6696c 100644 (file)
@@ -51,6 +51,11 @@ struct am33xx_pm_platform_data {
                               unsigned long args);
        struct  am33xx_pm_sram_addr *(*get_sram_addrs)(void);
        void __iomem *(*get_rtc_base_addr)(void);
+       void (*save_context)(void);
+       void (*restore_context)(void);
+       void (*prepare_rtc_suspend)(void);
+       void (*prepare_rtc_resume)(void);
+       int (*check_off_mode_enable)(void);
 };
 
 struct am33xx_pm_sram_data {