]>
Commit | Line | Data |
---|---|---|
6ae9ca9c JB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // Copyright (c) 2020 BayLibre, SAS. | |
4 | // Author: Jerome Brunet <[email protected]> | |
5 | ||
6 | #include <linux/bitfield.h> | |
7 | #include <linux/clk.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/of_platform.h> | |
10 | #include <linux/regmap.h> | |
11 | #include <linux/reset.h> | |
12 | #include <sound/soc.h> | |
13 | #include <sound/soc-dai.h> | |
14 | ||
15 | #include <dt-bindings/sound/meson-aiu.h> | |
16 | #include "aiu.h" | |
17 | #include "aiu-fifo.h" | |
18 | ||
19 | #define AIU_I2S_MISC_958_SRC_SHIFT 3 | |
20 | ||
21 | static const char * const aiu_spdif_encode_sel_texts[] = { | |
22 | "SPDIF", "I2S", | |
23 | }; | |
24 | ||
25 | static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, | |
26 | AIU_I2S_MISC_958_SRC_SHIFT, | |
27 | aiu_spdif_encode_sel_texts); | |
28 | ||
29 | static const struct snd_kcontrol_new aiu_spdif_encode_mux = | |
30 | SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); | |
31 | ||
32 | static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { | |
33 | SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, | |
34 | &aiu_spdif_encode_mux), | |
35 | }; | |
36 | ||
37 | static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { | |
38 | { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, | |
39 | { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, | |
40 | { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, | |
41 | { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, | |
42 | }; | |
43 | ||
44 | int aiu_of_xlate_dai_name(struct snd_soc_component *component, | |
45 | struct of_phandle_args *args, | |
46 | const char **dai_name, | |
47 | unsigned int component_id) | |
48 | { | |
49 | struct snd_soc_dai *dai; | |
50 | int id; | |
51 | ||
52 | if (args->args_count != 2) | |
53 | return -EINVAL; | |
54 | ||
55 | if (args->args[0] != component_id) | |
56 | return -EINVAL; | |
57 | ||
58 | id = args->args[1]; | |
59 | ||
60 | if (id < 0 || id >= component->num_dai) | |
61 | return -EINVAL; | |
62 | ||
63 | for_each_component_dais(component, dai) { | |
64 | if (id == 0) | |
65 | break; | |
66 | id--; | |
67 | } | |
68 | ||
69 | *dai_name = dai->driver->name; | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
74 | static int aiu_cpu_of_xlate_dai_name(struct snd_soc_component *component, | |
75 | struct of_phandle_args *args, | |
76 | const char **dai_name) | |
77 | { | |
78 | return aiu_of_xlate_dai_name(component, args, dai_name, AIU_CPU); | |
79 | } | |
80 | ||
81 | static int aiu_cpu_component_probe(struct snd_soc_component *component) | |
82 | { | |
83 | struct aiu *aiu = snd_soc_component_get_drvdata(component); | |
84 | ||
85 | /* Required for the SPDIF Source control operation */ | |
86 | return clk_prepare_enable(aiu->i2s.clks[PCLK].clk); | |
87 | } | |
88 | ||
89 | static void aiu_cpu_component_remove(struct snd_soc_component *component) | |
90 | { | |
91 | struct aiu *aiu = snd_soc_component_get_drvdata(component); | |
92 | ||
93 | clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); | |
94 | } | |
95 | ||
96 | static const struct snd_soc_component_driver aiu_cpu_component = { | |
97 | .name = "AIU CPU", | |
98 | .dapm_widgets = aiu_cpu_dapm_widgets, | |
99 | .num_dapm_widgets = ARRAY_SIZE(aiu_cpu_dapm_widgets), | |
100 | .dapm_routes = aiu_cpu_dapm_routes, | |
101 | .num_dapm_routes = ARRAY_SIZE(aiu_cpu_dapm_routes), | |
102 | .of_xlate_dai_name = aiu_cpu_of_xlate_dai_name, | |
103 | .pointer = aiu_fifo_pointer, | |
104 | .probe = aiu_cpu_component_probe, | |
105 | .remove = aiu_cpu_component_remove, | |
106 | }; | |
107 | ||
108 | static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { | |
109 | [CPU_I2S_FIFO] = { | |
110 | .name = "I2S FIFO", | |
111 | .playback = { | |
112 | .stream_name = "I2S FIFO Playback", | |
113 | .channels_min = 2, | |
114 | .channels_max = 8, | |
115 | .rates = SNDRV_PCM_RATE_CONTINUOUS, | |
116 | .rate_min = 5512, | |
117 | .rate_max = 192000, | |
118 | .formats = AIU_FORMATS, | |
119 | }, | |
120 | .ops = &aiu_fifo_i2s_dai_ops, | |
121 | .pcm_new = aiu_fifo_pcm_new, | |
122 | .probe = aiu_fifo_i2s_dai_probe, | |
123 | .remove = aiu_fifo_dai_remove, | |
124 | }, | |
125 | [CPU_SPDIF_FIFO] = { | |
126 | .name = "SPDIF FIFO", | |
127 | .playback = { | |
128 | .stream_name = "SPDIF FIFO Playback", | |
129 | .channels_min = 2, | |
130 | .channels_max = 2, | |
131 | .rates = SNDRV_PCM_RATE_CONTINUOUS, | |
132 | .rate_min = 5512, | |
133 | .rate_max = 192000, | |
134 | .formats = AIU_FORMATS, | |
135 | }, | |
136 | .ops = &aiu_fifo_spdif_dai_ops, | |
137 | .pcm_new = aiu_fifo_pcm_new, | |
138 | .probe = aiu_fifo_spdif_dai_probe, | |
139 | .remove = aiu_fifo_dai_remove, | |
140 | }, | |
141 | [CPU_I2S_ENCODER] = { | |
142 | .name = "I2S Encoder", | |
143 | .playback = { | |
144 | .stream_name = "I2S Encoder Playback", | |
145 | .channels_min = 2, | |
146 | .channels_max = 8, | |
147 | .rates = SNDRV_PCM_RATE_8000_192000, | |
148 | .formats = AIU_FORMATS, | |
149 | }, | |
150 | .ops = &aiu_encoder_i2s_dai_ops, | |
151 | }, | |
152 | [CPU_SPDIF_ENCODER] = { | |
153 | .name = "SPDIF Encoder", | |
154 | .playback = { | |
155 | .stream_name = "SPDIF Encoder Playback", | |
156 | .channels_min = 2, | |
157 | .channels_max = 2, | |
158 | .rates = (SNDRV_PCM_RATE_32000 | | |
159 | SNDRV_PCM_RATE_44100 | | |
160 | SNDRV_PCM_RATE_48000 | | |
161 | SNDRV_PCM_RATE_88200 | | |
162 | SNDRV_PCM_RATE_96000 | | |
163 | SNDRV_PCM_RATE_176400 | | |
164 | SNDRV_PCM_RATE_192000), | |
165 | .formats = AIU_FORMATS, | |
166 | }, | |
167 | .ops = &aiu_encoder_spdif_dai_ops, | |
168 | } | |
169 | }; | |
170 | ||
171 | static const struct regmap_config aiu_regmap_cfg = { | |
172 | .reg_bits = 32, | |
173 | .val_bits = 32, | |
174 | .reg_stride = 4, | |
175 | .max_register = 0x2ac, | |
176 | }; | |
177 | ||
178 | static int aiu_clk_bulk_get(struct device *dev, | |
179 | const char * const *ids, | |
180 | unsigned int num, | |
181 | struct aiu_interface *interface) | |
182 | { | |
183 | struct clk_bulk_data *clks; | |
184 | int i, ret; | |
185 | ||
269f0017 | 186 | clks = devm_kcalloc(dev, num, sizeof(*clks), GFP_KERNEL); |
6ae9ca9c JB |
187 | if (!clks) |
188 | return -ENOMEM; | |
189 | ||
190 | for (i = 0; i < num; i++) | |
191 | clks[i].id = ids[i]; | |
192 | ||
193 | ret = devm_clk_bulk_get(dev, num, clks); | |
194 | if (ret < 0) | |
195 | return ret; | |
196 | ||
197 | interface->clks = clks; | |
198 | interface->clk_num = num; | |
199 | return 0; | |
200 | } | |
201 | ||
202 | static const char * const aiu_i2s_ids[] = { | |
203 | [PCLK] = "i2s_pclk", | |
204 | [AOCLK] = "i2s_aoclk", | |
205 | [MCLK] = "i2s_mclk", | |
206 | [MIXER] = "i2s_mixer", | |
207 | }; | |
208 | ||
209 | static const char * const aiu_spdif_ids[] = { | |
210 | [PCLK] = "spdif_pclk", | |
211 | [AOCLK] = "spdif_aoclk", | |
212 | [MCLK] = "spdif_mclk_sel" | |
213 | }; | |
214 | ||
215 | static int aiu_clk_get(struct device *dev) | |
216 | { | |
217 | struct aiu *aiu = dev_get_drvdata(dev); | |
218 | int ret; | |
219 | ||
220 | aiu->pclk = devm_clk_get(dev, "pclk"); | |
221 | if (IS_ERR(aiu->pclk)) { | |
222 | if (PTR_ERR(aiu->pclk) != -EPROBE_DEFER) | |
223 | dev_err(dev, "Can't get the aiu pclk\n"); | |
224 | return PTR_ERR(aiu->pclk); | |
225 | } | |
226 | ||
227 | aiu->spdif_mclk = devm_clk_get(dev, "spdif_mclk"); | |
228 | if (IS_ERR(aiu->spdif_mclk)) { | |
229 | if (PTR_ERR(aiu->spdif_mclk) != -EPROBE_DEFER) | |
230 | dev_err(dev, "Can't get the aiu spdif master clock\n"); | |
231 | return PTR_ERR(aiu->spdif_mclk); | |
232 | } | |
233 | ||
234 | ret = aiu_clk_bulk_get(dev, aiu_i2s_ids, ARRAY_SIZE(aiu_i2s_ids), | |
235 | &aiu->i2s); | |
236 | if (ret) { | |
237 | if (ret != -EPROBE_DEFER) | |
238 | dev_err(dev, "Can't get the i2s clocks\n"); | |
239 | return ret; | |
240 | } | |
241 | ||
242 | ret = aiu_clk_bulk_get(dev, aiu_spdif_ids, ARRAY_SIZE(aiu_spdif_ids), | |
243 | &aiu->spdif); | |
244 | if (ret) { | |
245 | if (ret != -EPROBE_DEFER) | |
246 | dev_err(dev, "Can't get the spdif clocks\n"); | |
247 | return ret; | |
248 | } | |
249 | ||
250 | ret = clk_prepare_enable(aiu->pclk); | |
251 | if (ret) { | |
252 | dev_err(dev, "peripheral clock enable failed\n"); | |
253 | return ret; | |
254 | } | |
255 | ||
256 | ret = devm_add_action_or_reset(dev, | |
257 | (void(*)(void *))clk_disable_unprepare, | |
258 | aiu->pclk); | |
259 | if (ret) | |
260 | dev_err(dev, "failed to add reset action on pclk"); | |
261 | ||
262 | return ret; | |
263 | } | |
264 | ||
265 | static int aiu_probe(struct platform_device *pdev) | |
266 | { | |
267 | struct device *dev = &pdev->dev; | |
268 | void __iomem *regs; | |
269 | struct regmap *map; | |
270 | struct aiu *aiu; | |
271 | int ret; | |
272 | ||
273 | aiu = devm_kzalloc(dev, sizeof(*aiu), GFP_KERNEL); | |
274 | if (!aiu) | |
275 | return -ENOMEM; | |
edc76180 MB |
276 | |
277 | aiu->platform = device_get_match_data(dev); | |
278 | if (!aiu->platform) | |
279 | return -ENODEV; | |
280 | ||
6ae9ca9c JB |
281 | platform_set_drvdata(pdev, aiu); |
282 | ||
283 | ret = device_reset(dev); | |
284 | if (ret) { | |
285 | if (ret != -EPROBE_DEFER) | |
286 | dev_err(dev, "Failed to reset device\n"); | |
287 | return ret; | |
288 | } | |
289 | ||
290 | regs = devm_platform_ioremap_resource(pdev, 0); | |
291 | if (IS_ERR(regs)) | |
292 | return PTR_ERR(regs); | |
293 | ||
294 | map = devm_regmap_init_mmio(dev, regs, &aiu_regmap_cfg); | |
295 | if (IS_ERR(map)) { | |
296 | dev_err(dev, "failed to init regmap: %ld\n", | |
297 | PTR_ERR(map)); | |
298 | return PTR_ERR(map); | |
299 | } | |
300 | ||
301 | aiu->i2s.irq = platform_get_irq_byname(pdev, "i2s"); | |
6e700f06 | 302 | if (aiu->i2s.irq < 0) |
6ae9ca9c | 303 | return aiu->i2s.irq; |
6ae9ca9c JB |
304 | |
305 | aiu->spdif.irq = platform_get_irq_byname(pdev, "spdif"); | |
6e700f06 | 306 | if (aiu->spdif.irq < 0) |
6ae9ca9c | 307 | return aiu->spdif.irq; |
6ae9ca9c JB |
308 | |
309 | ret = aiu_clk_get(dev); | |
310 | if (ret) | |
311 | return ret; | |
312 | ||
313 | /* Register the cpu component of the aiu */ | |
314 | ret = snd_soc_register_component(dev, &aiu_cpu_component, | |
315 | aiu_cpu_dai_drv, | |
316 | ARRAY_SIZE(aiu_cpu_dai_drv)); | |
b82b734c | 317 | if (ret) { |
6ae9ca9c | 318 | dev_err(dev, "Failed to register cpu component\n"); |
b82b734c JB |
319 | return ret; |
320 | } | |
6ae9ca9c | 321 | |
b82b734c JB |
322 | /* Register the hdmi codec control component */ |
323 | ret = aiu_hdmi_ctrl_register_component(dev); | |
324 | if (ret) { | |
325 | dev_err(dev, "Failed to register hdmi control component\n"); | |
326 | goto err; | |
327 | } | |
328 | ||
65816025 | 329 | /* Register the internal dac control component on gxl */ |
edc76180 | 330 | if (aiu->platform->has_acodec) { |
65816025 JB |
331 | ret = aiu_acodec_ctrl_register_component(dev); |
332 | if (ret) { | |
333 | dev_err(dev, | |
334 | "Failed to register acodec control component\n"); | |
335 | goto err; | |
336 | } | |
337 | } | |
338 | ||
b82b734c JB |
339 | return 0; |
340 | err: | |
341 | snd_soc_unregister_component(dev); | |
6ae9ca9c JB |
342 | return ret; |
343 | } | |
344 | ||
345 | static int aiu_remove(struct platform_device *pdev) | |
346 | { | |
347 | snd_soc_unregister_component(&pdev->dev); | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
edc76180 MB |
352 | static const struct aiu_platform_data aiu_gxbb_pdata = { |
353 | .has_acodec = false, | |
3e25c445 | 354 | .has_clk_ctrl_more_i2s_div = true, |
edc76180 MB |
355 | }; |
356 | ||
357 | static const struct aiu_platform_data aiu_gxl_pdata = { | |
358 | .has_acodec = true, | |
3e25c445 MB |
359 | .has_clk_ctrl_more_i2s_div = true, |
360 | }; | |
361 | ||
362 | static const struct aiu_platform_data aiu_meson8_pdata = { | |
363 | .has_acodec = false, | |
364 | .has_clk_ctrl_more_i2s_div = false, | |
edc76180 MB |
365 | }; |
366 | ||
6ae9ca9c | 367 | static const struct of_device_id aiu_of_match[] = { |
edc76180 MB |
368 | { .compatible = "amlogic,aiu-gxbb", .data = &aiu_gxbb_pdata }, |
369 | { .compatible = "amlogic,aiu-gxl", .data = &aiu_gxl_pdata }, | |
3e25c445 MB |
370 | { .compatible = "amlogic,aiu-meson8", .data = &aiu_meson8_pdata }, |
371 | { .compatible = "amlogic,aiu-meson8b", .data = &aiu_meson8_pdata }, | |
6ae9ca9c JB |
372 | {} |
373 | }; | |
374 | MODULE_DEVICE_TABLE(of, aiu_of_match); | |
375 | ||
376 | static struct platform_driver aiu_pdrv = { | |
377 | .probe = aiu_probe, | |
378 | .remove = aiu_remove, | |
379 | .driver = { | |
380 | .name = "meson-aiu", | |
381 | .of_match_table = aiu_of_match, | |
382 | }, | |
383 | }; | |
384 | module_platform_driver(aiu_pdrv); | |
385 | ||
386 | MODULE_DESCRIPTION("Meson AIU Driver"); | |
387 | MODULE_AUTHOR("Jerome Brunet <[email protected]>"); | |
388 | MODULE_LICENSE("GPL v2"); |