From b018e44a68dc2f4df819ae194e39e07313841dad Mon Sep 17 00:00:00 2001 From: Yangbo Lu Date: Wed, 17 Jan 2018 15:27:58 +0800 Subject: [PATCH 15/30] cpufreq: support layerscape This is an integrated patch for layerscape pm support. Signed-off-by: Tang Yuantian Signed-off-by: Yangbo Lu --- drivers/cpufreq/Kconfig | 2 +- drivers/cpufreq/qoriq-cpufreq.c | 176 +++++++++++++++------------------------- drivers/firmware/psci.c | 12 ++- drivers/soc/fsl/rcpm.c | 158 ++++++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+), 113 deletions(-) create mode 100644 drivers/soc/fsl/rcpm.c --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -334,7 +334,7 @@ endif config QORIQ_CPUFREQ tristate "CPU frequency scaling driver for Freescale QorIQ SoCs" - depends on OF && COMMON_CLK && (PPC_E500MC || ARM) + depends on OF && COMMON_CLK && (PPC_E500MC || ARM || ARM64) depends on !CPU_THERMAL || THERMAL select CLK_QORIQ help --- a/drivers/cpufreq/qoriq-cpufreq.c +++ b/drivers/cpufreq/qoriq-cpufreq.c @@ -11,6 +11,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -22,10 +23,6 @@ #include #include -#if !defined(CONFIG_ARM) -#include /* for get_hard_smp_processor_id() in UP configs */ -#endif - /** * struct cpu_data * @pclk: the parent clock of cpu @@ -37,73 +34,51 @@ struct cpu_data { struct thermal_cooling_device *cdev; }; +/* + * Don't use cpufreq on this SoC -- used when the SoC would have otherwise + * matched a more generic compatible. + */ +#define SOC_BLACKLIST 1 + /** * struct soc_data - SoC specific data - * @freq_mask: mask the disallowed frequencies - * @flag: unique flags + * @flags: SOC_xxx */ struct soc_data { - u32 freq_mask[4]; - u32 flag; -}; - -#define FREQ_MASK 1 -/* see hardware specification for the allowed frqeuencies */ -static const struct soc_data sdata[] = { - { /* used by p2041 and p3041 */ - .freq_mask = {0x8, 0x8, 0x2, 0x2}, - .flag = FREQ_MASK, - }, - { /* used by p5020 */ - .freq_mask = {0x8, 0x2}, - .flag = FREQ_MASK, - }, - { /* used by p4080, p5040 */ - .freq_mask = {0}, - .flag = 0, - }, + u32 flags; }; -/* - * the minimum allowed core frequency, in Hz - * for chassis v1.0, >= platform frequency - * for chassis v2.0, >= platform frequency / 2 - */ -static u32 min_cpufreq; -static const u32 *fmask; - -#if defined(CONFIG_ARM) -static int get_cpu_physical_id(int cpu) -{ - return topology_core_id(cpu); -} -#else -static int get_cpu_physical_id(int cpu) -{ - return get_hard_smp_processor_id(cpu); -} -#endif - static u32 get_bus_freq(void) { struct device_node *soc; u32 sysfreq; + struct clk *pltclk; + int ret; + /* get platform freq by searching bus-frequency property */ soc = of_find_node_by_type(NULL, "soc"); - if (!soc) - return 0; - - if (of_property_read_u32(soc, "bus-frequency", &sysfreq)) - sysfreq = 0; + if (soc) { + ret = of_property_read_u32(soc, "bus-frequency", &sysfreq); + of_node_put(soc); + if (!ret) + return sysfreq; + } - of_node_put(soc); + /* get platform freq by its clock name */ + pltclk = clk_get(NULL, "cg-pll0-div1"); + if (IS_ERR(pltclk)) { + pr_err("%s: can't get bus frequency %ld\n", + __func__, PTR_ERR(pltclk)); + return PTR_ERR(pltclk); + } - return sysfreq; + return clk_get_rate(pltclk); } -static struct device_node *cpu_to_clk_node(int cpu) +static struct clk *cpu_to_clk(int cpu) { - struct device_node *np, *clk_np; + struct device_node *np; + struct clk *clk; if (!cpu_present(cpu)) return NULL; @@ -112,37 +87,28 @@ static struct device_node *cpu_to_clk_no if (!np) return NULL; - clk_np = of_parse_phandle(np, "clocks", 0); - if (!clk_np) - return NULL; - + clk = of_clk_get(np, 0); of_node_put(np); - - return clk_np; + return clk; } /* traverse cpu nodes to get cpu mask of sharing clock wire */ static void set_affected_cpus(struct cpufreq_policy *policy) { - struct device_node *np, *clk_np; struct cpumask *dstp = policy->cpus; + struct clk *clk; int i; - np = cpu_to_clk_node(policy->cpu); - if (!np) - return; - for_each_present_cpu(i) { - clk_np = cpu_to_clk_node(i); - if (!clk_np) + clk = cpu_to_clk(i); + if (IS_ERR(clk)) { + pr_err("%s: no clock for cpu %d\n", __func__, i); continue; + } - if (clk_np == np) + if (clk_is_match(policy->clk, clk)) cpumask_set_cpu(i, dstp); - - of_node_put(clk_np); } - of_node_put(np); } /* reduce the duplicated frequencies in frequency table */ @@ -198,10 +164,11 @@ static void freq_table_sort(struct cpufr static int qoriq_cpufreq_cpu_init(struct cpufreq_policy *policy) { - struct device_node *np, *pnode; + struct device_node *np; int i, count, ret; - u32 freq, mask; + u32 freq; struct clk *clk; + const struct clk_hw *hwclk; struct cpufreq_frequency_table *table; struct cpu_data *data; unsigned int cpu = policy->cpu; @@ -221,17 +188,13 @@ static int qoriq_cpufreq_cpu_init(struct goto err_nomem2; } - pnode = of_parse_phandle(np, "clocks", 0); - if (!pnode) { - pr_err("%s: could not get clock information\n", __func__); - goto err_nomem2; - } + hwclk = __clk_get_hw(policy->clk); + count = clk_hw_get_num_parents(hwclk); - count = of_property_count_strings(pnode, "clock-names"); data->pclk = kcalloc(count, sizeof(struct clk *), GFP_KERNEL); if (!data->pclk) { pr_err("%s: no memory\n", __func__); - goto err_node; + goto err_nomem2; } table = kcalloc(count + 1, sizeof(*table), GFP_KERNEL); @@ -240,23 +203,11 @@ static int qoriq_cpufreq_cpu_init(struct goto err_pclk; } - if (fmask) - mask = fmask[get_cpu_physical_id(cpu)]; - else - mask = 0x0; - for (i = 0; i < count; i++) { - clk = of_clk_get(pnode, i); + clk = clk_hw_get_parent_by_index(hwclk, i)->clk; data->pclk[i] = clk; freq = clk_get_rate(clk); - /* - * the clock is valid if its frequency is not masked - * and large than minimum allowed frequency. - */ - if (freq < min_cpufreq || (mask & (1 << i))) - table[i].frequency = CPUFREQ_ENTRY_INVALID; - else - table[i].frequency = freq / 1000; + table[i].frequency = freq / 1000; table[i].driver_data = i; } freq_table_redup(table, count); @@ -282,7 +233,6 @@ static int qoriq_cpufreq_cpu_init(struct policy->cpuinfo.transition_latency = u64temp + 1; of_node_put(np); - of_node_put(pnode); return 0; @@ -290,10 +240,7 @@ err_nomem1: kfree(table); err_pclk: kfree(data->pclk); -err_node: - of_node_put(pnode); err_nomem2: - policy->driver_data = NULL; kfree(data); err_np: of_node_put(np); @@ -357,12 +304,25 @@ static struct cpufreq_driver qoriq_cpufr .attr = cpufreq_generic_attr, }; +static const struct soc_data blacklist = { + .flags = SOC_BLACKLIST, +}; + static const struct of_device_id node_matches[] __initconst = { - { .compatible = "fsl,p2041-clockgen", .data = &sdata[0], }, - { .compatible = "fsl,p3041-clockgen", .data = &sdata[0], }, - { .compatible = "fsl,p5020-clockgen", .data = &sdata[1], }, - { .compatible = "fsl,p4080-clockgen", .data = &sdata[2], }, - { .compatible = "fsl,p5040-clockgen", .data = &sdata[2], }, + /* e6500 cannot use cpufreq due to erratum A-008083 */ + { .compatible = "fsl,b4420-clockgen", &blacklist }, + { .compatible = "fsl,b4860-clockgen", &blacklist }, + { .compatible = "fsl,t2080-clockgen", &blacklist }, + { .compatible = "fsl,t4240-clockgen", &blacklist }, + + { .compatible = "fsl,ls1012a-clockgen", }, + { .compatible = "fsl,ls1021a-clockgen", }, + { .compatible = "fsl,ls1043a-clockgen", }, + { .compatible = "fsl,ls1046a-clockgen", }, + { .compatible = "fsl,ls1088a-clockgen", }, + { .compatible = "fsl,ls2080a-clockgen", }, + { .compatible = "fsl,p4080-clockgen", }, + { .compatible = "fsl,qoriq-clockgen-1.0", }, { .compatible = "fsl,qoriq-clockgen-2.0", }, {} }; @@ -380,16 +340,12 @@ static int __init qoriq_cpufreq_init(voi match = of_match_node(node_matches, np); data = match->data; - if (data) { - if (data->flag) - fmask = data->freq_mask; - min_cpufreq = get_bus_freq(); - } else { - min_cpufreq = get_bus_freq() / 2; - } of_node_put(np); + if (data && data->flags & SOC_BLACKLIST) + return -ENODEV; + ret = cpufreq_register_driver(&qoriq_cpufreq_driver); if (!ret) pr_info("Freescale QorIQ CPU frequency scaling driver\n"); --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -437,8 +437,12 @@ CPUIDLE_METHOD_OF_DECLARE(psci, "psci", static int psci_system_suspend(unsigned long unused) { - return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), - virt_to_phys(cpu_resume), 0, 0); + u32 state; + + state = ( 2 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | + (1 << PSCI_0_2_POWER_STATE_TYPE_SHIFT); + + return psci_cpu_suspend(state, virt_to_phys(cpu_resume)); } static int psci_system_suspend_enter(suspend_state_t state) @@ -458,6 +462,8 @@ static void __init psci_init_system_susp if (!IS_ENABLED(CONFIG_SUSPEND)) return; + suspend_set_ops(&psci_suspend_ops); + ret = psci_features(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND)); if (ret != PSCI_RET_NOT_SUPPORTED) @@ -562,6 +568,8 @@ static void __init psci_0_2_set_function arm_pm_restart = psci_sys_reset; pm_power_off = psci_sys_poweroff; + psci_init_system_suspend(); + suspend_set_ops(&psci_suspend_ops); } /* --- /dev/null +++ b/drivers/soc/fsl/rcpm.c @@ -0,0 +1,158 @@ +/* + * Run Control and Power Management (RCPM) driver + * + * Copyright 2016 NXP + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + */ +#define pr_fmt(fmt) "RCPM: %s: " fmt, __func__ + +#include +#include +#include +#include +#include + +/* RCPM register offset */ +#define RCPM_IPPDEXPCR0 0x140 + +#define RCPM_WAKEUP_CELL_SIZE 2 + +struct rcpm_config { + int ipp_num; + int ippdexpcr_offset; + u32 ippdexpcr[2]; + void *rcpm_reg_base; +}; + +static struct rcpm_config *rcpm; + +static inline void rcpm_reg_write(u32 offset, u32 value) +{ + iowrite32be(value, rcpm->rcpm_reg_base + offset); +} + +static inline u32 rcpm_reg_read(u32 offset) +{ + return ioread32be(rcpm->rcpm_reg_base + offset); +} + +static void rcpm_wakeup_fixup(struct device *dev, void *data) +{ + struct device_node *node = dev ? dev->of_node : NULL; + u32 value[RCPM_WAKEUP_CELL_SIZE]; + int ret, i; + + if (!dev || !node || !device_may_wakeup(dev)) + return; + + /* + * Get the values in the "rcpm-wakeup" property. + * Three values are: + * The first is a pointer to the RCPM node. + * The second is the value of the ippdexpcr0 register. + * The third is the value of the ippdexpcr1 register. + */ + ret = of_property_read_u32_array(node, "fsl,rcpm-wakeup", + value, RCPM_WAKEUP_CELL_SIZE); + if (ret) + return; + + pr_debug("wakeup source: the device %s\n", node->full_name); + + for (i = 0; i < rcpm->ipp_num; i++) + rcpm->ippdexpcr[i] |= value[i + 1]; +} + +static int rcpm_suspend_prepare(void) +{ + int i; + u32 val; + + BUG_ON(!rcpm); + + for (i = 0; i < rcpm->ipp_num; i++) + rcpm->ippdexpcr[i] = 0; + + dpm_for_each_dev(NULL, rcpm_wakeup_fixup); + + for (i = 0; i < rcpm->ipp_num; i++) { + if (rcpm->ippdexpcr[i]) { + val = rcpm_reg_read(rcpm->ippdexpcr_offset + 4 * i); + rcpm_reg_write(rcpm->ippdexpcr_offset + 4 * i, + val | rcpm->ippdexpcr[i]); + pr_debug("ippdexpcr%d = 0x%x\n", i, rcpm->ippdexpcr[i]); + } + } + + return 0; +} + +static int rcpm_suspend_notifier_call(struct notifier_block *bl, + unsigned long state, + void *unused) +{ + switch (state) { + case PM_SUSPEND_PREPARE: + rcpm_suspend_prepare(); + break; + } + + return NOTIFY_DONE; +} + +static struct rcpm_config rcpm_default_config = { + .ipp_num = 1, + .ippdexpcr_offset = RCPM_IPPDEXPCR0, +}; + +static const struct of_device_id rcpm_matches[] = { + { + .compatible = "fsl,qoriq-rcpm-2.1", + .data = &rcpm_default_config, + }, + {} +}; + +static struct notifier_block rcpm_suspend_notifier = { + .notifier_call = rcpm_suspend_notifier_call, +}; + +static int __init layerscape_rcpm_init(void) +{ + const struct of_device_id *match; + struct device_node *np; + + np = of_find_matching_node_and_match(NULL, rcpm_matches, &match); + if (!np) { + pr_err("Can't find the RCPM node.\n"); + return -EINVAL; + } + + if (match->data) + rcpm = (struct rcpm_config *)match->data; + else + return -EINVAL; + + rcpm->rcpm_reg_base = of_iomap(np, 0); + of_node_put(np); + if (!rcpm->rcpm_reg_base) + return -ENOMEM; + + register_pm_notifier(&rcpm_suspend_notifier); + + pr_info("The RCPM driver initialized.\n"); + + return 0; +} + +subsys_initcall(layerscape_rcpm_init);