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