diff options
author | Kukjin Kim <kgene.kim@samsung.com> | 2010-01-13 18:13:37 -0500 |
---|---|---|
committer | Ben Dooks <ben-linux@fluff.org> | 2010-01-15 05:16:16 -0500 |
commit | 1a0e8a52ad56075663d8e120e2468fc96fb6fa6c (patch) | |
tree | 1ff1246f8bfc638e6fc2fab1c3d25b1aba6bb8c1 /arch/arm/plat-s5p | |
parent | 209fecd1b8e65b8046efbbc8314d449e53c4c6b6 (diff) |
ARM: S5P6440: Add Clock and PLL support
This patch adds clock and pll support for S5P6440. This patch are based on
Harald Welte's patches and Ben's plat-samsung.
Signed-off-by: Thomas Abraham <thomas.ab@samsung.com>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Diffstat (limited to 'arch/arm/plat-s5p')
-rw-r--r-- | arch/arm/plat-s5p/clock.c | 136 | ||||
-rw-r--r-- | arch/arm/plat-s5p/include/plat/pll.h | 83 | ||||
-rw-r--r-- | arch/arm/plat-s5p/include/plat/s5p-clock.h | 38 | ||||
-rw-r--r-- | arch/arm/plat-s5p/s5p6440-clock.c | 698 |
4 files changed, 955 insertions, 0 deletions
diff --git a/arch/arm/plat-s5p/clock.c b/arch/arm/plat-s5p/clock.c new file mode 100644 index 00000000000..3d3c0f1934f --- /dev/null +++ b/arch/arm/plat-s5p/clock.c | |||
@@ -0,0 +1,136 @@ | |||
1 | /* linux/arch/arm/plat-s5p/clock.c | ||
2 | * | ||
3 | * Copyright 2009 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com/ | ||
5 | * | ||
6 | * S5P - Common clock support | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/init.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/list.h> | ||
17 | #include <linux/errno.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/sysdev.h> | ||
21 | #include <linux/io.h> | ||
22 | #include <asm/div64.h> | ||
23 | |||
24 | #include <plat/clock.h> | ||
25 | #include <plat/clock-clksrc.h> | ||
26 | #include <plat/s5p-clock.h> | ||
27 | |||
28 | /* fin_apll, fin_mpll and fin_epll are all the same clock, which we call | ||
29 | * clk_ext_xtal_mux. | ||
30 | */ | ||
31 | struct clk clk_ext_xtal_mux = { | ||
32 | .name = "ext_xtal", | ||
33 | .id = -1, | ||
34 | }; | ||
35 | |||
36 | /* 48MHz USB Phy clock output */ | ||
37 | struct clk clk_48m = { | ||
38 | .name = "clk_48m", | ||
39 | .id = -1, | ||
40 | .rate = 48000000, | ||
41 | }; | ||
42 | |||
43 | /* APLL clock output | ||
44 | * No need .ctrlbit, this is always on | ||
45 | */ | ||
46 | struct clk clk_fout_apll = { | ||
47 | .name = "fout_apll", | ||
48 | .id = -1, | ||
49 | }; | ||
50 | |||
51 | /* MPLL clock output | ||
52 | * No need .ctrlbit, this is always on | ||
53 | */ | ||
54 | struct clk clk_fout_mpll = { | ||
55 | .name = "fout_mpll", | ||
56 | .id = -1, | ||
57 | }; | ||
58 | |||
59 | /* EPLL clock output */ | ||
60 | struct clk clk_fout_epll = { | ||
61 | .name = "fout_epll", | ||
62 | .id = -1, | ||
63 | .ctrlbit = (1 << 31), | ||
64 | }; | ||
65 | |||
66 | /* ARM clock */ | ||
67 | struct clk clk_arm = { | ||
68 | .name = "armclk", | ||
69 | .id = -1, | ||
70 | .rate = 0, | ||
71 | .ctrlbit = 0, | ||
72 | }; | ||
73 | |||
74 | /* Possible clock sources for APLL Mux */ | ||
75 | static struct clk *clk_src_apll_list[] = { | ||
76 | [0] = &clk_fin_apll, | ||
77 | [1] = &clk_fout_apll, | ||
78 | }; | ||
79 | |||
80 | struct clksrc_sources clk_src_apll = { | ||
81 | .sources = clk_src_apll_list, | ||
82 | .nr_sources = ARRAY_SIZE(clk_src_apll_list), | ||
83 | }; | ||
84 | |||
85 | /* Possible clock sources for MPLL Mux */ | ||
86 | static struct clk *clk_src_mpll_list[] = { | ||
87 | [0] = &clk_fin_mpll, | ||
88 | [1] = &clk_fout_mpll, | ||
89 | }; | ||
90 | |||
91 | struct clksrc_sources clk_src_mpll = { | ||
92 | .sources = clk_src_mpll_list, | ||
93 | .nr_sources = ARRAY_SIZE(clk_src_mpll_list), | ||
94 | }; | ||
95 | |||
96 | /* Possible clock sources for EPLL Mux */ | ||
97 | static struct clk *clk_src_epll_list[] = { | ||
98 | [0] = &clk_fin_epll, | ||
99 | [1] = &clk_fout_epll, | ||
100 | }; | ||
101 | |||
102 | struct clksrc_sources clk_src_epll = { | ||
103 | .sources = clk_src_epll_list, | ||
104 | .nr_sources = ARRAY_SIZE(clk_src_epll_list), | ||
105 | }; | ||
106 | |||
107 | int s5p_gatectrl(void __iomem *reg, struct clk *clk, int enable) | ||
108 | { | ||
109 | unsigned int ctrlbit = clk->ctrlbit; | ||
110 | u32 con; | ||
111 | |||
112 | con = __raw_readl(reg); | ||
113 | con = enable ? (con | ctrlbit) : (con & ~ctrlbit); | ||
114 | __raw_writel(con, reg); | ||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static struct clk *s5p_clks[] __initdata = { | ||
119 | &clk_ext_xtal_mux, | ||
120 | &clk_48m, | ||
121 | &clk_fout_apll, | ||
122 | &clk_fout_mpll, | ||
123 | &clk_fout_epll, | ||
124 | &clk_arm, | ||
125 | }; | ||
126 | |||
127 | void __init s5p_register_clocks(unsigned long xtal_freq) | ||
128 | { | ||
129 | int ret; | ||
130 | |||
131 | clk_ext_xtal_mux.rate = xtal_freq; | ||
132 | |||
133 | ret = s3c24xx_register_clocks(s5p_clks, ARRAY_SIZE(s5p_clks)); | ||
134 | if (ret > 0) | ||
135 | printk(KERN_ERR "Failed to register s5p clocks\n"); | ||
136 | } | ||
diff --git a/arch/arm/plat-s5p/include/plat/pll.h b/arch/arm/plat-s5p/include/plat/pll.h new file mode 100644 index 00000000000..d48325bb29e --- /dev/null +++ b/arch/arm/plat-s5p/include/plat/pll.h | |||
@@ -0,0 +1,83 @@ | |||
1 | /* arch/arm/plat-s5p/include/plat/pll.h | ||
2 | * | ||
3 | * Copyright (c) 2009 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com/ | ||
5 | * | ||
6 | * S5P PLL code | ||
7 | * | ||
8 | * Based on arch/arm/plat-s3c64xx/include/plat/pll.h | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | */ | ||
14 | |||
15 | #define PLL45XX_MDIV_MASK (0x3FF) | ||
16 | #define PLL45XX_PDIV_MASK (0x3F) | ||
17 | #define PLL45XX_SDIV_MASK (0x7) | ||
18 | #define PLL45XX_MDIV_SHIFT (16) | ||
19 | #define PLL45XX_PDIV_SHIFT (8) | ||
20 | #define PLL45XX_SDIV_SHIFT (0) | ||
21 | |||
22 | #include <asm/div64.h> | ||
23 | |||
24 | enum pll45xx_type_t { | ||
25 | pll_4500, | ||
26 | pll_4502, | ||
27 | pll_4508 | ||
28 | }; | ||
29 | |||
30 | static inline unsigned long s5p_get_pll45xx(unsigned long baseclk, u32 pll_con, | ||
31 | enum pll45xx_type_t pll_type) | ||
32 | { | ||
33 | u32 mdiv, pdiv, sdiv; | ||
34 | u64 fvco = baseclk; | ||
35 | |||
36 | mdiv = (pll_con >> PLL45XX_MDIV_SHIFT) & PLL45XX_MDIV_MASK; | ||
37 | pdiv = (pll_con >> PLL45XX_PDIV_SHIFT) & PLL45XX_PDIV_MASK; | ||
38 | sdiv = (pll_con >> PLL45XX_SDIV_SHIFT) & PLL45XX_SDIV_MASK; | ||
39 | |||
40 | if (pll_type == pll_4508) | ||
41 | sdiv = sdiv - 1; | ||
42 | |||
43 | fvco *= mdiv; | ||
44 | do_div(fvco, (pdiv << sdiv)); | ||
45 | |||
46 | return (unsigned long)fvco; | ||
47 | } | ||
48 | |||
49 | #define PLL90XX_MDIV_MASK (0xFF) | ||
50 | #define PLL90XX_PDIV_MASK (0x3F) | ||
51 | #define PLL90XX_SDIV_MASK (0x7) | ||
52 | #define PLL90XX_KDIV_MASK (0xffff) | ||
53 | #define PLL90XX_MDIV_SHIFT (16) | ||
54 | #define PLL90XX_PDIV_SHIFT (8) | ||
55 | #define PLL90XX_SDIV_SHIFT (0) | ||
56 | #define PLL90XX_KDIV_SHIFT (0) | ||
57 | |||
58 | static inline unsigned long s5p_get_pll90xx(unsigned long baseclk, | ||
59 | u32 pll_con, u32 pll_conk) | ||
60 | { | ||
61 | unsigned long result; | ||
62 | u32 mdiv, pdiv, sdiv, kdiv; | ||
63 | u64 tmp; | ||
64 | |||
65 | mdiv = (pll_con >> PLL90XX_MDIV_SHIFT) & PLL90XX_MDIV_MASK; | ||
66 | pdiv = (pll_con >> PLL90XX_PDIV_SHIFT) & PLL90XX_PDIV_MASK; | ||
67 | sdiv = (pll_con >> PLL90XX_SDIV_SHIFT) & PLL90XX_SDIV_MASK; | ||
68 | kdiv = pll_conk & PLL90XX_KDIV_MASK; | ||
69 | |||
70 | /* We need to multiple baseclk by mdiv (the integer part) and kdiv | ||
71 | * which is in 2^16ths, so shift mdiv up (does not overflow) and | ||
72 | * add kdiv before multiplying. The use of tmp is to avoid any | ||
73 | * overflows before shifting bac down into result when multipling | ||
74 | * by the mdiv and kdiv pair. | ||
75 | */ | ||
76 | |||
77 | tmp = baseclk; | ||
78 | tmp *= (mdiv << 16) + kdiv; | ||
79 | do_div(tmp, (pdiv << sdiv)); | ||
80 | result = tmp >> 16; | ||
81 | |||
82 | return result; | ||
83 | } | ||
diff --git a/arch/arm/plat-s5p/include/plat/s5p-clock.h b/arch/arm/plat-s5p/include/plat/s5p-clock.h new file mode 100644 index 00000000000..e1a7444b882 --- /dev/null +++ b/arch/arm/plat-s5p/include/plat/s5p-clock.h | |||
@@ -0,0 +1,38 @@ | |||
1 | /* linux/arch/arm/plat-s5p/include/plat/s5p-clock.h | ||
2 | * | ||
3 | * Copyright 2009 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com/ | ||
5 | * | ||
6 | * Header file for s5p clock support | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #ifndef __ASM_PLAT_S5P_CLOCK_H | ||
14 | #define __ASM_PLAT_S5P_CLOCK_H __FILE__ | ||
15 | |||
16 | #include <linux/clk.h> | ||
17 | |||
18 | #define GET_DIV(clk, field) ((((clk) & field##_MASK) >> field##_SHIFT) + 1) | ||
19 | |||
20 | #define clk_fin_apll clk_ext_xtal_mux | ||
21 | #define clk_fin_mpll clk_ext_xtal_mux | ||
22 | #define clk_fin_epll clk_ext_xtal_mux | ||
23 | |||
24 | extern struct clk clk_ext_xtal_mux; | ||
25 | extern struct clk clk_48m; | ||
26 | extern struct clk clk_fout_apll; | ||
27 | extern struct clk clk_fout_mpll; | ||
28 | extern struct clk clk_fout_epll; | ||
29 | extern struct clk clk_arm; | ||
30 | |||
31 | extern struct clksrc_sources clk_src_apll; | ||
32 | extern struct clksrc_sources clk_src_mpll; | ||
33 | extern struct clksrc_sources clk_src_epll; | ||
34 | |||
35 | extern int s5p6440_clk48m_ctrl(struct clk *clk, int enable); | ||
36 | extern int s5p_gatectrl(void __iomem *reg, struct clk *clk, int enable); | ||
37 | |||
38 | #endif /* __ASM_PLAT_S5P_CLOCK_H */ | ||
diff --git a/arch/arm/plat-s5p/s5p6440-clock.c b/arch/arm/plat-s5p/s5p6440-clock.c new file mode 100644 index 00000000000..2f412f8c121 --- /dev/null +++ b/arch/arm/plat-s5p/s5p6440-clock.c | |||
@@ -0,0 +1,698 @@ | |||
1 | /* linux/arch/arm/plat-s5p/s5p6440-clock.c | ||
2 | * | ||
3 | * Copyright (c) 2009 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com/ | ||
5 | * | ||
6 | * S5P6440 - Clock support | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/init.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/list.h> | ||
17 | #include <linux/errno.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/sysdev.h> | ||
21 | #include <linux/io.h> | ||
22 | |||
23 | #include <mach/hardware.h> | ||
24 | #include <mach/map.h> | ||
25 | |||
26 | #include <plat/cpu-freq.h> | ||
27 | #include <mach/regs-clock.h> | ||
28 | #include <plat/clock.h> | ||
29 | #include <plat/cpu.h> | ||
30 | #include <plat/clock-clksrc.h> | ||
31 | #include <plat/s5p-clock.h> | ||
32 | #include <plat/pll.h> | ||
33 | #include <plat/s5p6440.h> | ||
34 | |||
35 | /* APLL Mux output clock */ | ||
36 | static struct clksrc_clk clk_mout_apll = { | ||
37 | .clk = { | ||
38 | .name = "mout_apll", | ||
39 | .id = -1, | ||
40 | }, | ||
41 | .sources = &clk_src_apll, | ||
42 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 0, .size = 1 }, | ||
43 | }; | ||
44 | |||
45 | static int s5p6440_epll_enable(struct clk *clk, int enable) | ||
46 | { | ||
47 | unsigned int ctrlbit = clk->ctrlbit; | ||
48 | unsigned int epll_con = __raw_readl(S5P_EPLL_CON) & ~ctrlbit; | ||
49 | |||
50 | if (enable) | ||
51 | __raw_writel(epll_con | ctrlbit, S5P_EPLL_CON); | ||
52 | else | ||
53 | __raw_writel(epll_con, S5P_EPLL_CON); | ||
54 | |||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static unsigned long s5p6440_epll_get_rate(struct clk *clk) | ||
59 | { | ||
60 | return clk->rate; | ||
61 | } | ||
62 | |||
63 | static u32 epll_div[][5] = { | ||
64 | { 36000000, 0, 48, 1, 4 }, | ||
65 | { 48000000, 0, 32, 1, 3 }, | ||
66 | { 60000000, 0, 40, 1, 3 }, | ||
67 | { 72000000, 0, 48, 1, 3 }, | ||
68 | { 84000000, 0, 28, 1, 2 }, | ||
69 | { 96000000, 0, 32, 1, 2 }, | ||
70 | { 32768000, 45264, 43, 1, 4 }, | ||
71 | { 45158000, 6903, 30, 1, 3 }, | ||
72 | { 49152000, 50332, 32, 1, 3 }, | ||
73 | { 67738000, 10398, 45, 1, 3 }, | ||
74 | { 73728000, 9961, 49, 1, 3 } | ||
75 | }; | ||
76 | |||
77 | static int s5p6440_epll_set_rate(struct clk *clk, unsigned long rate) | ||
78 | { | ||
79 | unsigned int epll_con, epll_con_k; | ||
80 | unsigned int i; | ||
81 | |||
82 | if (clk->rate == rate) /* Return if nothing changed */ | ||
83 | return 0; | ||
84 | |||
85 | epll_con = __raw_readl(S5P_EPLL_CON); | ||
86 | epll_con_k = __raw_readl(S5P_EPLL_CON_K); | ||
87 | |||
88 | epll_con_k &= ~(PLL90XX_KDIV_MASK); | ||
89 | epll_con &= ~(PLL90XX_MDIV_MASK | PLL90XX_PDIV_MASK | PLL90XX_SDIV_MASK); | ||
90 | |||
91 | for (i = 0; i < ARRAY_SIZE(epll_div); i++) { | ||
92 | if (epll_div[i][0] == rate) { | ||
93 | epll_con_k |= (epll_div[i][1] << PLL90XX_KDIV_SHIFT); | ||
94 | epll_con |= (epll_div[i][2] << PLL90XX_MDIV_SHIFT) | | ||
95 | (epll_div[i][3] << PLL90XX_PDIV_SHIFT) | | ||
96 | (epll_div[i][4] << PLL90XX_SDIV_SHIFT); | ||
97 | break; | ||
98 | } | ||
99 | } | ||
100 | |||
101 | if (i == ARRAY_SIZE(epll_div)) { | ||
102 | printk(KERN_ERR "%s: Invalid Clock EPLL Frequency\n", __func__); | ||
103 | return -EINVAL; | ||
104 | } | ||
105 | |||
106 | __raw_writel(epll_con, S5P_EPLL_CON); | ||
107 | __raw_writel(epll_con_k, S5P_EPLL_CON_K); | ||
108 | |||
109 | clk->rate = rate; | ||
110 | |||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static struct clk_ops s5p6440_epll_ops = { | ||
115 | .get_rate = s5p6440_epll_get_rate, | ||
116 | .set_rate = s5p6440_epll_set_rate, | ||
117 | }; | ||
118 | |||
119 | static struct clksrc_clk clk_mout_epll = { | ||
120 | .clk = { | ||
121 | .name = "mout_epll", | ||
122 | .id = -1, | ||
123 | }, | ||
124 | .sources = &clk_src_epll, | ||
125 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 2, .size = 1 }, | ||
126 | }; | ||
127 | |||
128 | static struct clksrc_clk clk_mout_mpll = { | ||
129 | .clk = { | ||
130 | .name = "mout_mpll", | ||
131 | .id = -1, | ||
132 | }, | ||
133 | .sources = &clk_src_mpll, | ||
134 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 1, .size = 1 }, | ||
135 | }; | ||
136 | |||
137 | static struct clk clk_h_low = { | ||
138 | .name = "hclk_low", | ||
139 | .id = -1, | ||
140 | .rate = 0, | ||
141 | .parent = NULL, | ||
142 | .ctrlbit = 0, | ||
143 | .ops = &clk_ops_def_setrate, | ||
144 | }; | ||
145 | |||
146 | static struct clk clk_p_low = { | ||
147 | .name = "pclk_low", | ||
148 | .id = -1, | ||
149 | .rate = 0, | ||
150 | .parent = NULL, | ||
151 | .ctrlbit = 0, | ||
152 | .ops = &clk_ops_def_setrate, | ||
153 | }; | ||
154 | |||
155 | enum perf_level { | ||
156 | L0 = 532*1000, | ||
157 | L1 = 266*1000, | ||
158 | L2 = 133*1000, | ||
159 | }; | ||
160 | |||
161 | static const u32 clock_table[][3] = { | ||
162 | /*{ARM_CLK, DIVarm, DIVhclk}*/ | ||
163 | {L0 * 1000, (0 << ARM_DIV_RATIO_SHIFT), (3 << S5P_CLKDIV0_HCLK_SHIFT)}, | ||
164 | {L1 * 1000, (1 << ARM_DIV_RATIO_SHIFT), (1 << S5P_CLKDIV0_HCLK_SHIFT)}, | ||
165 | {L2 * 1000, (3 << ARM_DIV_RATIO_SHIFT), (0 << S5P_CLKDIV0_HCLK_SHIFT)}, | ||
166 | }; | ||
167 | |||
168 | static unsigned long s5p6440_armclk_get_rate(struct clk *clk) | ||
169 | { | ||
170 | unsigned long rate = clk_get_rate(clk->parent); | ||
171 | u32 clkdiv; | ||
172 | |||
173 | /* divisor mask starts at bit0, so no need to shift */ | ||
174 | clkdiv = __raw_readl(ARM_CLK_DIV) & ARM_DIV_MASK; | ||
175 | |||
176 | return rate / (clkdiv + 1); | ||
177 | } | ||
178 | |||
179 | static unsigned long s5p6440_armclk_round_rate(struct clk *clk, | ||
180 | unsigned long rate) | ||
181 | { | ||
182 | u32 iter; | ||
183 | |||
184 | for (iter = 1 ; iter < ARRAY_SIZE(clock_table) ; iter++) { | ||
185 | if (rate > clock_table[iter][0]) | ||
186 | return clock_table[iter-1][0]; | ||
187 | } | ||
188 | |||
189 | return clock_table[ARRAY_SIZE(clock_table) - 1][0]; | ||
190 | } | ||
191 | |||
192 | static int s5p6440_armclk_set_rate(struct clk *clk, unsigned long rate) | ||
193 | { | ||
194 | u32 round_tmp; | ||
195 | u32 iter; | ||
196 | u32 clk_div0_tmp; | ||
197 | u32 cur_rate = clk->ops->get_rate(clk); | ||
198 | unsigned long flags; | ||
199 | |||
200 | round_tmp = clk->ops->round_rate(clk, rate); | ||
201 | if (round_tmp == cur_rate) | ||
202 | return 0; | ||
203 | |||
204 | |||
205 | for (iter = 0 ; iter < ARRAY_SIZE(clock_table) ; iter++) { | ||
206 | if (round_tmp == clock_table[iter][0]) | ||
207 | break; | ||
208 | } | ||
209 | |||
210 | if (iter >= ARRAY_SIZE(clock_table)) | ||
211 | iter = ARRAY_SIZE(clock_table) - 1; | ||
212 | |||
213 | local_irq_save(flags); | ||
214 | if (cur_rate > round_tmp) { | ||
215 | /* Frequency Down */ | ||
216 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & ~(ARM_DIV_MASK); | ||
217 | clk_div0_tmp |= clock_table[iter][1]; | ||
218 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); | ||
219 | |||
220 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & | ||
221 | ~(S5P_CLKDIV0_HCLK_MASK); | ||
222 | clk_div0_tmp |= clock_table[iter][2]; | ||
223 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); | ||
224 | |||
225 | |||
226 | } else { | ||
227 | /* Frequency Up */ | ||
228 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & | ||
229 | ~(S5P_CLKDIV0_HCLK_MASK); | ||
230 | clk_div0_tmp |= clock_table[iter][2]; | ||
231 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); | ||
232 | |||
233 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & ~(ARM_DIV_MASK); | ||
234 | clk_div0_tmp |= clock_table[iter][1]; | ||
235 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); | ||
236 | } | ||
237 | local_irq_restore(flags); | ||
238 | |||
239 | clk->rate = clock_table[iter][0]; | ||
240 | |||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | static struct clk_ops s5p6440_clkarm_ops = { | ||
245 | .get_rate = s5p6440_armclk_get_rate, | ||
246 | .set_rate = s5p6440_armclk_set_rate, | ||
247 | .round_rate = s5p6440_armclk_round_rate, | ||
248 | }; | ||
249 | |||
250 | static unsigned long s5p6440_clk_doutmpll_get_rate(struct clk *clk) | ||
251 | { | ||
252 | unsigned long rate = clk_get_rate(clk->parent); | ||
253 | |||
254 | if (__raw_readl(S5P_CLK_DIV0) & S5P_CLKDIV0_MPLL_MASK) | ||
255 | rate /= 2; | ||
256 | |||
257 | return rate; | ||
258 | } | ||
259 | |||
260 | static struct clk clk_dout_mpll = { | ||
261 | .name = "dout_mpll", | ||
262 | .id = -1, | ||
263 | .parent = &clk_mout_mpll.clk, | ||
264 | .ops = &(struct clk_ops) { | ||
265 | .get_rate = s5p6440_clk_doutmpll_get_rate, | ||
266 | }, | ||
267 | }; | ||
268 | |||
269 | int s5p6440_clk48m_ctrl(struct clk *clk, int enable) | ||
270 | { | ||
271 | unsigned long flags; | ||
272 | u32 val; | ||
273 | |||
274 | /* can't rely on clock lock, this register has other usages */ | ||
275 | local_irq_save(flags); | ||
276 | |||
277 | val = __raw_readl(S5P_OTHERS); | ||
278 | if (enable) | ||
279 | val |= S5P_OTHERS_USB_SIG_MASK; | ||
280 | else | ||
281 | val &= ~S5P_OTHERS_USB_SIG_MASK; | ||
282 | |||
283 | __raw_writel(val, S5P_OTHERS); | ||
284 | |||
285 | local_irq_restore(flags); | ||
286 | |||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | static int s5p6440_pclk_ctrl(struct clk *clk, int enable) | ||
291 | { | ||
292 | return s5p_gatectrl(S5P_CLK_GATE_PCLK, clk, enable); | ||
293 | } | ||
294 | |||
295 | static int s5p6440_hclk0_ctrl(struct clk *clk, int enable) | ||
296 | { | ||
297 | return s5p_gatectrl(S5P_CLK_GATE_HCLK0, clk, enable); | ||
298 | } | ||
299 | |||
300 | static int s5p6440_hclk1_ctrl(struct clk *clk, int enable) | ||
301 | { | ||
302 | return s5p_gatectrl(S5P_CLK_GATE_HCLK1, clk, enable); | ||
303 | } | ||
304 | |||
305 | static int s5p6440_sclk_ctrl(struct clk *clk, int enable) | ||
306 | { | ||
307 | return s5p_gatectrl(S5P_CLK_GATE_SCLK0, clk, enable); | ||
308 | } | ||
309 | |||
310 | static int s5p6440_mem_ctrl(struct clk *clk, int enable) | ||
311 | { | ||
312 | return s5p_gatectrl(S5P_CLK_GATE_MEM0, clk, enable); | ||
313 | } | ||
314 | |||
315 | /* | ||
316 | * The following clocks will be disabled during clock initialization. It is | ||
317 | * recommended to keep the following clocks disabled until the driver requests | ||
318 | * for enabling the clock. | ||
319 | */ | ||
320 | static struct clk init_clocks_disable[] = { | ||
321 | { | ||
322 | .name = "nand", | ||
323 | .id = -1, | ||
324 | .parent = &clk_h, | ||
325 | .enable = s5p6440_mem_ctrl, | ||
326 | .ctrlbit = S5P_CLKCON_MEM0_HCLK_NFCON, | ||
327 | }, { | ||
328 | .name = "adc", | ||
329 | .id = -1, | ||
330 | .parent = &clk_p_low, | ||
331 | .enable = s5p6440_pclk_ctrl, | ||
332 | .ctrlbit = S5P_CLKCON_PCLK_TSADC, | ||
333 | }, { | ||
334 | .name = "i2c", | ||
335 | .id = -1, | ||
336 | .parent = &clk_p_low, | ||
337 | .enable = s5p6440_pclk_ctrl, | ||
338 | .ctrlbit = S5P_CLKCON_PCLK_IIC0, | ||
339 | }, { | ||
340 | .name = "i2s_v40", | ||
341 | .id = 0, | ||
342 | .parent = &clk_p_low, | ||
343 | .enable = s5p6440_pclk_ctrl, | ||
344 | .ctrlbit = S5P_CLKCON_PCLK_IIS2, | ||
345 | }, { | ||
346 | .name = "spi", | ||
347 | .id = 0, | ||
348 | .parent = &clk_p_low, | ||
349 | .enable = s5p6440_pclk_ctrl, | ||
350 | .ctrlbit = S5P_CLKCON_PCLK_SPI0, | ||
351 | }, { | ||
352 | .name = "spi", | ||
353 | .id = 1, | ||
354 | .parent = &clk_p_low, | ||
355 | .enable = s5p6440_pclk_ctrl, | ||
356 | .ctrlbit = S5P_CLKCON_PCLK_SPI1, | ||
357 | }, { | ||
358 | .name = "sclk_spi_48", | ||
359 | .id = 0, | ||
360 | .parent = &clk_48m, | ||
361 | .enable = s5p6440_sclk_ctrl, | ||
362 | .ctrlbit = S5P_CLKCON_SCLK0_SPI0_48, | ||
363 | }, { | ||
364 | .name = "sclk_spi_48", | ||
365 | .id = 1, | ||
366 | .parent = &clk_48m, | ||
367 | .enable = s5p6440_sclk_ctrl, | ||
368 | .ctrlbit = S5P_CLKCON_SCLK0_SPI1_48, | ||
369 | }, { | ||
370 | .name = "mmc_48m", | ||
371 | .id = 0, | ||
372 | .parent = &clk_48m, | ||
373 | .enable = s5p6440_sclk_ctrl, | ||
374 | .ctrlbit = S5P_CLKCON_SCLK0_MMC0_48, | ||
375 | }, { | ||
376 | .name = "mmc_48m", | ||
377 | .id = 1, | ||
378 | .parent = &clk_48m, | ||
379 | .enable = s5p6440_sclk_ctrl, | ||
380 | .ctrlbit = S5P_CLKCON_SCLK0_MMC1_48, | ||
381 | }, { | ||
382 | .name = "mmc_48m", | ||
383 | .id = 2, | ||
384 | .parent = &clk_48m, | ||
385 | .enable = s5p6440_sclk_ctrl, | ||
386 | .ctrlbit = S5P_CLKCON_SCLK0_MMC2_48, | ||
387 | }, { | ||
388 | .name = "otg", | ||
389 | .id = -1, | ||
390 | .parent = &clk_h_low, | ||
391 | .enable = s5p6440_hclk0_ctrl, | ||
392 | .ctrlbit = S5P_CLKCON_HCLK0_USB | ||
393 | }, { | ||
394 | .name = "post", | ||
395 | .id = -1, | ||
396 | .parent = &clk_h_low, | ||
397 | .enable = s5p6440_hclk0_ctrl, | ||
398 | .ctrlbit = S5P_CLKCON_HCLK0_POST0 | ||
399 | }, { | ||
400 | .name = "lcd", | ||
401 | .id = -1, | ||
402 | .parent = &clk_h_low, | ||
403 | .enable = s5p6440_hclk1_ctrl, | ||
404 | .ctrlbit = S5P_CLKCON_HCLK1_DISPCON, | ||
405 | }, { | ||
406 | .name = "hsmmc", | ||
407 | .id = 0, | ||
408 | .parent = &clk_h_low, | ||
409 | .enable = s5p6440_hclk0_ctrl, | ||
410 | .ctrlbit = S5P_CLKCON_HCLK0_HSMMC0, | ||
411 | }, { | ||
412 | .name = "hsmmc", | ||
413 | .id = 1, | ||
414 | .parent = &clk_h_low, | ||
415 | .enable = s5p6440_hclk0_ctrl, | ||
416 | .ctrlbit = S5P_CLKCON_HCLK0_HSMMC1, | ||
417 | }, { | ||
418 | .name = "hsmmc", | ||
419 | .id = 2, | ||
420 | .parent = &clk_h_low, | ||
421 | .enable = s5p6440_hclk0_ctrl, | ||
422 | .ctrlbit = S5P_CLKCON_HCLK0_HSMMC2, | ||
423 | }, { | ||
424 | .name = "rtc", | ||
425 | .id = -1, | ||
426 | .parent = &clk_p_low, | ||
427 | .enable = s5p6440_pclk_ctrl, | ||
428 | .ctrlbit = S5P_CLKCON_PCLK_RTC, | ||
429 | }, { | ||
430 | .name = "watchdog", | ||
431 | .id = -1, | ||
432 | .parent = &clk_p_low, | ||
433 | .enable = s5p6440_pclk_ctrl, | ||
434 | .ctrlbit = S5P_CLKCON_PCLK_WDT, | ||
435 | }, { | ||
436 | .name = "timers", | ||
437 | .id = -1, | ||
438 | .parent = &clk_p_low, | ||
439 | .enable = s5p6440_pclk_ctrl, | ||
440 | .ctrlbit = S5P_CLKCON_PCLK_PWM, | ||
441 | } | ||
442 | }; | ||
443 | |||
444 | /* | ||
445 | * The following clocks will be enabled during clock initialization. | ||
446 | */ | ||
447 | static struct clk init_clocks[] = { | ||
448 | { | ||
449 | .name = "gpio", | ||
450 | .id = -1, | ||
451 | .parent = &clk_p_low, | ||
452 | .enable = s5p6440_pclk_ctrl, | ||
453 | .ctrlbit = S5P_CLKCON_PCLK_GPIO, | ||
454 | }, { | ||
455 | .name = "uart", | ||
456 | .id = 0, | ||
457 | .parent = &clk_p_low, | ||
458 | .enable = s5p6440_pclk_ctrl, | ||
459 | .ctrlbit = S5P_CLKCON_PCLK_UART0, | ||
460 | }, { | ||
461 | .name = "uart", | ||
462 | .id = 1, | ||
463 | .parent = &clk_p_low, | ||
464 | .enable = s5p6440_pclk_ctrl, | ||
465 | .ctrlbit = S5P_CLKCON_PCLK_UART1, | ||
466 | }, { | ||
467 | .name = "uart", | ||
468 | .id = 2, | ||
469 | .parent = &clk_p_low, | ||
470 | .enable = s5p6440_pclk_ctrl, | ||
471 | .ctrlbit = S5P_CLKCON_PCLK_UART2, | ||
472 | }, { | ||
473 | .name = "uart", | ||
474 | .id = 3, | ||
475 | .parent = &clk_p_low, | ||
476 | .enable = s5p6440_pclk_ctrl, | ||
477 | .ctrlbit = S5P_CLKCON_PCLK_UART3, | ||
478 | } | ||
479 | }; | ||
480 | |||
481 | static struct clk clk_iis_cd_v40 = { | ||
482 | .name = "iis_cdclk_v40", | ||
483 | .id = -1, | ||
484 | }; | ||
485 | |||
486 | static struct clk clk_pcm_cd = { | ||
487 | .name = "pcm_cdclk", | ||
488 | .id = -1, | ||
489 | }; | ||
490 | |||
491 | static struct clk *clkset_spi_mmc_list[] = { | ||
492 | &clk_mout_epll.clk, | ||
493 | &clk_dout_mpll, | ||
494 | &clk_fin_epll, | ||
495 | }; | ||
496 | |||
497 | static struct clksrc_sources clkset_spi_mmc = { | ||
498 | .sources = clkset_spi_mmc_list, | ||
499 | .nr_sources = ARRAY_SIZE(clkset_spi_mmc_list), | ||
500 | }; | ||
501 | |||
502 | static struct clk *clkset_uart_list[] = { | ||
503 | &clk_mout_epll.clk, | ||
504 | &clk_dout_mpll | ||
505 | }; | ||
506 | |||
507 | static struct clksrc_sources clkset_uart = { | ||
508 | .sources = clkset_uart_list, | ||
509 | .nr_sources = ARRAY_SIZE(clkset_uart_list), | ||
510 | }; | ||
511 | |||
512 | static struct clksrc_clk clksrcs[] = { | ||
513 | { | ||
514 | .clk = { | ||
515 | .name = "mmc_bus", | ||
516 | .id = 0, | ||
517 | .ctrlbit = S5P_CLKCON_SCLK0_MMC0, | ||
518 | .enable = s5p6440_sclk_ctrl, | ||
519 | }, | ||
520 | .sources = &clkset_spi_mmc, | ||
521 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 18, .size = 2 }, | ||
522 | .reg_div = { .reg = S5P_CLK_DIV1, .shift = 0, .size = 4 }, | ||
523 | }, { | ||
524 | .clk = { | ||
525 | .name = "mmc_bus", | ||
526 | .id = 1, | ||
527 | .ctrlbit = S5P_CLKCON_SCLK0_MMC1, | ||
528 | .enable = s5p6440_sclk_ctrl, | ||
529 | }, | ||
530 | .sources = &clkset_spi_mmc, | ||
531 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 20, .size = 2 }, | ||
532 | .reg_div = { .reg = S5P_CLK_DIV1, .shift = 4, .size = 4 }, | ||
533 | }, { | ||
534 | .clk = { | ||
535 | .name = "mmc_bus", | ||
536 | .id = 2, | ||
537 | .ctrlbit = S5P_CLKCON_SCLK0_MMC2, | ||
538 | .enable = s5p6440_sclk_ctrl, | ||
539 | }, | ||
540 | .sources = &clkset_spi_mmc, | ||
541 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 22, .size = 2 }, | ||
542 | .reg_div = { .reg = S5P_CLK_DIV1, .shift = 8, .size = 4 }, | ||
543 | }, { | ||
544 | .clk = { | ||
545 | .name = "uclk1", | ||
546 | .id = -1, | ||
547 | .ctrlbit = S5P_CLKCON_SCLK0_UART, | ||
548 | .enable = s5p6440_sclk_ctrl, | ||
549 | }, | ||
550 | .sources = &clkset_uart, | ||
551 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 13, .size = 1 }, | ||
552 | .reg_div = { .reg = S5P_CLK_DIV2, .shift = 16, .size = 4 }, | ||
553 | }, { | ||
554 | .clk = { | ||
555 | .name = "spi_epll", | ||
556 | .id = 0, | ||
557 | .ctrlbit = S5P_CLKCON_SCLK0_SPI0, | ||
558 | .enable = s5p6440_sclk_ctrl, | ||
559 | }, | ||
560 | .sources = &clkset_spi_mmc, | ||
561 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 14, .size = 2 }, | ||
562 | .reg_div = { .reg = S5P_CLK_DIV2, .shift = 0, .size = 4 }, | ||
563 | }, { | ||
564 | .clk = { | ||
565 | .name = "spi_epll", | ||
566 | .id = 1, | ||
567 | .ctrlbit = S5P_CLKCON_SCLK0_SPI1, | ||
568 | .enable = s5p6440_sclk_ctrl, | ||
569 | }, | ||
570 | .sources = &clkset_spi_mmc, | ||
571 | .reg_src = { .reg = S5P_CLK_SRC0, .shift = 16, .size = 2 }, | ||
572 | .reg_div = { .reg = S5P_CLK_DIV2, .shift = 4, .size = 4 }, | ||
573 | } | ||
574 | }; | ||
575 | |||
576 | /* Clock initialisation code */ | ||
577 | static struct clksrc_clk *init_parents[] = { | ||
578 | &clk_mout_apll, | ||
579 | &clk_mout_epll, | ||
580 | &clk_mout_mpll, | ||
581 | }; | ||
582 | |||
583 | void __init_or_cpufreq s5p6440_setup_clocks(void) | ||
584 | { | ||
585 | struct clk *xtal_clk; | ||
586 | unsigned long xtal; | ||
587 | unsigned long fclk; | ||
588 | unsigned long hclk; | ||
589 | unsigned long hclk_low; | ||
590 | unsigned long pclk; | ||
591 | unsigned long pclk_low; | ||
592 | unsigned long epll; | ||
593 | unsigned long apll; | ||
594 | unsigned long mpll; | ||
595 | unsigned int ptr; | ||
596 | u32 clkdiv0; | ||
597 | u32 clkdiv3; | ||
598 | |||
599 | /* Set S5P6440 functions for clk_fout_epll */ | ||
600 | clk_fout_epll.enable = s5p6440_epll_enable; | ||
601 | clk_fout_epll.ops = &s5p6440_epll_ops; | ||
602 | |||
603 | /* Set S5P6440 functions for arm clock */ | ||
604 | clk_arm.parent = &clk_mout_apll.clk; | ||
605 | clk_arm.ops = &s5p6440_clkarm_ops; | ||
606 | clk_48m.enable = s5p6440_clk48m_ctrl; | ||
607 | |||
608 | clkdiv0 = __raw_readl(S5P_CLK_DIV0); | ||
609 | clkdiv3 = __raw_readl(S5P_CLK_DIV3); | ||
610 | |||
611 | xtal_clk = clk_get(NULL, "ext_xtal"); | ||
612 | BUG_ON(IS_ERR(xtal_clk)); | ||
613 | |||
614 | xtal = clk_get_rate(xtal_clk); | ||
615 | clk_put(xtal_clk); | ||
616 | |||
617 | epll = s5p_get_pll90xx(xtal, __raw_readl(S5P_EPLL_CON), | ||
618 | __raw_readl(S5P_EPLL_CON_K)); | ||
619 | mpll = s5p_get_pll45xx(xtal, __raw_readl(S5P_MPLL_CON), pll_4502); | ||
620 | apll = s5p_get_pll45xx(xtal, __raw_readl(S5P_APLL_CON), pll_4502); | ||
621 | |||
622 | printk(KERN_INFO "S5P6440: PLL settings, A=%ld.%ldMHz, M=%ld.%ldMHz," \ | ||
623 | " E=%ld.%ldMHz\n", | ||
624 | print_mhz(apll), print_mhz(mpll), print_mhz(epll)); | ||
625 | |||
626 | fclk = apll / GET_DIV(clkdiv0, S5P_CLKDIV0_ARM); | ||
627 | hclk = fclk / GET_DIV(clkdiv0, S5P_CLKDIV0_HCLK); | ||
628 | pclk = hclk / GET_DIV(clkdiv0, S5P_CLKDIV0_PCLK); | ||
629 | |||
630 | if (__raw_readl(S5P_OTHERS) & S5P_OTHERS_HCLK_LOW_SEL_MPLL) { | ||
631 | /* Asynchronous mode */ | ||
632 | hclk_low = mpll / GET_DIV(clkdiv3, S5P_CLKDIV3_HCLK_LOW); | ||
633 | } else { | ||
634 | /* Synchronous mode */ | ||
635 | hclk_low = apll / GET_DIV(clkdiv3, S5P_CLKDIV3_HCLK_LOW); | ||
636 | } | ||
637 | |||
638 | pclk_low = hclk_low / GET_DIV(clkdiv3, S5P_CLKDIV3_PCLK_LOW); | ||
639 | |||
640 | printk(KERN_INFO "S5P6440: HCLK=%ld.%ldMHz, HCLK_LOW=%ld.%ldMHz," \ | ||
641 | " PCLK=%ld.%ldMHz, PCLK_LOW=%ld.%ldMHz\n", | ||
642 | print_mhz(hclk), print_mhz(hclk_low), | ||
643 | print_mhz(pclk), print_mhz(pclk_low)); | ||
644 | |||
645 | clk_fout_mpll.rate = mpll; | ||
646 | clk_fout_epll.rate = epll; | ||
647 | clk_fout_apll.rate = apll; | ||
648 | |||
649 | clk_f.rate = fclk; | ||
650 | clk_h.rate = hclk; | ||
651 | clk_p.rate = pclk; | ||
652 | clk_h_low.rate = hclk_low; | ||
653 | clk_p_low.rate = pclk_low; | ||
654 | |||
655 | for (ptr = 0; ptr < ARRAY_SIZE(init_parents); ptr++) | ||
656 | s3c_set_clksrc(init_parents[ptr], true); | ||
657 | |||
658 | for (ptr = 0; ptr < ARRAY_SIZE(clksrcs); ptr++) | ||
659 | s3c_set_clksrc(&clksrcs[ptr], true); | ||
660 | } | ||
661 | |||
662 | static struct clk *clks[] __initdata = { | ||
663 | &clk_ext, | ||
664 | &clk_mout_epll.clk, | ||
665 | &clk_mout_mpll.clk, | ||
666 | &clk_dout_mpll, | ||
667 | &clk_iis_cd_v40, | ||
668 | &clk_pcm_cd, | ||
669 | &clk_p_low, | ||
670 | &clk_h_low, | ||
671 | }; | ||
672 | |||
673 | void __init s5p6440_register_clocks(void) | ||
674 | { | ||
675 | struct clk *clkp; | ||
676 | int ret; | ||
677 | int ptr; | ||
678 | |||
679 | ret = s3c24xx_register_clocks(clks, ARRAY_SIZE(clks)); | ||
680 | if (ret > 0) | ||
681 | printk(KERN_ERR "Failed to register %u clocks\n", ret); | ||
682 | |||
683 | s3c_register_clksrc(clksrcs, ARRAY_SIZE(clksrcs)); | ||
684 | s3c_register_clocks(init_clocks, ARRAY_SIZE(init_clocks)); | ||
685 | |||
686 | clkp = init_clocks_disable; | ||
687 | for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) { | ||
688 | |||
689 | ret = s3c24xx_register_clock(clkp); | ||
690 | if (ret < 0) { | ||
691 | printk(KERN_ERR "Failed to register clock %s (%d)\n", | ||
692 | clkp->name, ret); | ||
693 | } | ||
694 | (clkp->enable)(clkp, 0); | ||
695 | } | ||
696 | |||
697 | s3c_pwmclk_init(); | ||
698 | } | ||