[PATCH] cpufreq: SPEAr: Add CPUFreq driver
Shawn Guo
shawn.guo at linaro.org
Mon Nov 26 23:01:37 EST 2012
On Sun, Nov 25, 2012 at 01:09:28AM +0530, Viresh Kumar wrote:
> From: Deepak Sikri <deepak.sikri at st.com>
>
> SPEAr is an ARM based family of SoCs. This patch adds in support of cpufreq
> driver for SPEAr SoCs. It is supported via DT only and so bindings are present
> in binding document.
>
> Signed-off-by: Deepak Sikri <deepak.sikri at st.com>
> Signed-off-by: Viresh Kumar <viresh.kumar at linaro.org>
> ---
> .../devicetree/bindings/cpufreq/cpufreq-spear.txt | 29 ++
> arch/arm/Kconfig | 1 +
> drivers/cpufreq/Kconfig.arm | 7 +
> drivers/cpufreq/Makefile | 1 +
> drivers/cpufreq/spear-cpufreq.c | 291 +++++++++++++++++++++
> 5 files changed, 329 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/cpufreq/cpufreq-spear.txt
> create mode 100644 drivers/cpufreq/spear-cpufreq.c
>
> diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-spear.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-spear.txt
> new file mode 100644
> index 0000000..4cf2819
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-spear.txt
> @@ -0,0 +1,29 @@
> +SPEAr cpufreq driver
> +-------------------
> +
> +SPEAr SoC cpufreq driver for CPU frequency scaling.
> +It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) systems
> +which share clock across all CPUs.
> +
> +Required properties:
> +- compatible: "st,cpufreq-spear"
> +- cpufreq_tbl: Table of frequencies CPU could be transitioned into, in the
> + increasing order.
> +
> +Optional properties:
> +- clock-latency: Specify the possible maximum transition latency for clock, in
> + unit of nanoseconds.
> +
> +Examples:
> +--------
> +
> +cpufreq {
> + compatible = "st,cpufreq-spear";
I do not think we need a "cpufreq" node, as we already have node for
cpu to contain these.
> + cpufreq_tbl = < 166000
> + 200000
> + 250000
> + 300000
> + 400000
> + 500000
> + 600000 >;
It's a little bit unusual that cpu runs such a freq range
(166 ~ 600 MHz) at a fixed voltage. You really have no way
to scale voltage?
> +};
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index 14f8160..44bb5cf 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -902,6 +902,7 @@ config ARCH_NOMADIK
>
> config PLAT_SPEAR
> bool "ST SPEAr"
> + select ARCH_HAS_CPUFREQ
> select ARCH_REQUIRE_GPIOLIB
> select ARM_AMBA
> select CLKDEV_LOOKUP
> diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
> index b63c335..ee0ef07 100644
> --- a/drivers/cpufreq/Kconfig.arm
> +++ b/drivers/cpufreq/Kconfig.arm
> @@ -76,3 +76,10 @@ config ARM_EXYNOS5250_CPUFREQ
> help
> This adds the CPUFreq driver for Samsung EXYNOS5250
> SoC.
> +
> +config ARM_SPEAR_CPUFREQ
> + bool "SPEAr CPUFreq support"
> + depends on PLAT_SPEAR
> + default y
> + help
> + This adds the CPUFreq driver support for SPEAr SOCs.
> diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
> index 5b1413e..1f254ec0 100644
> --- a/drivers/cpufreq/Makefile
> +++ b/drivers/cpufreq/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
> obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o
> obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o
> obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
> +obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
>
> ##################################################################################
> # PowerPC platform drivers
> diff --git a/drivers/cpufreq/spear-cpufreq.c b/drivers/cpufreq/spear-cpufreq.c
> new file mode 100644
> index 0000000..a7fe880
> --- /dev/null
> +++ b/drivers/cpufreq/spear-cpufreq.c
> @@ -0,0 +1,291 @@
> +/*
> + * drivers/cpufreq/spear-cpufreq.c
> + *
> + * CPU Frequency Scaling for SPEAr platform
> + *
> + * Copyright (C) 2012 ST Microelectronics
> + * Deepak Sikri <deepak.sikri at st.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/clk.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +/* SPEAr CPUFreq driver data structure */
> +static struct {
> + struct clk *clk;
> + unsigned int transition_latency;
> + struct cpufreq_frequency_table *freq_tbl;
> + u32 cnt;
> +} spear_cpufreq;
> +
> +int spear_cpufreq_verify(struct cpufreq_policy *policy)
> +{
> + return cpufreq_frequency_table_verify(policy, spear_cpufreq.freq_tbl);
> +}
> +
> +static unsigned int spear_cpufreq_get(unsigned int cpu)
> +{
> + return clk_get_rate(spear_cpufreq.clk) / 1000;
> +}
> +
> +static struct clk *spear1340_cpu_get_possible_parent(unsigned long newfreq)
> +{
> + struct clk *sys_pclk;
> + int pclk;
> + /*
> + * In SPEAr1340, cpu clk's parent sys clk can take input from
> + * following sources
> + */
> + const char *sys_clk_src[] = {
> + "sys_syn_clk",
> + "pll1_clk",
> + "pll2_clk",
> + "pll3_clk",
> + };
> +
> + /*
> + * As sys clk can have multiple source with their own range
> + * limitation so we choose possible sources accordingly
> + */
> + if (newfreq <= 300000000)
> + pclk = 0; /* src is sys_syn_clk */
> + else if (newfreq > 300000000 && newfreq <= 500000000)
> + pclk = 3; /* src is pll3_clk */
> + else if (newfreq == 600000000)
> + pclk = 1; /* src is pll1_clk */
> + else
> + return ERR_PTR(-EINVAL);
> +
> + /* Get parent to sys clock */
> + sys_pclk = clk_get(NULL, sys_clk_src[pclk]);
> + if (IS_ERR(sys_pclk))
> + pr_err("Failed to get %s clock\n", sys_clk_src[pclk]);
> +
> + return sys_pclk;
> +}
> +
> +/*
> + * In SPEAr1340, we cannot use newfreq directly because we need to actually
> + * access a source clock (clk) which might not be ancestor of cpu at present.
> + * Hence in SPEAr1340 we would operate on source clock directly before switching
> + * cpu clock to it.
> + */
> +static int spear1340_set_cpu_rate(struct clk *sys_pclk, unsigned long newfreq)
> +{
> + struct clk *sys_clk;
> + int ret = 0;
> +
> + sys_clk = clk_get_parent(spear_cpufreq.clk);
> + if (IS_ERR(sys_clk)) {
> + pr_err("failed to get cpu's parent (sys) clock\n");
> + return PTR_ERR(sys_clk);
> + }
> +
> + /* Set the rate of the source clock before changing the parent */
> + ret = clk_set_rate(sys_pclk, newfreq);
> + if (ret) {
> + pr_err("Failed to set sys clk rate to %lu\n", newfreq);
> + return ret;
> + }
> +
> + ret = clk_set_parent(sys_clk, sys_pclk);
> + if (ret) {
> + pr_err("Failed to set sys clk parent\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int spear_cpufreq_target(struct cpufreq_policy *policy,
> + unsigned int target_freq, unsigned int relation)
> +{
> + struct cpufreq_freqs freqs;
> + unsigned long newfreq;
> + struct clk *srcclk;
> + int index, ret, mult = 1;
> +
> + if (cpufreq_frequency_table_target(policy, spear_cpufreq.freq_tbl,
> + target_freq, relation, &index))
> + return -EINVAL;
> +
> + freqs.cpu = policy->cpu;
> + freqs.old = spear_cpufreq_get(0);
> +
> + newfreq = spear_cpufreq.freq_tbl[index].frequency * 1000;
> + if (of_machine_is_compatible("st,spear1340")) {
> + /*
> + * SPEAr1340 is special in the sense that due to the possibility
> + * of multiple clock sources for cpu clk's parent we can have
> + * different clock source for different frequency of cpu clk.
> + * Hence we need to choose one from amongst these possible clock
> + * sources.
> + */
> + srcclk = spear1340_cpu_get_possible_parent(newfreq);
>From what I can see, if spear1340 clock driver can handle such special
setup of clk, the driver will be nothing SPEAr specific and can probably
be saved by just using cpufreq-cpu0.
Copy Mike to know what's the best way of handling this in clock code.
Shawn
> + if (IS_ERR(srcclk)) {
> + pr_err("Failed to get src clk\n");
> + return PTR_ERR(srcclk);
> + }
> +
> + /* SPEAr1340: src clk is always 2 * intended cpu clk */
> + mult = 2;
> + } else {
> + /*
> + * src clock to be altered is ancestor of cpu clock. Hence we
> + * can directly work on cpu clk
> + */
> + srcclk = spear_cpufreq.clk;
> + }
> +
> + newfreq = clk_round_rate(srcclk, newfreq * mult);
> + if (newfreq < 0) {
> + pr_err("clk_round_rate failed for cpu src clock\n");
> + return newfreq;
> + }
> +
> + freqs.new = newfreq / 1000;
> + freqs.new /= mult;
> + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
> +
> + if (mult == 2)
> + ret = spear1340_set_cpu_rate(srcclk, newfreq);
> + else
> + ret = clk_set_rate(spear_cpufreq.clk, newfreq);
> +
> + /* Get current rate after clk_set_rate, in case of failure */
> + if (ret) {
> + pr_err("CPU Freq: cpu clk_set_rate failed: %d\n", ret);
> + freqs.new = clk_get_rate(spear_cpufreq.clk) / 1000;
> + }
> +
> + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
> + return ret;
> +}
> +
> +static int spear_cpufreq_init(struct cpufreq_policy *policy)
> +{
> + int ret;
> +
> + ret = cpufreq_frequency_table_cpuinfo(policy, spear_cpufreq.freq_tbl);
> + if (ret) {
> + pr_err("cpufreq_frequency_table_cpuinfo() failed");
> + return ret;
> + }
> +
> + cpufreq_frequency_table_get_attr(spear_cpufreq.freq_tbl, policy->cpu);
> + policy->cpuinfo.transition_latency = spear_cpufreq.transition_latency;
> + policy->cur = spear_cpufreq_get(0);
> +
> + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
> + cpumask_copy(policy->related_cpus, policy->cpus);
> +
> + return 0;
> +}
> +
> +static int spear_cpufreq_exit(struct cpufreq_policy *policy)
> +{
> + cpufreq_frequency_table_put_attr(policy->cpu);
> + return 0;
> +}
> +
> +static struct freq_attr *spear_cpufreq_attr[] = {
> + &cpufreq_freq_attr_scaling_available_freqs,
> + NULL,
> +};
> +
> +static struct cpufreq_driver spear_cpufreq_driver = {
> + .name = "cpufreq-spear",
> + .flags = CPUFREQ_STICKY,
> + .verify = spear_cpufreq_verify,
> + .target = spear_cpufreq_target,
> + .get = spear_cpufreq_get,
> + .init = spear_cpufreq_init,
> + .exit = spear_cpufreq_exit,
> + .attr = spear_cpufreq_attr,
> +};
> +
> +static int spear_cpufreq_driver_init(void)
> +{
> + struct device_node *np;
> + const struct property *prop;
> + struct cpufreq_frequency_table *freq_tbl;
> + const __be32 *val;
> + int cnt, i, ret;
> +
> + np = of_find_compatible_node(NULL, NULL, "st,cpufreq-spear");
> + if (!np) {
> + pr_err("No cpufreq node found");
> + return -ENODEV;
> + }
> +
> + if (of_property_read_u32(np, "clock-latency",
> + &spear_cpufreq.transition_latency))
> + spear_cpufreq.transition_latency = CPUFREQ_ETERNAL;
> +
> + prop = of_find_property(np, "cpufreq_tbl", NULL);
> + if (!prop || !prop->value) {
> + pr_err("Invalid cpufreq_tbl");
> + ret = -ENODEV;
> + goto out_put_node;
> + }
> +
> + cnt = prop->length / sizeof(u32);
> + val = prop->value;
> +
> + freq_tbl = kmalloc(sizeof(*freq_tbl) * (cnt + 1), GFP_KERNEL);
> + if (!freq_tbl) {
> + ret = -ENOMEM;
> + goto out_put_node;
> + }
> +
> + for (i = 0; i < cnt; i++) {
> + freq_tbl[i].index = i;
> + freq_tbl[i].frequency = be32_to_cpup(val++);
> + }
> +
> + freq_tbl[i].index = i;
> + freq_tbl[i].frequency = CPUFREQ_TABLE_END;
> +
> + spear_cpufreq.freq_tbl = freq_tbl;
> +
> + of_node_put(np);
> +
> + spear_cpufreq.clk = clk_get(NULL, "cpu_clk");
> + if (IS_ERR(spear_cpufreq.clk)) {
> + pr_err("Unable to get CPU clock\n");
> + ret = PTR_ERR(spear_cpufreq.clk);
> + goto out_put_mem;
> + }
> +
> + ret = cpufreq_register_driver(&spear_cpufreq_driver);
> + if (!ret)
> + return 0;
> +
> + pr_err("failed register driver: %d\n", ret);
> + clk_put(spear_cpufreq.clk);
> +
> +out_put_mem:
> + kfree(freq_tbl);
> + return ret;
> +
> +out_put_node:
> + of_node_put(np);
> + return ret;
> +}
> +late_initcall(spear_cpufreq_driver_init);
> +
> +MODULE_AUTHOR("Deepak Sikri <deepak.sikri at st.com>");
> +MODULE_DESCRIPTION("SPEAr CPUFreq driver");
> +MODULE_LICENSE("GPL");
> --
> 1.7.12.rc2.18.g61b472e
>
> _______________________________________________
> devicetree-discuss mailing list
> devicetree-discuss at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/devicetree-discuss
More information about the linux-arm-kernel
mailing list