diff options
Diffstat (limited to 'arch/powerpc/sysdev/fsl_pmc.c')
-rw-r--r-- | arch/powerpc/sysdev/fsl_pmc.c | 162 |
1 files changed, 144 insertions, 18 deletions
diff --git a/arch/powerpc/sysdev/fsl_pmc.c b/arch/powerpc/sysdev/fsl_pmc.c index 592a0f8..61dba33 100644 --- a/arch/powerpc/sysdev/fsl_pmc.c +++ b/arch/powerpc/sysdev/fsl_pmc.c @@ -2,6 +2,7 @@ * Suspend/resume support * * Copyright 2009 MontaVista Software, Inc. + * Copyright 2010-2012 Freescale Semiconductor Inc. * * Author: Anton Vorontsov <avorontsov@ru.mvista.com> * @@ -19,39 +20,154 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/of_platform.h> +#include <linux/pm.h> +#include <asm/switch_to.h> +#include <asm/cacheflush.h> + +#include <sysdev/fsl_soc.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 + enable_kernel_fp(); + + 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) + if (((pmc_flag & PMC_SLEEP) && (state == PM_SUSPEND_STANDBY)) || + ((pmc_flag & PMC_DEEP_SLEEP) && (state == PM_SUSPEND_MEM))) + return 1; + else return 0; - return 1; } static const struct platform_suspend_ops pmc_suspend_ops = { @@ -59,14 +175,24 @@ static const struct platform_suspend_ops pmc_suspend_ops = { .enter = pmc_suspend_enter, }; -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); + + pr_info("Freescale PMC driver\n"); return 0; } |