diff options
author | Stephen Boyd <sboyd@codeaurora.org> | 2014-08-07 15:24:13 -0700 |
---|---|---|
committer | Georgi Djakov <georgi.djakov@linaro.org> | 2015-02-19 10:50:54 +0200 |
commit | 8a71249f93c0d92ad865decf9b191ad38bd1c7c8 (patch) | |
tree | 8070436df2e9babe55bcf264a52f35d9a9d0aeab | |
parent | 59b4b05285e2c1cd2bc9afd9625b225453732cba (diff) |
soc: Add cpu_pwr_ctl snapshot
This is a snapshot of the cpu_pwr_ctl.c file as of msm-3.10
commit:
acdce027751d5a7488b283f0ce3111f873a5816d (Merge "defconfig: arm64:
Enable ONESHOT_SYNC for msm8994")
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
-rw-r--r-- | drivers/soc/qcom/Kconfig | 7 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/cpu_pwr_ctl.c | 518 |
3 files changed, 527 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 858ba535061..f7298512d58 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -70,5 +70,12 @@ config MSM_SCM bool "Secure Channel Manager (SCM) support" default n +config MSM_CPU_PWR_CTL + bool "Cpu subsystem power control" + depends on SMP && (ARM || ARM64) + default y + help + Provides routines to power on cpu rails and l2 cache + controller during coldboot. endif # ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index d34fafbf4c7..d94eea843b9 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_MSM_SCM) += scm.o scm-boot.o obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o smd_private.o smd_init_dt.o smsm_debug.o obj-$(CONFIG_MSM_SMEM) += smem.o smem_debug.o obj-$(CONFIG_MSM_SPM_V2) += spm-v2.o spm_devices.o + +obj-$(CONFIG_MSM_CPU_PWR_CTL) += cpu_pwr_ctl.o diff --git a/drivers/soc/qcom/cpu_pwr_ctl.c b/drivers/soc/qcom/cpu_pwr_ctl.c new file mode 100644 index 00000000000..c4979f0ee5c --- /dev/null +++ b/drivers/soc/qcom/cpu_pwr_ctl.c @@ -0,0 +1,518 @@ +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* MSM CPU Subsystem power control operations + */ + +#include <linux/bitops.h> +#include <linux/cpu.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/smp.h> + +#include <soc/qcom/cpu_pwr_ctl.h> +#include <soc/qcom/spm.h> +#include <soc/qcom/scm.h> + +#include <asm/barrier.h> +#include <asm/cacheflush.h> + +/* CPU power domain register offsets */ +#define CPU_PWR_CTL 0x4 +#define CPU_PWR_GATE_CTL 0x14 +#define LDO_BHS_PWR_CTL 0x28 + +/* L2 power domain register offsets */ +#define L2_PWR_CTL_OVERRIDE 0xc +#define L2_PWR_CTL 0x14 +#define L2_PWR_STATUS 0x18 +#define L2_CORE_CBCR 0x58 +#define L1_RST_DIS 0x284 + +#define L2_SPM_STS 0xc +#define L2_VREG_CTL 0x1c + +#define SCM_IO_READ 1 +#define SCM_IO_WRITE 2 + +/* + * struct msm_l2ccc_of_info: represents of data for l2 cache clock controller. + * @compat: compat string for l2 cache clock controller + * @l2_pon: l2 cache power on routine + */ +struct msm_l2ccc_of_info { + const char *compat; + int (*l2_power_on) (struct device_node *dn, u32 l2_mask, int cpu); + u32 l2_power_on_mask; +}; + + +static int power_on_l2_msm8916(struct device_node *l2ccc_node, u32 pon_mask, + int cpu) +{ + u32 pon_status; + void __iomem *l2_base; + + l2_base = of_iomap(l2ccc_node, 0); + if (!l2_base) + return -ENOMEM; + + /* Skip power-on sequence if l2 cache is already powered up*/ + pon_status = (__raw_readl(l2_base + L2_PWR_STATUS) & pon_mask) + == pon_mask; + if (pon_status) { + iounmap(l2_base); + return 0; + } + + /* Close L2/SCU Logic GDHS and power up the cache */ + writel_relaxed(0x10D700, l2_base + L2_PWR_CTL); + + /* Assert PRESETDBGn */ + writel_relaxed(0x400000, l2_base + L2_PWR_CTL_OVERRIDE); + mb(); + udelay(2); + + /* De-assert L2/SCU memory Clamp */ + writel_relaxed(0x101700, l2_base + L2_PWR_CTL); + + /* Wakeup L2/SCU RAMs by deasserting sleep signals */ + writel_relaxed(0x101703, l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* Enable clocks via SW_CLK_EN */ + writel_relaxed(0x01, l2_base + L2_CORE_CBCR); + + /* De-assert L2/SCU logic clamp */ + writel_relaxed(0x101603, l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* De-assert PRESSETDBg */ + writel_relaxed(0x0, l2_base + L2_PWR_CTL_OVERRIDE); + + /* De-assert L2/SCU Logic reset */ + writel_relaxed(0x100203, l2_base + L2_PWR_CTL); + mb(); + udelay(54); + + /* Turn on the PMIC_APC */ + writel_relaxed(0x10100203, l2_base + L2_PWR_CTL); + + /* Set H/W clock control for the cpu CBC block */ + writel_relaxed(0x03, l2_base + L2_CORE_CBCR); + mb(); + iounmap(l2_base); + + return 0; +} + +static int kick_l2spm_8994(struct device_node *l2ccc_node, + struct device_node *vctl_node) +{ + struct resource res; + int val, ret = 0; + void __iomem *l2spm_base = of_iomap(vctl_node, 0); + + if (!l2spm_base) + return -ENOMEM; + + if (!(__raw_readl(l2spm_base + L2_SPM_STS) & 0xFFFF0000)) + goto bail_l2_pwr_bit; + + ret = of_address_to_resource(l2ccc_node, 1, &res); + if (ret) + goto bail_l2_pwr_bit; + + /* L2 is executing sleep state machine, + * let's softly kick it awake + */ + val = scm_call_atomic1(SCM_SVC_IO, SCM_IO_READ, (u32)res.start); + val |= BIT(0); + scm_call_atomic2(SCM_SVC_IO, SCM_IO_WRITE, (u32)res.start, val); + + /* Wait until the SPM status indicates that the PWR_CTL + * bits are clear. + */ + while (readl_relaxed(l2spm_base + L2_SPM_STS) & 0xFFFF0000) { + int timeout = 10; + + BUG_ON(!timeout--); + cpu_relax(); + usleep_range(100, 100); + } + + val = scm_call_atomic1(SCM_SVC_IO, SCM_IO_READ, (u32)res.start); + val &= ~BIT(0); + scm_call_atomic2(SCM_SVC_IO, SCM_IO_WRITE, (u32)res.start, val); + +bail_l2_pwr_bit: + iounmap(l2spm_base); + return ret; +} + +static int power_on_l2_msm8994(struct device_node *l2ccc_node, u32 pon_mask, + int cpu) +{ + u32 pon_status; + void __iomem *l2_base; + int ret = 0; + uint32_t val; + struct device_node *vctl_node; + + vctl_node = of_parse_phandle(l2ccc_node, "qcom,vctl-node", 0); + + if (!vctl_node) + return -ENODEV; + + l2_base = of_iomap(l2ccc_node, 0); + if (!l2_base) + return -ENOMEM; + + pon_status = (__raw_readl(l2_base + L2_PWR_CTL) & pon_mask) == pon_mask; + + /* Check L2 SPM Status */ + if (pon_status) { + ret = kick_l2spm_8994(l2ccc_node, vctl_node); + iounmap(l2_base); + return ret; + } + + /* Need to power on the rail */ + ret = of_property_read_u32(l2ccc_node, "qcom,vctl-val", &val); + if (ret) { + iounmap(l2_base); + pr_err("Unable to read L2 voltage\n"); + return -EFAULT; + } + + ret = msm_spm_turn_on_cpu_rail(vctl_node, val, cpu, L2_VREG_CTL); + if (ret) { + iounmap(l2_base); + pr_err("Error turning on power rail.\n"); + return -EFAULT; + } + + /* Enable L1 invalidation by h/w */ + writel_relaxed(0x00000000, l2_base + L1_RST_DIS); + mb(); + + /* Assert PRESETDBGn */ + writel_relaxed(0x00400000 , l2_base + L2_PWR_CTL_OVERRIDE); + mb(); + + /* Close L2/SCU Logic GDHS and power up the cache */ + writel_relaxed(0x00029716 , l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* De-assert L2/SCU memory Clamp */ + writel_relaxed(0x00023716 , l2_base + L2_PWR_CTL); + mb(); + + /* Wakeup L2/SCU RAMs by deasserting sleep signals */ + writel_relaxed(0x0002371E , l2_base + L2_PWR_CTL); + mb(); + + /* Un-gate clock and wait for sequential waking up + * of L2 rams with a delay of 2*X0 cycles + */ + writel_relaxed(0x0002371C , l2_base + L2_PWR_CTL); + mb(); + udelay(4); + + /* De-assert L2/SCU logic clamp */ + writel_relaxed(0x0002361C , l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* De-assert L2/SCU logic reset */ + writel_relaxed(0x00022218 , l2_base + L2_PWR_CTL); + mb(); + udelay(4); + + /* Turn on the PMIC_APC */ + writel_relaxed(0x10022218 , l2_base + L2_PWR_CTL); + mb(); + + /* De-assert PRESETDBGn */ + writel_relaxed(0x00000000 , l2_base + L2_PWR_CTL_OVERRIDE); + mb(); + iounmap(l2_base); + + return 0; +} + +static const struct msm_l2ccc_of_info l2ccc_info[] = { + { + .compat = "qcom,8994-l2ccc", + .l2_power_on = power_on_l2_msm8994, + .l2_power_on_mask = (BIT(9) | BIT(28)), + }, + { + .compat = "qcom,8916-l2ccc", + .l2_power_on = power_on_l2_msm8916, + .l2_power_on_mask = BIT(9), + }, +}; + +static int power_on_l2_cache(struct device_node *l2ccc_node, int cpu) +{ + int ret, i; + const char *compat; + + ret = of_property_read_string(l2ccc_node, "compatible", &compat); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(l2ccc_info); i++) { + const struct msm_l2ccc_of_info *ptr = &l2ccc_info[i]; + + if (!of_compat_cmp(ptr->compat, compat, strlen(compat))) + return ptr->l2_power_on(l2ccc_node, + ptr->l2_power_on_mask, cpu); + } + pr_err("Compat string not found for L2CCC node\n"); + return -EIO; +} + +int msm8994_unclamp_secondary_arm_cpu(unsigned int cpu) +{ + + int ret = 0; + struct device_node *cpu_node, *acc_node, *l2_node, *l2ccc_node; + void __iomem *acc_reg, *ldo_bhs_reg; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) + return -ENODEV; + + acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0); + if (!acc_node) { + ret = -ENODEV; + goto out_acc; + } + + l2_node = of_parse_phandle(cpu_node, "next-level-cache", 0); + if (!l2_node) { + ret = -ENODEV; + goto out_l2; + } + + l2ccc_node = of_parse_phandle(l2_node, "power-domain", 0); + if (!l2ccc_node) { + ret = -ENODEV; + goto out_l2; + } + + /* + * Ensure L2-cache of the CPU is powered on before + * unclamping cpu power rails. + */ + + ret = power_on_l2_cache(l2ccc_node, cpu); + if (ret) { + pr_err("L2 cache power up failed for CPU%d\n", cpu); + goto out_l2ccc; + } + + ldo_bhs_reg = of_iomap(acc_node, 0); + if (!ldo_bhs_reg) { + ret = -ENOMEM; + goto out_bhs_reg; + } + + acc_reg = of_iomap(acc_node, 1); + if (!acc_reg) { + ret = -ENOMEM; + goto out_acc_reg; + } + + /* Assert head switch enable few */ + writel_relaxed(0x00000001, acc_reg + CPU_PWR_GATE_CTL); + mb(); + udelay(1); + + /* Assert head switch enable rest */ + writel_relaxed(0x00000003, acc_reg + CPU_PWR_GATE_CTL); + mb(); + udelay(1); + + /* De-assert coremem clamp. This is asserted by default */ + writel_relaxed(0x00000079, acc_reg + CPU_PWR_CTL); + mb(); + + /* Close coremem array gdhs */ + writel_relaxed(0x0000007D, acc_reg + CPU_PWR_CTL); + mb(); + udelay(1); + + /* De-assert clamp */ + writel_relaxed(0x0000003D, acc_reg + CPU_PWR_CTL); + mb(); + + /* De-assert clamp */ + writel_relaxed(0x0000003C, acc_reg + CPU_PWR_CTL); + mb(); + udelay(1); + + /* De-assert core0 reset */ + writel_relaxed(0x0000000C, acc_reg + CPU_PWR_CTL); + mb(); + + /* Assert PWRDUP */ + writel_relaxed(0x0000008C, acc_reg + CPU_PWR_CTL); + mb(); + + iounmap(acc_reg); +out_acc_reg: + iounmap(ldo_bhs_reg); +out_bhs_reg: + of_node_put(l2ccc_node); +out_l2ccc: + of_node_put(l2_node); +out_l2: + of_node_put(acc_node); +out_acc: + of_node_put(cpu_node); + + return ret; +} + +int msm_unclamp_secondary_arm_cpu(unsigned int cpu) +{ + + int ret = 0; + struct device_node *cpu_node, *acc_node, *l2_node, *l2ccc_node; + void __iomem *reg; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) + return -ENODEV; + + acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0); + if (!acc_node) { + ret = -ENODEV; + goto out_acc; + } + + l2_node = of_parse_phandle(cpu_node, "next-level-cache", 0); + if (!l2_node) { + ret = -ENODEV; + goto out_l2; + } + + l2ccc_node = of_parse_phandle(l2_node, "power-domain", 0); + if (!l2ccc_node) { + ret = -ENODEV; + goto out_l2; + } + + /* Ensure L2-cache of the CPU is powered on before + * unclamping cpu power rails. + */ + ret = power_on_l2_cache(l2ccc_node, cpu); + if (ret) { + pr_err("L2 cache power up failed for CPU%d\n", cpu); + goto out_l2ccc; + } + + reg = of_iomap(acc_node, 0); + if (!reg) { + ret = -ENOMEM; + goto out_acc_reg; + } + + /* Assert Reset on cpu-n */ + writel_relaxed(0x00000033, reg + CPU_PWR_CTL); + mb(); + + /*Program skew to 16 X0 clock cycles*/ + writel_relaxed(0x10000001, reg + CPU_PWR_GATE_CTL); + mb(); + udelay(2); + + /* De-assert coremem clamp */ + writel_relaxed(0x00000031, reg + CPU_PWR_CTL); + mb(); + + /* Close coremem array gdhs */ + writel_relaxed(0x00000039, reg + CPU_PWR_CTL); + mb(); + udelay(2); + + /* De-assert cpu-n clamp */ + writel_relaxed(0x00020038, reg + CPU_PWR_CTL); + mb(); + udelay(2); + + /* De-assert cpu-n reset */ + writel_relaxed(0x00020008, reg + CPU_PWR_CTL); + mb(); + + /* Assert PWRDUP signal on core-n */ + writel_relaxed(0x00020088, reg + CPU_PWR_CTL); + mb(); + + /* Secondary CPU-N is now alive */ + iounmap(reg); +out_acc_reg: + of_node_put(l2ccc_node); +out_l2ccc: + of_node_put(l2_node); +out_l2: + of_node_put(acc_node); +out_acc: + of_node_put(cpu_node); + + return ret; +} + +int msm_unclamp_secondary_arm_cpu_sim(unsigned int cpu) +{ + int ret = 0; + struct device_node *cpu_node, *acc_node; + void __iomem *reg; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) { + ret = -ENODEV; + goto out_acc; + } + + acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0); + if (!acc_node) { + ret = -ENODEV; + goto out_acc; + } + + reg = of_iomap(acc_node, 0); + if (!reg) { + ret = -ENOMEM; + goto out_acc; + } + + writel_relaxed(0x800, reg + CPU_PWR_CTL); + writel_relaxed(0x3FFF, reg + CPU_PWR_GATE_CTL); + mb(); + iounmap(reg); + +out_acc: + of_node_put(cpu_node); + + return ret; +} |