summaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorRan Wang <ran.wang_1@nxp.com>2018-03-28 16:32:08 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2021-11-02 16:23:17 +0800
commit112f8a4cc85344e5f885cd5e9aa589d1979c486c (patch)
tree8400242056f980f4d06de99b755205cb7d80db60 /arch/powerpc/sysdev
parent12d4c3a3614df5da7403f2030d94b433da0cd1ff (diff)
powerpc/pm: add sleep and deep sleep on QorIQ SoCs
In sleep mode, the clocks of CPU core and unused IP blocks are turned off (IP blocks allowed to wake up system will running). Some QorIQ SoCs like MPC8536, P1022 and T104x, have deep sleep PM mode in addtion to the sleep PM mode. While in deep sleep mode, additionally, the power supply is removed from CPU core and most IP blocks. Only the blocks needed to wake up the chip out of deep sleep are ON. This feature supports 32-bit and 36-bit address space. The sleep mode is equal to the Standby state in Linux. The deep sleep mode is equal to the Suspend-to-RAM state of Linux Power Management. Command to enter sleep mode. echo standby > /sys/power/state Command to enter deep sleep mode. echo mem > /sys/power/state Signed-off-by: Dave Liu <daveliu@freescale.com> Signed-off-by: Li Yang <leoli@freescale.com> Signed-off-by: Jin Qing <b24347@freescale.com> Signed-off-by: Jerry Huang <Chang-Ming.Huang@freescale.com> Signed-off-by: Ramneek Mehresh <ramneek.mehresh@freescale.com> Signed-off-by: Zhao Chenhui <chenhui.zhao@freescale.com> Signed-off-by: Wang Dongsheng <dongsheng.wang@freescale.com> Signed-off-by: Tang Yuantian <Yuantian.Tang@freescale.com> Signed-off-by: Xie Xiaobo <X.Xie@freescale.com> Signed-off-by: Zhao Qiang <B45475@freescale.com> Signed-off-by: Shengzhou Liu <Shengzhou.Liu@freescale.com> Signed-off-by: Ran Wang <ran.wang_1@nxp.com>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/fsl_pmc.c176
-rw-r--r--arch/powerpc/sysdev/fsl_soc.c31
-rw-r--r--arch/powerpc/sysdev/fsl_soc.h18
3 files changed, 206 insertions, 19 deletions
diff --git a/arch/powerpc/sysdev/fsl_pmc.c b/arch/powerpc/sysdev/fsl_pmc.c
index 76896de970ca..e72c03d32702 100644
--- a/arch/powerpc/sysdev/fsl_pmc.c
+++ b/arch/powerpc/sysdev/fsl_pmc.c
@@ -16,54 +16,192 @@
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
+#include <linux/pm.h>
+#include <asm/cacheflush.h>
+
+#include <sysdev/fsl_soc.h>
+#include <asm/switch_to.h>
+#include <asm/fsl_pm.h>
struct pmc_regs {
__be32 devdisr;
__be32 devdisr2;
- __be32 :32;
- __be32 :32;
- __be32 pmcsr;
-#define PMCSR_SLP (1 << 17)
+ __be32 res1;
+ __be32 res2;
+ __be32 powmgtcsr;
+#define POWMGTCSR_SLP 0x00020000
+#define POWMGTCSR_DPSLP 0x00100000
+#define POWMGTCSR_LOSSLESS 0x00400000
+ __be32 res3[2];
+ __be32 pmcdr;
};
-static struct device *pmc_dev;
static struct pmc_regs __iomem *pmc_regs;
+static unsigned int pmc_flag;
+
+#define PMC_SLEEP 0x1
+#define PMC_DEEP_SLEEP 0x2
+#define PMC_LOSSLESS 0x4
+
+/**
+ * mpc85xx_pmc_set_wake - enable devices as wakeup event source
+ * @dev: a device affected
+ * @enable: True to enable event generation; false to disable
+ *
+ * This enables the device as a wakeup event source, or disables it.
+ *
+ * RETURN VALUE:
+ * 0 is returned on success.
+ * -EINVAL is returned if device is not supposed to wake up the system.
+ * -ENODEV is returned if PMC is unavailable.
+ * Error code depending on the platform is returned if both the platform and
+ * the native mechanism fail to enable the generation of wake-up events
+ */
+int mpc85xx_pmc_set_wake(struct device *dev, bool enable)
+{
+ int ret = 0;
+ struct device_node *clk_np;
+ const u32 *prop;
+ u32 pmcdr_mask;
+
+ if (!pmc_regs) {
+ dev_err(dev, "%s: PMC is unavailable\n", __func__);
+ return -ENODEV;
+ }
+
+ if (enable && !device_may_wakeup(dev))
+ return -EINVAL;
+
+ clk_np = of_parse_phandle(dev->of_node, "fsl,pmc-handle", 0);
+ if (!clk_np)
+ return -EINVAL;
+
+ prop = of_get_property(clk_np, "fsl,pmcdr-mask", NULL);
+ if (!prop) {
+ ret = -EINVAL;
+ goto out;
+ }
+ pmcdr_mask = be32_to_cpup(prop);
+
+ if (enable)
+ /* clear to enable clock in low power mode */
+ clrbits32(&pmc_regs->pmcdr, pmcdr_mask);
+ else
+ setbits32(&pmc_regs->pmcdr, pmcdr_mask);
+
+out:
+ of_node_put(clk_np);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_wake);
+
+/**
+ * mpc85xx_pmc_set_lossless_ethernet - enable lossless ethernet
+ * in (deep) sleep mode
+ * @enable: True to enable event generation; false to disable
+ */
+void mpc85xx_pmc_set_lossless_ethernet(int enable)
+{
+ if (pmc_flag & PMC_LOSSLESS) {
+ if (enable)
+ setbits32(&pmc_regs->powmgtcsr, POWMGTCSR_LOSSLESS);
+ else
+ clrbits32(&pmc_regs->powmgtcsr, POWMGTCSR_LOSSLESS);
+ }
+}
+EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_lossless_ethernet);
static int pmc_suspend_enter(suspend_state_t state)
{
- int ret;
+ int ret = 0;
+ int result;
+
+ switch (state) {
+#ifdef CONFIG_PPC_85xx
+ case PM_SUSPEND_MEM:
+#ifdef CONFIG_SPE
+ enable_kernel_spe();
+#endif
+#ifdef CONFIG_PPC_FPU
+ enable_kernel_fp();
+#endif
+
+ pr_debug("%s: Entering deep sleep\n", __func__);
+
+ local_irq_disable();
+ mpc85xx_enter_deep_sleep(get_immrbase(), POWMGTCSR_DPSLP);
+
+ pr_debug("%s: Resumed from deep sleep\n", __func__);
+ break;
+#endif
- setbits32(&pmc_regs->pmcsr, PMCSR_SLP);
- /* At this point, the CPU is asleep. */
+ case PM_SUSPEND_STANDBY:
+ local_irq_disable();
+ flush_dcache_L1();
- /* Upon resume, wait for SLP bit to be clear. */
- ret = spin_event_timeout((in_be32(&pmc_regs->pmcsr) & PMCSR_SLP) == 0,
- 10000, 10) ? 0 : -ETIMEDOUT;
- if (ret)
- dev_err(pmc_dev, "tired waiting for SLP bit to clear\n");
+ setbits32(&pmc_regs->powmgtcsr, POWMGTCSR_SLP);
+ /* At this point, the CPU is asleep. */
+
+ /* Upon resume, wait for SLP bit to be clear. */
+ result = spin_event_timeout(
+ (in_be32(&pmc_regs->powmgtcsr) & POWMGTCSR_SLP) == 0,
+ 10000, 10);
+ if (!result) {
+ pr_err("%s: timeout waiting for SLP bit "
+ "to be cleared\n", __func__);
+ ret = -ETIMEDOUT;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+
+ }
return ret;
}
static int pmc_suspend_valid(suspend_state_t state)
{
- if (state != PM_SUSPEND_STANDBY)
- return 0;
- return 1;
+ set_pm_suspend_state(state);
+
+ if (((pmc_flag & PMC_SLEEP) && (state == PM_SUSPEND_STANDBY)) ||
+ ((pmc_flag & PMC_DEEP_SLEEP) && (state == PM_SUSPEND_MEM)))
+ return 1;
+
+ set_pm_suspend_state(PM_SUSPEND_ON);
+ return 0;
+}
+
+static void pmc_suspend_end(void)
+{
+ set_pm_suspend_state(PM_SUSPEND_ON);
}
static const struct platform_suspend_ops pmc_suspend_ops = {
.valid = pmc_suspend_valid,
.enter = pmc_suspend_enter,
+ .end = pmc_suspend_end,
};
-static int pmc_probe(struct platform_device *ofdev)
+static int pmc_probe(struct platform_device *pdev)
{
- pmc_regs = of_iomap(ofdev->dev.of_node, 0);
+ struct device_node *np = pdev->dev.of_node;
+
+ pmc_regs = of_iomap(np, 0);
if (!pmc_regs)
return -ENOMEM;
- pmc_dev = &ofdev->dev;
+ pmc_flag = PMC_SLEEP;
+ if (of_device_is_compatible(np, "fsl,mpc8536-pmc"))
+ pmc_flag |= PMC_DEEP_SLEEP;
+
+ if (of_device_is_compatible(np, "fsl,p1022-pmc"))
+ pmc_flag |= PMC_DEEP_SLEEP | PMC_LOSSLESS;
+
suspend_set_ops(&pmc_suspend_ops);
+ set_pm_suspend_state(PM_SUSPEND_ON);
+
+ pr_info("Freescale PMC driver\n");
return 0;
}
diff --git a/arch/powerpc/sysdev/fsl_soc.c b/arch/powerpc/sysdev/fsl_soc.c
index 90ad16161604..21af6d5c075a 100644
--- a/arch/powerpc/sysdev/fsl_soc.c
+++ b/arch/powerpc/sysdev/fsl_soc.c
@@ -42,6 +42,37 @@ extern void init_fcc_ioports(struct fs_platform_info*);
extern void init_fec_ioports(struct fs_platform_info*);
extern void init_smc_ioports(struct fs_uart_platform_info*);
static phys_addr_t immrbase = -1;
+static phys_addr_t dcsrbase = -1;
+
+phys_addr_t get_dcsrbase(void)
+{
+ struct device_node *np;
+ const __be32 *prop;
+ int size;
+ u32 naddr;
+
+ if (dcsrbase != -1)
+ return dcsrbase;
+
+ np = of_find_compatible_node(NULL, NULL, "fsl,dcsr");
+ if (!np)
+ return -1;
+
+ prop = of_get_property(np, "#address-cells", &size);
+ if (prop && size == 4)
+ naddr = be32_to_cpup(prop);
+ else
+ naddr = 2;
+
+ prop = of_get_property(np, "ranges", NULL);
+ if (prop)
+ dcsrbase = of_translate_address(np, prop + naddr);
+
+ of_node_put(np);
+
+ return dcsrbase;
+}
+EXPORT_SYMBOL(get_dcsrbase);
phys_addr_t get_immrbase(void)
{
diff --git a/arch/powerpc/sysdev/fsl_soc.h b/arch/powerpc/sysdev/fsl_soc.h
index db11b06eb38f..674ed672bd62 100644
--- a/arch/powerpc/sysdev/fsl_soc.h
+++ b/arch/powerpc/sysdev/fsl_soc.h
@@ -7,6 +7,7 @@
struct spi_device;
+extern phys_addr_t get_dcsrbase(void);
extern phys_addr_t get_immrbase(void);
#if defined(CONFIG_CPM) || defined(CONFIG_QUICC_ENGINE)
extern u32 get_brgfreq(void);
@@ -44,5 +45,22 @@ extern struct platform_diu_data_ops diu_ops;
void __noreturn fsl_hv_restart(char *cmd);
void __noreturn fsl_hv_halt(void);
+/*
+ * Cast the ccsrbar to 64-bit parameter so that the assembly
+ * code can be compatible with both 32-bit & 36-bit.
+ */
+extern void mpc85xx_enter_deep_sleep(u64 ccsrbar, u32 powmgtreq);
+
+#ifdef CONFIG_FSL_PMC
+int mpc85xx_pmc_set_wake(struct device *dev, bool enable);
+void mpc85xx_pmc_set_lossless_ethernet(int enable);
+#else
+static inline int mpc85xx_pmc_set_wake(struct device *dev, bool enable)
+{
+ return -ENODEV;
+}
+#define mpc85xx_pmc_set_lossless_ethernet(enable) do { } while (0)
+#endif
+
#endif
#endif