1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (c) 2020 BayLibre, SAS. |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> |
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 | const 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 | const 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(c: component); |
84 | |
85 | /* Required for the SPDIF Source control operation */ |
86 | return clk_prepare_enable(clk: 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(c: component); |
92 | |
93 | clk_disable_unprepare(clk: 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 | #ifdef CONFIG_DEBUG_FS |
107 | .debugfs_prefix = "cpu" , |
108 | #endif |
109 | }; |
110 | |
111 | static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { |
112 | [CPU_I2S_FIFO] = { |
113 | .name = "I2S FIFO" , |
114 | .playback = { |
115 | .stream_name = "I2S FIFO Playback" , |
116 | .channels_min = 2, |
117 | .channels_max = 8, |
118 | .rates = SNDRV_PCM_RATE_CONTINUOUS, |
119 | .rate_min = 5512, |
120 | .rate_max = 192000, |
121 | .formats = AIU_FORMATS, |
122 | }, |
123 | .ops = &aiu_fifo_i2s_dai_ops, |
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 | }, |
138 | [CPU_I2S_ENCODER] = { |
139 | .name = "I2S Encoder" , |
140 | .playback = { |
141 | .stream_name = "I2S Encoder Playback" , |
142 | .channels_min = 2, |
143 | .channels_max = 8, |
144 | .rates = SNDRV_PCM_RATE_8000_192000, |
145 | .formats = AIU_FORMATS, |
146 | }, |
147 | .ops = &aiu_encoder_i2s_dai_ops, |
148 | }, |
149 | [CPU_SPDIF_ENCODER] = { |
150 | .name = "SPDIF Encoder" , |
151 | .playback = { |
152 | .stream_name = "SPDIF Encoder Playback" , |
153 | .channels_min = 2, |
154 | .channels_max = 2, |
155 | .rates = (SNDRV_PCM_RATE_32000 | |
156 | SNDRV_PCM_RATE_44100 | |
157 | SNDRV_PCM_RATE_48000 | |
158 | SNDRV_PCM_RATE_88200 | |
159 | SNDRV_PCM_RATE_96000 | |
160 | SNDRV_PCM_RATE_176400 | |
161 | SNDRV_PCM_RATE_192000), |
162 | .formats = AIU_FORMATS, |
163 | }, |
164 | .ops = &aiu_encoder_spdif_dai_ops, |
165 | } |
166 | }; |
167 | |
168 | static const struct regmap_config aiu_regmap_cfg = { |
169 | .reg_bits = 32, |
170 | .val_bits = 32, |
171 | .reg_stride = 4, |
172 | .max_register = 0x2ac, |
173 | }; |
174 | |
175 | static int aiu_clk_bulk_get(struct device *dev, |
176 | const char * const *ids, |
177 | unsigned int num, |
178 | struct aiu_interface *interface) |
179 | { |
180 | struct clk_bulk_data *clks; |
181 | int i, ret; |
182 | |
183 | clks = devm_kcalloc(dev, n: num, size: sizeof(*clks), GFP_KERNEL); |
184 | if (!clks) |
185 | return -ENOMEM; |
186 | |
187 | for (i = 0; i < num; i++) |
188 | clks[i].id = ids[i]; |
189 | |
190 | ret = devm_clk_bulk_get(dev, num_clks: num, clks); |
191 | if (ret < 0) |
192 | return ret; |
193 | |
194 | interface->clks = clks; |
195 | interface->clk_num = num; |
196 | return 0; |
197 | } |
198 | |
199 | static const char * const aiu_i2s_ids[] = { |
200 | [PCLK] = "i2s_pclk" , |
201 | [AOCLK] = "i2s_aoclk" , |
202 | [MCLK] = "i2s_mclk" , |
203 | [MIXER] = "i2s_mixer" , |
204 | }; |
205 | |
206 | static const char * const aiu_spdif_ids[] = { |
207 | [PCLK] = "spdif_pclk" , |
208 | [AOCLK] = "spdif_aoclk" , |
209 | [MCLK] = "spdif_mclk_sel" |
210 | }; |
211 | |
212 | static int aiu_clk_get(struct device *dev) |
213 | { |
214 | struct aiu *aiu = dev_get_drvdata(dev); |
215 | struct clk *pclk; |
216 | int ret; |
217 | |
218 | pclk = devm_clk_get_enabled(dev, id: "pclk" ); |
219 | if (IS_ERR(ptr: pclk)) |
220 | return dev_err_probe(dev, err: PTR_ERR(ptr: pclk), fmt: "Can't get the aiu pclk\n" ); |
221 | |
222 | aiu->spdif_mclk = devm_clk_get(dev, id: "spdif_mclk" ); |
223 | if (IS_ERR(ptr: aiu->spdif_mclk)) |
224 | return dev_err_probe(dev, err: PTR_ERR(ptr: aiu->spdif_mclk), |
225 | fmt: "Can't get the aiu spdif master clock\n" ); |
226 | |
227 | ret = aiu_clk_bulk_get(dev, ids: aiu_i2s_ids, ARRAY_SIZE(aiu_i2s_ids), |
228 | interface: &aiu->i2s); |
229 | if (ret) |
230 | return dev_err_probe(dev, err: ret, fmt: "Can't get the i2s clocks\n" ); |
231 | |
232 | ret = aiu_clk_bulk_get(dev, ids: aiu_spdif_ids, ARRAY_SIZE(aiu_spdif_ids), |
233 | interface: &aiu->spdif); |
234 | if (ret) |
235 | return dev_err_probe(dev, err: ret, fmt: "Can't get the spdif clocks\n" ); |
236 | |
237 | return ret; |
238 | } |
239 | |
240 | static int aiu_probe(struct platform_device *pdev) |
241 | { |
242 | struct device *dev = &pdev->dev; |
243 | void __iomem *regs; |
244 | struct regmap *map; |
245 | struct aiu *aiu; |
246 | int ret; |
247 | |
248 | aiu = devm_kzalloc(dev, size: sizeof(*aiu), GFP_KERNEL); |
249 | if (!aiu) |
250 | return -ENOMEM; |
251 | |
252 | aiu->platform = device_get_match_data(dev); |
253 | if (!aiu->platform) |
254 | return -ENODEV; |
255 | |
256 | platform_set_drvdata(pdev, data: aiu); |
257 | |
258 | ret = device_reset(dev); |
259 | if (ret) |
260 | return dev_err_probe(dev, err: ret, fmt: "Failed to reset device\n" ); |
261 | |
262 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
263 | if (IS_ERR(ptr: regs)) |
264 | return PTR_ERR(ptr: regs); |
265 | |
266 | map = devm_regmap_init_mmio(dev, regs, &aiu_regmap_cfg); |
267 | if (IS_ERR(ptr: map)) { |
268 | dev_err(dev, "failed to init regmap: %ld\n" , |
269 | PTR_ERR(map)); |
270 | return PTR_ERR(ptr: map); |
271 | } |
272 | |
273 | aiu->i2s.irq = platform_get_irq_byname(pdev, "i2s" ); |
274 | if (aiu->i2s.irq < 0) |
275 | return aiu->i2s.irq; |
276 | |
277 | aiu->spdif.irq = platform_get_irq_byname(pdev, "spdif" ); |
278 | if (aiu->spdif.irq < 0) |
279 | return aiu->spdif.irq; |
280 | |
281 | ret = aiu_clk_get(dev); |
282 | if (ret) |
283 | return ret; |
284 | |
285 | /* Register the cpu component of the aiu */ |
286 | ret = snd_soc_register_component(dev, component_driver: &aiu_cpu_component, |
287 | dai_drv: aiu_cpu_dai_drv, |
288 | ARRAY_SIZE(aiu_cpu_dai_drv)); |
289 | if (ret) { |
290 | dev_err(dev, "Failed to register cpu component\n" ); |
291 | return ret; |
292 | } |
293 | |
294 | /* Register the hdmi codec control component */ |
295 | ret = aiu_hdmi_ctrl_register_component(dev); |
296 | if (ret) { |
297 | dev_err(dev, "Failed to register hdmi control component\n" ); |
298 | goto err; |
299 | } |
300 | |
301 | /* Register the internal dac control component on gxl */ |
302 | if (aiu->platform->has_acodec) { |
303 | ret = aiu_acodec_ctrl_register_component(dev); |
304 | if (ret) { |
305 | dev_err(dev, |
306 | "Failed to register acodec control component\n" ); |
307 | goto err; |
308 | } |
309 | } |
310 | |
311 | return 0; |
312 | err: |
313 | snd_soc_unregister_component(dev); |
314 | return ret; |
315 | } |
316 | |
317 | static void aiu_remove(struct platform_device *pdev) |
318 | { |
319 | snd_soc_unregister_component(dev: &pdev->dev); |
320 | } |
321 | |
322 | static const struct aiu_platform_data aiu_gxbb_pdata = { |
323 | .has_acodec = false, |
324 | .has_clk_ctrl_more_i2s_div = true, |
325 | }; |
326 | |
327 | static const struct aiu_platform_data aiu_gxl_pdata = { |
328 | .has_acodec = true, |
329 | .has_clk_ctrl_more_i2s_div = true, |
330 | }; |
331 | |
332 | static const struct aiu_platform_data aiu_meson8_pdata = { |
333 | .has_acodec = false, |
334 | .has_clk_ctrl_more_i2s_div = false, |
335 | }; |
336 | |
337 | static const struct of_device_id aiu_of_match[] = { |
338 | { .compatible = "amlogic,aiu-gxbb" , .data = &aiu_gxbb_pdata }, |
339 | { .compatible = "amlogic,aiu-gxl" , .data = &aiu_gxl_pdata }, |
340 | { .compatible = "amlogic,aiu-meson8" , .data = &aiu_meson8_pdata }, |
341 | { .compatible = "amlogic,aiu-meson8b" , .data = &aiu_meson8_pdata }, |
342 | {} |
343 | }; |
344 | MODULE_DEVICE_TABLE(of, aiu_of_match); |
345 | |
346 | static struct platform_driver aiu_pdrv = { |
347 | .probe = aiu_probe, |
348 | .remove_new = aiu_remove, |
349 | .driver = { |
350 | .name = "meson-aiu" , |
351 | .of_match_table = aiu_of_match, |
352 | }, |
353 | }; |
354 | module_platform_driver(aiu_pdrv); |
355 | |
356 | MODULE_DESCRIPTION("Meson AIU Driver" ); |
357 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>" ); |
358 | MODULE_LICENSE("GPL v2" ); |
359 | |