aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Herring <rob.herring@calxeda.com>2012-03-13 19:19:19 -0400
committerMike Turquette <mturquette@linaro.org>2012-07-11 20:58:47 -0400
commit8d4d9f52081cd45bde42b9452eba95f38feb7dae (patch)
tree81b614f5f842150c910737a9e1f5414de3b49b4b
parent260b6aa03edd87a3a85c137e7b95305d9eb40485 (diff)
clk: add highbank clock support
This adds real clock support to Calxeda Highbank SOC using the common clock infrastructure. Signed-off-by: Rob Herring <rob.herring@calxeda.com> [mturquette@linaro.org: fixed up invalid writes to const struct member] Signed-off-by: Mike Turquette <mturquette@linaro.org>
-rw-r--r--Documentation/devicetree/bindings/clock/calxeda.txt17
-rw-r--r--arch/arm/Kconfig1
-rw-r--r--arch/arm/boot/dts/highbank.dts91
-rw-r--r--arch/arm/mach-highbank/Makefile2
-rw-r--r--arch/arm/mach-highbank/clock.c62
-rw-r--r--arch/arm/mach-highbank/highbank.c7
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-highbank.c346
8 files changed, 463 insertions, 64 deletions
diff --git a/Documentation/devicetree/bindings/clock/calxeda.txt b/Documentation/devicetree/bindings/clock/calxeda.txt
new file mode 100644
index 000000000000..0a6ac1bdcda1
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/calxeda.txt
@@ -0,0 +1,17 @@
1Device Tree Clock bindings for Calxeda highbank platform
2
3This binding uses the common clock binding[1].
4
5[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
6
7Required properties:
8- compatible : shall be one of the following:
9 "calxeda,hb-pll-clock" - for a PLL clock
10 "calxeda,hb-a9periph-clock" - The A9 peripheral clock divided from the
11 A9 clock.
12 "calxeda,hb-a9bus-clock" - The A9 bus clock divided from the A9 clock.
13 "calxeda,hb-emmc-clock" - Divided clock for MMC/SD controller.
14- reg : shall be the control register offset from SYSREGs base for the clock.
15- clocks : shall be the input parent clock phandle for the clock. This is
16 either an oscillator or a pll output.
17- #clock-cells : from common clock binding; shall be set to 0.
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 6f8cf405d3ec..f3c60ff41692 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -352,6 +352,7 @@ config ARCH_HIGHBANK
352 select ARM_TIMER_SP804 352 select ARM_TIMER_SP804
353 select CACHE_L2X0 353 select CACHE_L2X0
354 select CLKDEV_LOOKUP 354 select CLKDEV_LOOKUP
355 select COMMON_CLK
355 select CPU_V7 356 select CPU_V7
356 select GENERIC_CLOCKEVENTS 357 select GENERIC_CLOCKEVENTS
357 select HAVE_ARM_SCU 358 select HAVE_ARM_SCU
diff --git a/arch/arm/boot/dts/highbank.dts b/arch/arm/boot/dts/highbank.dts
index 83e72294aefb..2e1cfa00c25b 100644
--- a/arch/arm/boot/dts/highbank.dts
+++ b/arch/arm/boot/dts/highbank.dts
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright 2011 Calxeda, Inc. 2 * Copyright 2011-2012 Calxeda, Inc.
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify it 4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License, 5 * under the terms and conditions of the GNU General Public License,
@@ -24,6 +24,7 @@
24 compatible = "calxeda,highbank"; 24 compatible = "calxeda,highbank";
25 #address-cells = <1>; 25 #address-cells = <1>;
26 #size-cells = <1>; 26 #size-cells = <1>;
27 clock-ranges;
27 28
28 cpus { 29 cpus {
29 #address-cells = <1>; 30 #address-cells = <1>;
@@ -33,24 +34,32 @@
33 compatible = "arm,cortex-a9"; 34 compatible = "arm,cortex-a9";
34 reg = <0>; 35 reg = <0>;
35 next-level-cache = <&L2>; 36 next-level-cache = <&L2>;
37 clocks = <&a9pll>;
38 clock-names = "cpu";
36 }; 39 };
37 40
38 cpu@1 { 41 cpu@1 {
39 compatible = "arm,cortex-a9"; 42 compatible = "arm,cortex-a9";
40 reg = <1>; 43 reg = <1>;
41 next-level-cache = <&L2>; 44 next-level-cache = <&L2>;
45 clocks = <&a9pll>;
46 clock-names = "cpu";
42 }; 47 };
43 48
44 cpu@2 { 49 cpu@2 {
45 compatible = "arm,cortex-a9"; 50 compatible = "arm,cortex-a9";
46 reg = <2>; 51 reg = <2>;
47 next-level-cache = <&L2>; 52 next-level-cache = <&L2>;
53 clocks = <&a9pll>;
54 clock-names = "cpu";
48 }; 55 };
49 56
50 cpu@3 { 57 cpu@3 {
51 compatible = "arm,cortex-a9"; 58 compatible = "arm,cortex-a9";
52 reg = <3>; 59 reg = <3>;
53 next-level-cache = <&L2>; 60 next-level-cache = <&L2>;
61 clocks = <&a9pll>;
62 clock-names = "cpu";
54 }; 63 };
55 }; 64 };
56 65
@@ -75,12 +84,14 @@
75 compatible = "arm,cortex-a9-twd-timer"; 84 compatible = "arm,cortex-a9-twd-timer";
76 reg = <0xfff10600 0x20>; 85 reg = <0xfff10600 0x20>;
77 interrupts = <1 13 0xf01>; 86 interrupts = <1 13 0xf01>;
87 clocks = <&a9periphclk>;
78 }; 88 };
79 89
80 watchdog@fff10620 { 90 watchdog@fff10620 {
81 compatible = "arm,cortex-a9-twd-wdt"; 91 compatible = "arm,cortex-a9-twd-wdt";
82 reg = <0xfff10620 0x20>; 92 reg = <0xfff10620 0x20>;
83 interrupts = <1 14 0xf01>; 93 interrupts = <1 14 0xf01>;
94 clocks = <&a9periphclk>;
84 }; 95 };
85 96
86 intc: interrupt-controller@fff11000 { 97 intc: interrupt-controller@fff11000 {
@@ -116,12 +127,15 @@
116 compatible = "calxeda,hb-sdhci"; 127 compatible = "calxeda,hb-sdhci";
117 reg = <0xffe0e000 0x1000>; 128 reg = <0xffe0e000 0x1000>;
118 interrupts = <0 90 4>; 129 interrupts = <0 90 4>;
130 clocks = <&eclk>;
119 }; 131 };
120 132
121 ipc@fff20000 { 133 ipc@fff20000 {
122 compatible = "arm,pl320", "arm,primecell"; 134 compatible = "arm,pl320", "arm,primecell";
123 reg = <0xfff20000 0x1000>; 135 reg = <0xfff20000 0x1000>;
124 interrupts = <0 7 4>; 136 interrupts = <0 7 4>;
137 clocks = <&pclk>;
138 clock-names = "apb_pclk";
125 }; 139 };
126 140
127 gpioe: gpio@fff30000 { 141 gpioe: gpio@fff30000 {
@@ -130,6 +144,8 @@
130 gpio-controller; 144 gpio-controller;
131 reg = <0xfff30000 0x1000>; 145 reg = <0xfff30000 0x1000>;
132 interrupts = <0 14 4>; 146 interrupts = <0 14 4>;
147 clocks = <&pclk>;
148 clock-names = "apb_pclk";
133 }; 149 };
134 150
135 gpiof: gpio@fff31000 { 151 gpiof: gpio@fff31000 {
@@ -138,6 +154,8 @@
138 gpio-controller; 154 gpio-controller;
139 reg = <0xfff31000 0x1000>; 155 reg = <0xfff31000 0x1000>;
140 interrupts = <0 15 4>; 156 interrupts = <0 15 4>;
157 clocks = <&pclk>;
158 clock-names = "apb_pclk";
141 }; 159 };
142 160
143 gpiog: gpio@fff32000 { 161 gpiog: gpio@fff32000 {
@@ -146,6 +164,8 @@
146 gpio-controller; 164 gpio-controller;
147 reg = <0xfff32000 0x1000>; 165 reg = <0xfff32000 0x1000>;
148 interrupts = <0 16 4>; 166 interrupts = <0 16 4>;
167 clocks = <&pclk>;
168 clock-names = "apb_pclk";
149 }; 169 };
150 170
151 gpioh: gpio@fff33000 { 171 gpioh: gpio@fff33000 {
@@ -154,24 +174,32 @@
154 gpio-controller; 174 gpio-controller;
155 reg = <0xfff33000 0x1000>; 175 reg = <0xfff33000 0x1000>;
156 interrupts = <0 17 4>; 176 interrupts = <0 17 4>;
177 clocks = <&pclk>;
178 clock-names = "apb_pclk";
157 }; 179 };
158 180
159 timer { 181 timer {
160 compatible = "arm,sp804", "arm,primecell"; 182 compatible = "arm,sp804", "arm,primecell";
161 reg = <0xfff34000 0x1000>; 183 reg = <0xfff34000 0x1000>;
162 interrupts = <0 18 4>; 184 interrupts = <0 18 4>;
185 clocks = <&pclk>;
186 clock-names = "apb_pclk";
163 }; 187 };
164 188
165 rtc@fff35000 { 189 rtc@fff35000 {
166 compatible = "arm,pl031", "arm,primecell"; 190 compatible = "arm,pl031", "arm,primecell";
167 reg = <0xfff35000 0x1000>; 191 reg = <0xfff35000 0x1000>;
168 interrupts = <0 19 4>; 192 interrupts = <0 19 4>;
193 clocks = <&pclk>;
194 clock-names = "apb_pclk";
169 }; 195 };
170 196
171 serial@fff36000 { 197 serial@fff36000 {
172 compatible = "arm,pl011", "arm,primecell"; 198 compatible = "arm,pl011", "arm,primecell";
173 reg = <0xfff36000 0x1000>; 199 reg = <0xfff36000 0x1000>;
174 interrupts = <0 20 4>; 200 interrupts = <0 20 4>;
201 clocks = <&pclk>;
202 clock-names = "apb_pclk";
175 }; 203 };
176 204
177 smic@fff3a000 { 205 smic@fff3a000 {
@@ -186,12 +214,73 @@
186 sregs@fff3c000 { 214 sregs@fff3c000 {
187 compatible = "calxeda,hb-sregs"; 215 compatible = "calxeda,hb-sregs";
188 reg = <0xfff3c000 0x1000>; 216 reg = <0xfff3c000 0x1000>;
217
218 clocks {
219 #address-cells = <1>;
220 #size-cells = <0>;
221
222 osc: oscillator {
223 #clock-cells = <0>;
224 compatible = "fixed-clock";
225 clock-frequency = <33333000>;
226 };
227
228 ddrpll: ddrpll {
229 #clock-cells = <0>;
230 compatible = "calxeda,hb-pll-clock";
231 clocks = <&osc>;
232 reg = <0x108>;
233 };
234
235 a9pll: a9pll {
236 #clock-cells = <0>;
237 compatible = "calxeda,hb-pll-clock";
238 clocks = <&osc>;
239 reg = <0x100>;
240 };
241
242 a9periphclk: a9periphclk {
243 #clock-cells = <0>;
244 compatible = "calxeda,hb-a9periph-clock";
245 clocks = <&a9pll>;
246 reg = <0x104>;
247 };
248
249 a9bclk: a9bclk {
250 #clock-cells = <0>;
251 compatible = "calxeda,hb-a9bus-clock";
252 clocks = <&a9pll>;
253 reg = <0x104>;
254 };
255
256 emmcpll: emmcpll {
257 #clock-cells = <0>;
258 compatible = "calxeda,hb-pll-clock";
259 clocks = <&osc>;
260 reg = <0x10C>;
261 };
262
263 eclk: eclk {
264 #clock-cells = <0>;
265 compatible = "calxeda,hb-emmc-clock";
266 clocks = <&emmcpll>;
267 reg = <0x114>;
268 };
269
270 pclk: pclk {
271 #clock-cells = <0>;
272 compatible = "fixed-clock";
273 clock-frequency = <150000000>;
274 };
275 };
189 }; 276 };
190 277
191 dma@fff3d000 { 278 dma@fff3d000 {
192 compatible = "arm,pl330", "arm,primecell"; 279 compatible = "arm,pl330", "arm,primecell";
193 reg = <0xfff3d000 0x1000>; 280 reg = <0xfff3d000 0x1000>;
194 interrupts = <0 92 4>; 281 interrupts = <0 92 4>;
282 clocks = <&pclk>;
283 clock-names = "apb_pclk";
195 }; 284 };
196 285
197 ethernet@fff50000 { 286 ethernet@fff50000 {
diff --git a/arch/arm/mach-highbank/Makefile b/arch/arm/mach-highbank/Makefile
index ded4652ada80..3ec8bdd25d09 100644
--- a/arch/arm/mach-highbank/Makefile
+++ b/arch/arm/mach-highbank/Makefile
@@ -1,4 +1,4 @@
1obj-y := clock.o highbank.o system.o smc.o 1obj-y := highbank.o system.o smc.o
2 2
3plus_sec := $(call as-instr,.arch_extension sec,+sec) 3plus_sec := $(call as-instr,.arch_extension sec,+sec)
4AFLAGS_smc.o :=-Wa,-march=armv7-a$(plus_sec) 4AFLAGS_smc.o :=-Wa,-march=armv7-a$(plus_sec)
diff --git a/arch/arm/mach-highbank/clock.c b/arch/arm/mach-highbank/clock.c
deleted file mode 100644
index c25a2ae4fde1..000000000000
--- a/arch/arm/mach-highbank/clock.c
+++ /dev/null
@@ -1,62 +0,0 @@
1/*
2 * Copyright 2011 Calxeda, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#include <linux/module.h>
17#include <linux/kernel.h>
18#include <linux/errno.h>
19#include <linux/clk.h>
20#include <linux/clkdev.h>
21
22struct clk {
23 unsigned long rate;
24};
25
26int clk_enable(struct clk *clk)
27{
28 return 0;
29}
30
31void clk_disable(struct clk *clk)
32{}
33
34unsigned long clk_get_rate(struct clk *clk)
35{
36 return clk->rate;
37}
38
39long clk_round_rate(struct clk *clk, unsigned long rate)
40{
41 return clk->rate;
42}
43
44int clk_set_rate(struct clk *clk, unsigned long rate)
45{
46 return 0;
47}
48
49static struct clk eclk = { .rate = 200000000 };
50static struct clk pclk = { .rate = 150000000 };
51
52static struct clk_lookup lookups[] = {
53 { .clk = &pclk, .con_id = "apb_pclk", },
54 { .clk = &pclk, .dev_id = "sp804", },
55 { .clk = &eclk, .dev_id = "ffe0e000.sdhci", },
56 { .clk = &pclk, .dev_id = "fff36000.serial", },
57};
58
59void __init highbank_clocks_init(void)
60{
61 clkdev_add_table(lookups, ARRAY_SIZE(lookups));
62}
diff --git a/arch/arm/mach-highbank/highbank.c b/arch/arm/mach-highbank/highbank.c
index 8777612b1a42..d75b0a78d88a 100644
--- a/arch/arm/mach-highbank/highbank.c
+++ b/arch/arm/mach-highbank/highbank.c
@@ -105,6 +105,11 @@ static void __init highbank_init_irq(void)
105#endif 105#endif
106} 106}
107 107
108static struct clk_lookup lookup = {
109 .dev_id = "sp804",
110 .con_id = NULL,
111};
112
108static void __init highbank_timer_init(void) 113static void __init highbank_timer_init(void)
109{ 114{
110 int irq; 115 int irq;
@@ -122,6 +127,8 @@ static void __init highbank_timer_init(void)
122 irq = irq_of_parse_and_map(np, 0); 127 irq = irq_of_parse_and_map(np, 0);
123 128
124 highbank_clocks_init(); 129 highbank_clocks_init();
130 lookup.clk = of_clk_get(np, 0);
131 clkdev_add(&lookup);
125 132
126 sp804_clocksource_and_sched_clock_init(timer_base + 0x20, "timer1"); 133 sp804_clocksource_and_sched_clock_init(timer_base + 0x20, "timer1");
127 sp804_clockevents_init(timer_base, irq, "timer0"); 134 sp804_clockevents_init(timer_base, irq, "timer0");
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index a4d67d7a7534..02ffdf647b5e 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o
3obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ 3obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \
4 clk-mux.o clk-divider.o clk-fixed-factor.o 4 clk-mux.o clk-divider.o clk-fixed-factor.o
5# SoCs specific 5# SoCs specific
6obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
6obj-$(CONFIG_ARCH_MXS) += mxs/ 7obj-$(CONFIG_ARCH_MXS) += mxs/
7obj-$(CONFIG_PLAT_SPEAR) += spear/ 8obj-$(CONFIG_PLAT_SPEAR) += spear/
8obj-$(CONFIG_ARCH_U300) += clk-u300.o 9obj-$(CONFIG_ARCH_U300) += clk-u300.o
diff --git a/drivers/clk/clk-highbank.c b/drivers/clk/clk-highbank.c
new file mode 100644
index 000000000000..52fecadf004a
--- /dev/null
+++ b/drivers/clk/clk-highbank.c
@@ -0,0 +1,346 @@
1/*
2 * Copyright 2011-2012 Calxeda, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <linux/kernel.h>
18#include <linux/slab.h>
19#include <linux/err.h>
20#include <linux/clk-provider.h>
21#include <linux/io.h>
22#include <linux/of.h>
23
24extern void __iomem *sregs_base;
25
26#define HB_PLL_LOCK_500 0x20000000
27#define HB_PLL_LOCK 0x10000000
28#define HB_PLL_DIVF_SHIFT 20
29#define HB_PLL_DIVF_MASK 0x0ff00000
30#define HB_PLL_DIVQ_SHIFT 16
31#define HB_PLL_DIVQ_MASK 0x00070000
32#define HB_PLL_DIVR_SHIFT 8
33#define HB_PLL_DIVR_MASK 0x00001f00
34#define HB_PLL_RANGE_SHIFT 4
35#define HB_PLL_RANGE_MASK 0x00000070
36#define HB_PLL_BYPASS 0x00000008
37#define HB_PLL_RESET 0x00000004
38#define HB_PLL_EXT_BYPASS 0x00000002
39#define HB_PLL_EXT_ENA 0x00000001
40
41#define HB_PLL_VCO_MIN_FREQ 2133000000
42#define HB_PLL_MAX_FREQ HB_PLL_VCO_MIN_FREQ
43#define HB_PLL_MIN_FREQ (HB_PLL_VCO_MIN_FREQ / 64)
44
45#define HB_A9_BCLK_DIV_MASK 0x00000006
46#define HB_A9_BCLK_DIV_SHIFT 1
47#define HB_A9_PCLK_DIV 0x00000001
48
49struct hb_clk {
50 struct clk_hw hw;
51 void __iomem *reg;
52 char *parent_name;
53};
54#define to_hb_clk(p) container_of(p, struct hb_clk, hw)
55
56static int clk_pll_prepare(struct clk_hw *hwclk)
57 {
58 struct hb_clk *hbclk = to_hb_clk(hwclk);
59 u32 reg;
60
61 reg = readl(hbclk->reg);
62 reg &= ~HB_PLL_RESET;
63 writel(reg, hbclk->reg);
64
65 while ((readl(hbclk->reg) & HB_PLL_LOCK) == 0)
66 ;
67 while ((readl(hbclk->reg) & HB_PLL_LOCK_500) == 0)
68 ;
69
70 return 0;
71}
72
73static void clk_pll_unprepare(struct clk_hw *hwclk)
74{
75 struct hb_clk *hbclk = to_hb_clk(hwclk);
76 u32 reg;
77
78 reg = readl(hbclk->reg);
79 reg |= HB_PLL_RESET;
80 writel(reg, hbclk->reg);
81}
82
83static int clk_pll_enable(struct clk_hw *hwclk)
84{
85 struct hb_clk *hbclk = to_hb_clk(hwclk);
86 u32 reg;
87
88 reg = readl(hbclk->reg);
89 reg |= HB_PLL_EXT_ENA;
90 writel(reg, hbclk->reg);
91
92 return 0;
93}
94
95static void clk_pll_disable(struct clk_hw *hwclk)
96{
97 struct hb_clk *hbclk = to_hb_clk(hwclk);
98 u32 reg;
99
100 reg = readl(hbclk->reg);
101 reg &= ~HB_PLL_EXT_ENA;
102 writel(reg, hbclk->reg);
103}
104
105static unsigned long clk_pll_recalc_rate(struct clk_hw *hwclk,
106 unsigned long parent_rate)
107{
108 struct hb_clk *hbclk = to_hb_clk(hwclk);
109 unsigned long divf, divq, vco_freq, reg;
110
111 reg = readl(hbclk->reg);
112 if (reg & HB_PLL_EXT_BYPASS)
113 return parent_rate;
114
115 divf = (reg & HB_PLL_DIVF_MASK) >> HB_PLL_DIVF_SHIFT;
116 divq = (reg & HB_PLL_DIVQ_MASK) >> HB_PLL_DIVQ_SHIFT;
117 vco_freq = parent_rate * (divf + 1);
118
119 return vco_freq / (1 << divq);
120}
121
122static void clk_pll_calc(unsigned long rate, unsigned long ref_freq,
123 u32 *pdivq, u32 *pdivf)
124{
125 u32 divq, divf;
126 unsigned long vco_freq;
127
128 if (rate < HB_PLL_MIN_FREQ)
129 rate = HB_PLL_MIN_FREQ;
130 if (rate > HB_PLL_MAX_FREQ)
131 rate = HB_PLL_MAX_FREQ;
132
133 for (divq = 1; divq <= 6; divq++) {
134 if ((rate * (1 << divq)) >= HB_PLL_VCO_MIN_FREQ)
135 break;
136 }
137
138 vco_freq = rate * (1 << divq);
139 divf = (vco_freq + (ref_freq / 2)) / ref_freq;
140 divf--;
141
142 *pdivq = divq;
143 *pdivf = divf;
144}
145
146static long clk_pll_round_rate(struct clk_hw *hwclk, unsigned long rate,
147 unsigned long *parent_rate)
148{
149 u32 divq, divf;
150 unsigned long ref_freq = *parent_rate;
151
152 clk_pll_calc(rate, ref_freq, &divq, &divf);
153
154 return (ref_freq * (divf + 1)) / (1 << divq);
155}
156
157static int clk_pll_set_rate(struct clk_hw *hwclk, unsigned long rate,
158 unsigned long parent_rate)
159{
160 struct hb_clk *hbclk = to_hb_clk(hwclk);
161 u32 divq, divf;
162 u32 reg;
163
164 clk_pll_calc(rate, parent_rate, &divq, &divf);
165
166 reg = readl(hbclk->reg);
167 if (divf != ((reg & HB_PLL_DIVF_MASK) >> HB_PLL_DIVF_SHIFT)) {
168 /* Need to re-lock PLL, so put it into bypass mode */
169 reg |= HB_PLL_EXT_BYPASS;
170 writel(reg | HB_PLL_EXT_BYPASS, hbclk->reg);
171
172 writel(reg | HB_PLL_RESET, hbclk->reg);
173 reg &= ~(HB_PLL_DIVF_MASK | HB_PLL_DIVQ_MASK);
174 reg |= (divf << HB_PLL_DIVF_SHIFT) | (divq << HB_PLL_DIVQ_SHIFT);
175 writel(reg | HB_PLL_RESET, hbclk->reg);
176 writel(reg, hbclk->reg);
177
178 while ((readl(hbclk->reg) & HB_PLL_LOCK) == 0)
179 ;
180 while ((readl(hbclk->reg) & HB_PLL_LOCK_500) == 0)
181 ;
182 reg |= HB_PLL_EXT_ENA;
183 reg &= ~HB_PLL_EXT_BYPASS;
184 } else {
185 reg &= ~HB_PLL_DIVQ_MASK;
186 reg |= divq << HB_PLL_DIVQ_SHIFT;
187 }
188 writel(reg, hbclk->reg);
189
190 return 0;
191}
192
193static const struct clk_ops clk_pll_ops = {
194 .prepare = clk_pll_prepare,
195 .unprepare = clk_pll_unprepare,
196 .enable = clk_pll_enable,
197 .disable = clk_pll_disable,
198 .recalc_rate = clk_pll_recalc_rate,
199 .round_rate = clk_pll_round_rate,
200 .set_rate = clk_pll_set_rate,
201};
202
203static unsigned long clk_cpu_periphclk_recalc_rate(struct clk_hw *hwclk,
204 unsigned long parent_rate)
205{
206 struct hb_clk *hbclk = to_hb_clk(hwclk);
207 u32 div = (readl(hbclk->reg) & HB_A9_PCLK_DIV) ? 8 : 4;
208 return parent_rate / div;
209}
210
211static const struct clk_ops a9periphclk_ops = {
212 .recalc_rate = clk_cpu_periphclk_recalc_rate,
213};
214
215static unsigned long clk_cpu_a9bclk_recalc_rate(struct clk_hw *hwclk,
216 unsigned long parent_rate)
217{
218 struct hb_clk *hbclk = to_hb_clk(hwclk);
219 u32 div = (readl(hbclk->reg) & HB_A9_BCLK_DIV_MASK) >> HB_A9_BCLK_DIV_SHIFT;
220
221 return parent_rate / (div + 2);
222}
223
224static const struct clk_ops a9bclk_ops = {
225 .recalc_rate = clk_cpu_a9bclk_recalc_rate,
226};
227
228static unsigned long clk_periclk_recalc_rate(struct clk_hw *hwclk,
229 unsigned long parent_rate)
230{
231 struct hb_clk *hbclk = to_hb_clk(hwclk);
232 u32 div;
233
234 div = readl(hbclk->reg) & 0x1f;
235 div++;
236 div *= 2;
237
238 return parent_rate / div;
239}
240
241static long clk_periclk_round_rate(struct clk_hw *hwclk, unsigned long rate,
242 unsigned long *parent_rate)
243{
244 u32 div;
245
246 div = *parent_rate / rate;
247 div++;
248 div &= ~0x1;
249
250 return *parent_rate / div;
251}
252
253static int clk_periclk_set_rate(struct clk_hw *hwclk, unsigned long rate,
254 unsigned long parent_rate)
255{
256 struct hb_clk *hbclk = to_hb_clk(hwclk);
257 u32 div;
258
259 div = parent_rate / rate;
260 if (div & 0x1)
261 return -EINVAL;
262
263 writel(div >> 1, hbclk->reg);
264 return 0;
265}
266
267static const struct clk_ops periclk_ops = {
268 .recalc_rate = clk_periclk_recalc_rate,
269 .round_rate = clk_periclk_round_rate,
270 .set_rate = clk_periclk_set_rate,
271};
272
273static __init struct clk *hb_clk_init(struct device_node *node, const struct clk_ops *ops)
274{
275 u32 reg;
276 struct clk *clk;
277 struct hb_clk *hb_clk;
278 const char *clk_name = node->name;
279 const char *parent_name;
280 struct clk_init_data init;
281 int rc;
282
283 rc = of_property_read_u32(node, "reg", &reg);
284 if (WARN_ON(rc))
285 return NULL;
286
287 hb_clk = kzalloc(sizeof(*hb_clk), GFP_KERNEL);
288 if (WARN_ON(!hb_clk))
289 return NULL;
290
291 hb_clk->reg = sregs_base + reg;
292
293 of_property_read_string(node, "clock-output-names", &clk_name);
294
295 init.name = clk_name;
296 init.ops = ops;
297 init.flags = 0;
298 parent_name = of_clk_get_parent_name(node, 0);
299 init.parent_names = &parent_name;
300 init.num_parents = 1;
301
302 hb_clk->hw.init = &init;
303
304 clk = clk_register(NULL, &hb_clk->hw);
305 if (WARN_ON(IS_ERR(clk))) {
306 kfree(hb_clk);
307 return NULL;
308 }
309 rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
310 return clk;
311}
312
313static void __init hb_pll_init(struct device_node *node)
314{
315 hb_clk_init(node, &clk_pll_ops);
316}
317
318static void __init hb_a9periph_init(struct device_node *node)
319{
320 hb_clk_init(node, &a9periphclk_ops);
321}
322
323static void __init hb_a9bus_init(struct device_node *node)
324{
325 struct clk *clk = hb_clk_init(node, &a9bclk_ops);
326 clk_prepare_enable(clk);
327}
328
329static void __init hb_emmc_init(struct device_node *node)
330{
331 hb_clk_init(node, &periclk_ops);
332}
333
334static const __initconst struct of_device_id clk_match[] = {
335 { .compatible = "fixed-clock", .data = of_fixed_clk_setup, },
336 { .compatible = "calxeda,hb-pll-clock", .data = hb_pll_init, },
337 { .compatible = "calxeda,hb-a9periph-clock", .data = hb_a9periph_init, },
338 { .compatible = "calxeda,hb-a9bus-clock", .data = hb_a9bus_init, },
339 { .compatible = "calxeda,hb-emmc-clock", .data = hb_emmc_init, },
340 {}
341};
342
343void __init highbank_clocks_init(void)
344{
345 of_clk_init(clk_match);
346}