Commit | Line | Data |
---|---|---|
6601b803 SN |
1 | /* |
2 | * CPU frequency scaling for DaVinci | |
3 | * | |
4 | * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ | |
5 | * | |
6 | * Based on linux/arch/arm/plat-omap/cpu-omap.c. Original Copyright follows: | |
7 | * | |
8 | * Copyright (C) 2005 Nokia Corporation | |
9 | * Written by Tony Lindgren <tony@atomide.com> | |
10 | * | |
11 | * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King | |
12 | * | |
13 | * Copyright (C) 2007-2008 Texas Instruments, Inc. | |
14 | * Updated to support OMAP3 | |
15 | * Rajendra Nayak <rnayak@ti.com> | |
16 | * | |
17 | * This program is free software; you can redistribute it and/or modify | |
18 | * it under the terms of the GNU General Public License version 2 as | |
19 | * published by the Free Software Foundation. | |
20 | */ | |
21 | #include <linux/types.h> | |
22 | #include <linux/cpufreq.h> | |
23 | #include <linux/init.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/clk.h> | |
40b46b3b | 26 | #include <linux/platform_data/davinci-cpufreq.h> |
6601b803 | 27 | #include <linux/platform_device.h> |
dc28094b | 28 | #include <linux/export.h> |
6601b803 | 29 | |
6601b803 SN |
30 | struct davinci_cpufreq { |
31 | struct device *dev; | |
32 | struct clk *armclk; | |
30a2c5d2 SN |
33 | struct clk *asyncclk; |
34 | unsigned long asyncrate; | |
6601b803 SN |
35 | }; |
36 | static struct davinci_cpufreq cpufreq; | |
37 | ||
9c0ebcf7 | 38 | static int davinci_target(struct cpufreq_policy *policy, unsigned int idx) |
6601b803 | 39 | { |
6601b803 SN |
40 | struct davinci_cpufreq_config *pdata = cpufreq.dev->platform_data; |
41 | struct clk *armclk = cpufreq.armclk; | |
d4019f0a VK |
42 | unsigned int old_freq, new_freq; |
43 | int ret = 0; | |
6601b803 | 44 | |
652ed95d | 45 | old_freq = policy->cur; |
d4019f0a | 46 | new_freq = pdata->freq_table[idx].frequency; |
6601b803 SN |
47 | |
48 | /* if moving to higher frequency, up the voltage beforehand */ | |
d4019f0a | 49 | if (pdata->set_voltage && new_freq > old_freq) { |
fca97b33 SN |
50 | ret = pdata->set_voltage(idx); |
51 | if (ret) | |
d4019f0a | 52 | return ret; |
fca97b33 | 53 | } |
6601b803 | 54 | |
b4088173 | 55 | ret = clk_set_rate(armclk, new_freq * 1000); |
fca97b33 | 56 | if (ret) |
d4019f0a | 57 | return ret; |
6601b803 | 58 | |
30a2c5d2 SN |
59 | if (cpufreq.asyncclk) { |
60 | ret = clk_set_rate(cpufreq.asyncclk, cpufreq.asyncrate); | |
61 | if (ret) | |
d4019f0a | 62 | return ret; |
30a2c5d2 SN |
63 | } |
64 | ||
6601b803 | 65 | /* if moving to lower freq, lower the voltage after lowering freq */ |
d4019f0a | 66 | if (pdata->set_voltage && new_freq < old_freq) |
6601b803 SN |
67 | pdata->set_voltage(idx); |
68 | ||
d4019f0a | 69 | return 0; |
6601b803 SN |
70 | } |
71 | ||
079db590 | 72 | static int davinci_cpu_init(struct cpufreq_policy *policy) |
6601b803 SN |
73 | { |
74 | int result = 0; | |
75 | struct davinci_cpufreq_config *pdata = cpufreq.dev->platform_data; | |
76 | struct cpufreq_frequency_table *freq_table = pdata->freq_table; | |
77 | ||
78 | if (policy->cpu != 0) | |
79 | return -EINVAL; | |
80 | ||
13d5e27a SN |
81 | /* Finish platform specific initialization */ |
82 | if (pdata->init) { | |
83 | result = pdata->init(); | |
84 | if (result) | |
85 | return result; | |
86 | } | |
87 | ||
652ed95d VK |
88 | policy->clk = cpufreq.armclk; |
89 | ||
6601b803 SN |
90 | /* |
91 | * Time measurement across the target() function yields ~1500-1800us | |
92 | * time taken with no drivers on notification list. | |
25985edc | 93 | * Setting the latency to 2000 us to accommodate addition of drivers |
6601b803 SN |
94 | * to pre/post change notification list. |
95 | */ | |
af8c4cfa | 96 | return cpufreq_generic_init(policy, freq_table, 2000 * 1000); |
6601b803 SN |
97 | } |
98 | ||
6601b803 | 99 | static struct cpufreq_driver davinci_driver = { |
ae6b4271 | 100 | .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK, |
9d4de290 | 101 | .verify = cpufreq_generic_frequency_table_verify, |
9c0ebcf7 | 102 | .target_index = davinci_target, |
652ed95d | 103 | .get = cpufreq_generic_get, |
6601b803 | 104 | .init = davinci_cpu_init, |
6601b803 | 105 | .name = "davinci", |
39d0c362 | 106 | .attr = cpufreq_generic_attr, |
6601b803 SN |
107 | }; |
108 | ||
109 | static int __init davinci_cpufreq_probe(struct platform_device *pdev) | |
110 | { | |
111 | struct davinci_cpufreq_config *pdata = pdev->dev.platform_data; | |
30a2c5d2 | 112 | struct clk *asyncclk; |
6601b803 SN |
113 | |
114 | if (!pdata) | |
115 | return -EINVAL; | |
116 | if (!pdata->freq_table) | |
117 | return -EINVAL; | |
118 | ||
119 | cpufreq.dev = &pdev->dev; | |
120 | ||
121 | cpufreq.armclk = clk_get(NULL, "arm"); | |
122 | if (IS_ERR(cpufreq.armclk)) { | |
123 | dev_err(cpufreq.dev, "Unable to get ARM clock\n"); | |
124 | return PTR_ERR(cpufreq.armclk); | |
125 | } | |
126 | ||
30a2c5d2 SN |
127 | asyncclk = clk_get(cpufreq.dev, "async"); |
128 | if (!IS_ERR(asyncclk)) { | |
129 | cpufreq.asyncclk = asyncclk; | |
130 | cpufreq.asyncrate = clk_get_rate(asyncclk); | |
131 | } | |
132 | ||
6601b803 SN |
133 | return cpufreq_register_driver(&davinci_driver); |
134 | } | |
135 | ||
136 | static int __exit davinci_cpufreq_remove(struct platform_device *pdev) | |
137 | { | |
138 | clk_put(cpufreq.armclk); | |
139 | ||
30a2c5d2 SN |
140 | if (cpufreq.asyncclk) |
141 | clk_put(cpufreq.asyncclk); | |
142 | ||
6601b803 SN |
143 | return cpufreq_unregister_driver(&davinci_driver); |
144 | } | |
145 | ||
146 | static struct platform_driver davinci_cpufreq_driver = { | |
147 | .driver = { | |
148 | .name = "cpufreq-davinci", | |
6601b803 SN |
149 | }, |
150 | .remove = __exit_p(davinci_cpufreq_remove), | |
151 | }; | |
152 | ||
3aa3e840 | 153 | int __init davinci_cpufreq_init(void) |
6601b803 SN |
154 | { |
155 | return platform_driver_probe(&davinci_cpufreq_driver, | |
156 | davinci_cpufreq_probe); | |
157 | } | |
6601b803 | 158 |