linux/drivers/cpufreq/kirkwood-cpufreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *      kirkwood_freq.c: cpufreq driver for the Marvell kirkwood
   4 *
   5 *      Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
   6 */
   7
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/clk.h>
  11#include <linux/cpufreq.h>
  12#include <linux/of_device.h>
  13#include <linux/platform_device.h>
  14#include <linux/io.h>
  15#include <asm/proc-fns.h>
  16
  17#define CPU_SW_INT_BLK BIT(28)
  18
  19static struct priv
  20{
  21        struct clk *cpu_clk;
  22        struct clk *ddr_clk;
  23        struct clk *powersave_clk;
  24        struct device *dev;
  25        void __iomem *base;
  26} priv;
  27
  28#define STATE_CPU_FREQ 0x01
  29#define STATE_DDR_FREQ 0x02
  30
  31/*
  32 * Kirkwood can swap the clock to the CPU between two clocks:
  33 *
  34 * - cpu clk
  35 * - ddr clk
  36 *
  37 * The frequencies are set at runtime before registering this table.
  38 */
  39static struct cpufreq_frequency_table kirkwood_freq_table[] = {
  40        {0, STATE_CPU_FREQ,     0}, /* CPU uses cpuclk */
  41        {0, STATE_DDR_FREQ,     0}, /* CPU uses ddrclk */
  42        {0, 0,                  CPUFREQ_TABLE_END},
  43};
  44
  45static unsigned int kirkwood_cpufreq_get_cpu_frequency(unsigned int cpu)
  46{
  47        return clk_get_rate(priv.powersave_clk) / 1000;
  48}
  49
  50static int kirkwood_cpufreq_target(struct cpufreq_policy *policy,
  51                            unsigned int index)
  52{
  53        unsigned int state = kirkwood_freq_table[index].driver_data;
  54        unsigned long reg;
  55
  56        local_irq_disable();
  57
  58        /* Disable interrupts to the CPU */
  59        reg = readl_relaxed(priv.base);
  60        reg |= CPU_SW_INT_BLK;
  61        writel_relaxed(reg, priv.base);
  62
  63        switch (state) {
  64        case STATE_CPU_FREQ:
  65                clk_set_parent(priv.powersave_clk, priv.cpu_clk);
  66                break;
  67        case STATE_DDR_FREQ:
  68                clk_set_parent(priv.powersave_clk, priv.ddr_clk);
  69                break;
  70        }
  71
  72        /* Wait-for-Interrupt, while the hardware changes frequency */
  73        cpu_do_idle();
  74
  75        /* Enable interrupts to the CPU */
  76        reg = readl_relaxed(priv.base);
  77        reg &= ~CPU_SW_INT_BLK;
  78        writel_relaxed(reg, priv.base);
  79
  80        local_irq_enable();
  81
  82        return 0;
  83}
  84
  85/* Module init and exit code */
  86static int kirkwood_cpufreq_cpu_init(struct cpufreq_policy *policy)
  87{
  88        cpufreq_generic_init(policy, kirkwood_freq_table, 5000);
  89        return 0;
  90}
  91
  92static struct cpufreq_driver kirkwood_cpufreq_driver = {
  93        .flags  = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
  94        .get    = kirkwood_cpufreq_get_cpu_frequency,
  95        .verify = cpufreq_generic_frequency_table_verify,
  96        .target_index = kirkwood_cpufreq_target,
  97        .init   = kirkwood_cpufreq_cpu_init,
  98        .name   = "kirkwood-cpufreq",
  99        .attr   = cpufreq_generic_attr,
 100};
 101
 102static int kirkwood_cpufreq_probe(struct platform_device *pdev)
 103{
 104        struct device_node *np;
 105        int err;
 106
 107        priv.dev = &pdev->dev;
 108
 109        priv.base = devm_platform_ioremap_resource(pdev, 0);
 110        if (IS_ERR(priv.base))
 111                return PTR_ERR(priv.base);
 112
 113        np = of_cpu_device_node_get(0);
 114        if (!np) {
 115                dev_err(&pdev->dev, "failed to get cpu device node\n");
 116                return -ENODEV;
 117        }
 118
 119        priv.cpu_clk = of_clk_get_by_name(np, "cpu_clk");
 120        if (IS_ERR(priv.cpu_clk)) {
 121                dev_err(priv.dev, "Unable to get cpuclk\n");
 122                err = PTR_ERR(priv.cpu_clk);
 123                goto out_node;
 124        }
 125
 126        err = clk_prepare_enable(priv.cpu_clk);
 127        if (err) {
 128                dev_err(priv.dev, "Unable to prepare cpuclk\n");
 129                goto out_node;
 130        }
 131
 132        kirkwood_freq_table[0].frequency = clk_get_rate(priv.cpu_clk) / 1000;
 133
 134        priv.ddr_clk = of_clk_get_by_name(np, "ddrclk");
 135        if (IS_ERR(priv.ddr_clk)) {
 136                dev_err(priv.dev, "Unable to get ddrclk\n");
 137                err = PTR_ERR(priv.ddr_clk);
 138                goto out_cpu;
 139        }
 140
 141        err = clk_prepare_enable(priv.ddr_clk);
 142        if (err) {
 143                dev_err(priv.dev, "Unable to prepare ddrclk\n");
 144                goto out_cpu;
 145        }
 146        kirkwood_freq_table[1].frequency = clk_get_rate(priv.ddr_clk) / 1000;
 147
 148        priv.powersave_clk = of_clk_get_by_name(np, "powersave");
 149        if (IS_ERR(priv.powersave_clk)) {
 150                dev_err(priv.dev, "Unable to get powersave\n");
 151                err = PTR_ERR(priv.powersave_clk);
 152                goto out_ddr;
 153        }
 154        err = clk_prepare_enable(priv.powersave_clk);
 155        if (err) {
 156                dev_err(priv.dev, "Unable to prepare powersave clk\n");
 157                goto out_ddr;
 158        }
 159
 160        err = cpufreq_register_driver(&kirkwood_cpufreq_driver);
 161        if (err) {
 162                dev_err(priv.dev, "Failed to register cpufreq driver\n");
 163                goto out_powersave;
 164        }
 165
 166        of_node_put(np);
 167        return 0;
 168
 169out_powersave:
 170        clk_disable_unprepare(priv.powersave_clk);
 171out_ddr:
 172        clk_disable_unprepare(priv.ddr_clk);
 173out_cpu:
 174        clk_disable_unprepare(priv.cpu_clk);
 175out_node:
 176        of_node_put(np);
 177
 178        return err;
 179}
 180
 181static int kirkwood_cpufreq_remove(struct platform_device *pdev)
 182{
 183        cpufreq_unregister_driver(&kirkwood_cpufreq_driver);
 184
 185        clk_disable_unprepare(priv.powersave_clk);
 186        clk_disable_unprepare(priv.ddr_clk);
 187        clk_disable_unprepare(priv.cpu_clk);
 188
 189        return 0;
 190}
 191
 192static struct platform_driver kirkwood_cpufreq_platform_driver = {
 193        .probe = kirkwood_cpufreq_probe,
 194        .remove = kirkwood_cpufreq_remove,
 195        .driver = {
 196                .name = "kirkwood-cpufreq",
 197        },
 198};
 199
 200module_platform_driver(kirkwood_cpufreq_platform_driver);
 201
 202MODULE_LICENSE("GPL v2");
 203MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch");
 204MODULE_DESCRIPTION("cpufreq driver for Marvell's kirkwood CPU");
 205MODULE_ALIAS("platform:kirkwood-cpufreq");
 206