cpufreq: powerpc: Add cpufreq driver for Freescale e500mc SoCs
authorTang Yuantian <yuantian.tang@freescale.com>
Wed, 5 Jun 2013 09:30:48 +0000 (09:30 +0000)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Wed, 5 Jun 2013 12:19:28 +0000 (14:19 +0200)
Add cpufreq driver for Freescale e500mc, e5500 and e6500 SoCs
which are capable of changing the CPU frequency dynamically

Signed-off-by: Tang Yuantian <Yuantian.Tang@freescale.com>
Signed-off-by: Li Yang <leoli@freescale.com>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/cpufreq/Kconfig.powerpc
drivers/cpufreq/Makefile
drivers/cpufreq/ppc-corenet-cpufreq.c [new file with mode: 0644]

index 9c926ca0d718020b048b1efc09b0b55c7b1efe2f..88f629e5a393af9cda7846642b7f5d57791d5a1d 100644 (file)
@@ -23,3 +23,13 @@ config CPU_FREQ_MAPLE
        help
          This adds support for frequency switching on Maple 970FX
          Evaluation Board and compatible boards (IBM JS2x blades).
+
+config PPC_CORENET_CPUFREQ
+       tristate "CPU frequency scaling driver for Freescale E500MC SoCs"
+       depends on PPC_E500MC && OF && COMMON_CLK
+       select CPU_FREQ_TABLE
+       select CLK_PPC_CORENET
+       help
+         This adds the CPUFreq driver support for Freescale e500mc,
+         e5500 and e6500 series SoCs which are capable of changing
+         the CPU's frequency dynamically.
index 315b9231feb17f00d52fd4374f47c3bd9b6d57c4..c956094ccc39e79618fcaab38094c22e67c8d993 100644 (file)
@@ -79,6 +79,7 @@ obj-$(CONFIG_CPU_FREQ_CBE)            += ppc-cbe-cpufreq.o
 ppc-cbe-cpufreq-y                      += ppc_cbe_cpufreq_pervasive.o ppc_cbe_cpufreq.o
 obj-$(CONFIG_CPU_FREQ_CBE_PMI)         += ppc_cbe_cpufreq_pmi.o
 obj-$(CONFIG_CPU_FREQ_MAPLE)           += maple-cpufreq.o
+obj-$(CONFIG_PPC_CORENET_CPUFREQ)   += ppc-corenet-cpufreq.o
 
 ##################################################################################
 # Other platform drivers
