loongson1-cpufreq.c (5656B)
1/* 2 * CPU Frequency Scaling for Loongson 1 SoC 3 * 4 * Copyright (C) 2014-2016 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/io.h> 17#include <linux/module.h> 18#include <linux/platform_device.h> 19#include <linux/slab.h> 20 21#include <cpufreq.h> 22#include <loongson1.h> 23 24struct ls1x_cpufreq { 25 struct device *dev; 26 struct clk *clk; /* CPU clk */ 27 struct clk *mux_clk; /* MUX of CPU clk */ 28 struct clk *pll_clk; /* PLL clk */ 29 struct clk *osc_clk; /* OSC clk */ 30 unsigned int max_freq; 31 unsigned int min_freq; 32}; 33 34static struct ls1x_cpufreq *cpufreq; 35 36static int ls1x_cpufreq_notifier(struct notifier_block *nb, 37 unsigned long val, void *data) 38{ 39 if (val == CPUFREQ_POSTCHANGE) 40 current_cpu_data.udelay_val = loops_per_jiffy; 41 42 return NOTIFY_OK; 43} 44 45static struct notifier_block ls1x_cpufreq_notifier_block = { 46 .notifier_call = ls1x_cpufreq_notifier 47}; 48 49static int ls1x_cpufreq_target(struct cpufreq_policy *policy, 50 unsigned int index) 51{ 52 struct device *cpu_dev = get_cpu_device(policy->cpu); 53 unsigned int old_freq, new_freq; 54 55 old_freq = policy->cur; 56 new_freq = policy->freq_table[index].frequency; 57 58 /* 59 * The procedure of reconfiguring CPU clk is as below. 60 * 61 * - Reparent CPU clk to OSC clk 62 * - Reset CPU clock (very important) 63 * - Reconfigure CPU DIV 64 * - Reparent CPU clk back to CPU DIV clk 65 */ 66 67 clk_set_parent(policy->clk, cpufreq->osc_clk); 68 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) | RST_CPU_EN | RST_CPU, 69 LS1X_CLK_PLL_DIV); 70 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) & ~(RST_CPU_EN | RST_CPU), 71 LS1X_CLK_PLL_DIV); 72 clk_set_rate(cpufreq->mux_clk, new_freq * 1000); 73 clk_set_parent(policy->clk, cpufreq->mux_clk); 74 dev_dbg(cpu_dev, "%u KHz --> %u KHz\n", old_freq, new_freq); 75 76 return 0; 77} 78 79static int ls1x_cpufreq_init(struct cpufreq_policy *policy) 80{ 81 struct device *cpu_dev = get_cpu_device(policy->cpu); 82 struct cpufreq_frequency_table *freq_tbl; 83 unsigned int pll_freq, freq; 84 int steps, i; 85 86 pll_freq = clk_get_rate(cpufreq->pll_clk) / 1000; 87 88 steps = 1 << DIV_CPU_WIDTH; 89 freq_tbl = kcalloc(steps, sizeof(*freq_tbl), GFP_KERNEL); 90 if (!freq_tbl) 91 return -ENOMEM; 92 93 for (i = 0; i < (steps - 1); i++) { 94 freq = pll_freq / (i + 1); 95 if ((freq < cpufreq->min_freq) || (freq > cpufreq->max_freq)) 96 freq_tbl[i].frequency = CPUFREQ_ENTRY_INVALID; 97 else 98 freq_tbl[i].frequency = freq; 99 dev_dbg(cpu_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 = cpufreq->clk; 106 cpufreq_generic_init(policy, freq_tbl, 0); 107 108 return 0; 109} 110 111static int ls1x_cpufreq_exit(struct cpufreq_policy *policy) 112{ 113 kfree(policy->freq_table); 114 return 0; 115} 116 117static struct cpufreq_driver ls1x_cpufreq_driver = { 118 .name = "cpufreq-ls1x", 119 .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, 120 .verify = cpufreq_generic_frequency_table_verify, 121 .target_index = ls1x_cpufreq_target, 122 .get = cpufreq_generic_get, 123 .init = ls1x_cpufreq_init, 124 .exit = ls1x_cpufreq_exit, 125 .attr = cpufreq_generic_attr, 126}; 127 128static int ls1x_cpufreq_remove(struct platform_device *pdev) 129{ 130 cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block, 131 CPUFREQ_TRANSITION_NOTIFIER); 132 cpufreq_unregister_driver(&ls1x_cpufreq_driver); 133 134 return 0; 135} 136 137static int ls1x_cpufreq_probe(struct platform_device *pdev) 138{ 139 struct plat_ls1x_cpufreq *pdata = dev_get_platdata(&pdev->dev); 140 struct clk *clk; 141 int ret; 142 143 if (!pdata || !pdata->clk_name || !pdata->osc_clk_name) { 144 dev_err(&pdev->dev, "platform data missing\n"); 145 return -EINVAL; 146 } 147 148 cpufreq = 149 devm_kzalloc(&pdev->dev, sizeof(struct ls1x_cpufreq), GFP_KERNEL); 150 if (!cpufreq) 151 return -ENOMEM; 152 153 cpufreq->dev = &pdev->dev; 154 155 clk = devm_clk_get(&pdev->dev, pdata->clk_name); 156 if (IS_ERR(clk)) { 157 dev_err(&pdev->dev, "unable to get %s clock\n", 158 pdata->clk_name); 159 return PTR_ERR(clk); 160 } 161 cpufreq->clk = clk; 162 163 clk = clk_get_parent(clk); 164 if (IS_ERR(clk)) { 165 dev_err(&pdev->dev, "unable to get parent of %s clock\n", 166 __clk_get_name(cpufreq->clk)); 167 return PTR_ERR(clk); 168 } 169 cpufreq->mux_clk = clk; 170 171 clk = clk_get_parent(clk); 172 if (IS_ERR(clk)) { 173 dev_err(&pdev->dev, "unable to get parent of %s clock\n", 174 __clk_get_name(cpufreq->mux_clk)); 175 return PTR_ERR(clk); 176 } 177 cpufreq->pll_clk = clk; 178 179 clk = devm_clk_get(&pdev->dev, pdata->osc_clk_name); 180 if (IS_ERR(clk)) { 181 dev_err(&pdev->dev, "unable to get %s clock\n", 182 pdata->osc_clk_name); 183 return PTR_ERR(clk); 184 } 185 cpufreq->osc_clk = clk; 186 187 cpufreq->max_freq = pdata->max_freq; 188 cpufreq->min_freq = pdata->min_freq; 189 190 ret = cpufreq_register_driver(&ls1x_cpufreq_driver); 191 if (ret) { 192 dev_err(&pdev->dev, 193 "failed to register CPUFreq driver: %d\n", ret); 194 return ret; 195 } 196 197 ret = cpufreq_register_notifier(&ls1x_cpufreq_notifier_block, 198 CPUFREQ_TRANSITION_NOTIFIER); 199 200 if (ret) { 201 dev_err(&pdev->dev, 202 "failed to register CPUFreq notifier: %d\n",ret); 203 cpufreq_unregister_driver(&ls1x_cpufreq_driver); 204 } 205 206 return ret; 207} 208 209static struct platform_driver ls1x_cpufreq_platdrv = { 210 .probe = ls1x_cpufreq_probe, 211 .remove = ls1x_cpufreq_remove, 212 .driver = { 213 .name = "ls1x-cpufreq", 214 }, 215}; 216 217module_platform_driver(ls1x_cpufreq_platdrv); 218 219MODULE_ALIAS("platform:ls1x-cpufreq"); 220MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>"); 221MODULE_DESCRIPTION("Loongson1 CPUFreq driver"); 222MODULE_LICENSE("GPL");