Commit | Line | Data |
---|---|---|
64f102b6 YS |
1 | /* |
2 | * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. | |
3 | */ | |
4 | ||
5 | /* | |
6 | * The code contained herein is licensed under the GNU General Public | |
7 | * License. You may obtain a copy of the GNU General Public License | |
8 | * Version 2 or later at the following locations: | |
9 | * | |
10 | * http://www.opensource.org/licenses/gpl-license.html | |
11 | * http://www.gnu.org/copyleft/gpl.html | |
12 | */ | |
13 | ||
14 | /* | |
15 | * A driver for the Freescale Semiconductor i.MXC CPUfreq module. | |
16 | * The CPUFREQ driver is for controling CPU frequency. It allows you to change | |
17 | * the CPU clock speed on the fly. | |
18 | */ | |
19 | ||
20 | #include <linux/cpufreq.h> | |
21 | #include <linux/clk.h> | |
22 | #include <linux/err.h> | |
23 | #include <linux/slab.h> | |
24 | #include <mach/hardware.h> | |
25 | #include <mach/clock.h> | |
26 | ||
27 | #define CLK32_FREQ 32768 | |
28 | #define NANOSECOND (1000 * 1000 * 1000) | |
29 | ||
30 | struct cpu_op *(*get_cpu_op)(int *op); | |
31 | ||
32 | static int cpu_freq_khz_min; | |
33 | static int cpu_freq_khz_max; | |
34 | ||
35 | static struct clk *cpu_clk; | |
36 | static struct cpufreq_frequency_table *imx_freq_table; | |
37 | ||
38 | static int cpu_op_nr; | |
39 | static struct cpu_op *cpu_op_tbl; | |
40 | ||
41 | static int set_cpu_freq(int freq) | |
42 | { | |
43 | int ret = 0; | |
44 | int org_cpu_rate; | |
45 | ||
46 | org_cpu_rate = clk_get_rate(cpu_clk); | |
47 | if (org_cpu_rate == freq) | |
48 | return ret; | |
49 | ||
50 | ret = clk_set_rate(cpu_clk, freq); | |
51 | if (ret != 0) { | |
52 | printk(KERN_DEBUG "cannot set CPU clock rate\n"); | |
53 | return ret; | |
54 | } | |
55 | ||
56 | return ret; | |
57 | } | |
58 | ||
59 | static int mxc_verify_speed(struct cpufreq_policy *policy) | |
60 | { | |
61 | if (policy->cpu != 0) | |
62 | return -EINVAL; | |
63 | ||
64 | return cpufreq_frequency_table_verify(policy, imx_freq_table); | |
65 | } | |
66 | ||
67 | static unsigned int mxc_get_speed(unsigned int cpu) | |
68 | { | |
69 | if (cpu) | |
70 | return 0; | |
71 | ||
72 | return clk_get_rate(cpu_clk) / 1000; | |
73 | } | |
74 | ||
75 | static int mxc_set_target(struct cpufreq_policy *policy, | |
76 | unsigned int target_freq, unsigned int relation) | |
77 | { | |
78 | struct cpufreq_freqs freqs; | |
79 | int freq_Hz; | |
80 | int ret = 0; | |
81 | unsigned int index; | |
82 | ||
83 | cpufreq_frequency_table_target(policy, imx_freq_table, | |
84 | target_freq, relation, &index); | |
85 | freq_Hz = imx_freq_table[index].frequency * 1000; | |
86 | ||
87 | freqs.old = clk_get_rate(cpu_clk) / 1000; | |
88 | freqs.new = freq_Hz / 1000; | |
89 | freqs.cpu = 0; | |
90 | freqs.flags = 0; | |
91 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | |
92 | ||
93 | ret = set_cpu_freq(freq_Hz); | |
94 | ||
95 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | |
96 | ||
97 | return ret; | |
98 | } | |
99 | ||
100 | static int __init mxc_cpufreq_init(struct cpufreq_policy *policy) | |
101 | { | |
102 | int ret; | |
103 | int i; | |
104 | ||
105 | printk(KERN_INFO "i.MXC CPU frequency driver\n"); | |
106 | ||
107 | if (policy->cpu != 0) | |
108 | return -EINVAL; | |
109 | ||
110 | if (!get_cpu_op) | |
111 | return -EINVAL; | |
112 | ||
113 | cpu_clk = clk_get(NULL, "cpu_clk"); | |
114 | if (IS_ERR(cpu_clk)) { | |
115 | printk(KERN_ERR "%s: failed to get cpu clock\n", __func__); | |
116 | return PTR_ERR(cpu_clk); | |
117 | } | |
118 | ||
119 | cpu_op_tbl = get_cpu_op(&cpu_op_nr); | |
120 | ||
121 | cpu_freq_khz_min = cpu_op_tbl[0].cpu_rate / 1000; | |
122 | cpu_freq_khz_max = cpu_op_tbl[0].cpu_rate / 1000; | |
123 | ||
124 | imx_freq_table = kmalloc( | |
125 | sizeof(struct cpufreq_frequency_table) * (cpu_op_nr + 1), | |
126 | GFP_KERNEL); | |
127 | if (!imx_freq_table) { | |
128 | ret = -ENOMEM; | |
129 | goto err1; | |
130 | } | |
131 | ||
132 | for (i = 0; i < cpu_op_nr; i++) { | |
133 | imx_freq_table[i].index = i; | |
134 | imx_freq_table[i].frequency = cpu_op_tbl[i].cpu_rate / 1000; | |
135 | ||
136 | if ((cpu_op_tbl[i].cpu_rate / 1000) < cpu_freq_khz_min) | |
137 | cpu_freq_khz_min = cpu_op_tbl[i].cpu_rate / 1000; | |
138 | ||
139 | if ((cpu_op_tbl[i].cpu_rate / 1000) > cpu_freq_khz_max) | |
140 | cpu_freq_khz_max = cpu_op_tbl[i].cpu_rate / 1000; | |
141 | } | |
142 | ||
143 | imx_freq_table[i].index = i; | |
144 | imx_freq_table[i].frequency = CPUFREQ_TABLE_END; | |
145 | ||
146 | policy->cur = clk_get_rate(cpu_clk) / 1000; | |
64f102b6 YS |
147 | policy->min = policy->cpuinfo.min_freq = cpu_freq_khz_min; |
148 | policy->max = policy->cpuinfo.max_freq = cpu_freq_khz_max; | |
149 | ||
150 | /* Manual states, that PLL stabilizes in two CLK32 periods */ | |
151 | policy->cpuinfo.transition_latency = 2 * NANOSECOND / CLK32_FREQ; | |
152 | ||
153 | ret = cpufreq_frequency_table_cpuinfo(policy, imx_freq_table); | |
154 | ||
155 | if (ret < 0) { | |
156 | printk(KERN_ERR "%s: failed to register i.MXC CPUfreq \ | |
157 | with error code %d\n", __func__, ret); | |
158 | goto err; | |
159 | } | |
160 | ||
161 | cpufreq_frequency_table_get_attr(imx_freq_table, policy->cpu); | |
162 | return 0; | |
163 | err: | |
164 | kfree(imx_freq_table); | |
165 | err1: | |
166 | clk_put(cpu_clk); | |
167 | return ret; | |
168 | } | |
169 | ||
170 | static int mxc_cpufreq_exit(struct cpufreq_policy *policy) | |
171 | { | |
172 | cpufreq_frequency_table_put_attr(policy->cpu); | |
173 | ||
174 | set_cpu_freq(cpu_freq_khz_max * 1000); | |
175 | clk_put(cpu_clk); | |
176 | kfree(imx_freq_table); | |
177 | return 0; | |
178 | } | |
179 | ||
180 | static struct cpufreq_driver mxc_driver = { | |
181 | .flags = CPUFREQ_STICKY, | |
182 | .verify = mxc_verify_speed, | |
183 | .target = mxc_set_target, | |
184 | .get = mxc_get_speed, | |
185 | .init = mxc_cpufreq_init, | |
186 | .exit = mxc_cpufreq_exit, | |
187 | .name = "imx", | |
188 | }; | |
189 | ||
190 | static int __devinit mxc_cpufreq_driver_init(void) | |
191 | { | |
192 | return cpufreq_register_driver(&mxc_driver); | |
193 | } | |
194 | ||
195 | static void mxc_cpufreq_driver_exit(void) | |
196 | { | |
197 | cpufreq_unregister_driver(&mxc_driver); | |
198 | } | |
199 | ||
200 | module_init(mxc_cpufreq_driver_init); | |
201 | module_exit(mxc_cpufreq_driver_exit); | |
202 | ||
203 | MODULE_AUTHOR("Freescale Semiconductor Inc. Yong Shen <yong.shen@linaro.org>"); | |
204 | MODULE_DESCRIPTION("CPUfreq driver for i.MX"); | |
205 | MODULE_LICENSE("GPL"); |