summaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorChenhui Zhao <chenhui.zhao@freescale.com>2014-02-17 09:58:56 (GMT)
committerJose Rivera <German.Rivera@freescale.com>2014-04-05 21:52:55 (GMT)
commitd92b336def412d29863f0086d3edffb05df2ebc5 (patch)
tree4e62bddc7809a3b85330e2c731934a12e95a0bae /arch/powerpc/sysdev
parent67d36dfe1a4095561287da8ce2bafd0a62a4dddc (diff)
downloadlinux-fsl-qoriq-d92b336def412d29863f0086d3edffb05df2ebc5.tar.xz
powerpc/rcpm: add RCPM driver
There is a RCPM (Run Control/Power Management) in Freescale QorIQ series processors. The device performs tasks associated with device run control and power management. The driver implements some features: mask/unmask irq, enter/exit low power states, freeze time base, etc. There are two versions of register map in RCPM, which is specified by the compatible entry in the RCPM node of device tree. Change-Id: I113211311c7241df95f067103d0ed81ada26d2ed Signed-off-by: Chenhui Zhao <chenhui.zhao@freescale.com> Reviewed-on: http://git.am.freescale.net:8181/10706 Tested-by: Review Code-CDREVIEW <CDREVIEW@freescale.com> Reviewed-by: Yang Li <LeoLi@freescale.com> Reviewed-by: Jose Rivera <German.Rivera@freescale.com>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/fsl_rcpm.c334
1 files changed, 257 insertions, 77 deletions
diff --git a/arch/powerpc/sysdev/fsl_rcpm.c b/arch/powerpc/sysdev/fsl_rcpm.c
index cd9d160..4f888f1 100644
--- a/arch/powerpc/sysdev/fsl_rcpm.c
+++ b/arch/powerpc/sysdev/fsl_rcpm.c
@@ -1,7 +1,9 @@
/*
* RCPM(Run Control/Power Management) support
*
- * Copyright 2012 Freescale Semiconductor Inc.
+ * Copyright 2012-2014 Freescale Semiconductor Inc.
+ *
+ * Author: Chenhui Zhao <chenhui.zhao@freescale.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -10,142 +12,320 @@
*/
#include <linux/types.h>
#include <linux/errno.h>
-#include <linux/suspend.h>
-#include <linux/device.h>
-#include <linux/delay.h>
#include <linux/of_address.h>
-#include <linux/of_platform.h>
#include <linux/export.h>
#include <asm/io.h>
-#include <asm/cacheflush.h>
#include <asm/fsl_guts.h>
+#include <asm/cputhreads.h>
+#include <asm/fsl_pm.h>
+
+#define RCPM_V1 1
+#define RCPM_V2 2
+
+const struct fsl_pm_ops *qoriq_pm_ops;
+
+static struct ccsr_rcpm_v1 __iomem *rcpm_v1_regs;
+static struct ccsr_rcpm_v2 __iomem *rcpm_v2_regs;
+
+static void rcpm_v1_irq_mask(int cpu)
+{
+ int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ setbits32(&rcpm_v1_regs->cpmimr, mask);
+ setbits32(&rcpm_v1_regs->cpmcimr, mask);
+ setbits32(&rcpm_v1_regs->cpmmcmr, mask);
+ setbits32(&rcpm_v1_regs->cpmnmimr, mask);
+}
-struct ccsr_rcpm __iomem *rcpm1_regs;
-struct ccsr_rcpm_v2 __iomem *rcpm2_regs;
+static void rcpm_v1_irq_unmask(int cpu)
+{
+ int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ clrbits32(&rcpm_v1_regs->cpmimr, mask);
+ clrbits32(&rcpm_v1_regs->cpmcimr, mask);
+ clrbits32(&rcpm_v1_regs->cpmmcmr, mask);
+ clrbits32(&rcpm_v1_regs->cpmnmimr, mask);
+}
+
+static void rcpm_v1_set_ip_power(int enable, u32 mask)
+{
+ if (enable)
+ setbits32(&rcpm_v1_regs->ippdexpcr, mask);
+ else
+ clrbits32(&rcpm_v1_regs->ippdexpcr, mask);
+}
-static int rcpm_suspend_enter(suspend_state_t state)
+static void rcpm_v1_cpu_enter_state(int cpu, int state)
{
+ unsigned int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ switch (state) {
+ case E500_PM_PH10:
+ setbits32(&rcpm_v1_regs->cdozcr, mask);
+ break;
+ case E500_PM_PH15:
+ setbits32(&rcpm_v1_regs->cnapcr, mask);
+ break;
+ default:
+ pr_err("%s: Unknown cpu PM state (%d)\n", __func__, state);
+ break;
+ }
+}
+
+static void rcpm_v1_cpu_exit_state(int cpu, int state)
+{
+ unsigned int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ switch (state) {
+ case E500_PM_PH10:
+ clrbits32(&rcpm_v1_regs->cdozcr, mask);
+ break;
+ case E500_PM_PH15:
+ clrbits32(&rcpm_v1_regs->cnapcr, mask);
+ break;
+ default:
+ pr_err("%s: Unknown cpu PM state (%d)\n", __func__, state);
+ break;
+ }
+}
+
+static int rcpm_v1_plat_enter_state(int state)
+{
+ u32 *pmcsr_reg = &rcpm_v1_regs->powmgtcsr;
int ret = 0;
int result;
switch (state) {
- case PM_SUSPEND_STANDBY:
-
- flush_dcache_L1();
- flush_backside_L2_cache();
+ case PLAT_PM_SLEEP:
+ setbits32(pmcsr_reg, RCPM_POWMGTCSR_SLP);
- setbits32(&rcpm1_regs->powmgtcsr, RCPM_POWMGTCSR_SLP);
/* At this point, the device is in sleep mode. */
- /* Upon resume, wait for SLP bit to be clear. */
+ /* Upon resume, wait for RCPM_POWMGTCSR_SLP bit to be clear. */
result = spin_event_timeout(
- (in_be32(&rcpm1_regs->powmgtcsr) & RCPM_POWMGTCSR_SLP) == 0,
- 10000, 10);
+ !(in_be32(pmcsr_reg) & RCPM_POWMGTCSR_SLP), 10000, 10);
if (!result) {
- pr_err("%s: timeout waiting for SLP bit "
- "to be cleared\n", __func__);
+ pr_err("%s: timeout waiting for SLP bit to be cleared\n",
+ __func__);
ret = -ETIMEDOUT;
}
break;
-
default:
+ pr_err("%s: Unknown platform PM state (%d)\n",
+ __func__, state);
ret = -EINVAL;
-
}
+
return ret;
}
-static int rcpm_v2_suspend_enter(suspend_state_t state)
+static void rcpm_common_freeze_time_base(u32 *tben_reg, int freeze)
+{
+ static u32 mask;
+
+ if (freeze) {
+ mask = in_be32(tben_reg);
+ clrbits32(tben_reg, mask);
+ } else {
+ setbits32(tben_reg, mask);
+ }
+
+ /* read back to push the previous write */
+ in_be32(tben_reg);
+}
+
+static void rcpm_v1_freeze_time_base(int freeze)
+{
+ rcpm_common_freeze_time_base(&rcpm_v1_regs->ctbenr, freeze);
+}
+
+static void rcpm_v2_freeze_time_base(int freeze)
+{
+ rcpm_common_freeze_time_base(&rcpm_v2_regs->pctbenr, freeze);
+}
+
+static void rcpm_v2_irq_mask(int cpu)
+{
+ int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ setbits32(&rcpm_v2_regs->tpmimr0, mask);
+ setbits32(&rcpm_v2_regs->tpmcimr0, mask);
+ setbits32(&rcpm_v2_regs->tpmmcmr0, mask);
+ setbits32(&rcpm_v2_regs->tpmnmimr0, mask);
+}
+
+static void rcpm_v2_irq_unmask(int cpu)
+{
+ int hw_cpu = get_hard_smp_processor_id(cpu);
+ unsigned int mask = 1 << hw_cpu;
+
+ clrbits32(&rcpm_v2_regs->tpmimr0, mask);
+ clrbits32(&rcpm_v2_regs->tpmcimr0, mask);
+ clrbits32(&rcpm_v2_regs->tpmmcmr0, mask);
+ clrbits32(&rcpm_v2_regs->tpmnmimr0, mask);
+}
+
+static void rcpm_v2_set_ip_power(int enable, u32 mask)
+{
+ if (enable)
+ /* enable power of IP blocks in deep sleep mode */
+ setbits32(&rcpm_v2_regs->ippdexpcr[0], mask);
+ else
+ clrbits32(&rcpm_v2_regs->ippdexpcr[0], mask);
+}
+
+static void rcpm_v2_cpu_enter_state(int cpu, int state)
+{
+ unsigned int hw_cpu = get_hard_smp_processor_id(cpu);
+ u32 mask = 1 << cpu_core_index_of_thread(hw_cpu);
+
+ switch (state) {
+ case E500_PM_PH10:
+ /* one bit corresponds to one thread for PH10 of 6500 */
+ setbits32(&rcpm_v2_regs->tph10setr0, 1 << hw_cpu);
+ break;
+ case E500_PM_PH15:
+ setbits32(&rcpm_v2_regs->pcph15setr, mask);
+ break;
+ case E500_PM_PH20:
+ setbits32(&rcpm_v2_regs->pcph20setr, mask);
+ break;
+ case E500_PM_PH30:
+ setbits32(&rcpm_v2_regs->pcph30setr, mask);
+ break;
+ default:
+ pr_err("%s: Unknown cpu PM state (%d)\n", __func__, state);
+ }
+}
+
+static void rcpm_v2_cpu_exit_state(int cpu, int state)
+{
+ unsigned int hw_cpu = get_hard_smp_processor_id(cpu);
+ u32 mask = 1 << cpu_core_index_of_thread(hw_cpu);
+
+ switch (state) {
+ case E500_PM_PH10:
+ setbits32(&rcpm_v2_regs->tph10clrr0, 1 << hw_cpu);
+ break;
+ case E500_PM_PH15:
+ setbits32(&rcpm_v2_regs->pcph15clrr, mask);
+ break;
+ case E500_PM_PH20:
+ setbits32(&rcpm_v2_regs->pcph20clrr, mask);
+ break;
+ case E500_PM_PH30:
+ setbits32(&rcpm_v2_regs->pcph30clrr, mask);
+ break;
+ default:
+ pr_err("%s: Unknown cpu PM state (%d)\n", __func__, state);
+ }
+}
+
+static int rcpm_v2_plat_enter_state(int state)
{
+ u32 *pmcsr_reg = &rcpm_v2_regs->powmgtcsr;
int ret = 0;
int result;
switch (state) {
- case PM_SUSPEND_STANDBY:
-
+ case PLAT_PM_LPM20:
/* clear previous LPM20 status */
- setbits32(&rcpm2_regs->powmgtcsr, RCPM_POWMGTCSR_P_LPM20_ST);
+ setbits32(pmcsr_reg, RCPM_POWMGTCSR_P_LPM20_ST);
/* enter LPM20 status */
- setbits32(&rcpm2_regs->powmgtcsr, RCPM_POWMGTCSR_LPM20_RQ);
+ setbits32(pmcsr_reg, RCPM_POWMGTCSR_LPM20_RQ);
/* At this point, the device is in LPM20 status. */
/* resume ... */
result = spin_event_timeout(
- (in_be32(&rcpm2_regs->powmgtcsr) & RCPM_POWMGTCSR_LPM20_ST)
- == 0, 10000, 10);
+ !(in_be32(pmcsr_reg) & RCPM_POWMGTCSR_LPM20_ST), 10000, 10);
if (!result) {
pr_err("%s: timeout waiting for LPM20 bit to be cleared\n",
- __func__);
+ __func__);
ret = -ETIMEDOUT;
}
-
break;
-
default:
+ pr_err("%s: Unknown platform PM state (%d)\n",
+ __func__, state);
ret = -EINVAL;
-
}
return ret;
-
-}
-
-static int rcpm_suspend_valid(suspend_state_t state)
-{
- if (state == PM_SUSPEND_STANDBY)
- return 1;
- else
- return 0;
}
-static struct platform_suspend_ops rcpm_suspend_ops = {
- .valid = rcpm_suspend_valid,
+static const struct fsl_pm_ops qoriq_rcpm_v1_ops = {
+ .irq_mask = rcpm_v1_irq_mask,
+ .irq_unmask = rcpm_v1_irq_unmask,
+ .cpu_enter_state = rcpm_v1_cpu_enter_state,
+ .cpu_exit_state = rcpm_v1_cpu_exit_state,
+ .plat_enter_state = rcpm_v1_plat_enter_state,
+ .set_ip_power = rcpm_v1_set_ip_power,
+ .freeze_time_base = rcpm_v1_freeze_time_base,
};
-static int rcpm_probe(struct platform_device *pdev)
-{
- struct device_node *np = pdev->dev.of_node;
+static const struct fsl_pm_ops qoriq_rcpm_v2_ops = {
+ .irq_mask = rcpm_v2_irq_mask,
+ .irq_unmask = rcpm_v2_irq_unmask,
+ .cpu_enter_state = rcpm_v2_cpu_enter_state,
+ .cpu_exit_state = rcpm_v2_cpu_exit_state,
+ .plat_enter_state = rcpm_v2_plat_enter_state,
+ .set_ip_power = rcpm_v2_set_ip_power,
+ .freeze_time_base = rcpm_v2_freeze_time_base,
+};
- if (of_device_is_compatible(np, "fsl,qoriq-rcpm-2.0")) {
- rcpm2_regs = of_iomap(np, 0);
- if (!rcpm2_regs)
- return -ENOMEM;
+static const struct of_device_id rcpm_matches[] = {
+ {
+ .compatible = "fsl,qoriq-rcpm-1.0",
+ .data = (void *)RCPM_V1,
+ },
+ {
+ .compatible = "fsl,qoriq-rcpm-2.0",
+ .data = (void *)RCPM_V2,
+ },
+ {},
+};
- rcpm_suspend_ops.enter = rcpm_v2_suspend_enter;
- } else {
- rcpm1_regs= of_iomap(np, 0);
- if (!rcpm1_regs)
- return -ENOMEM;
+int fsl_rcpm_init(void)
+{
+ struct device_node *np;
+ const struct of_device_id *match;
+ void __iomem *base;
- rcpm_suspend_ops.enter = rcpm_suspend_enter;
+ np = of_find_matching_node_and_match(NULL, rcpm_matches, &match);
+ if (!np) {
+ pr_err("%s: can't find the rcpm node.\n", __func__);
+ return -EINVAL;
}
- suspend_set_ops(&rcpm_suspend_ops);
+ base = of_iomap(np, 0);
+ of_node_put(np);
+ if (!base)
+ return -ENOMEM;
- dev_info(&pdev->dev, "Freescale RCPM driver\n");
- return 0;
-}
+ switch ((unsigned long)match->data) {
+ case RCPM_V1:
+ rcpm_v1_regs = base;
+ qoriq_pm_ops = &qoriq_rcpm_v1_ops;
+ break;
-static const struct of_device_id rcpm_ids[] = {
- { .compatible = "fsl,qoriq-rcpm-1.0", },
- { .compatible = "fsl,qoriq-rcpm-2.0", },
- { },
-};
+ case RCPM_V2:
+ rcpm_v2_regs = base;
+ qoriq_pm_ops = &qoriq_rcpm_v2_ops;
+ break;
-static struct platform_driver rcpm_driver = {
- .driver = {
- .name = "fsl-rcpm",
- .owner = THIS_MODULE,
- .of_match_table = rcpm_ids,
- },
- .probe = rcpm_probe,
-};
+ default:
+ break;
+ }
-static int __init rcpm_init(void)
-{
- return platform_driver_register(&rcpm_driver);
+ return 0;
}
-device_initcall(rcpm_init);
+/* need to call this before SMP init */
+early_initcall(fsl_rcpm_init);