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 23static 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 33static 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 42static struct notifier_block ls1x_cpufreq_notifier_block = { 43 .notifier_call = ls1x_cpufreq_notifier 44}; 45 46static 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 75static 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); 109out: 110 return ret; 111} 112 113static int ls1x_cpufreq_exit(struct cpufreq_policy *policy) 114{ 115 kfree(policy->freq_table); 116 return 0; 117} 118 119static 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 130static 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 139static 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); 206out: 207 return ret; 208} 209 210static struct platform_driver ls1x_cpufreq_platdrv = { 211 .driver = { 212 .name = "ls1x-cpufreq", 213 }, 214 .probe = ls1x_cpufreq_probe, 215 .remove = ls1x_cpufreq_remove, 216}; 217 218module_platform_driver(ls1x_cpufreq_platdrv); 219 220MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>"); 221MODULE_DESCRIPTION("Loongson 1 CPUFreq driver"); 222MODULE_LICENSE("GPL"); 223