diff --git a/drivers/cpufreq/ppc-corenet-cpufreq.c b/drivers/cpufreq/ppc-corenet-cpufreq.c
new file mode 100644 (file)
index 0000000..3cae452
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2013 Freescale Semiconductor, Inc.
+ *
+ * CPU Frequency Scaling driver for Freescale PowerPC corenet SoCs.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt)    KBUILD_MODNAME ": " fmt
+
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/errno.h>
+#include <sysdev/fsl_soc.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+
+/**
+ * struct cpu_data - per CPU data struct
+ * @clk: the clk of CPU
+ * @parent: the parent node of cpu clock
+ * @table: frequency table
+ */
+struct cpu_data {
+       struct clk *clk;
+       struct device_node *parent;
+       struct cpufreq_frequency_table *table;
+};
+
+/**
+ * struct soc_data - SoC specific data
+ * @freq_mask: mask the disallowed frequencies
+ * @flag: unique flags
+ */
+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,
+       },
+};
+
+/*
+ * 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;
+
+/* serialize frequency changes  */
+static DEFINE_MUTEX(cpufreq_lock);
+static DEFINE_PER_CPU(struct cpu_data *, cpu_data);
+
+/* cpumask in a cluster */
+static DEFINE_PER_CPU(cpumask_var_t, cpu_mask);
+
+#ifndef CONFIG_SMP
+static inline const struct cpumask *cpu_core_mask(int cpu)
+{
+       return cpumask_of(0);
+}
+#endif
+
+static unsigned int corenet_cpufreq_get_speed(unsigned int cpu)
+{
+       struct cpu_data *data = per_cpu(cpu_data, cpu);
+
+       return clk_get_rate(data->clk) / 1000;
+}
+
+/* reduce the duplicated frequencies in frequency table */
+static void freq_table_redup(struct cpufreq_frequency_table *freq_table,
+               int count)
+{
+       int i, j;
+
+       for (i = 1; i < count; i++) {
+               for (j = 0; j < i; j++) {
+                       if (freq_table[j].frequency == CPUFREQ_ENTRY_INVALID ||
+                                       freq_table[j].frequency !=
+                                       freq_table[i].frequency)
+                               continue;
+
+                       freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
+                       break;
+               }
+       }
+}
+
+/* sort the frequencies in frequency table in descenting order */
+static void freq_table_sort(struct cpufreq_frequency_table *freq_table,
+               int count)
+{
+       int i, j, ind;
+       unsigned int freq, max_freq;
+       struct cpufreq_frequency_table table;
+       for (i = 0; i < count - 1; i++) {
+               max_freq = freq_table[i].frequency;
+               ind = i;
+               for (j = i + 1; j < count; j++) {
+                       freq = freq_table[j].frequency;
+                       if (freq == CPUFREQ_ENTRY_INVALID ||
+                                       freq <= max_freq)
+                               continue;
+                       ind = j;
+                       max_freq = freq;
+               }
+
+               if (ind != i) {
+                       /* exchange the frequencies */
+                       table.driver_data = freq_table[i].driver_data;
+                       table.frequency = freq_table[i].frequency;
+                       freq_table[i].driver_data = freq_table[ind].driver_data;
+                       freq_table[i].frequency = freq_table[ind].frequency;
+                       freq_table[ind].driver_data = table.driver_data;
+                       freq_table[ind].frequency = table.frequency;
+               }
+       }
+}
+
+static int corenet_cpufreq_cpu_init(struct cpufreq_policy *policy)
+{
+       struct device_node *np;
+       int i, count, ret;
+       u32 freq, mask;
+       struct clk *clk;
+       struct cpufreq_frequency_table *table;
+       struct cpu_data *data;
+       unsigned int cpu = policy->cpu;
+
+       np = of_get_cpu_node(cpu, NULL);
+       if (!np)
+               return -ENODEV;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data) {
+               pr_err("%s: no memory\n", __func__);
+               goto err_np;
+       }
+
+       data->clk = of_clk_get(np, 0);
+       if (IS_ERR(data->clk)) {
+               pr_err("%s: no clock information\n", __func__);
+               goto err_nomem2;
+       }
+
+       data->parent = of_parse_phandle(np, "clocks", 0);
+       if (!data->parent) {
+               pr_err("%s: could not get clock information\n", __func__);
+               goto err_nomem2;
+       }
+
+       count = of_property_count_strings(data->parent, "clock-names");
+       table = kcalloc(count + 1, sizeof(*table), GFP_KERNEL);
+       if (!table) {
+               pr_err("%s: no memory\n", __func__);
+               goto err_node;
+       }
+
+       if (fmask)
+               mask = fmask[get_hard_smp_processor_id(cpu)];
+       else
+               mask = 0x0;
+
+       for (i = 0; i < count; i++) {
+               clk = of_clk_get(data->parent, i);
+               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].driver_data = i;
+       }
+       freq_table_redup(table, count);
+       freq_table_sort(table, count);
+       table[i].frequency = CPUFREQ_TABLE_END;
+
+       /* set the min and max frequency properly */
+       ret = cpufreq_frequency_table_cpuinfo(policy, table);
+       if (ret) {
+               pr_err("invalid frequency table: %d\n", ret);
+               goto err_nomem1;
+       }
+
+       data->table = table;
+       per_cpu(cpu_data, cpu) = data;
+
+       /* update ->cpus if we have cluster, no harm if not */
+       cpumask_copy(policy->cpus, per_cpu(cpu_mask, cpu));
+       for_each_cpu(i, per_cpu(cpu_mask, cpu))
+               per_cpu(cpu_data, i) = data;
+
+       policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
+       policy->cur = corenet_cpufreq_get_speed(policy->cpu);
+
+       cpufreq_frequency_table_get_attr(table, cpu);
+       of_node_put(np);
+
+       return 0;
+
+err_nomem1:
+       kfree(table);
+err_node:
+       of_node_put(data->parent);
+err_nomem2:
+       per_cpu(cpu_data, cpu) = NULL;
+       kfree(data);
+err_np:
+       of_node_put(np);
+
+       return -ENODEV;
+}
+
+static int __exit corenet_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+{
+       struct cpu_data *data = per_cpu(cpu_data, policy->cpu);
+       unsigned int cpu;
+
+       cpufreq_frequency_table_put_attr(policy->cpu);
+       of_node_put(data->parent);
+       kfree(data->table);
+       kfree(data);
+
+       for_each_cpu(cpu, per_cpu(cpu_mask, policy->cpu))
+               per_cpu(cpu_data, cpu) = NULL;
+
+       return 0;
+}
+
+static int corenet_cpufreq_verify(struct cpufreq_policy *policy)
+{
+       struct cpufreq_frequency_table *table =
+               per_cpu(cpu_data, policy->cpu)->table;
+
+       return cpufreq_frequency_table_verify(policy, table);
+}
+
+static int corenet_cpufreq_target(struct cpufreq_policy *policy,
+               unsigned int target_freq, unsigned int relation)
+{
+       struct cpufreq_freqs freqs;
+       unsigned int new;
+       struct clk *parent;
+       int ret;
+       struct cpu_data *data = per_cpu(cpu_data, policy->cpu);
+
+       cpufreq_frequency_table_target(policy, data->table,
+                       target_freq, relation, &new);
+
+       if (policy->cur == data->table[new].frequency)
+               return 0;
+
+       freqs.old = policy->cur;
+       freqs.new = data->table[new].frequency;
+
+       mutex_lock(&cpufreq_lock);
+       cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
+
+       parent = of_clk_get(data->parent, data->table[new].driver_data);
+       ret = clk_set_parent(data->clk, parent);
+       if (ret)
+               freqs.new = freqs.old;
+
+       cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
+       mutex_unlock(&cpufreq_lock);
+
+       return ret;
+}
+
+static struct freq_attr *corenet_cpufreq_attr[] = {
+       &cpufreq_freq_attr_scaling_available_freqs,
+       NULL,
+};
+
+static struct cpufreq_driver ppc_corenet_cpufreq_driver = {
+       .name           = "ppc_cpufreq",
+       .owner          = THIS_MODULE,
+       .flags          = CPUFREQ_CONST_LOOPS,
+       .init           = corenet_cpufreq_cpu_init,
+       .exit           = __exit_p(corenet_cpufreq_cpu_exit),
+       .verify         = corenet_cpufreq_verify,
+       .target         = corenet_cpufreq_target,
+       .get            = corenet_cpufreq_get_speed,
+       .attr           = corenet_cpufreq_attr,
+};
+
+static const struct of_device_id node_matches[] __initdata = {
+       { .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], },
+       { .compatible = "fsl,qoriq-clockgen-2.0", },
+       {}
+};
+
+static int __init ppc_corenet_cpufreq_init(void)
+{
+       int ret;
+       struct device_node  *np;
+       const struct of_device_id *match;
+       const struct soc_data *data;
+       unsigned int cpu;
+
+       np = of_find_matching_node(NULL, node_matches);
+       if (!np)
+               return -ENODEV;
+
+       for_each_possible_cpu(cpu) {
+               if (!alloc_cpumask_var(&per_cpu(cpu_mask, cpu), GFP_KERNEL))
+                       goto err_mask;
+               cpumask_copy(per_cpu(cpu_mask, cpu), cpu_core_mask(cpu));
+       }
+
+       match = of_match_node(node_matches, np);
+       data = match->data;
+       if (data) {
+               if (data->flag)
+                       fmask = data->freq_mask;
+               min_cpufreq = fsl_get_sys_freq();
+       } else {
+               min_cpufreq = fsl_get_sys_freq() / 2;
+       }
+
+       of_node_put(np);
+
+       ret = cpufreq_register_driver(&ppc_corenet_cpufreq_driver);
+       if (!ret)
+               pr_info("Freescale PowerPC corenet CPU frequency scaling driver\n");
+
+       return ret;
+
+err_mask:
+       for_each_possible_cpu(cpu)
+               free_cpumask_var(per_cpu(cpu_mask, cpu));
+
+       return -ENOMEM;
+}
+module_init(ppc_corenet_cpufreq_init);
+
+static void __exit ppc_corenet_cpufreq_exit(void)
+{
+       unsigned int cpu;
+
+       for_each_possible_cpu(cpu)
+               free_cpumask_var(per_cpu(cpu_mask, cpu));
+
+       cpufreq_unregister_driver(&ppc_corenet_cpufreq_driver);
+}
+module_exit(ppc_corenet_cpufreq_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tang Yuantian <Yuantian.Tang@freescale.com>");
+MODULE_DESCRIPTION("cpufreq driver for Freescale e500mc series SoCs");