1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2013 Freescale Semiconductor, Inc. |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/cpu.h> |
8 | #include <linux/cpufreq.h> |
9 | #include <linux/err.h> |
10 | #include <linux/module.h> |
11 | #include <linux/nvmem-consumer.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/pm_opp.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regulator/consumer.h> |
17 | #include <linux/mfd/syscon.h> |
18 | #include <linux/regmap.h> |
19 | |
20 | #define PU_SOC_VOLTAGE_NORMAL 1250000 |
21 | #define PU_SOC_VOLTAGE_HIGH 1275000 |
22 | #define FREQ_1P2_GHZ 1200000000 |
23 | |
24 | static struct regulator *arm_reg; |
25 | static struct regulator *pu_reg; |
26 | static struct regulator *soc_reg; |
27 | |
28 | enum IMX6_CPUFREQ_CLKS { |
29 | ARM, |
30 | PLL1_SYS, |
31 | STEP, |
32 | PLL1_SW, |
33 | PLL2_PFD2_396M, |
34 | /* MX6UL requires two more clks */ |
35 | PLL2_BUS, |
36 | SECONDARY_SEL, |
37 | }; |
38 | #define IMX6Q_CPUFREQ_CLK_NUM 5 |
39 | #define IMX6UL_CPUFREQ_CLK_NUM 7 |
40 | |
41 | static int num_clks; |
42 | static struct clk_bulk_data clks[] = { |
43 | { .id = "arm" }, |
44 | { .id = "pll1_sys" }, |
45 | { .id = "step" }, |
46 | { .id = "pll1_sw" }, |
47 | { .id = "pll2_pfd2_396m" }, |
48 | { .id = "pll2_bus" }, |
49 | { .id = "secondary_sel" }, |
50 | }; |
51 | |
52 | static struct device *cpu_dev; |
53 | static struct cpufreq_frequency_table *freq_table; |
54 | static unsigned int max_freq; |
55 | static unsigned int transition_latency; |
56 | |
57 | static u32 *imx6_soc_volt; |
58 | static u32 soc_opp_count; |
59 | |
60 | static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index) |
61 | { |
62 | struct dev_pm_opp *opp; |
63 | unsigned long freq_hz, volt, volt_old; |
64 | unsigned int old_freq, new_freq; |
65 | bool pll1_sys_temp_enabled = false; |
66 | int ret; |
67 | |
68 | new_freq = freq_table[index].frequency; |
69 | freq_hz = new_freq * 1000; |
70 | old_freq = clk_get_rate(clk: clks[ARM].clk) / 1000; |
71 | |
72 | opp = dev_pm_opp_find_freq_ceil(dev: cpu_dev, freq: &freq_hz); |
73 | if (IS_ERR(ptr: opp)) { |
74 | dev_err(cpu_dev, "failed to find OPP for %ld\n" , freq_hz); |
75 | return PTR_ERR(ptr: opp); |
76 | } |
77 | |
78 | volt = dev_pm_opp_get_voltage(opp); |
79 | dev_pm_opp_put(opp); |
80 | |
81 | volt_old = regulator_get_voltage(regulator: arm_reg); |
82 | |
83 | dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n" , |
84 | old_freq / 1000, volt_old / 1000, |
85 | new_freq / 1000, volt / 1000); |
86 | |
87 | /* scaling up? scale voltage before frequency */ |
88 | if (new_freq > old_freq) { |
89 | if (!IS_ERR(ptr: pu_reg)) { |
90 | ret = regulator_set_voltage_tol(regulator: pu_reg, new_uV: imx6_soc_volt[index], tol_uV: 0); |
91 | if (ret) { |
92 | dev_err(cpu_dev, "failed to scale vddpu up: %d\n" , ret); |
93 | return ret; |
94 | } |
95 | } |
96 | ret = regulator_set_voltage_tol(regulator: soc_reg, new_uV: imx6_soc_volt[index], tol_uV: 0); |
97 | if (ret) { |
98 | dev_err(cpu_dev, "failed to scale vddsoc up: %d\n" , ret); |
99 | return ret; |
100 | } |
101 | ret = regulator_set_voltage_tol(regulator: arm_reg, new_uV: volt, tol_uV: 0); |
102 | if (ret) { |
103 | dev_err(cpu_dev, |
104 | "failed to scale vddarm up: %d\n" , ret); |
105 | return ret; |
106 | } |
107 | } |
108 | |
109 | /* |
110 | * The setpoints are selected per PLL/PDF frequencies, so we need to |
111 | * reprogram PLL for frequency scaling. The procedure of reprogramming |
112 | * PLL1 is as below. |
113 | * For i.MX6UL, it has a secondary clk mux, the cpu frequency change |
114 | * flow is slightly different from other i.MX6 OSC. |
115 | * The cpu frequeny change flow for i.MX6(except i.MX6UL) is as below: |
116 | * - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it |
117 | * - Reprogram pll1_sys_clk and reparent pll1_sw_clk back to it |
118 | * - Disable pll2_pfd2_396m_clk |
119 | */ |
120 | if (of_machine_is_compatible(compat: "fsl,imx6ul" ) || |
121 | of_machine_is_compatible(compat: "fsl,imx6ull" )) { |
122 | /* |
123 | * When changing pll1_sw_clk's parent to pll1_sys_clk, |
124 | * CPU may run at higher than 528MHz, this will lead to |
125 | * the system unstable if the voltage is lower than the |
126 | * voltage of 528MHz, so lower the CPU frequency to one |
127 | * half before changing CPU frequency. |
128 | */ |
129 | clk_set_rate(clk: clks[ARM].clk, rate: (old_freq >> 1) * 1000); |
130 | clk_set_parent(clk: clks[PLL1_SW].clk, parent: clks[PLL1_SYS].clk); |
131 | if (freq_hz > clk_get_rate(clk: clks[PLL2_PFD2_396M].clk)) |
132 | clk_set_parent(clk: clks[SECONDARY_SEL].clk, |
133 | parent: clks[PLL2_BUS].clk); |
134 | else |
135 | clk_set_parent(clk: clks[SECONDARY_SEL].clk, |
136 | parent: clks[PLL2_PFD2_396M].clk); |
137 | clk_set_parent(clk: clks[STEP].clk, parent: clks[SECONDARY_SEL].clk); |
138 | clk_set_parent(clk: clks[PLL1_SW].clk, parent: clks[STEP].clk); |
139 | if (freq_hz > clk_get_rate(clk: clks[PLL2_BUS].clk)) { |
140 | clk_set_rate(clk: clks[PLL1_SYS].clk, rate: new_freq * 1000); |
141 | clk_set_parent(clk: clks[PLL1_SW].clk, parent: clks[PLL1_SYS].clk); |
142 | } |
143 | } else { |
144 | clk_set_parent(clk: clks[STEP].clk, parent: clks[PLL2_PFD2_396M].clk); |
145 | clk_set_parent(clk: clks[PLL1_SW].clk, parent: clks[STEP].clk); |
146 | if (freq_hz > clk_get_rate(clk: clks[PLL2_PFD2_396M].clk)) { |
147 | clk_set_rate(clk: clks[PLL1_SYS].clk, rate: new_freq * 1000); |
148 | clk_set_parent(clk: clks[PLL1_SW].clk, parent: clks[PLL1_SYS].clk); |
149 | } else { |
150 | /* pll1_sys needs to be enabled for divider rate change to work. */ |
151 | pll1_sys_temp_enabled = true; |
152 | clk_prepare_enable(clk: clks[PLL1_SYS].clk); |
153 | } |
154 | } |
155 | |
156 | /* Ensure the arm clock divider is what we expect */ |
157 | ret = clk_set_rate(clk: clks[ARM].clk, rate: new_freq * 1000); |
158 | if (ret) { |
159 | int ret1; |
160 | |
161 | dev_err(cpu_dev, "failed to set clock rate: %d\n" , ret); |
162 | ret1 = regulator_set_voltage_tol(regulator: arm_reg, new_uV: volt_old, tol_uV: 0); |
163 | if (ret1) |
164 | dev_warn(cpu_dev, |
165 | "failed to restore vddarm voltage: %d\n" , ret1); |
166 | return ret; |
167 | } |
168 | |
169 | /* PLL1 is only needed until after ARM-PODF is set. */ |
170 | if (pll1_sys_temp_enabled) |
171 | clk_disable_unprepare(clk: clks[PLL1_SYS].clk); |
172 | |
173 | /* scaling down? scale voltage after frequency */ |
174 | if (new_freq < old_freq) { |
175 | ret = regulator_set_voltage_tol(regulator: arm_reg, new_uV: volt, tol_uV: 0); |
176 | if (ret) |
177 | dev_warn(cpu_dev, |
178 | "failed to scale vddarm down: %d\n" , ret); |
179 | ret = regulator_set_voltage_tol(regulator: soc_reg, new_uV: imx6_soc_volt[index], tol_uV: 0); |
180 | if (ret) |
181 | dev_warn(cpu_dev, "failed to scale vddsoc down: %d\n" , ret); |
182 | if (!IS_ERR(ptr: pu_reg)) { |
183 | ret = regulator_set_voltage_tol(regulator: pu_reg, new_uV: imx6_soc_volt[index], tol_uV: 0); |
184 | if (ret) |
185 | dev_warn(cpu_dev, "failed to scale vddpu down: %d\n" , ret); |
186 | } |
187 | } |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | static int imx6q_cpufreq_init(struct cpufreq_policy *policy) |
193 | { |
194 | policy->clk = clks[ARM].clk; |
195 | cpufreq_generic_init(policy, table: freq_table, transition_latency); |
196 | policy->suspend_freq = max_freq; |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | static struct cpufreq_driver imx6q_cpufreq_driver = { |
202 | .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | |
203 | CPUFREQ_IS_COOLING_DEV, |
204 | .verify = cpufreq_generic_frequency_table_verify, |
205 | .target_index = imx6q_set_target, |
206 | .get = cpufreq_generic_get, |
207 | .init = imx6q_cpufreq_init, |
208 | .register_em = cpufreq_register_em_with_opp, |
209 | .name = "imx6q-cpufreq" , |
210 | .attr = cpufreq_generic_attr, |
211 | .suspend = cpufreq_generic_suspend, |
212 | }; |
213 | |
214 | static void imx6x_disable_freq_in_opp(struct device *dev, unsigned long freq) |
215 | { |
216 | int ret = dev_pm_opp_disable(dev, freq); |
217 | |
218 | if (ret < 0 && ret != -ENODEV) |
219 | dev_warn(dev, "failed to disable %ldMHz OPP\n" , freq / 1000000); |
220 | } |
221 | |
222 | #define OCOTP_CFG3 0x440 |
223 | #define OCOTP_CFG3_SPEED_SHIFT 16 |
224 | #define OCOTP_CFG3_SPEED_1P2GHZ 0x3 |
225 | #define OCOTP_CFG3_SPEED_996MHZ 0x2 |
226 | #define OCOTP_CFG3_SPEED_852MHZ 0x1 |
227 | |
228 | static int imx6q_opp_check_speed_grading(struct device *dev) |
229 | { |
230 | u32 val; |
231 | int ret; |
232 | |
233 | if (of_property_present(np: dev->of_node, propname: "nvmem-cells" )) { |
234 | ret = nvmem_cell_read_u32(dev, cell_id: "speed_grade" , val: &val); |
235 | if (ret) |
236 | return ret; |
237 | } else { |
238 | struct regmap *ocotp; |
239 | |
240 | ocotp = syscon_regmap_lookup_by_compatible(s: "fsl,imx6q-ocotp" ); |
241 | if (IS_ERR(ptr: ocotp)) |
242 | return -ENOENT; |
243 | |
244 | /* |
245 | * SPEED_GRADING[1:0] defines the max speed of ARM: |
246 | * 2b'11: 1200000000Hz; |
247 | * 2b'10: 996000000Hz; |
248 | * 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz. |
249 | * 2b'00: 792000000Hz; |
250 | * We need to set the max speed of ARM according to fuse map. |
251 | */ |
252 | regmap_read(map: ocotp, OCOTP_CFG3, val: &val); |
253 | } |
254 | |
255 | val >>= OCOTP_CFG3_SPEED_SHIFT; |
256 | val &= 0x3; |
257 | |
258 | if (val < OCOTP_CFG3_SPEED_996MHZ) |
259 | imx6x_disable_freq_in_opp(dev, freq: 996000000); |
260 | |
261 | if (of_machine_is_compatible(compat: "fsl,imx6q" ) || |
262 | of_machine_is_compatible(compat: "fsl,imx6qp" )) { |
263 | if (val != OCOTP_CFG3_SPEED_852MHZ) |
264 | imx6x_disable_freq_in_opp(dev, freq: 852000000); |
265 | |
266 | if (val != OCOTP_CFG3_SPEED_1P2GHZ) |
267 | imx6x_disable_freq_in_opp(dev, freq: 1200000000); |
268 | } |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | #define OCOTP_CFG3_6UL_SPEED_696MHZ 0x2 |
274 | #define OCOTP_CFG3_6ULL_SPEED_792MHZ 0x2 |
275 | #define OCOTP_CFG3_6ULL_SPEED_900MHZ 0x3 |
276 | |
277 | static int imx6ul_opp_check_speed_grading(struct device *dev) |
278 | { |
279 | u32 val; |
280 | int ret = 0; |
281 | |
282 | if (of_property_present(np: dev->of_node, propname: "nvmem-cells" )) { |
283 | ret = nvmem_cell_read_u32(dev, cell_id: "speed_grade" , val: &val); |
284 | if (ret) |
285 | return ret; |
286 | } else { |
287 | struct regmap *ocotp; |
288 | |
289 | ocotp = syscon_regmap_lookup_by_compatible(s: "fsl,imx6ul-ocotp" ); |
290 | if (IS_ERR(ptr: ocotp)) |
291 | ocotp = syscon_regmap_lookup_by_compatible(s: "fsl,imx6ull-ocotp" ); |
292 | |
293 | if (IS_ERR(ptr: ocotp)) |
294 | return -ENOENT; |
295 | |
296 | regmap_read(map: ocotp, OCOTP_CFG3, val: &val); |
297 | } |
298 | |
299 | /* |
300 | * Speed GRADING[1:0] defines the max speed of ARM: |
301 | * 2b'00: Reserved; |
302 | * 2b'01: 528000000Hz; |
303 | * 2b'10: 696000000Hz on i.MX6UL, 792000000Hz on i.MX6ULL; |
304 | * 2b'11: 900000000Hz on i.MX6ULL only; |
305 | * We need to set the max speed of ARM according to fuse map. |
306 | */ |
307 | val >>= OCOTP_CFG3_SPEED_SHIFT; |
308 | val &= 0x3; |
309 | |
310 | if (of_machine_is_compatible(compat: "fsl,imx6ul" )) |
311 | if (val != OCOTP_CFG3_6UL_SPEED_696MHZ) |
312 | imx6x_disable_freq_in_opp(dev, freq: 696000000); |
313 | |
314 | if (of_machine_is_compatible(compat: "fsl,imx6ull" )) { |
315 | if (val < OCOTP_CFG3_6ULL_SPEED_792MHZ) |
316 | imx6x_disable_freq_in_opp(dev, freq: 792000000); |
317 | |
318 | if (val != OCOTP_CFG3_6ULL_SPEED_900MHZ) |
319 | imx6x_disable_freq_in_opp(dev, freq: 900000000); |
320 | } |
321 | |
322 | return ret; |
323 | } |
324 | |
325 | static int imx6q_cpufreq_probe(struct platform_device *pdev) |
326 | { |
327 | struct device_node *np; |
328 | struct dev_pm_opp *opp; |
329 | unsigned long min_volt, max_volt; |
330 | int num, ret; |
331 | const struct property *prop; |
332 | const __be32 *val; |
333 | u32 nr, i, j; |
334 | |
335 | cpu_dev = get_cpu_device(cpu: 0); |
336 | if (!cpu_dev) { |
337 | pr_err("failed to get cpu0 device\n" ); |
338 | return -ENODEV; |
339 | } |
340 | |
341 | np = of_node_get(node: cpu_dev->of_node); |
342 | if (!np) { |
343 | dev_err(cpu_dev, "failed to find cpu0 node\n" ); |
344 | return -ENOENT; |
345 | } |
346 | |
347 | if (of_machine_is_compatible(compat: "fsl,imx6ul" ) || |
348 | of_machine_is_compatible(compat: "fsl,imx6ull" )) |
349 | num_clks = IMX6UL_CPUFREQ_CLK_NUM; |
350 | else |
351 | num_clks = IMX6Q_CPUFREQ_CLK_NUM; |
352 | |
353 | ret = clk_bulk_get(dev: cpu_dev, num_clks, clks); |
354 | if (ret) |
355 | goto put_node; |
356 | |
357 | arm_reg = regulator_get(dev: cpu_dev, id: "arm" ); |
358 | pu_reg = regulator_get_optional(dev: cpu_dev, id: "pu" ); |
359 | soc_reg = regulator_get(dev: cpu_dev, id: "soc" ); |
360 | if (PTR_ERR(ptr: arm_reg) == -EPROBE_DEFER || |
361 | PTR_ERR(ptr: soc_reg) == -EPROBE_DEFER || |
362 | PTR_ERR(ptr: pu_reg) == -EPROBE_DEFER) { |
363 | ret = -EPROBE_DEFER; |
364 | dev_dbg(cpu_dev, "regulators not ready, defer\n" ); |
365 | goto put_reg; |
366 | } |
367 | if (IS_ERR(ptr: arm_reg) || IS_ERR(ptr: soc_reg)) { |
368 | dev_err(cpu_dev, "failed to get regulators\n" ); |
369 | ret = -ENOENT; |
370 | goto put_reg; |
371 | } |
372 | |
373 | ret = dev_pm_opp_of_add_table(dev: cpu_dev); |
374 | if (ret < 0) { |
375 | dev_err(cpu_dev, "failed to init OPP table: %d\n" , ret); |
376 | goto put_reg; |
377 | } |
378 | |
379 | if (of_machine_is_compatible(compat: "fsl,imx6ul" ) || |
380 | of_machine_is_compatible(compat: "fsl,imx6ull" )) { |
381 | ret = imx6ul_opp_check_speed_grading(dev: cpu_dev); |
382 | } else { |
383 | ret = imx6q_opp_check_speed_grading(dev: cpu_dev); |
384 | } |
385 | if (ret) { |
386 | dev_err_probe(dev: cpu_dev, err: ret, fmt: "failed to read ocotp\n" ); |
387 | goto out_free_opp; |
388 | } |
389 | |
390 | num = dev_pm_opp_get_opp_count(dev: cpu_dev); |
391 | if (num < 0) { |
392 | ret = num; |
393 | dev_err(cpu_dev, "no OPP table is found: %d\n" , ret); |
394 | goto out_free_opp; |
395 | } |
396 | |
397 | ret = dev_pm_opp_init_cpufreq_table(dev: cpu_dev, table: &freq_table); |
398 | if (ret) { |
399 | dev_err(cpu_dev, "failed to init cpufreq table: %d\n" , ret); |
400 | goto out_free_opp; |
401 | } |
402 | |
403 | /* Make imx6_soc_volt array's size same as arm opp number */ |
404 | imx6_soc_volt = devm_kcalloc(dev: cpu_dev, n: num, size: sizeof(*imx6_soc_volt), |
405 | GFP_KERNEL); |
406 | if (imx6_soc_volt == NULL) { |
407 | ret = -ENOMEM; |
408 | goto free_freq_table; |
409 | } |
410 | |
411 | prop = of_find_property(np, name: "fsl,soc-operating-points" , NULL); |
412 | if (!prop || !prop->value) |
413 | goto soc_opp_out; |
414 | |
415 | /* |
416 | * Each OPP is a set of tuples consisting of frequency and |
417 | * voltage like <freq-kHz vol-uV>. |
418 | */ |
419 | nr = prop->length / sizeof(u32); |
420 | if (nr % 2 || (nr / 2) < num) |
421 | goto soc_opp_out; |
422 | |
423 | for (j = 0; j < num; j++) { |
424 | val = prop->value; |
425 | for (i = 0; i < nr / 2; i++) { |
426 | unsigned long freq = be32_to_cpup(p: val++); |
427 | unsigned long volt = be32_to_cpup(p: val++); |
428 | if (freq_table[j].frequency == freq) { |
429 | imx6_soc_volt[soc_opp_count++] = volt; |
430 | break; |
431 | } |
432 | } |
433 | } |
434 | |
435 | soc_opp_out: |
436 | /* use fixed soc opp volt if no valid soc opp info found in dtb */ |
437 | if (soc_opp_count != num) { |
438 | dev_warn(cpu_dev, "can NOT find valid fsl,soc-operating-points property in dtb, use default value!\n" ); |
439 | for (j = 0; j < num; j++) |
440 | imx6_soc_volt[j] = PU_SOC_VOLTAGE_NORMAL; |
441 | if (freq_table[num - 1].frequency * 1000 == FREQ_1P2_GHZ) |
442 | imx6_soc_volt[num - 1] = PU_SOC_VOLTAGE_HIGH; |
443 | } |
444 | |
445 | if (of_property_read_u32(np, propname: "clock-latency" , out_value: &transition_latency)) |
446 | transition_latency = CPUFREQ_ETERNAL; |
447 | |
448 | /* |
449 | * Calculate the ramp time for max voltage change in the |
450 | * VDDSOC and VDDPU regulators. |
451 | */ |
452 | ret = regulator_set_voltage_time(regulator: soc_reg, old_uV: imx6_soc_volt[0], new_uV: imx6_soc_volt[num - 1]); |
453 | if (ret > 0) |
454 | transition_latency += ret * 1000; |
455 | if (!IS_ERR(ptr: pu_reg)) { |
456 | ret = regulator_set_voltage_time(regulator: pu_reg, old_uV: imx6_soc_volt[0], new_uV: imx6_soc_volt[num - 1]); |
457 | if (ret > 0) |
458 | transition_latency += ret * 1000; |
459 | } |
460 | |
461 | /* |
462 | * OPP is maintained in order of increasing frequency, and |
463 | * freq_table initialised from OPP is therefore sorted in the |
464 | * same order. |
465 | */ |
466 | max_freq = freq_table[--num].frequency; |
467 | opp = dev_pm_opp_find_freq_exact(dev: cpu_dev, |
468 | freq: freq_table[0].frequency * 1000, available: true); |
469 | min_volt = dev_pm_opp_get_voltage(opp); |
470 | dev_pm_opp_put(opp); |
471 | opp = dev_pm_opp_find_freq_exact(dev: cpu_dev, freq: max_freq * 1000, available: true); |
472 | max_volt = dev_pm_opp_get_voltage(opp); |
473 | dev_pm_opp_put(opp); |
474 | |
475 | ret = regulator_set_voltage_time(regulator: arm_reg, old_uV: min_volt, new_uV: max_volt); |
476 | if (ret > 0) |
477 | transition_latency += ret * 1000; |
478 | |
479 | ret = cpufreq_register_driver(driver_data: &imx6q_cpufreq_driver); |
480 | if (ret) { |
481 | dev_err(cpu_dev, "failed register driver: %d\n" , ret); |
482 | goto free_freq_table; |
483 | } |
484 | |
485 | of_node_put(node: np); |
486 | return 0; |
487 | |
488 | free_freq_table: |
489 | dev_pm_opp_free_cpufreq_table(dev: cpu_dev, table: &freq_table); |
490 | out_free_opp: |
491 | dev_pm_opp_of_remove_table(dev: cpu_dev); |
492 | put_reg: |
493 | if (!IS_ERR(ptr: arm_reg)) |
494 | regulator_put(regulator: arm_reg); |
495 | if (!IS_ERR(ptr: pu_reg)) |
496 | regulator_put(regulator: pu_reg); |
497 | if (!IS_ERR(ptr: soc_reg)) |
498 | regulator_put(regulator: soc_reg); |
499 | |
500 | clk_bulk_put(num_clks, clks); |
501 | put_node: |
502 | of_node_put(node: np); |
503 | |
504 | return ret; |
505 | } |
506 | |
507 | static void imx6q_cpufreq_remove(struct platform_device *pdev) |
508 | { |
509 | cpufreq_unregister_driver(driver_data: &imx6q_cpufreq_driver); |
510 | dev_pm_opp_free_cpufreq_table(dev: cpu_dev, table: &freq_table); |
511 | dev_pm_opp_of_remove_table(dev: cpu_dev); |
512 | regulator_put(regulator: arm_reg); |
513 | if (!IS_ERR(ptr: pu_reg)) |
514 | regulator_put(regulator: pu_reg); |
515 | regulator_put(regulator: soc_reg); |
516 | |
517 | clk_bulk_put(num_clks, clks); |
518 | } |
519 | |
520 | static struct platform_driver imx6q_cpufreq_platdrv = { |
521 | .driver = { |
522 | .name = "imx6q-cpufreq" , |
523 | }, |
524 | .probe = imx6q_cpufreq_probe, |
525 | .remove_new = imx6q_cpufreq_remove, |
526 | }; |
527 | module_platform_driver(imx6q_cpufreq_platdrv); |
528 | |
529 | MODULE_ALIAS("platform:imx6q-cpufreq" ); |
530 | MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>" ); |
531 | MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver" ); |
532 | MODULE_LICENSE("GPL" ); |
533 | |