Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cpufreq: kirkwood: Add a cpufreq driver for Marvell Kirkwood SoCs

The Marvell Kirkwood SoCs have simple cpufreq support in hardware. The
CPU can either use the a high speed cpu clock, or the slower DDR
clock. Add a driver to swap between these two clock sources.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Acked-by: Jason Cooper <jason@lakedaemon.net>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Andrew Lunn and committed by
Rafael J. Wysocki
2a4bd9f0 93f0822d

+294
+27
Documentation/devicetree/bindings/arm/kirkwood.txt
··· 1 + Marvell Kirkwood Platforms Device Tree Bindings 2 + ----------------------------------------------- 3 + 4 + Boards with a SoC of the Marvell Kirkwood 5 + shall have the following property: 6 + 7 + Required root node property: 8 + 9 + compatible: must contain "marvell,kirkwood"; 10 + 11 + In order to support the kirkwood cpufreq driver, there must be a node 12 + cpus/cpu@0 with three clocks, "cpu_clk", "ddrclk" and "powersave", 13 + where the "powersave" clock is a gating clock used to switch the CPU 14 + between the "cpu_clk" and the "ddrclk". 15 + 16 + Example: 17 + 18 + cpus { 19 + #address-cells = <1>; 20 + #size-cells = <0>; 21 + 22 + cpu@0 { 23 + device_type = "cpu"; 24 + compatible = "marvell,sheeva-88SV131"; 25 + clocks = <&core_clk 1>, <&core_clk 3>, <&gate_clk 11>; 26 + clock-names = "cpu_clk", "ddrclk", "powersave"; 27 + };
+1
drivers/clk/mvebu/clk-gating-ctrl.c
··· 193 193 { "runit", NULL, 7 }, 194 194 { "xor0", NULL, 8 }, 195 195 { "audio", NULL, 9 }, 196 + { "powersave", "cpuclk", 11 }, 196 197 { "sata0", NULL, 14 }, 197 198 { "sata1", NULL, 15 }, 198 199 { "xor1", NULL, 16 },
+6
drivers/cpufreq/Kconfig.arm
··· 77 77 This adds the CPUFreq driver for Samsung EXYNOS5250 78 78 SoC. 79 79 80 + config ARM_KIRKWOOD_CPUFREQ 81 + def_bool ARCH_KIRKWOOD && OF 82 + help 83 + This adds the CPUFreq driver for Marvell Kirkwood 84 + SoCs. 85 + 80 86 config ARM_IMX6Q_CPUFREQ 81 87 tristate "Freescale i.MX6Q cpufreq support" 82 88 depends on SOC_IMX6Q
+1
drivers/cpufreq/Makefile
··· 52 52 obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o 53 53 obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o 54 54 obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o 55 + obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o 55 56 obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o 56 57 obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o 57 58 obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o
+259
drivers/cpufreq/kirkwood-cpufreq.c
··· 1 + /* 2 + * kirkwood_freq.c: cpufreq driver for the Marvell kirkwood 3 + * 4 + * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch> 5 + * 6 + * This program is free software; you can redistribute it and/or 7 + * modify it under the terms of the GNU General Public License 8 + * as published by the Free Software Foundation; either version 9 + * 2 of the License, or (at your option) any later version. 10 + */ 11 + 12 + #include <linux/kernel.h> 13 + #include <linux/module.h> 14 + #include <linux/clk.h> 15 + #include <linux/clk-provider.h> 16 + #include <linux/cpufreq.h> 17 + #include <linux/of.h> 18 + #include <linux/platform_device.h> 19 + #include <linux/io.h> 20 + #include <asm/proc-fns.h> 21 + 22 + #define CPU_SW_INT_BLK BIT(28) 23 + 24 + static struct priv 25 + { 26 + struct clk *cpu_clk; 27 + struct clk *ddr_clk; 28 + struct clk *powersave_clk; 29 + struct device *dev; 30 + void __iomem *base; 31 + } priv; 32 + 33 + #define STATE_CPU_FREQ 0x01 34 + #define STATE_DDR_FREQ 0x02 35 + 36 + /* 37 + * Kirkwood can swap the clock to the CPU between two clocks: 38 + * 39 + * - cpu clk 40 + * - ddr clk 41 + * 42 + * The frequencies are set at runtime before registering this * 43 + * table. 44 + */ 45 + static struct cpufreq_frequency_table kirkwood_freq_table[] = { 46 + {STATE_CPU_FREQ, 0}, /* CPU uses cpuclk */ 47 + {STATE_DDR_FREQ, 0}, /* CPU uses ddrclk */ 48 + {0, CPUFREQ_TABLE_END}, 49 + }; 50 + 51 + static unsigned int kirkwood_cpufreq_get_cpu_frequency(unsigned int cpu) 52 + { 53 + if (__clk_is_enabled(priv.powersave_clk)) 54 + return kirkwood_freq_table[1].frequency; 55 + return kirkwood_freq_table[0].frequency; 56 + } 57 + 58 + static void kirkwood_cpufreq_set_cpu_state(unsigned int index) 59 + { 60 + struct cpufreq_freqs freqs; 61 + unsigned int state = kirkwood_freq_table[index].index; 62 + unsigned long reg; 63 + 64 + freqs.old = kirkwood_cpufreq_get_cpu_frequency(0); 65 + freqs.new = kirkwood_freq_table[index].frequency; 66 + freqs.cpu = 0; /* Kirkwood is UP */ 67 + 68 + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); 69 + 70 + dev_dbg(priv.dev, "Attempting to set frequency to %i KHz\n", 71 + kirkwood_freq_table[index].frequency); 72 + dev_dbg(priv.dev, "old frequency was %i KHz\n", 73 + kirkwood_cpufreq_get_cpu_frequency(0)); 74 + 75 + if (freqs.old != freqs.new) { 76 + local_irq_disable(); 77 + 78 + /* Disable interrupts to the CPU */ 79 + reg = readl_relaxed(priv.base); 80 + reg |= CPU_SW_INT_BLK; 81 + writel_relaxed(reg, priv.base); 82 + 83 + switch (state) { 84 + case STATE_CPU_FREQ: 85 + clk_disable(priv.powersave_clk); 86 + break; 87 + case STATE_DDR_FREQ: 88 + clk_enable(priv.powersave_clk); 89 + break; 90 + } 91 + 92 + /* Wait-for-Interrupt, while the hardware changes frequency */ 93 + cpu_do_idle(); 94 + 95 + /* Enable interrupts to the CPU */ 96 + reg = readl_relaxed(priv.base); 97 + reg &= ~CPU_SW_INT_BLK; 98 + writel_relaxed(reg, priv.base); 99 + 100 + local_irq_enable(); 101 + } 102 + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 103 + }; 104 + 105 + static int kirkwood_cpufreq_verify(struct cpufreq_policy *policy) 106 + { 107 + return cpufreq_frequency_table_verify(policy, kirkwood_freq_table); 108 + } 109 + 110 + static int kirkwood_cpufreq_target(struct cpufreq_policy *policy, 111 + unsigned int target_freq, 112 + unsigned int relation) 113 + { 114 + unsigned int index = 0; 115 + 116 + if (cpufreq_frequency_table_target(policy, kirkwood_freq_table, 117 + target_freq, relation, &index)) 118 + return -EINVAL; 119 + 120 + kirkwood_cpufreq_set_cpu_state(index); 121 + 122 + return 0; 123 + } 124 + 125 + /* Module init and exit code */ 126 + static int kirkwood_cpufreq_cpu_init(struct cpufreq_policy *policy) 127 + { 128 + int result; 129 + 130 + /* cpuinfo and default policy values */ 131 + policy->cpuinfo.transition_latency = 5000; /* 5uS */ 132 + policy->cur = kirkwood_cpufreq_get_cpu_frequency(0); 133 + 134 + result = cpufreq_frequency_table_cpuinfo(policy, kirkwood_freq_table); 135 + if (result) 136 + return result; 137 + 138 + cpufreq_frequency_table_get_attr(kirkwood_freq_table, policy->cpu); 139 + 140 + return 0; 141 + } 142 + 143 + static int kirkwood_cpufreq_cpu_exit(struct cpufreq_policy *policy) 144 + { 145 + cpufreq_frequency_table_put_attr(policy->cpu); 146 + return 0; 147 + } 148 + 149 + static struct freq_attr *kirkwood_cpufreq_attr[] = { 150 + &cpufreq_freq_attr_scaling_available_freqs, 151 + NULL, 152 + }; 153 + 154 + static struct cpufreq_driver kirkwood_cpufreq_driver = { 155 + .get = kirkwood_cpufreq_get_cpu_frequency, 156 + .verify = kirkwood_cpufreq_verify, 157 + .target = kirkwood_cpufreq_target, 158 + .init = kirkwood_cpufreq_cpu_init, 159 + .exit = kirkwood_cpufreq_cpu_exit, 160 + .name = "kirkwood-cpufreq", 161 + .owner = THIS_MODULE, 162 + .attr = kirkwood_cpufreq_attr, 163 + }; 164 + 165 + static int kirkwood_cpufreq_probe(struct platform_device *pdev) 166 + { 167 + struct device_node *np; 168 + struct resource *res; 169 + int err; 170 + 171 + priv.dev = &pdev->dev; 172 + 173 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 174 + if (!res) { 175 + dev_err(&pdev->dev, "Cannot get memory resource\n"); 176 + return -ENODEV; 177 + } 178 + priv.base = devm_request_and_ioremap(&pdev->dev, res); 179 + if (!priv.base) { 180 + dev_err(&pdev->dev, "Cannot ioremap\n"); 181 + return -EADDRNOTAVAIL; 182 + } 183 + 184 + np = of_find_node_by_path("/cpus/cpu@0"); 185 + if (!np) 186 + return -ENODEV; 187 + 188 + priv.cpu_clk = of_clk_get_by_name(np, "cpu_clk"); 189 + if (IS_ERR(priv.cpu_clk)) { 190 + dev_err(priv.dev, "Unable to get cpuclk"); 191 + return PTR_ERR(priv.cpu_clk); 192 + } 193 + 194 + clk_prepare_enable(priv.cpu_clk); 195 + kirkwood_freq_table[0].frequency = clk_get_rate(priv.cpu_clk) / 1000; 196 + 197 + priv.ddr_clk = of_clk_get_by_name(np, "ddrclk"); 198 + if (IS_ERR(priv.ddr_clk)) { 199 + dev_err(priv.dev, "Unable to get ddrclk"); 200 + err = PTR_ERR(priv.ddr_clk); 201 + goto out_cpu; 202 + } 203 + 204 + clk_prepare_enable(priv.ddr_clk); 205 + kirkwood_freq_table[1].frequency = clk_get_rate(priv.ddr_clk) / 1000; 206 + 207 + priv.powersave_clk = of_clk_get_by_name(np, "powersave"); 208 + if (IS_ERR(priv.powersave_clk)) { 209 + dev_err(priv.dev, "Unable to get powersave"); 210 + err = PTR_ERR(priv.powersave_clk); 211 + goto out_ddr; 212 + } 213 + clk_prepare(priv.powersave_clk); 214 + 215 + of_node_put(np); 216 + np = NULL; 217 + 218 + err = cpufreq_register_driver(&kirkwood_cpufreq_driver); 219 + if (!err) 220 + return 0; 221 + 222 + dev_err(priv.dev, "Failed to register cpufreq driver"); 223 + 224 + clk_disable_unprepare(priv.powersave_clk); 225 + out_ddr: 226 + clk_disable_unprepare(priv.ddr_clk); 227 + out_cpu: 228 + clk_disable_unprepare(priv.cpu_clk); 229 + of_node_put(np); 230 + 231 + return err; 232 + } 233 + 234 + static int kirkwood_cpufreq_remove(struct platform_device *pdev) 235 + { 236 + cpufreq_unregister_driver(&kirkwood_cpufreq_driver); 237 + 238 + clk_disable_unprepare(priv.powersave_clk); 239 + clk_disable_unprepare(priv.ddr_clk); 240 + clk_disable_unprepare(priv.cpu_clk); 241 + 242 + return 0; 243 + } 244 + 245 + static struct platform_driver kirkwood_cpufreq_platform_driver = { 246 + .probe = kirkwood_cpufreq_probe, 247 + .remove = kirkwood_cpufreq_remove, 248 + .driver = { 249 + .name = "kirkwood-cpufreq", 250 + .owner = THIS_MODULE, 251 + }, 252 + }; 253 + 254 + module_platform_driver(kirkwood_cpufreq_platform_driver); 255 + 256 + MODULE_LICENSE("GPL v2"); 257 + MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch"); 258 + MODULE_DESCRIPTION("cpufreq driver for Marvell's kirkwood CPU"); 259 + MODULE_ALIAS("platform:kirkwood-cpufreq");