1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Clk driver for NXP LPC18xx/LPC43xx Clock Control Unit (CCU) |
4 | * |
5 | * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/io.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/string.h> |
16 | |
17 | #include <dt-bindings/clock/lpc18xx-ccu.h> |
18 | |
19 | /* Bit defines for CCU branch configuration register */ |
20 | #define LPC18XX_CCU_RUN BIT(0) |
21 | #define LPC18XX_CCU_AUTO BIT(1) |
22 | #define LPC18XX_CCU_DIV BIT(5) |
23 | #define LPC18XX_CCU_DIVSTAT BIT(27) |
24 | |
25 | /* CCU branch feature bits */ |
26 | #define CCU_BRANCH_IS_BUS BIT(0) |
27 | #define CCU_BRANCH_HAVE_DIV2 BIT(1) |
28 | |
29 | struct lpc18xx_branch_clk_data { |
30 | const char **name; |
31 | int num; |
32 | }; |
33 | |
34 | struct lpc18xx_clk_branch { |
35 | const char *base_name; |
36 | const char *name; |
37 | u16 offset; |
38 | u16 flags; |
39 | struct clk *clk; |
40 | struct clk_gate gate; |
41 | }; |
42 | |
43 | static struct lpc18xx_clk_branch clk_branches[] = { |
44 | {"base_apb3_clk" , "apb3_bus" , CLK_APB3_BUS, CCU_BRANCH_IS_BUS}, |
45 | {"base_apb3_clk" , "apb3_i2c1" , CLK_APB3_I2C1, 0}, |
46 | {"base_apb3_clk" , "apb3_dac" , CLK_APB3_DAC, 0}, |
47 | {"base_apb3_clk" , "apb3_adc0" , CLK_APB3_ADC0, 0}, |
48 | {"base_apb3_clk" , "apb3_adc1" , CLK_APB3_ADC1, 0}, |
49 | {"base_apb3_clk" , "apb3_can0" , CLK_APB3_CAN0, 0}, |
50 | |
51 | {"base_apb1_clk" , "apb1_bus" , CLK_APB1_BUS, CCU_BRANCH_IS_BUS}, |
52 | {"base_apb1_clk" , "apb1_mc_pwm" , CLK_APB1_MOTOCON_PWM, 0}, |
53 | {"base_apb1_clk" , "apb1_i2c0" , CLK_APB1_I2C0, 0}, |
54 | {"base_apb1_clk" , "apb1_i2s" , CLK_APB1_I2S, 0}, |
55 | {"base_apb1_clk" , "apb1_can1" , CLK_APB1_CAN1, 0}, |
56 | |
57 | {"base_spifi_clk" , "spifi" , CLK_SPIFI, 0}, |
58 | |
59 | {"base_cpu_clk" , "cpu_bus" , CLK_CPU_BUS, CCU_BRANCH_IS_BUS}, |
60 | {"base_cpu_clk" , "cpu_spifi" , CLK_CPU_SPIFI, 0}, |
61 | {"base_cpu_clk" , "cpu_gpio" , CLK_CPU_GPIO, 0}, |
62 | {"base_cpu_clk" , "cpu_lcd" , CLK_CPU_LCD, 0}, |
63 | {"base_cpu_clk" , "cpu_ethernet" , CLK_CPU_ETHERNET, 0}, |
64 | {"base_cpu_clk" , "cpu_usb0" , CLK_CPU_USB0, 0}, |
65 | {"base_cpu_clk" , "cpu_emc" , CLK_CPU_EMC, 0}, |
66 | {"base_cpu_clk" , "cpu_sdio" , CLK_CPU_SDIO, 0}, |
67 | {"base_cpu_clk" , "cpu_dma" , CLK_CPU_DMA, 0}, |
68 | {"base_cpu_clk" , "cpu_core" , CLK_CPU_CORE, 0}, |
69 | {"base_cpu_clk" , "cpu_sct" , CLK_CPU_SCT, 0}, |
70 | {"base_cpu_clk" , "cpu_usb1" , CLK_CPU_USB1, 0}, |
71 | {"base_cpu_clk" , "cpu_emcdiv" , CLK_CPU_EMCDIV, CCU_BRANCH_HAVE_DIV2}, |
72 | {"base_cpu_clk" , "cpu_flasha" , CLK_CPU_FLASHA, CCU_BRANCH_HAVE_DIV2}, |
73 | {"base_cpu_clk" , "cpu_flashb" , CLK_CPU_FLASHB, CCU_BRANCH_HAVE_DIV2}, |
74 | {"base_cpu_clk" , "cpu_m0app" , CLK_CPU_M0APP, CCU_BRANCH_HAVE_DIV2}, |
75 | {"base_cpu_clk" , "cpu_adchs" , CLK_CPU_ADCHS, CCU_BRANCH_HAVE_DIV2}, |
76 | {"base_cpu_clk" , "cpu_eeprom" , CLK_CPU_EEPROM, CCU_BRANCH_HAVE_DIV2}, |
77 | {"base_cpu_clk" , "cpu_wwdt" , CLK_CPU_WWDT, 0}, |
78 | {"base_cpu_clk" , "cpu_uart0" , CLK_CPU_UART0, 0}, |
79 | {"base_cpu_clk" , "cpu_uart1" , CLK_CPU_UART1, 0}, |
80 | {"base_cpu_clk" , "cpu_ssp0" , CLK_CPU_SSP0, 0}, |
81 | {"base_cpu_clk" , "cpu_timer0" , CLK_CPU_TIMER0, 0}, |
82 | {"base_cpu_clk" , "cpu_timer1" , CLK_CPU_TIMER1, 0}, |
83 | {"base_cpu_clk" , "cpu_scu" , CLK_CPU_SCU, 0}, |
84 | {"base_cpu_clk" , "cpu_creg" , CLK_CPU_CREG, 0}, |
85 | {"base_cpu_clk" , "cpu_ritimer" , CLK_CPU_RITIMER, 0}, |
86 | {"base_cpu_clk" , "cpu_uart2" , CLK_CPU_UART2, 0}, |
87 | {"base_cpu_clk" , "cpu_uart3" , CLK_CPU_UART3, 0}, |
88 | {"base_cpu_clk" , "cpu_timer2" , CLK_CPU_TIMER2, 0}, |
89 | {"base_cpu_clk" , "cpu_timer3" , CLK_CPU_TIMER3, 0}, |
90 | {"base_cpu_clk" , "cpu_ssp1" , CLK_CPU_SSP1, 0}, |
91 | {"base_cpu_clk" , "cpu_qei" , CLK_CPU_QEI, 0}, |
92 | |
93 | {"base_periph_clk" , "periph_bus" , CLK_PERIPH_BUS, CCU_BRANCH_IS_BUS}, |
94 | {"base_periph_clk" , "periph_core" , CLK_PERIPH_CORE, 0}, |
95 | {"base_periph_clk" , "periph_sgpio" , CLK_PERIPH_SGPIO, 0}, |
96 | |
97 | {"base_usb0_clk" , "usb0" , CLK_USB0, 0}, |
98 | {"base_usb1_clk" , "usb1" , CLK_USB1, 0}, |
99 | {"base_spi_clk" , "spi" , CLK_SPI, 0}, |
100 | {"base_adchs_clk" , "adchs" , CLK_ADCHS, 0}, |
101 | |
102 | {"base_audio_clk" , "audio" , CLK_AUDIO, 0}, |
103 | {"base_uart3_clk" , "apb2_uart3" , CLK_APB2_UART3, 0}, |
104 | {"base_uart2_clk" , "apb2_uart2" , CLK_APB2_UART2, 0}, |
105 | {"base_uart1_clk" , "apb0_uart1" , CLK_APB0_UART1, 0}, |
106 | {"base_uart0_clk" , "apb0_uart0" , CLK_APB0_UART0, 0}, |
107 | {"base_ssp1_clk" , "apb2_ssp1" , CLK_APB2_SSP1, 0}, |
108 | {"base_ssp0_clk" , "apb0_ssp0" , CLK_APB0_SSP0, 0}, |
109 | {"base_sdio_clk" , "sdio" , CLK_SDIO, 0}, |
110 | }; |
111 | |
112 | static struct clk *lpc18xx_ccu_branch_clk_get(struct of_phandle_args *clkspec, |
113 | void *data) |
114 | { |
115 | struct lpc18xx_branch_clk_data *clk_data = data; |
116 | unsigned int offset = clkspec->args[0]; |
117 | int i, j; |
118 | |
119 | for (i = 0; i < ARRAY_SIZE(clk_branches); i++) { |
120 | if (clk_branches[i].offset != offset) |
121 | continue; |
122 | |
123 | for (j = 0; j < clk_data->num; j++) { |
124 | if (!strcmp(clk_branches[i].base_name, clk_data->name[j])) |
125 | return clk_branches[i].clk; |
126 | } |
127 | } |
128 | |
129 | pr_err("%s: invalid clock offset %d\n" , __func__, offset); |
130 | |
131 | return ERR_PTR(error: -EINVAL); |
132 | } |
133 | |
134 | static int lpc18xx_ccu_gate_endisable(struct clk_hw *hw, bool enable) |
135 | { |
136 | struct clk_gate *gate = to_clk_gate(hw); |
137 | u32 val; |
138 | |
139 | /* |
140 | * Divider field is write only, so divider stat field must |
141 | * be read so divider field can be set accordingly. |
142 | */ |
143 | val = readl(addr: gate->reg); |
144 | if (val & LPC18XX_CCU_DIVSTAT) |
145 | val |= LPC18XX_CCU_DIV; |
146 | |
147 | if (enable) { |
148 | val |= LPC18XX_CCU_RUN; |
149 | } else { |
150 | /* |
151 | * To safely disable a branch clock a squence of two separate |
152 | * writes must be used. First write should set the AUTO bit |
153 | * and the next write should clear the RUN bit. |
154 | */ |
155 | val |= LPC18XX_CCU_AUTO; |
156 | writel(val, addr: gate->reg); |
157 | |
158 | val &= ~LPC18XX_CCU_RUN; |
159 | } |
160 | |
161 | writel(val, addr: gate->reg); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static int lpc18xx_ccu_gate_enable(struct clk_hw *hw) |
167 | { |
168 | return lpc18xx_ccu_gate_endisable(hw, enable: true); |
169 | } |
170 | |
171 | static void lpc18xx_ccu_gate_disable(struct clk_hw *hw) |
172 | { |
173 | lpc18xx_ccu_gate_endisable(hw, enable: false); |
174 | } |
175 | |
176 | static int lpc18xx_ccu_gate_is_enabled(struct clk_hw *hw) |
177 | { |
178 | const struct clk_hw *parent; |
179 | |
180 | /* |
181 | * The branch clock registers are only accessible |
182 | * if the base (parent) clock is enabled. Register |
183 | * access with a disabled base clock will hang the |
184 | * system. |
185 | */ |
186 | parent = clk_hw_get_parent(hw); |
187 | if (!parent) |
188 | return 0; |
189 | |
190 | if (!clk_hw_is_enabled(hw: parent)) |
191 | return 0; |
192 | |
193 | return clk_gate_ops.is_enabled(hw); |
194 | } |
195 | |
196 | static const struct clk_ops lpc18xx_ccu_gate_ops = { |
197 | .enable = lpc18xx_ccu_gate_enable, |
198 | .disable = lpc18xx_ccu_gate_disable, |
199 | .is_enabled = lpc18xx_ccu_gate_is_enabled, |
200 | }; |
201 | |
202 | static void lpc18xx_ccu_register_branch_gate_div(struct lpc18xx_clk_branch *branch, |
203 | void __iomem *reg_base, |
204 | const char *parent) |
205 | { |
206 | const struct clk_ops *div_ops = NULL; |
207 | struct clk_divider *div = NULL; |
208 | struct clk_hw *div_hw = NULL; |
209 | |
210 | if (branch->flags & CCU_BRANCH_HAVE_DIV2) { |
211 | div = kzalloc(size: sizeof(*div), GFP_KERNEL); |
212 | if (!div) |
213 | return; |
214 | |
215 | div->reg = branch->offset + reg_base; |
216 | div->flags = CLK_DIVIDER_READ_ONLY; |
217 | div->shift = 27; |
218 | div->width = 1; |
219 | |
220 | div_hw = &div->hw; |
221 | div_ops = &clk_divider_ro_ops; |
222 | } |
223 | |
224 | branch->gate.reg = branch->offset + reg_base; |
225 | branch->gate.bit_idx = 0; |
226 | |
227 | branch->clk = clk_register_composite(NULL, name: branch->name, parent_names: &parent, num_parents: 1, |
228 | NULL, NULL, |
229 | rate_hw: div_hw, rate_ops: div_ops, |
230 | gate_hw: &branch->gate.hw, gate_ops: &lpc18xx_ccu_gate_ops, flags: 0); |
231 | if (IS_ERR(ptr: branch->clk)) { |
232 | kfree(objp: div); |
233 | pr_warn("%s: failed to register %s\n" , __func__, branch->name); |
234 | return; |
235 | } |
236 | |
237 | /* Grab essential branch clocks for CPU and SDRAM */ |
238 | switch (branch->offset) { |
239 | case CLK_CPU_EMC: |
240 | case CLK_CPU_CORE: |
241 | case CLK_CPU_CREG: |
242 | case CLK_CPU_EMCDIV: |
243 | clk_prepare_enable(clk: branch->clk); |
244 | } |
245 | } |
246 | |
247 | static void lpc18xx_ccu_register_branch_clks(void __iomem *reg_base, |
248 | const char *base_name) |
249 | { |
250 | const char *parent = base_name; |
251 | int i; |
252 | |
253 | for (i = 0; i < ARRAY_SIZE(clk_branches); i++) { |
254 | if (strcmp(clk_branches[i].base_name, base_name)) |
255 | continue; |
256 | |
257 | lpc18xx_ccu_register_branch_gate_div(branch: &clk_branches[i], reg_base, |
258 | parent); |
259 | |
260 | if (clk_branches[i].flags & CCU_BRANCH_IS_BUS) |
261 | parent = clk_branches[i].name; |
262 | } |
263 | } |
264 | |
265 | static void __init lpc18xx_ccu_init(struct device_node *np) |
266 | { |
267 | struct lpc18xx_branch_clk_data *clk_data; |
268 | void __iomem *reg_base; |
269 | int i, ret; |
270 | |
271 | reg_base = of_iomap(node: np, index: 0); |
272 | if (!reg_base) { |
273 | pr_warn("%s: failed to map address range\n" , __func__); |
274 | return; |
275 | } |
276 | |
277 | clk_data = kzalloc(size: sizeof(*clk_data), GFP_KERNEL); |
278 | if (!clk_data) { |
279 | iounmap(addr: reg_base); |
280 | return; |
281 | } |
282 | |
283 | clk_data->num = of_property_count_strings(np, propname: "clock-names" ); |
284 | clk_data->name = kcalloc(n: clk_data->num, size: sizeof(char *), GFP_KERNEL); |
285 | if (!clk_data->name) { |
286 | iounmap(addr: reg_base); |
287 | kfree(objp: clk_data); |
288 | return; |
289 | } |
290 | |
291 | for (i = 0; i < clk_data->num; i++) { |
292 | ret = of_property_read_string_index(np, propname: "clock-names" , index: i, |
293 | output: &clk_data->name[i]); |
294 | if (ret) { |
295 | pr_warn("%s: failed to get clock name at idx %d\n" , |
296 | __func__, i); |
297 | continue; |
298 | } |
299 | |
300 | lpc18xx_ccu_register_branch_clks(reg_base, base_name: clk_data->name[i]); |
301 | } |
302 | |
303 | of_clk_add_provider(np, clk_src_get: lpc18xx_ccu_branch_clk_get, data: clk_data); |
304 | } |
305 | CLK_OF_DECLARE(lpc18xx_ccu, "nxp,lpc1850-ccu" , lpc18xx_ccu_init); |
306 | |