Commit | Line | Data |
---|---|---|
ec6bced6 TL |
1 | /* |
2 | * linux/arch/arm/plat-omap/cpu-omap.c | |
3 | * | |
4 | * CPU frequency scaling for OMAP | |
5 | * | |
6 | * Copyright (C) 2005 Nokia Corporation | |
7 | * Written by Tony Lindgren <tony@atomide.com> | |
8 | * | |
9 | * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License version 2 as | |
13 | * published by the Free Software Foundation. | |
14 | */ | |
15 | #include <linux/types.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/sched.h> | |
18 | #include <linux/cpufreq.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/err.h> | |
f8ce2547 | 22 | #include <linux/clk.h> |
fced80c7 | 23 | #include <linux/io.h> |
ec6bced6 | 24 | |
a09e64fb | 25 | #include <mach/hardware.h> |
ce491cf8 | 26 | #include <plat/clock.h> |
ec6bced6 TL |
27 | #include <asm/system.h> |
28 | ||
a7ca9d2b TL |
29 | #define VERY_HI_RATE 900000000 |
30 | ||
aeec2990 KH |
31 | static struct cpufreq_frequency_table *freq_table; |
32 | ||
a7ca9d2b TL |
33 | #ifdef CONFIG_ARCH_OMAP1 |
34 | #define MPU_CLK "mpu" | |
35 | #else | |
36 | #define MPU_CLK "virt_prcm_set" | |
37 | #endif | |
38 | ||
b8488fbe HD |
39 | static struct clk *mpu_clk; |
40 | ||
ec6bced6 TL |
41 | /* TODO: Add support for SDRAM timing changes */ |
42 | ||
43 | int omap_verify_speed(struct cpufreq_policy *policy) | |
44 | { | |
aeec2990 KH |
45 | if (freq_table) |
46 | return cpufreq_frequency_table_verify(policy, freq_table); | |
47 | ||
ec6bced6 TL |
48 | if (policy->cpu) |
49 | return -EINVAL; | |
50 | ||
51 | cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, | |
52 | policy->cpuinfo.max_freq); | |
b8488fbe | 53 | |
ec6bced6 TL |
54 | policy->min = clk_round_rate(mpu_clk, policy->min * 1000) / 1000; |
55 | policy->max = clk_round_rate(mpu_clk, policy->max * 1000) / 1000; | |
56 | cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, | |
57 | policy->cpuinfo.max_freq); | |
ec6bced6 TL |
58 | return 0; |
59 | } | |
60 | ||
61 | unsigned int omap_getspeed(unsigned int cpu) | |
62 | { | |
ec6bced6 TL |
63 | unsigned long rate; |
64 | ||
65 | if (cpu) | |
66 | return 0; | |
67 | ||
ec6bced6 | 68 | rate = clk_get_rate(mpu_clk) / 1000; |
ec6bced6 TL |
69 | return rate; |
70 | } | |
71 | ||
72 | static int omap_target(struct cpufreq_policy *policy, | |
73 | unsigned int target_freq, | |
74 | unsigned int relation) | |
75 | { | |
ec6bced6 TL |
76 | struct cpufreq_freqs freqs; |
77 | int ret = 0; | |
78 | ||
aeec2990 KH |
79 | /* Ensure desired rate is within allowed range. Some govenors |
80 | * (ondemand) will just pass target_freq=0 to get the minimum. */ | |
60c45ae1 EN |
81 | if (target_freq < policy->min) |
82 | target_freq = policy->min; | |
83 | if (target_freq > policy->max) | |
84 | target_freq = policy->max; | |
aeec2990 | 85 | |
ec6bced6 TL |
86 | freqs.old = omap_getspeed(0); |
87 | freqs.new = clk_round_rate(mpu_clk, target_freq * 1000) / 1000; | |
88 | freqs.cpu = 0; | |
89 | ||
aeec2990 KH |
90 | if (freqs.old == freqs.new) |
91 | return ret; | |
92 | ||
ec6bced6 | 93 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); |
aeec2990 KH |
94 | #ifdef CONFIG_CPU_FREQ_DEBUG |
95 | printk(KERN_DEBUG "cpufreq-omap: transition: %u --> %u\n", | |
96 | freqs.old, freqs.new); | |
97 | #endif | |
98 | ret = clk_set_rate(mpu_clk, freqs.new * 1000); | |
ec6bced6 | 99 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); |
ec6bced6 TL |
100 | |
101 | return ret; | |
102 | } | |
103 | ||
104 | static int __init omap_cpu_init(struct cpufreq_policy *policy) | |
105 | { | |
aeec2990 KH |
106 | int result = 0; |
107 | ||
a7ca9d2b | 108 | mpu_clk = clk_get(NULL, MPU_CLK); |
ec6bced6 TL |
109 | if (IS_ERR(mpu_clk)) |
110 | return PTR_ERR(mpu_clk); | |
111 | ||
112 | if (policy->cpu != 0) | |
113 | return -EINVAL; | |
aeec2990 | 114 | |
ec6bced6 | 115 | policy->cur = policy->min = policy->max = omap_getspeed(0); |
aeec2990 KH |
116 | |
117 | clk_init_cpufreq_table(&freq_table); | |
118 | if (freq_table) { | |
119 | result = cpufreq_frequency_table_cpuinfo(policy, freq_table); | |
120 | if (!result) | |
121 | cpufreq_frequency_table_get_attr(freq_table, | |
122 | policy->cpu); | |
123 | } else { | |
124 | policy->cpuinfo.min_freq = clk_round_rate(mpu_clk, 0) / 1000; | |
125 | policy->cpuinfo.max_freq = clk_round_rate(mpu_clk, | |
126 | VERY_HI_RATE) / 1000; | |
127 | } | |
128 | ||
129 | /* FIXME: what's the actual transition time? */ | |
b029839c | 130 | policy->cpuinfo.transition_latency = 300 * 1000; |
ec6bced6 TL |
131 | |
132 | return 0; | |
133 | } | |
134 | ||
b8488fbe HD |
135 | static int omap_cpu_exit(struct cpufreq_policy *policy) |
136 | { | |
4e37c10d | 137 | clk_exit_cpufreq_table(&freq_table); |
b8488fbe HD |
138 | clk_put(mpu_clk); |
139 | return 0; | |
140 | } | |
141 | ||
aeec2990 KH |
142 | static struct freq_attr *omap_cpufreq_attr[] = { |
143 | &cpufreq_freq_attr_scaling_available_freqs, | |
144 | NULL, | |
145 | }; | |
146 | ||
ec6bced6 TL |
147 | static struct cpufreq_driver omap_driver = { |
148 | .flags = CPUFREQ_STICKY, | |
149 | .verify = omap_verify_speed, | |
150 | .target = omap_target, | |
151 | .get = omap_getspeed, | |
152 | .init = omap_cpu_init, | |
b8488fbe | 153 | .exit = omap_cpu_exit, |
ec6bced6 | 154 | .name = "omap", |
aeec2990 | 155 | .attr = omap_cpufreq_attr, |
ec6bced6 TL |
156 | }; |
157 | ||
158 | static int __init omap_cpufreq_init(void) | |
159 | { | |
160 | return cpufreq_register_driver(&omap_driver); | |
161 | } | |
162 | ||
163 | arch_initcall(omap_cpufreq_init); | |
aeec2990 KH |
164 | |
165 | /* | |
166 | * if ever we want to remove this, upon cleanup call: | |
167 | * | |
168 | * cpufreq_unregister_driver() | |
169 | * cpufreq_frequency_table_put_attr() | |
170 | */ | |
171 |