diff options
| author | Kelvin Cheung <keguang.zhang@gmail.com> | 2014-10-17 06:23:31 -0400 |
|---|---|---|
| committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-11-05 19:23:44 -0500 |
| commit | a0a22cf14472fa5cd44c8d107eba1a827df9549c (patch) | |
| tree | 270046b54c19dfb657cfbe01792190e106ac8bf5 /drivers | |
| parent | b3a826c05bd96122fd54c297062c495dbcc7511e (diff) | |
cpufreq: Loongson1: Add cpufreq driver for Loongson1B
This patch adds cpufreq driver for Loongson1B which
is capable of changing the CPU frequency dynamically.
Signed-off-by: Kelvin Cheung <keguang.zhang@gmail.com>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/cpufreq/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/cpufreq/Makefile | 1 | ||||
| -rw-r--r-- | drivers/cpufreq/ls1x-cpufreq.c | 223 |
3 files changed, 234 insertions, 0 deletions
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 73df7dbc250a..4de4dfae4ccc 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig | |||
| @@ -249,6 +249,16 @@ config LOONGSON2_CPUFREQ | |||
| 249 | 249 | ||
| 250 | If in doubt, say N. | 250 | If in doubt, say N. |
| 251 | 251 | ||
| 252 | config LOONGSON1_CPUFREQ | ||
| 253 | tristate "Loongson1 CPUFreq Driver" | ||
| 254 | help | ||
| 255 | This option adds a CPUFreq driver for loongson1 processors which | ||
| 256 | support software configurable cpu frequency. | ||
| 257 | |||
| 258 | For details, take a look at <file:Documentation/cpu-freq/>. | ||
| 259 | |||
| 260 | If in doubt, say N. | ||
| 261 | |||
| 252 | endmenu | 262 | endmenu |
| 253 | 263 | ||
| 254 | menu "PowerPC CPU frequency scaling drivers" | 264 | menu "PowerPC CPU frequency scaling drivers" |
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 40c53dc1937e..215e447abec6 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile | |||
| @@ -98,6 +98,7 @@ obj-$(CONFIG_CRIS_MACH_ARTPEC3) += cris-artpec3-cpufreq.o | |||
| 98 | obj-$(CONFIG_ETRAXFS) += cris-etraxfs-cpufreq.o | 98 | obj-$(CONFIG_ETRAXFS) += cris-etraxfs-cpufreq.o |
| 99 | obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o | 99 | obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o |
| 100 | obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o | 100 | obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o |
| 101 | obj-$(CONFIG_LOONGSON1_CPUFREQ) += ls1x-cpufreq.o | ||
| 101 | obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o | 102 | obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o |
| 102 | obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o | 103 | obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o |
| 103 | obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o | 104 | obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o |
diff --git a/drivers/cpufreq/ls1x-cpufreq.c b/drivers/cpufreq/ls1x-cpufreq.c new file mode 100644 index 000000000000..25fbd6a1374f --- /dev/null +++ b/drivers/cpufreq/ls1x-cpufreq.c | |||
| @@ -0,0 +1,223 @@ | |||
| 1 | /* | ||
| 2 | * CPU Frequency Scaling for Loongson 1 SoC | ||
| 3 | * | ||
| 4 | * Copyright (C) 2014 Zhang, Keguang <keguang.zhang@gmail.com> | ||
| 5 | * | ||
| 6 | * This file is licensed under the terms of the GNU General Public | ||
| 7 | * License version 2. This program is licensed "as is" without any | ||
| 8 | * warranty of any kind, whether express or implied. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include <linux/clk.h> | ||
| 12 | #include <linux/clk-provider.h> | ||
| 13 | #include <linux/cpu.h> | ||
| 14 | #include <linux/cpufreq.h> | ||
| 15 | #include <linux/delay.h> | ||
| 16 | #include <linux/module.h> | ||
| 17 | #include <linux/platform_device.h> | ||
| 18 | #include <linux/slab.h> | ||
| 19 | |||
| 20 | #include <asm/mach-loongson1/cpufreq.h> | ||
| 21 | #include <asm/mach-loongson1/loongson1.h> | ||
| 22 | |||
| 23 | static struct { | ||
| 24 | struct device *dev; | ||
| 25 | struct clk *clk; /* CPU clk */ | ||
| 26 | struct clk *mux_clk; /* MUX of CPU clk */ | ||
| 27 | struct clk *pll_clk; /* PLL clk */ | ||
| 28 | struct clk *osc_clk; /* OSC clk */ | ||
| 29 | unsigned int max_freq; | ||
| 30 | unsigned int min_freq; | ||
| 31 | } ls1x_cpufreq; | ||
| 32 | |||
| 33 | static int ls1x_cpufreq_notifier(struct notifier_block *nb, | ||
| 34 | unsigned long val, void *data) | ||
| 35 | { | ||
| 36 | if (val == CPUFREQ_POSTCHANGE) | ||
| 37 | current_cpu_data.udelay_val = loops_per_jiffy; | ||
| 38 | |||
| 39 | return NOTIFY_OK; | ||
| 40 | } | ||
| 41 | |||
| 42 | static struct notifier_block ls1x_cpufreq_notifier_block = { | ||
| 43 | .notifier_call = ls1x_cpufreq_notifier | ||
| 44 | }; | ||
| 45 | |||
| 46 | static int ls1x_cpufreq_target(struct cpufreq_policy *policy, | ||
| 47 | unsigned int index) | ||
| 48 | { | ||
| 49 | unsigned int old_freq, new_freq; | ||
| 50 | |||
| 51 | old_freq = policy->cur; | ||
| 52 | new_freq = policy->freq_table[index].frequency; | ||
| 53 | |||
| 54 | /* | ||
| 55 | * The procedure of reconfiguring CPU clk is as below. | ||
| 56 | * | ||
| 57 | * - Reparent CPU clk to OSC clk | ||
| 58 | * - Reset CPU clock (very important) | ||
| 59 | * - Reconfigure CPU DIV | ||
| 60 | * - Reparent CPU clk back to CPU DIV clk | ||
| 61 | */ | ||
| 62 | |||
| 63 | dev_dbg(ls1x_cpufreq.dev, "%u KHz --> %u KHz\n", old_freq, new_freq); | ||
| 64 | clk_set_parent(policy->clk, ls1x_cpufreq.osc_clk); | ||
| 65 | __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) | RST_CPU_EN | RST_CPU, | ||
| 66 | LS1X_CLK_PLL_DIV); | ||
| 67 | __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) & ~(RST_CPU_EN | RST_CPU), | ||
| 68 | LS1X_CLK_PLL_DIV); | ||
| 69 | clk_set_rate(ls1x_cpufreq.mux_clk, new_freq * 1000); | ||
| 70 | clk_set_parent(policy->clk, ls1x_cpufreq.mux_clk); | ||
| 71 | |||
| 72 | return 0; | ||
| 73 | } | ||
| 74 | |||
| 75 | static int ls1x_cpufreq_init(struct cpufreq_policy *policy) | ||
| 76 | { | ||
| 77 | struct cpufreq_frequency_table *freq_tbl; | ||
| 78 | unsigned int pll_freq, freq; | ||
| 79 | int steps, i, ret; | ||
| 80 | |||
| 81 | pll_freq = clk_get_rate(ls1x_cpufreq.pll_clk) / 1000; | ||
| 82 | |||
| 83 | steps = 1 << DIV_CPU_WIDTH; | ||
| 84 | freq_tbl = kzalloc(sizeof(*freq_tbl) * steps, GFP_KERNEL); | ||
| 85 | if (!freq_tbl) { | ||
| 86 | dev_err(ls1x_cpufreq.dev, | ||
| 87 | "failed to alloc cpufreq_frequency_table\n"); | ||
| 88 | ret = -ENOMEM; | ||
| 89 | goto out; | ||
| 90 | } | ||
| 91 | |||
| 92 | for (i = 0; i < (steps - 1); i++) { | ||
| 93 | freq = pll_freq / (i + 1); | ||
| 94 | if ((freq < ls1x_cpufreq.min_freq) || | ||
| 95 | (freq > ls1x_cpufreq.max_freq)) | ||
| 96 | freq_tbl[i].frequency = CPUFREQ_ENTRY_INVALID; | ||
| 97 | else | ||
| 98 | freq_tbl[i].frequency = freq; | ||
| 99 | dev_dbg(ls1x_cpufreq.dev, | ||
| 100 | "cpufreq table: index %d: frequency %d\n", i, | ||
| 101 | freq_tbl[i].frequency); | ||
| 102 | } | ||
| 103 | freq_tbl[i].frequency = CPUFREQ_TABLE_END; | ||
| 104 | |||
| 105 | policy->clk = ls1x_cpufreq.clk; | ||
| 106 | ret = cpufreq_generic_init(policy, freq_tbl, 0); | ||
| 107 | if (ret) | ||
| 108 | kfree(freq_tbl); | ||
| 109 | out: | ||
| 110 | return ret; | ||
| 111 | } | ||
| 112 | |||
| 113 | static int ls1x_cpufreq_exit(struct cpufreq_policy *policy) | ||
| 114 | { | ||
| 115 | kfree(policy->freq_table); | ||
| 116 | return 0; | ||
| 117 | } | ||
| 118 | |||
| 119 | static struct cpufreq_driver ls1x_cpufreq_driver = { | ||
| 120 | .name = "cpufreq-ls1x", | ||
| 121 | .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK, | ||
| 122 | .verify = cpufreq_generic_frequency_table_verify, | ||
| 123 | .target_index = ls1x_cpufreq_target, | ||
| 124 | .get = cpufreq_generic_get, | ||
| 125 | .init = ls1x_cpufreq_init, | ||
| 126 | .exit = ls1x_cpufreq_exit, | ||
| 127 | .attr = cpufreq_generic_attr, | ||
| 128 | }; | ||
| 129 | |||
| 130 | static int ls1x_cpufreq_remove(struct platform_device *pdev) | ||
| 131 | { | ||
| 132 | cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block, | ||
| 133 | CPUFREQ_TRANSITION_NOTIFIER); | ||
| 134 | cpufreq_unregister_driver(&ls1x_cpufreq_driver); | ||
| 135 | |||
| 136 | return 0; | ||
| 137 | } | ||
| 138 | |||
| 139 | static int ls1x_cpufreq_probe(struct platform_device *pdev) | ||
| 140 | { | ||
| 141 | struct plat_ls1x_cpufreq *pdata = pdev->dev.platform_data; | ||
| 142 | struct clk *clk; | ||
| 143 | int ret; | ||
| 144 | |||
| 145 | if (!pdata || !pdata->clk_name || !pdata->osc_clk_name) | ||
| 146 | return -EINVAL; | ||
| 147 | |||
| 148 | ls1x_cpufreq.dev = &pdev->dev; | ||
| 149 | |||
| 150 | clk = devm_clk_get(&pdev->dev, pdata->clk_name); | ||
| 151 | if (IS_ERR(clk)) { | ||
| 152 | dev_err(ls1x_cpufreq.dev, "unable to get %s clock\n", | ||
| 153 | pdata->clk_name); | ||
| 154 | ret = PTR_ERR(clk); | ||
| 155 | goto out; | ||
| 156 | } | ||
| 157 | ls1x_cpufreq.clk = clk; | ||
| 158 | |||
| 159 | clk = clk_get_parent(clk); | ||
| 160 | if (IS_ERR(clk)) { | ||
| 161 | dev_err(ls1x_cpufreq.dev, "unable to get parent of %s clock\n", | ||
| 162 | __clk_get_name(ls1x_cpufreq.clk)); | ||
| 163 | ret = PTR_ERR(clk); | ||
| 164 | goto out; | ||
| 165 | } | ||
| 166 | ls1x_cpufreq.mux_clk = clk; | ||
| 167 | |||
| 168 | clk = clk_get_parent(clk); | ||
| 169 | if (IS_ERR(clk)) { | ||
| 170 | dev_err(ls1x_cpufreq.dev, "unable to get parent of %s clock\n", | ||
| 171 | __clk_get_name(ls1x_cpufreq.mux_clk)); | ||
| 172 | ret = PTR_ERR(clk); | ||
| 173 | goto out; | ||
| 174 | } | ||
| 175 | ls1x_cpufreq.pll_clk = clk; | ||
| 176 | |||
| 177 | clk = devm_clk_get(&pdev->dev, pdata->osc_clk_name); | ||
| 178 | if (IS_ERR(clk)) { | ||
| 179 | dev_err(ls1x_cpufreq.dev, "unable to get %s clock\n", | ||
| 180 | pdata->osc_clk_name); | ||
| 181 | ret = PTR_ERR(clk); | ||
| 182 | goto out; | ||
| 183 | } | ||
| 184 | ls1x_cpufreq.osc_clk = clk; | ||
| 185 | |||
| 186 | ls1x_cpufreq.max_freq = pdata->max_freq; | ||
| 187 | ls1x_cpufreq.min_freq = pdata->min_freq; | ||
| 188 | |||
| 189 | ret = cpufreq_register_driver(&ls1x_cpufreq_driver); | ||
| 190 | if (ret) { | ||
| 191 | dev_err(ls1x_cpufreq.dev, | ||
| 192 | "failed to register cpufreq driver: %d\n", ret); | ||
| 193 | goto out; | ||
| 194 | } | ||
| 195 | |||
| 196 | ret = cpufreq_register_notifier(&ls1x_cpufreq_notifier_block, | ||
| 197 | CPUFREQ_TRANSITION_NOTIFIER); | ||
| 198 | |||
| 199 | if (!ret) | ||
| 200 | goto out; | ||
| 201 | |||
| 202 | dev_err(ls1x_cpufreq.dev, "failed to register cpufreq notifier: %d\n", | ||
| 203 | ret); | ||
| 204 | |||
| 205 | cpufreq_unregister_driver(&ls1x_cpufreq_driver); | ||
| 206 | out: | ||
| 207 | return ret; | ||
| 208 | } | ||
| 209 | |||
| 210 | static struct platform_driver ls1x_cpufreq_platdrv = { | ||
| 211 | .driver = { | ||
| 212 | .name = "ls1x-cpufreq", | ||
| 213 | .owner = THIS_MODULE, | ||
| 214 | }, | ||
| 215 | .probe = ls1x_cpufreq_probe, | ||
| 216 | .remove = ls1x_cpufreq_remove, | ||
| 217 | }; | ||
| 218 | |||
| 219 | module_platform_driver(ls1x_cpufreq_platdrv); | ||
| 220 | |||
| 221 | MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>"); | ||
| 222 | MODULE_DESCRIPTION("Loongson 1 CPUFreq driver"); | ||
| 223 | MODULE_LICENSE("GPL"); | ||
