]>
Commit | Line | Data |
---|---|---|
76c54783 CK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Lochnagar clock control | |
4 | * | |
5 | * Copyright (c) 2017-2018 Cirrus Logic, Inc. and | |
6 | * Cirrus Logic International Semiconductor Ltd. | |
7 | * | |
8 | * Author: Charles Keepax <ckeepax@opensource.cirrus.com> | |
9 | */ | |
10 | ||
11 | #include <linux/clk-provider.h> | |
12 | #include <linux/device.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_device.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/regmap.h> | |
18 | ||
19 | #include <linux/mfd/lochnagar.h> | |
20 | #include <linux/mfd/lochnagar1_regs.h> | |
21 | #include <linux/mfd/lochnagar2_regs.h> | |
22 | ||
23 | #include <dt-bindings/clk/lochnagar.h> | |
24 | ||
25 | #define LOCHNAGAR_NUM_CLOCKS (LOCHNAGAR_SPDIF_CLKOUT + 1) | |
26 | ||
27 | struct lochnagar_clk { | |
28 | const char * const name; | |
29 | struct clk_hw hw; | |
30 | ||
31 | struct lochnagar_clk_priv *priv; | |
32 | ||
33 | u16 cfg_reg; | |
34 | u16 ena_mask; | |
35 | ||
36 | u16 src_reg; | |
37 | u16 src_mask; | |
38 | }; | |
39 | ||
40 | struct lochnagar_clk_priv { | |
41 | struct device *dev; | |
42 | struct regmap *regmap; | |
43 | enum lochnagar_type type; | |
44 | ||
45 | const char **parents; | |
46 | unsigned int nparents; | |
47 | ||
48 | struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS]; | |
49 | }; | |
50 | ||
51 | static const char * const lochnagar1_clk_parents[] = { | |
52 | "ln-none", | |
53 | "ln-spdif-mclk", | |
54 | "ln-psia1-mclk", | |
55 | "ln-psia2-mclk", | |
56 | "ln-cdc-clkout", | |
57 | "ln-dsp-clkout", | |
58 | "ln-pmic-32k", | |
59 | "ln-gf-mclk1", | |
60 | "ln-gf-mclk3", | |
61 | "ln-gf-mclk2", | |
62 | "ln-gf-mclk4", | |
63 | }; | |
64 | ||
65 | static const char * const lochnagar2_clk_parents[] = { | |
66 | "ln-none", | |
67 | "ln-cdc-clkout", | |
68 | "ln-dsp-clkout", | |
69 | "ln-pmic-32k", | |
70 | "ln-spdif-mclk", | |
71 | "ln-clk-12m", | |
72 | "ln-clk-11m", | |
73 | "ln-clk-24m", | |
74 | "ln-clk-22m", | |
75 | "ln-clk-8m", | |
76 | "ln-usb-clk-24m", | |
77 | "ln-gf-mclk1", | |
78 | "ln-gf-mclk3", | |
79 | "ln-gf-mclk2", | |
80 | "ln-psia1-mclk", | |
81 | "ln-psia2-mclk", | |
82 | "ln-spdif-clkout", | |
83 | "ln-adat-mclk", | |
84 | "ln-usb-clk-12m", | |
85 | }; | |
86 | ||
87 | #define LN1_CLK(ID, NAME, REG) \ | |
88 | [LOCHNAGAR_##ID] = { \ | |
89 | .name = NAME, \ | |
90 | .cfg_reg = LOCHNAGAR1_##REG, \ | |
91 | .ena_mask = LOCHNAGAR1_##ID##_ENA_MASK, \ | |
92 | .src_reg = LOCHNAGAR1_##ID##_SEL, \ | |
93 | .src_mask = LOCHNAGAR1_SRC_MASK, \ | |
94 | } | |
95 | ||
96 | #define LN2_CLK(ID, NAME) \ | |
97 | [LOCHNAGAR_##ID] = { \ | |
98 | .name = NAME, \ | |
99 | .cfg_reg = LOCHNAGAR2_##ID##_CTRL, \ | |
100 | .src_reg = LOCHNAGAR2_##ID##_CTRL, \ | |
101 | .ena_mask = LOCHNAGAR2_CLK_ENA_MASK, \ | |
102 | .src_mask = LOCHNAGAR2_CLK_SRC_MASK, \ | |
103 | } | |
104 | ||
105 | static const struct lochnagar_clk lochnagar1_clks[LOCHNAGAR_NUM_CLOCKS] = { | |
106 | LN1_CLK(CDC_MCLK1, "ln-cdc-mclk1", CDC_AIF_CTRL2), | |
107 | LN1_CLK(CDC_MCLK2, "ln-cdc-mclk2", CDC_AIF_CTRL2), | |
108 | LN1_CLK(DSP_CLKIN, "ln-dsp-clkin", DSP_AIF), | |
109 | LN1_CLK(GF_CLKOUT1, "ln-gf-clkout1", GF_AIF1), | |
110 | }; | |
111 | ||
112 | static const struct lochnagar_clk lochnagar2_clks[LOCHNAGAR_NUM_CLOCKS] = { | |
113 | LN2_CLK(CDC_MCLK1, "ln-cdc-mclk1"), | |
114 | LN2_CLK(CDC_MCLK2, "ln-cdc-mclk2"), | |
115 | LN2_CLK(DSP_CLKIN, "ln-dsp-clkin"), | |
116 | LN2_CLK(GF_CLKOUT1, "ln-gf-clkout1"), | |
117 | LN2_CLK(GF_CLKOUT2, "ln-gf-clkout2"), | |
118 | LN2_CLK(PSIA1_MCLK, "ln-psia1-mclk"), | |
119 | LN2_CLK(PSIA2_MCLK, "ln-psia2-mclk"), | |
120 | LN2_CLK(SPDIF_MCLK, "ln-spdif-mclk"), | |
121 | LN2_CLK(ADAT_MCLK, "ln-adat-mclk"), | |
122 | LN2_CLK(SOUNDCARD_MCLK, "ln-soundcard-mclk"), | |
123 | }; | |
124 | ||
125 | static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw) | |
126 | { | |
127 | return container_of(hw, struct lochnagar_clk, hw); | |
128 | } | |
129 | ||
130 | static int lochnagar_clk_prepare(struct clk_hw *hw) | |
131 | { | |
132 | struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); | |
133 | struct lochnagar_clk_priv *priv = lclk->priv; | |
134 | struct regmap *regmap = priv->regmap; | |
135 | int ret; | |
136 | ||
137 | ret = regmap_update_bits(regmap, lclk->cfg_reg, | |
138 | lclk->ena_mask, lclk->ena_mask); | |
139 | if (ret < 0) | |
140 | dev_dbg(priv->dev, "Failed to prepare %s: %d\n", | |
141 | lclk->name, ret); | |
142 | ||
143 | return ret; | |
144 | } | |
145 | ||
146 | static void lochnagar_clk_unprepare(struct clk_hw *hw) | |
147 | { | |
148 | struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); | |
149 | struct lochnagar_clk_priv *priv = lclk->priv; | |
150 | struct regmap *regmap = priv->regmap; | |
151 | int ret; | |
152 | ||
153 | ret = regmap_update_bits(regmap, lclk->cfg_reg, lclk->ena_mask, 0); | |
154 | if (ret < 0) | |
155 | dev_dbg(priv->dev, "Failed to unprepare %s: %d\n", | |
156 | lclk->name, ret); | |
157 | } | |
158 | ||
159 | static int lochnagar_clk_set_parent(struct clk_hw *hw, u8 index) | |
160 | { | |
161 | struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); | |
162 | struct lochnagar_clk_priv *priv = lclk->priv; | |
163 | struct regmap *regmap = priv->regmap; | |
164 | int ret; | |
165 | ||
166 | ret = regmap_update_bits(regmap, lclk->src_reg, lclk->src_mask, index); | |
167 | if (ret < 0) | |
168 | dev_dbg(priv->dev, "Failed to reparent %s: %d\n", | |
169 | lclk->name, ret); | |
170 | ||
171 | return ret; | |
172 | } | |
173 | ||
174 | static u8 lochnagar_clk_get_parent(struct clk_hw *hw) | |
175 | { | |
176 | struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); | |
177 | struct lochnagar_clk_priv *priv = lclk->priv; | |
178 | struct regmap *regmap = priv->regmap; | |
179 | unsigned int val; | |
180 | int ret; | |
181 | ||
182 | ret = regmap_read(regmap, lclk->src_reg, &val); | |
183 | if (ret < 0) { | |
184 | dev_dbg(priv->dev, "Failed to read parent of %s: %d\n", | |
185 | lclk->name, ret); | |
186 | return priv->nparents; | |
187 | } | |
188 | ||
189 | val &= lclk->src_mask; | |
190 | ||
191 | return val; | |
192 | } | |
193 | ||
194 | static const struct clk_ops lochnagar_clk_ops = { | |
195 | .prepare = lochnagar_clk_prepare, | |
196 | .unprepare = lochnagar_clk_unprepare, | |
197 | .set_parent = lochnagar_clk_set_parent, | |
198 | .get_parent = lochnagar_clk_get_parent, | |
199 | }; | |
200 | ||
201 | static int lochnagar_init_parents(struct lochnagar_clk_priv *priv) | |
202 | { | |
203 | struct device_node *np = priv->dev->of_node; | |
204 | int i, j; | |
205 | ||
206 | switch (priv->type) { | |
207 | case LOCHNAGAR1: | |
208 | memcpy(priv->lclks, lochnagar1_clks, sizeof(lochnagar1_clks)); | |
209 | ||
210 | priv->nparents = ARRAY_SIZE(lochnagar1_clk_parents); | |
211 | priv->parents = devm_kmemdup(priv->dev, lochnagar1_clk_parents, | |
212 | sizeof(lochnagar1_clk_parents), | |
213 | GFP_KERNEL); | |
214 | break; | |
215 | case LOCHNAGAR2: | |
216 | memcpy(priv->lclks, lochnagar2_clks, sizeof(lochnagar2_clks)); | |
217 | ||
218 | priv->nparents = ARRAY_SIZE(lochnagar2_clk_parents); | |
219 | priv->parents = devm_kmemdup(priv->dev, lochnagar2_clk_parents, | |
220 | sizeof(lochnagar2_clk_parents), | |
221 | GFP_KERNEL); | |
222 | break; | |
223 | default: | |
224 | dev_err(priv->dev, "Unknown Lochnagar type: %d\n", priv->type); | |
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | if (!priv->parents) | |
229 | return -ENOMEM; | |
230 | ||
231 | for (i = 0; i < priv->nparents; i++) { | |
232 | j = of_property_match_string(np, "clock-names", | |
233 | priv->parents[i]); | |
234 | if (j >= 0) | |
235 | priv->parents[i] = of_clk_get_parent_name(np, j); | |
236 | } | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | static struct clk_hw * | |
242 | lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) | |
243 | { | |
244 | struct lochnagar_clk_priv *priv = data; | |
245 | unsigned int idx = clkspec->args[0]; | |
246 | ||
247 | if (idx >= ARRAY_SIZE(priv->lclks)) { | |
248 | dev_err(priv->dev, "Invalid index %u\n", idx); | |
249 | return ERR_PTR(-EINVAL); | |
250 | } | |
251 | ||
252 | return &priv->lclks[idx].hw; | |
253 | } | |
254 | ||
255 | static int lochnagar_init_clks(struct lochnagar_clk_priv *priv) | |
256 | { | |
257 | struct clk_init_data clk_init = { | |
258 | .ops = &lochnagar_clk_ops, | |
259 | .parent_names = priv->parents, | |
260 | .num_parents = priv->nparents, | |
261 | }; | |
262 | struct lochnagar_clk *lclk; | |
263 | int ret, i; | |
264 | ||
265 | for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { | |
266 | lclk = &priv->lclks[i]; | |
267 | ||
268 | if (!lclk->name) | |
269 | continue; | |
270 | ||
271 | clk_init.name = lclk->name; | |
272 | ||
273 | lclk->priv = priv; | |
274 | lclk->hw.init = &clk_init; | |
275 | ||
276 | ret = devm_clk_hw_register(priv->dev, &lclk->hw); | |
277 | if (ret) { | |
278 | dev_err(priv->dev, "Failed to register %s: %d\n", | |
279 | lclk->name, ret); | |
280 | return ret; | |
281 | } | |
282 | } | |
283 | ||
284 | ret = devm_of_clk_add_hw_provider(priv->dev, lochnagar_of_clk_hw_get, | |
285 | priv); | |
286 | if (ret < 0) | |
287 | dev_err(priv->dev, "Failed to register provider: %d\n", ret); | |
288 | ||
289 | return ret; | |
290 | } | |
291 | ||
292 | static const struct of_device_id lochnagar_of_match[] = { | |
293 | { .compatible = "cirrus,lochnagar1-clk", .data = (void *)LOCHNAGAR1 }, | |
294 | { .compatible = "cirrus,lochnagar2-clk", .data = (void *)LOCHNAGAR2 }, | |
295 | {} | |
296 | }; | |
297 | MODULE_DEVICE_TABLE(of, lochnagar_of_match); | |
298 | ||
299 | static int lochnagar_clk_probe(struct platform_device *pdev) | |
300 | { | |
301 | struct device *dev = &pdev->dev; | |
302 | struct lochnagar_clk_priv *priv; | |
303 | const struct of_device_id *of_id; | |
304 | int ret; | |
305 | ||
306 | of_id = of_match_device(lochnagar_of_match, dev); | |
307 | if (!of_id) | |
308 | return -EINVAL; | |
309 | ||
310 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
311 | if (!priv) | |
312 | return -ENOMEM; | |
313 | ||
314 | priv->dev = dev; | |
315 | priv->regmap = dev_get_regmap(dev->parent, NULL); | |
316 | priv->type = (enum lochnagar_type)of_id->data; | |
317 | ||
318 | ret = lochnagar_init_parents(priv); | |
319 | if (ret) | |
320 | return ret; | |
321 | ||
322 | return lochnagar_init_clks(priv); | |
323 | } | |
324 | ||
325 | static struct platform_driver lochnagar_clk_driver = { | |
326 | .driver = { | |
327 | .name = "lochnagar-clk", | |
328 | .of_match_table = lochnagar_of_match, | |
329 | }, | |
330 | .probe = lochnagar_clk_probe, | |
331 | }; | |
332 | module_platform_driver(lochnagar_clk_driver); | |
333 | ||
334 | MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); | |
335 | MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board"); | |
336 | MODULE_LICENSE("GPL v2"); |