]> rtime.felk.cvut.cz Git - vajnamar/linux-xlnx.git/blob - drivers/cpufreq/zynq-cpufreq.c
Merge tag 'v3.10' into master-next
[vajnamar/linux-xlnx.git] / drivers / cpufreq / zynq-cpufreq.c
1 /*
2  * CPU frequency scaling for Zynq
3  *
4  * Based on drivers/cpufreq/omap-cpufreq.c,
5  * Copyright (C) 2005 Nokia Corporation
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 #include <linux/kernel.h>
17 #include <linux/cpufreq.h>
18 #include <linux/delay.h>
19 #include <linux/init.h>
20 #include <linux/err.h>
21 #include <linux/clk.h>
22 #include <linux/cpu.h>
23 #include <linux/cpumask.h>
24 #include <linux/module.h>
25 #include <linux/opp.h>
26 #include <linux/platform_device.h>
27 #include <asm/smp_plat.h>
28 #include <asm/cpu.h>
29
30 static atomic_t freq_table_users = ATOMIC_INIT(0);
31 static struct cpufreq_frequency_table *freq_table;
32 static struct device *mpu_dev;
33 static struct clk *cpuclk;
34
35 static int zynq_verify_speed(struct cpufreq_policy *policy)
36 {
37         if (!freq_table)
38                 return -EINVAL;
39         return cpufreq_frequency_table_verify(policy, freq_table);
40 }
41
42 static unsigned int zynq_getspeed(unsigned int cpu)
43 {
44         unsigned long rate;
45
46         if (cpu >= num_present_cpus())
47                 return 0;
48
49         rate = clk_get_rate(cpuclk) / 1000;
50         return rate;
51 }
52
53 static int zynq_target(struct cpufreq_policy *policy,
54                        unsigned int target_freq,
55                        unsigned int relation)
56 {
57         unsigned int i;
58         int ret = 0;
59         struct cpufreq_freqs freqs;
60
61         if (!freq_table) {
62                 dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
63                                 policy->cpu);
64                 return -EINVAL;
65         }
66
67         ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
68                         relation, &i);
69         if (ret) {
70                 dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
71                         __func__, policy->cpu, target_freq, ret);
72                 return ret;
73         }
74         freqs.new = freq_table[i].frequency;
75         if (!freqs.new) {
76                 dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
77                         policy->cpu, target_freq);
78                 return -EINVAL;
79         }
80
81         freqs.old = zynq_getspeed(policy->cpu);
82         freqs.cpu = policy->cpu;
83
84         if (freqs.old == freqs.new && policy->cur == freqs.new)
85                 return ret;
86
87         /* notifiers */
88         for_each_cpu(i, policy->cpus) {
89                 freqs.cpu = i;
90                 cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
91         }
92
93         dev_dbg(mpu_dev, "cpufreq-zynq: %u MHz --> %u MHz\n",
94                         freqs.old / 1000, freqs.new / 1000);
95
96         ret = clk_set_rate(cpuclk, freqs.new * 1000);
97
98         freqs.new = zynq_getspeed(policy->cpu);
99
100         /* notifiers */
101         for_each_cpu(i, policy->cpus) {
102                 freqs.cpu = i;
103                 cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
104         }
105
106         return ret;
107 }
108
109 static inline void freq_table_free(void)
110 {
111         if (atomic_dec_and_test(&freq_table_users))
112                 opp_free_cpufreq_table(mpu_dev, &freq_table);
113 }
114
115 static int __cpuinit zynq_cpu_init(struct cpufreq_policy *policy)
116 {
117         int result = 0;
118
119         cpuclk = clk_get_sys("CPU_6OR4X_CLK", NULL);
120         if (IS_ERR(cpuclk)) {
121                 pr_warn("Xilinx: cpufreq: Clock not found.");
122                 return PTR_ERR(cpuclk);
123         }
124
125         if (policy->cpu >= num_possible_cpus()) {
126                 result = -EINVAL;
127                 goto fail_ck;
128         }
129
130         policy->cur = policy->min = policy->max = zynq_getspeed(policy->cpu);
131
132         if (!freq_table)
133                 result = opp_init_cpufreq_table(mpu_dev, &freq_table);
134
135         if (result) {
136                 dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
137                                 __func__, policy->cpu, result);
138                 goto fail_ck;
139         }
140
141         atomic_inc(&freq_table_users);
142
143         result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
144         if (result)
145                 goto fail_table;
146
147         cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
148
149         policy->min = policy->cpuinfo.min_freq;
150         policy->max = policy->cpuinfo.max_freq;
151         policy->cur = zynq_getspeed(policy->cpu);
152
153         /*
154          * On Zynq configuartion, both processors share the voltage
155          * and clock. So both CPUs needs to be scaled together and hence
156          * needs software co-ordination. Use cpufreq affected_cpus
157          * interface to handle this scenario. Additional is_smp() check
158          * is to keep SMP_ON_UP build working.
159          */
160         if (is_smp()) {
161                 policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
162                 cpumask_setall(policy->cpus);
163         }
164
165         /* FIXME: what's the actual transition time? */
166         policy->cpuinfo.transition_latency = 300 * 1000;
167
168         return 0;
169
170 fail_table:
171         freq_table_free();
172 fail_ck:
173         clk_put(cpuclk);
174         return result;
175 }
176
177 static int zynq_cpu_exit(struct cpufreq_policy *policy)
178 {
179         freq_table_free();
180         clk_put(cpuclk);
181         return 0;
182 }
183
184 static struct freq_attr *zynq_cpufreq_attr[] = {
185         &cpufreq_freq_attr_scaling_available_freqs,
186         NULL,
187 };
188
189 static struct cpufreq_driver zynq_cpufreq_driver = {
190         .flags          = CPUFREQ_STICKY,
191         .verify         = zynq_verify_speed,
192         .target         = zynq_target,
193         .get            = zynq_getspeed,
194         .init           = zynq_cpu_init,
195         .exit           = zynq_cpu_exit,
196         .name           = "Zynq cpufreq",
197         .attr           = zynq_cpufreq_attr,
198 };
199
200 static int __init zynq_cpufreq_init(void)
201 {
202         struct device *dev = get_cpu_device(0);
203
204         if (!dev) {
205                 pr_warn("%s: Error: device not found.", __func__);
206                 return -EINVAL;
207         }
208         mpu_dev = dev;
209         return cpufreq_register_driver(&zynq_cpufreq_driver);
210 }
211
212 static void __exit zynq_cpufreq_exit(void)
213 {
214         cpufreq_unregister_driver(&zynq_cpufreq_driver);
215 }
216
217 MODULE_DESCRIPTION("cpufreq driver for Zynq");
218 MODULE_LICENSE("GPL");
219 module_init(zynq_cpufreq_init);
220 module_exit(zynq_cpufreq_exit);