Commit | Line | Data |
---|---|---|
aa9ad6ad HW |
1 | /* linux/arch/arm/plat-samsung/clock-clksrc.c |
2 | * | |
3 | * Copyright 2008 Simtec Electronics | |
4 | * Ben Dooks <ben@simtec.co.uk> | |
5 | * http://armlinux.simtec.co.uk/ | |
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 | ||
12 | #include <linux/init.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/list.h> | |
16 | #include <linux/errno.h> | |
17 | #include <linux/err.h> | |
18 | #include <linux/clk.h> | |
edbaa603 | 19 | #include <linux/device.h> |
aa9ad6ad HW |
20 | #include <linux/io.h> |
21 | ||
22 | #include <plat/clock.h> | |
23 | #include <plat/clock-clksrc.h> | |
24 | #include <plat/cpu-freq.h> | |
25 | ||
26 | static inline struct clksrc_clk *to_clksrc(struct clk *clk) | |
27 | { | |
28 | return container_of(clk, struct clksrc_clk, clk); | |
29 | } | |
30 | ||
31 | static inline u32 bit_mask(u32 shift, u32 nr_bits) | |
32 | { | |
33 | u32 mask = 0xffffffff >> (32 - nr_bits); | |
34 | ||
35 | return mask << shift; | |
36 | } | |
37 | ||
38 | static unsigned long s3c_getrate_clksrc(struct clk *clk) | |
39 | { | |
40 | struct clksrc_clk *sclk = to_clksrc(clk); | |
41 | unsigned long rate = clk_get_rate(clk->parent); | |
42 | u32 clkdiv = __raw_readl(sclk->reg_div.reg); | |
43 | u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size); | |
44 | ||
45 | clkdiv &= mask; | |
46 | clkdiv >>= sclk->reg_div.shift; | |
47 | clkdiv++; | |
48 | ||
49 | rate /= clkdiv; | |
50 | return rate; | |
51 | } | |
52 | ||
53 | static int s3c_setrate_clksrc(struct clk *clk, unsigned long rate) | |
54 | { | |
55 | struct clksrc_clk *sclk = to_clksrc(clk); | |
56 | void __iomem *reg = sclk->reg_div.reg; | |
57 | unsigned int div; | |
58 | u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size); | |
59 | u32 val; | |
60 | ||
61 | rate = clk_round_rate(clk, rate); | |
62 | div = clk_get_rate(clk->parent) / rate; | |
f9e011b6 | 63 | if (div > (1 << sclk->reg_div.size)) |
aa9ad6ad HW |
64 | return -EINVAL; |
65 | ||
66 | val = __raw_readl(reg); | |
67 | val &= ~mask; | |
68 | val |= (div - 1) << sclk->reg_div.shift; | |
69 | __raw_writel(val, reg); | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
74 | static int s3c_setparent_clksrc(struct clk *clk, struct clk *parent) | |
75 | { | |
76 | struct clksrc_clk *sclk = to_clksrc(clk); | |
77 | struct clksrc_sources *srcs = sclk->sources; | |
78 | u32 clksrc = __raw_readl(sclk->reg_src.reg); | |
79 | u32 mask = bit_mask(sclk->reg_src.shift, sclk->reg_src.size); | |
80 | int src_nr = -1; | |
81 | int ptr; | |
82 | ||
83 | for (ptr = 0; ptr < srcs->nr_sources; ptr++) | |
84 | if (srcs->sources[ptr] == parent) { | |
85 | src_nr = ptr; | |
86 | break; | |
87 | } | |
88 | ||
14235696 | 89 | if (src_nr >= 0) { |
aa9ad6ad HW |
90 | clk->parent = parent; |
91 | ||
92 | clksrc &= ~mask; | |
93 | clksrc |= src_nr << sclk->reg_src.shift; | |
94 | ||
95 | __raw_writel(clksrc, sclk->reg_src.reg); | |
96 | return 0; | |
97 | } | |
98 | ||
99 | return -EINVAL; | |
100 | } | |
101 | ||
102 | static unsigned long s3c_roundrate_clksrc(struct clk *clk, | |
103 | unsigned long rate) | |
104 | { | |
f9e011b6 | 105 | struct clksrc_clk *sclk = to_clksrc(clk); |
aa9ad6ad | 106 | unsigned long parent_rate = clk_get_rate(clk->parent); |
f9e011b6 | 107 | int max_div = 1 << sclk->reg_div.size; |
aa9ad6ad HW |
108 | int div; |
109 | ||
110 | if (rate >= parent_rate) | |
111 | rate = parent_rate; | |
112 | else { | |
113 | div = parent_rate / rate; | |
114 | if (parent_rate % rate) | |
115 | div++; | |
116 | ||
117 | if (div == 0) | |
118 | div = 1; | |
f9e011b6 BD |
119 | if (div > max_div) |
120 | div = max_div; | |
aa9ad6ad HW |
121 | |
122 | rate = parent_rate / div; | |
123 | } | |
124 | ||
125 | return rate; | |
126 | } | |
127 | ||
128 | /* Clock initialisation code */ | |
129 | ||
682e2b7d | 130 | void __init_or_cpufreq s3c_set_clksrc(struct clksrc_clk *clk, bool announce) |
aa9ad6ad HW |
131 | { |
132 | struct clksrc_sources *srcs = clk->sources; | |
133 | u32 mask = bit_mask(clk->reg_src.shift, clk->reg_src.size); | |
b8792dbf | 134 | u32 clksrc; |
aa9ad6ad | 135 | |
b8792dbf TA |
136 | if (!clk->reg_src.reg) { |
137 | if (!clk->clk.parent) | |
138 | printk(KERN_ERR "%s: no parent clock specified\n", | |
139 | clk->clk.name); | |
140 | return; | |
141 | } | |
aa9ad6ad | 142 | |
b8792dbf | 143 | clksrc = __raw_readl(clk->reg_src.reg); |
aa9ad6ad HW |
144 | clksrc &= mask; |
145 | clksrc >>= clk->reg_src.shift; | |
146 | ||
147 | if (clksrc > srcs->nr_sources || !srcs->sources[clksrc]) { | |
148 | printk(KERN_ERR "%s: bad source %d\n", | |
149 | clk->clk.name, clksrc); | |
150 | return; | |
151 | } | |
152 | ||
153 | clk->clk.parent = srcs->sources[clksrc]; | |
154 | ||
682e2b7d BD |
155 | if (announce) |
156 | printk(KERN_INFO "%s: source is %s (%d), rate is %ld\n", | |
157 | clk->clk.name, clk->clk.parent->name, clksrc, | |
158 | clk_get_rate(&clk->clk)); | |
aa9ad6ad HW |
159 | } |
160 | ||
b3bf41be BD |
161 | static struct clk_ops clksrc_ops = { |
162 | .set_parent = s3c_setparent_clksrc, | |
163 | .get_rate = s3c_getrate_clksrc, | |
164 | .set_rate = s3c_setrate_clksrc, | |
165 | .round_rate = s3c_roundrate_clksrc, | |
166 | }; | |
167 | ||
fb6e76cd BD |
168 | static struct clk_ops clksrc_ops_nodiv = { |
169 | .set_parent = s3c_setparent_clksrc, | |
170 | }; | |
171 | ||
14235696 BD |
172 | static struct clk_ops clksrc_ops_nosrc = { |
173 | .get_rate = s3c_getrate_clksrc, | |
174 | .set_rate = s3c_setrate_clksrc, | |
175 | .round_rate = s3c_roundrate_clksrc, | |
176 | }; | |
177 | ||
aa9ad6ad HW |
178 | void __init s3c_register_clksrc(struct clksrc_clk *clksrc, int size) |
179 | { | |
180 | int ret; | |
181 | ||
182 | for (; size > 0; size--, clksrc++) { | |
f3b464cc BD |
183 | if (!clksrc->reg_div.reg && !clksrc->reg_src.reg) |
184 | printk(KERN_ERR "%s: clock %s has no registers set\n", | |
185 | __func__, clksrc->clk.name); | |
186 | ||
aa9ad6ad | 187 | /* fill in the default functions */ |
fb6e76cd BD |
188 | |
189 | if (!clksrc->clk.ops) { | |
190 | if (!clksrc->reg_div.reg) | |
191 | clksrc->clk.ops = &clksrc_ops_nodiv; | |
14235696 BD |
192 | else if (!clksrc->reg_src.reg) |
193 | clksrc->clk.ops = &clksrc_ops_nosrc; | |
fb6e76cd BD |
194 | else |
195 | clksrc->clk.ops = &clksrc_ops; | |
196 | } | |
aa9ad6ad | 197 | |
682e2b7d BD |
198 | /* setup the clocksource, but do not announce it |
199 | * as it may be re-set by the setup routines | |
200 | * called after the rest of the clocks have been | |
201 | * registered | |
202 | */ | |
203 | s3c_set_clksrc(clksrc, false); | |
aa9ad6ad HW |
204 | |
205 | ret = s3c24xx_register_clock(&clksrc->clk); | |
206 | ||
207 | if (ret < 0) { | |
208 | printk(KERN_ERR "%s: failed to register %s (%d)\n", | |
209 | __func__, clksrc->clk.name, ret); | |
210 | } | |
211 | } | |
212 | } |