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 | ||
76c54783 CK |
19 | #include <linux/mfd/lochnagar1_regs.h> |
20 | #include <linux/mfd/lochnagar2_regs.h> | |
21 | ||
0384759b | 22 | #include <dt-bindings/clock/lochnagar.h> |
76c54783 CK |
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; | |
76c54783 CK |
42 | |
43 | struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS]; | |
44 | }; | |
45 | ||
055ca547 CK |
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"), | |
76c54783 CK |
60 | }; |
61 | ||
055ca547 CK |
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"), | |
76c54783 CK |
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 | ||
055ca547 CK |
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 | ||
76c54783 CK |
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(regmap, lclk->cfg_reg, | |
153 | lclk->ena_mask, 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(regmap, lclk->cfg_reg, lclk->ena_mask, 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(regmap, lclk->src_reg, lclk->src_mask, 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(regmap, lclk->src_reg, &val); | |
198 | if (ret < 0) { | |
199 | dev_dbg(priv->dev, "Failed to read parent of %s: %d\n", | |
200 | lclk->name, ret); | |
d6d251f9 | 201 | return clk_hw_get_num_parents(hw); |
76c54783 CK |
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 | .set_parent = lochnagar_clk_set_parent, | |
213 | .get_parent = lochnagar_clk_get_parent, | |
214 | }; | |
215 | ||
76c54783 CK |
216 | static struct clk_hw * |
217 | lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) | |
218 | { | |
219 | struct lochnagar_clk_priv *priv = data; | |
220 | unsigned int idx = clkspec->args[0]; | |
221 | ||
222 | if (idx >= ARRAY_SIZE(priv->lclks)) { | |
223 | dev_err(priv->dev, "Invalid index %u\n", idx); | |
224 | return ERR_PTR(-EINVAL); | |
225 | } | |
226 | ||
227 | return &priv->lclks[idx].hw; | |
228 | } | |
229 | ||
055ca547 CK |
230 | static const struct of_device_id lochnagar_of_match[] = { |
231 | { .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf }, | |
232 | { .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf }, | |
233 | {} | |
234 | }; | |
235 | MODULE_DEVICE_TABLE(of, lochnagar_of_match); | |
236 | ||
237 | static int lochnagar_clk_probe(struct platform_device *pdev) | |
76c54783 CK |
238 | { |
239 | struct clk_init_data clk_init = { | |
240 | .ops = &lochnagar_clk_ops, | |
76c54783 | 241 | }; |
055ca547 CK |
242 | struct device *dev = &pdev->dev; |
243 | struct lochnagar_clk_priv *priv; | |
244 | const struct of_device_id *of_id; | |
76c54783 | 245 | struct lochnagar_clk *lclk; |
055ca547 | 246 | struct lochnagar_config *conf; |
76c54783 CK |
247 | int ret, i; |
248 | ||
055ca547 CK |
249 | of_id = of_match_device(lochnagar_of_match, dev); |
250 | if (!of_id) | |
251 | return -EINVAL; | |
252 | ||
253 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
254 | if (!priv) | |
255 | return -ENOMEM; | |
256 | ||
257 | priv->dev = dev; | |
258 | priv->regmap = dev_get_regmap(dev->parent, NULL); | |
259 | conf = (struct lochnagar_config *)of_id->data; | |
260 | ||
261 | memcpy(priv->lclks, conf->clks, sizeof(priv->lclks)); | |
262 | ||
263 | clk_init.parent_data = conf->parents; | |
264 | clk_init.num_parents = conf->nparents; | |
265 | ||
76c54783 CK |
266 | for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { |
267 | lclk = &priv->lclks[i]; | |
268 | ||
269 | if (!lclk->name) | |
270 | continue; | |
271 | ||
272 | clk_init.name = lclk->name; | |
273 | ||
274 | lclk->priv = priv; | |
275 | lclk->hw.init = &clk_init; | |
276 | ||
055ca547 | 277 | ret = devm_clk_hw_register(dev, &lclk->hw); |
76c54783 | 278 | if (ret) { |
055ca547 | 279 | dev_err(dev, "Failed to register %s: %d\n", |
76c54783 CK |
280 | lclk->name, ret); |
281 | return ret; | |
282 | } | |
283 | } | |
284 | ||
055ca547 | 285 | ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv); |
76c54783 | 286 | if (ret < 0) |
055ca547 | 287 | dev_err(dev, "Failed to register provider: %d\n", ret); |
76c54783 CK |
288 | |
289 | return ret; | |
290 | } | |
291 | ||
76c54783 CK |
292 | static struct platform_driver lochnagar_clk_driver = { |
293 | .driver = { | |
294 | .name = "lochnagar-clk", | |
295 | .of_match_table = lochnagar_of_match, | |
296 | }, | |
297 | .probe = lochnagar_clk_probe, | |
298 | }; | |
299 | module_platform_driver(lochnagar_clk_driver); | |
300 | ||
301 | MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); | |
302 | MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board"); | |
303 | MODULE_LICENSE("GPL v2"); |