1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Clk driver for NXP LPC18xx/43xx Configuration Registers (CREG) |
4 | * |
5 | * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | #define LPC18XX_CREG_CREG0 0x004 |
17 | #define LPC18XX_CREG_CREG0_EN1KHZ BIT(0) |
18 | #define LPC18XX_CREG_CREG0_EN32KHZ BIT(1) |
19 | #define LPC18XX_CREG_CREG0_RESET32KHZ BIT(2) |
20 | #define LPC18XX_CREG_CREG0_PD32KHZ BIT(3) |
21 | |
22 | #define to_clk_creg(_hw) container_of(_hw, struct clk_creg_data, hw) |
23 | |
24 | enum { |
25 | CREG_CLK_1KHZ, |
26 | CREG_CLK_32KHZ, |
27 | CREG_CLK_MAX, |
28 | }; |
29 | |
30 | struct clk_creg_data { |
31 | struct clk_hw hw; |
32 | const char *name; |
33 | struct regmap *reg; |
34 | unsigned int en_mask; |
35 | const struct clk_ops *ops; |
36 | }; |
37 | |
38 | #define CREG_CLK(_name, _emask, _ops) \ |
39 | { \ |
40 | .name = _name, \ |
41 | .en_mask = LPC18XX_CREG_CREG0_##_emask, \ |
42 | .ops = &_ops, \ |
43 | } |
44 | |
45 | static int clk_creg_32k_prepare(struct clk_hw *hw) |
46 | { |
47 | struct clk_creg_data *creg = to_clk_creg(hw); |
48 | int ret; |
49 | |
50 | ret = regmap_update_bits(map: creg->reg, LPC18XX_CREG_CREG0, |
51 | LPC18XX_CREG_CREG0_PD32KHZ | |
52 | LPC18XX_CREG_CREG0_RESET32KHZ, val: 0); |
53 | |
54 | /* |
55 | * Powering up the 32k oscillator takes a long while |
56 | * and sadly there aren't any status bit to poll. |
57 | */ |
58 | msleep(msecs: 2500); |
59 | |
60 | return ret; |
61 | } |
62 | |
63 | static void clk_creg_32k_unprepare(struct clk_hw *hw) |
64 | { |
65 | struct clk_creg_data *creg = to_clk_creg(hw); |
66 | |
67 | regmap_update_bits(map: creg->reg, LPC18XX_CREG_CREG0, |
68 | LPC18XX_CREG_CREG0_PD32KHZ, |
69 | LPC18XX_CREG_CREG0_PD32KHZ); |
70 | } |
71 | |
72 | static int clk_creg_32k_is_prepared(struct clk_hw *hw) |
73 | { |
74 | struct clk_creg_data *creg = to_clk_creg(hw); |
75 | u32 reg; |
76 | |
77 | regmap_read(map: creg->reg, LPC18XX_CREG_CREG0, val: ®); |
78 | |
79 | return !(reg & LPC18XX_CREG_CREG0_PD32KHZ) && |
80 | !(reg & LPC18XX_CREG_CREG0_RESET32KHZ); |
81 | } |
82 | |
83 | static unsigned long clk_creg_1k_recalc_rate(struct clk_hw *hw, |
84 | unsigned long parent_rate) |
85 | { |
86 | return parent_rate / 32; |
87 | } |
88 | |
89 | static int clk_creg_enable(struct clk_hw *hw) |
90 | { |
91 | struct clk_creg_data *creg = to_clk_creg(hw); |
92 | |
93 | return regmap_update_bits(map: creg->reg, LPC18XX_CREG_CREG0, |
94 | mask: creg->en_mask, val: creg->en_mask); |
95 | } |
96 | |
97 | static void clk_creg_disable(struct clk_hw *hw) |
98 | { |
99 | struct clk_creg_data *creg = to_clk_creg(hw); |
100 | |
101 | regmap_update_bits(map: creg->reg, LPC18XX_CREG_CREG0, |
102 | mask: creg->en_mask, val: 0); |
103 | } |
104 | |
105 | static int clk_creg_is_enabled(struct clk_hw *hw) |
106 | { |
107 | struct clk_creg_data *creg = to_clk_creg(hw); |
108 | u32 reg; |
109 | |
110 | regmap_read(map: creg->reg, LPC18XX_CREG_CREG0, val: ®); |
111 | |
112 | return !!(reg & creg->en_mask); |
113 | } |
114 | |
115 | static const struct clk_ops clk_creg_32k = { |
116 | .enable = clk_creg_enable, |
117 | .disable = clk_creg_disable, |
118 | .is_enabled = clk_creg_is_enabled, |
119 | .prepare = clk_creg_32k_prepare, |
120 | .unprepare = clk_creg_32k_unprepare, |
121 | .is_prepared = clk_creg_32k_is_prepared, |
122 | }; |
123 | |
124 | static const struct clk_ops clk_creg_1k = { |
125 | .enable = clk_creg_enable, |
126 | .disable = clk_creg_disable, |
127 | .is_enabled = clk_creg_is_enabled, |
128 | .recalc_rate = clk_creg_1k_recalc_rate, |
129 | }; |
130 | |
131 | static struct clk_creg_data clk_creg_clocks[] = { |
132 | [CREG_CLK_1KHZ] = CREG_CLK("1khz_clk" , EN1KHZ, clk_creg_1k), |
133 | [CREG_CLK_32KHZ] = CREG_CLK("32khz_clk" , EN32KHZ, clk_creg_32k), |
134 | }; |
135 | |
136 | static struct clk *clk_register_creg_clk(struct device *dev, |
137 | struct clk_creg_data *creg_clk, |
138 | const char **parent_name, |
139 | struct regmap *syscon) |
140 | { |
141 | struct clk_init_data init; |
142 | |
143 | init.ops = creg_clk->ops; |
144 | init.name = creg_clk->name; |
145 | init.parent_names = parent_name; |
146 | init.num_parents = 1; |
147 | init.flags = 0; |
148 | |
149 | creg_clk->reg = syscon; |
150 | creg_clk->hw.init = &init; |
151 | |
152 | if (dev) |
153 | return devm_clk_register(dev, hw: &creg_clk->hw); |
154 | |
155 | return clk_register(NULL, hw: &creg_clk->hw); |
156 | } |
157 | |
158 | static struct clk *clk_creg_early[CREG_CLK_MAX]; |
159 | static struct clk_onecell_data clk_creg_early_data = { |
160 | .clks = clk_creg_early, |
161 | .clk_num = CREG_CLK_MAX, |
162 | }; |
163 | |
164 | static void __init lpc18xx_creg_clk_init(struct device_node *np) |
165 | { |
166 | const char *clk_32khz_parent; |
167 | struct regmap *syscon; |
168 | |
169 | syscon = syscon_node_to_regmap(np: np->parent); |
170 | if (IS_ERR(ptr: syscon)) { |
171 | pr_err("%s: syscon lookup failed\n" , __func__); |
172 | return; |
173 | } |
174 | |
175 | clk_32khz_parent = of_clk_get_parent_name(np, index: 0); |
176 | |
177 | clk_creg_early[CREG_CLK_32KHZ] = |
178 | clk_register_creg_clk(NULL, creg_clk: &clk_creg_clocks[CREG_CLK_32KHZ], |
179 | parent_name: &clk_32khz_parent, syscon); |
180 | clk_creg_early[CREG_CLK_1KHZ] = ERR_PTR(error: -EPROBE_DEFER); |
181 | |
182 | of_clk_add_provider(np, clk_src_get: of_clk_src_onecell_get, data: &clk_creg_early_data); |
183 | } |
184 | CLK_OF_DECLARE_DRIVER(lpc18xx_creg_clk, "nxp,lpc1850-creg-clk" , |
185 | lpc18xx_creg_clk_init); |
186 | |
187 | static struct clk *clk_creg[CREG_CLK_MAX]; |
188 | static struct clk_onecell_data clk_creg_data = { |
189 | .clks = clk_creg, |
190 | .clk_num = CREG_CLK_MAX, |
191 | }; |
192 | |
193 | static int lpc18xx_creg_clk_probe(struct platform_device *pdev) |
194 | { |
195 | struct device_node *np = pdev->dev.of_node; |
196 | struct regmap *syscon; |
197 | |
198 | syscon = syscon_node_to_regmap(np: np->parent); |
199 | if (IS_ERR(ptr: syscon)) { |
200 | dev_err(&pdev->dev, "syscon lookup failed\n" ); |
201 | return PTR_ERR(ptr: syscon); |
202 | } |
203 | |
204 | clk_creg[CREG_CLK_32KHZ] = clk_creg_early[CREG_CLK_32KHZ]; |
205 | clk_creg[CREG_CLK_1KHZ] = |
206 | clk_register_creg_clk(NULL, creg_clk: &clk_creg_clocks[CREG_CLK_1KHZ], |
207 | parent_name: &clk_creg_clocks[CREG_CLK_32KHZ].name, |
208 | syscon); |
209 | |
210 | return of_clk_add_provider(np, clk_src_get: of_clk_src_onecell_get, data: &clk_creg_data); |
211 | } |
212 | |
213 | static const struct of_device_id lpc18xx_creg_clk_of_match[] = { |
214 | { .compatible = "nxp,lpc1850-creg-clk" }, |
215 | {}, |
216 | }; |
217 | |
218 | static struct platform_driver lpc18xx_creg_clk_driver = { |
219 | .probe = lpc18xx_creg_clk_probe, |
220 | .driver = { |
221 | .name = "lpc18xx-creg-clk" , |
222 | .of_match_table = lpc18xx_creg_clk_of_match, |
223 | }, |
224 | }; |
225 | builtin_platform_driver(lpc18xx_creg_clk_driver); |
226 | |