linux/drivers/cpufreq/ppc-corenet-cpufreq.c
<<
>>
Prefs
   1/*
   2 * Copyright 2013 Freescale Semiconductor, Inc.
   3 *
   4 * CPU Frequency Scaling driver for Freescale PowerPC corenet SoCs.
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#define pr_fmt(fmt)     KBUILD_MODNAME ": " fmt
  12
  13#include <linux/clk.h>
  14#include <linux/cpufreq.h>
  15#include <linux/errno.h>
  16#include <linux/init.h>
  17#include <linux/kernel.h>
  18#include <linux/module.h>
  19#include <linux/mutex.h>
  20#include <linux/of.h>
  21#include <linux/slab.h>
  22#include <linux/smp.h>
  23#include <sysdev/fsl_soc.h>
  24
  25/**
  26 * struct cpu_data - per CPU data struct
  27 * @parent: the parent node of cpu clock
  28 * @table: frequency table
  29 */
  30struct cpu_data {
  31        struct device_node *parent;
  32        struct cpufreq_frequency_table *table;
  33};
  34
  35/**
  36 * struct soc_data - SoC specific data
  37 * @freq_mask: mask the disallowed frequencies
  38 * @flag: unique flags
  39 */
  40struct soc_data {
  41        u32 freq_mask[4];
  42        u32 flag;
  43};
  44
  45#define FREQ_MASK       1
  46/* see hardware specification for the allowed frqeuencies */
  47static const struct soc_data sdata[] = {
  48        { /* used by p2041 and p3041 */
  49                .freq_mask = {0x8, 0x8, 0x2, 0x2},
  50                .flag = FREQ_MASK,
  51        },
  52        { /* used by p5020 */
  53                .freq_mask = {0x8, 0x2},
  54                .flag = FREQ_MASK,
  55        },
  56        { /* used by p4080, p5040 */
  57                .freq_mask = {0},
  58                .flag = 0,
  59        },
  60};
  61
  62/*
  63 * the minimum allowed core frequency, in Hz
  64 * for chassis v1.0, >= platform frequency
  65 * for chassis v2.0, >= platform frequency / 2
  66 */
  67static u32 min_cpufreq;
  68static const u32 *fmask;
  69
  70static DEFINE_PER_CPU(struct cpu_data *, cpu_data);
  71
  72/* cpumask in a cluster */
  73static DEFINE_PER_CPU(cpumask_var_t, cpu_mask);
  74
  75#ifndef CONFIG_SMP
  76static inline const struct cpumask *cpu_core_mask(int cpu)
  77{
  78        return cpumask_of(0);
  79}
  80#endif
  81
  82/* reduce the duplicated frequencies in frequency table */
  83static void freq_table_redup(struct cpufreq_frequency_table *freq_table,
  84                int count)
  85{
  86        int i, j;
  87
  88        for (i = 1; i < count; i++) {
  89                for (j = 0; j < i; j++) {
  90                        if (freq_table[j].frequency == CPUFREQ_ENTRY_INVALID ||
  91                                        freq_table[j].frequency !=
  92                                        freq_table[i].frequency)
  93                                continue;
  94
  95                        freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
  96                        break;
  97                }
  98        }
  99}
 100
 101/* sort the frequencies in frequency table in descenting order */
 102static void freq_table_sort(struct cpufreq_frequency_table *freq_table,
 103                int count)
 104{
 105        int i, j, ind;
 106        unsigned int freq, max_freq;
 107        struct cpufreq_frequency_table table;
 108        for (i = 0; i < count - 1; i++) {
 109                max_freq = freq_table[i].frequency;
 110                ind = i;
 111                for (j = i + 1; j < count; j++) {
 112                        freq = freq_table[j].frequency;
 113                        if (freq == CPUFREQ_ENTRY_INVALID ||
 114                                        freq <= max_freq)
 115                                continue;
 116                        ind = j;
 117                        max_freq = freq;
 118                }
 119
 120                if (ind != i) {
 121                        /* exchange the frequencies */
 122                        table.driver_data = freq_table[i].driver_data;
 123                        table.frequency = freq_table[i].frequency;
 124                        freq_table[i].driver_data = freq_table[ind].driver_data;
 125                        freq_table[i].frequency = freq_table[ind].frequency;
 126                        freq_table[ind].driver_data = table.driver_data;
 127                        freq_table[ind].frequency = table.frequency;
 128                }
 129        }
 130}
 131
 132static int corenet_cpufreq_cpu_init(struct cpufreq_policy *policy)
 133{
 134        struct device_node *np;
 135        int i, count, ret;
 136        u32 freq, mask;
 137        struct clk *clk;
 138        struct cpufreq_frequency_table *table;
 139        struct cpu_data *data;
 140        unsigned int cpu = policy->cpu;
 141        u64 u64temp;
 142
 143        np = of_get_cpu_node(cpu, NULL);
 144        if (!np)
 145                return -ENODEV;
 146
 147        data = kzalloc(sizeof(*data), GFP_KERNEL);
 148        if (!data) {
 149                pr_err("%s: no memory\n", __func__);
 150                goto err_np;
 151        }
 152
 153        policy->clk = of_clk_get(np, 0);
 154        if (IS_ERR(policy->clk)) {
 155                pr_err("%s: no clock information\n", __func__);
 156                goto err_nomem2;
 157        }
 158
 159        data->parent = of_parse_phandle(np, "clocks", 0);
 160        if (!data->parent) {
 161                pr_err("%s: could not get clock information\n", __func__);
 162                goto err_nomem2;
 163        }
 164
 165        count = of_property_count_strings(data->parent, "clock-names");
 166        table = kcalloc(count + 1, sizeof(*table), GFP_KERNEL);
 167        if (!table) {
 168                pr_err("%s: no memory\n", __func__);
 169                goto err_node;
 170        }
 171
 172        if (fmask)
 173                mask = fmask[get_hard_smp_processor_id(cpu)];
 174        else
 175                mask = 0x0;
 176
 177        for (i = 0; i < count; i++) {
 178                clk = of_clk_get(data->parent, i);
 179                freq = clk_get_rate(clk);
 180                /*
 181                 * the clock is valid if its frequency is not masked
 182                 * and large than minimum allowed frequency.
 183                 */
 184                if (freq < min_cpufreq || (mask & (1 << i)))
 185                        table[i].frequency = CPUFREQ_ENTRY_INVALID;
 186                else
 187                        table[i].frequency = freq / 1000;
 188                table[i].driver_data = i;
 189        }
 190        freq_table_redup(table, count);
 191        freq_table_sort(table, count);
 192        table[i].frequency = CPUFREQ_TABLE_END;
 193
 194        /* set the min and max frequency properly */
 195        ret = cpufreq_table_validate_and_show(policy, table);
 196        if (ret) {
 197                pr_err("invalid frequency table: %d\n", ret);
 198                goto err_nomem1;
 199        }
 200
 201        data->table = table;
 202
 203        /* update ->cpus if we have cluster, no harm if not */
 204        cpumask_copy(policy->cpus, per_cpu(cpu_mask, cpu));
 205        for_each_cpu(i, per_cpu(cpu_mask, cpu))
 206                per_cpu(cpu_data, i) = data;
 207
 208        /* Minimum transition latency is 12 platform clocks */
 209        u64temp = 12ULL * NSEC_PER_SEC;
 210        do_div(u64temp, fsl_get_sys_freq());
 211        policy->cpuinfo.transition_latency = u64temp + 1;
 212
 213        of_node_put(np);
 214
 215        return 0;
 216
 217err_nomem1:
 218        kfree(table);
 219err_node:
 220        of_node_put(data->parent);
 221err_nomem2:
 222        per_cpu(cpu_data, cpu) = NULL;
 223        kfree(data);
 224err_np:
 225        of_node_put(np);
 226
 227        return -ENODEV;
 228}
 229
 230static int __exit corenet_cpufreq_cpu_exit(struct cpufreq_policy *policy)
 231{
 232        struct cpu_data *data = per_cpu(cpu_data, policy->cpu);
 233        unsigned int cpu;
 234
 235        of_node_put(data->parent);
 236        kfree(data->table);
 237        kfree(data);
 238
 239        for_each_cpu(cpu, per_cpu(cpu_mask, policy->cpu))
 240                per_cpu(cpu_data, cpu) = NULL;
 241
 242        return 0;
 243}
 244
 245static int corenet_cpufreq_target(struct cpufreq_policy *policy,
 246                unsigned int index)
 247{
 248        struct clk *parent;
 249        struct cpu_data *data = per_cpu(cpu_data, policy->cpu);
 250
 251        parent = of_clk_get(data->parent, data->table[index].driver_data);
 252        return clk_set_parent(policy->clk, parent);
 253}
 254
 255static struct cpufreq_driver ppc_corenet_cpufreq_driver = {
 256        .name           = "ppc_cpufreq",
 257        .flags          = CPUFREQ_CONST_LOOPS,
 258        .init           = corenet_cpufreq_cpu_init,
 259        .exit           = __exit_p(corenet_cpufreq_cpu_exit),
 260        .verify         = cpufreq_generic_frequency_table_verify,
 261        .target_index   = corenet_cpufreq_target,
 262        .get            = cpufreq_generic_get,
 263        .attr           = cpufreq_generic_attr,
 264};
 265
 266static const struct of_device_id node_matches[] __initdata = {
 267        { .compatible = "fsl,p2041-clockgen", .data = &sdata[0], },
 268        { .compatible = "fsl,p3041-clockgen", .data = &sdata[0], },
 269        { .compatible = "fsl,p5020-clockgen", .data = &sdata[1], },
 270        { .compatible = "fsl,p4080-clockgen", .data = &sdata[2], },
 271        { .compatible = "fsl,p5040-clockgen", .data = &sdata[2], },
 272        { .compatible = "fsl,qoriq-clockgen-2.0", },
 273        {}
 274};
 275
 276static int __init ppc_corenet_cpufreq_init(void)
 277{
 278        int ret;
 279        struct device_node  *np;
 280        const struct of_device_id *match;
 281        const struct soc_data *data;
 282        unsigned int cpu;
 283
 284        np = of_find_matching_node(NULL, node_matches);
 285        if (!np)
 286                return -ENODEV;
 287
 288        for_each_possible_cpu(cpu) {
 289                if (!alloc_cpumask_var(&per_cpu(cpu_mask, cpu), GFP_KERNEL))
 290                        goto err_mask;
 291                cpumask_copy(per_cpu(cpu_mask, cpu), cpu_core_mask(cpu));
 292        }
 293
 294        match = of_match_node(node_matches, np);
 295        data = match->data;
 296        if (data) {
 297                if (data->flag)
 298                        fmask = data->freq_mask;
 299                min_cpufreq = fsl_get_sys_freq();
 300        } else {
 301                min_cpufreq = fsl_get_sys_freq() / 2;
 302        }
 303
 304        of_node_put(np);
 305
 306        ret = cpufreq_register_driver(&ppc_corenet_cpufreq_driver);
 307        if (!ret)
 308                pr_info("Freescale PowerPC corenet CPU frequency scaling driver\n");
 309
 310        return ret;
 311
 312err_mask:
 313        for_each_possible_cpu(cpu)
 314                free_cpumask_var(per_cpu(cpu_mask, cpu));
 315
 316        return -ENOMEM;
 317}
 318module_init(ppc_corenet_cpufreq_init);
 319
 320static void __exit ppc_corenet_cpufreq_exit(void)
 321{
 322        unsigned int cpu;
 323
 324        for_each_possible_cpu(cpu)
 325                free_cpumask_var(per_cpu(cpu_mask, cpu));
 326
 327        cpufreq_unregister_driver(&ppc_corenet_cpufreq_driver);
 328}
 329module_exit(ppc_corenet_cpufreq_exit);
 330
 331MODULE_LICENSE("GPL");
 332MODULE_AUTHOR("Tang Yuantian <Yuantian.Tang@freescale.com>");
 333MODULE_DESCRIPTION("cpufreq driver for Freescale e500mc series SoCs");
 334
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.