]>
Commit | Line | Data |
---|---|---|
1 | --- linux-5.9.orig/sound/soc/bcm/Kconfig 2020-10-11 23:15:50.000000000 +0200 | |
2 | +++ linux-5.9/sound/soc/bcm/Kconfig 2020-12-14 01:08:25.450035831 +0100 | |
3 | @@ -26,3 +26,22 @@ | |
4 | DSL/PON chips (bcm63158, bcm63178) | |
5 | ||
6 | If you don't know what to do here, say N | |
7 | + | |
8 | +config SND_BCM2708_SOC_HIFIBERRY_DAC | |
9 | + tristate "Support for HifiBerry DAC" | |
10 | + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S | |
11 | + select SND_SOC_PCM5102A | |
12 | + select SND_RPI_SIMPLE_SOUNDCARD | |
13 | + help | |
14 | + Say Y or M if you want to add support for HifiBerry DAC. | |
15 | + | |
16 | +config SND_RPI_SIMPLE_SOUNDCARD | |
17 | + tristate "Support for Raspberry Pi simple soundcards" | |
18 | + help | |
19 | + Say Y or M if you want to add support Raspbery Pi simple soundcards | |
20 | + | |
21 | +config SND_RPI_WM8804_SOUNDCARD | |
22 | + tristate "Support for Raspberry Pi generic WM8804 soundcards" | |
23 | + help | |
24 | + Say Y or M if you want to add support for the Raspberry Pi | |
25 | + generic driver for WM8804 based soundcards. | |
26 | --- linux-5.9.orig/sound/soc/bcm/Makefile 2020-10-11 23:15:50.000000000 +0200 | |
27 | +++ linux-5.9/sound/soc/bcm/Makefile 2020-12-14 01:05:51.274295380 +0100 | |
28 | @@ -12,4 +12,10 @@ | |
29 | # BCM63XX Platform Support | |
30 | snd-soc-63xx-objs := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o | |
31 | ||
32 | -obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o | |
33 | \ No newline at end of file | |
34 | +obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o | |
35 | + | |
36 | +snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o | |
37 | +snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o | |
38 | + | |
39 | +obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o | |
40 | +obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o | |
41 | --- linux-5.9.orig/sound/soc/bcm/rpi-wm8804-soundcard.c 1970-01-01 01:00:00.000000000 +0100 | |
42 | +++ linux-5.9/sound/soc/bcm/rpi-wm8804-soundcard.c 2020-12-14 01:02:38.688758934 +0100 | |
43 | @@ -0,0 +1,410 @@ | |
44 | +// SPDX-License-Identifier: GPL-2.0 | |
45 | +/* | |
46 | + * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard. | |
47 | + * | |
48 | + * Copyright (C) 2018 Raspberry Pi. | |
49 | + * | |
50 | + * Authors: Tim Gover <tim.gover@raspberrypi.org> | |
51 | + * | |
52 | + * Generic driver for Pi Hat WM8804 digi sounds cards | |
53 | + * | |
54 | + * Based upon code from: | |
55 | + * justboom-digi.c | |
56 | + * by Milan Neskovic <info@justboom.co> | |
57 | + * | |
58 | + * iqaudio_digi.c | |
59 | + * by Daniel Matuschek <info@crazy-audio.com> | |
60 | + * | |
61 | + * allo-digione.c | |
62 | + * by Baswaraj <jaikumar@cem-solutions.net> | |
63 | + * | |
64 | + * hifiberry-digi.c | |
65 | + * Daniel Matuschek <info@crazy-audio.com> | |
66 | + * | |
67 | + * This program is free software; you can redistribute it and/or | |
68 | + * modify it under the terms of the GNU General Public License | |
69 | + * version 2 as published by the Free Software Foundation. | |
70 | + * | |
71 | + * This program is distributed in the hope that it will be useful, but | |
72 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | |
73 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
74 | + * General Public License for more details. | |
75 | + */ | |
76 | + | |
77 | +#include <linux/gpio/consumer.h> | |
78 | +#include <linux/platform_device.h> | |
79 | +#include <linux/module.h> | |
80 | + | |
81 | +#include <sound/core.h> | |
82 | +#include <sound/pcm.h> | |
83 | +#include <sound/pcm_params.h> | |
84 | +#include <sound/soc.h> | |
85 | + | |
86 | +#include "../codecs/wm8804.h" | |
87 | + | |
88 | +struct wm8804_clk_cfg { | |
89 | + unsigned int sysclk_freq; | |
90 | + unsigned int mclk_freq; | |
91 | + unsigned int mclk_div; | |
92 | +}; | |
93 | + | |
94 | +/* Parameters for generic functions */ | |
95 | +struct snd_rpi_wm8804_drvdata { | |
96 | + /* Required - pointer to the DAI structure */ | |
97 | + struct snd_soc_dai_link *dai; | |
98 | + /* Required - snd_soc_card name */ | |
99 | + const char *card_name; | |
100 | + /* Optional DT node names if card info is defined in DT */ | |
101 | + const char *card_name_dt; | |
102 | + const char *dai_name_dt; | |
103 | + const char *dai_stream_name_dt; | |
104 | + /* Optional probe extension - called prior to register_card */ | |
105 | + int (*probe)(struct platform_device *pdev); | |
106 | +}; | |
107 | + | |
108 | +static struct gpio_desc *snd_clk44gpio; | |
109 | +static struct gpio_desc *snd_clk48gpio; | |
110 | +static int wm8804_samplerate = 0; | |
111 | + | |
112 | +/* Forward declarations */ | |
113 | +static struct snd_soc_dai_link snd_allo_digione_dai[]; | |
114 | +static struct snd_soc_card snd_rpi_wm8804; | |
115 | + | |
116 | + | |
117 | +#define CLK_44EN_RATE 22579200UL | |
118 | +#define CLK_48EN_RATE 24576000UL | |
119 | + | |
120 | +static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate) | |
121 | +{ | |
122 | + switch (samplerate) { | |
123 | + case 11025: | |
124 | + case 22050: | |
125 | + case 44100: | |
126 | + case 88200: | |
127 | + case 176400: | |
128 | + gpiod_set_value_cansleep(snd_clk44gpio, 1); | |
129 | + gpiod_set_value_cansleep(snd_clk48gpio, 0); | |
130 | + return CLK_44EN_RATE; | |
131 | + default: | |
132 | + gpiod_set_value_cansleep(snd_clk48gpio, 1); | |
133 | + gpiod_set_value_cansleep(snd_clk44gpio, 0); | |
134 | + return CLK_48EN_RATE; | |
135 | + } | |
136 | +} | |
137 | + | |
138 | +static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate, | |
139 | + struct wm8804_clk_cfg *clk_cfg) | |
140 | +{ | |
141 | + clk_cfg->sysclk_freq = 27000000; | |
142 | + | |
143 | + if (samplerate <= 96000 || | |
144 | + snd_rpi_wm8804.dai_link == snd_allo_digione_dai) { | |
145 | + clk_cfg->mclk_freq = samplerate * 256; | |
146 | + clk_cfg->mclk_div = WM8804_MCLKDIV_256FS; | |
147 | + } else { | |
148 | + clk_cfg->mclk_freq = samplerate * 128; | |
149 | + clk_cfg->mclk_div = WM8804_MCLKDIV_128FS; | |
150 | + } | |
151 | + | |
152 | + if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))) | |
153 | + clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate); | |
154 | +} | |
155 | + | |
156 | +static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream, | |
157 | + struct snd_pcm_hw_params *params) | |
158 | +{ | |
159 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
160 | + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); | |
161 | + struct snd_soc_component *component = codec_dai->component; | |
162 | + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); | |
163 | + int sampling_freq = 1; | |
164 | + int ret; | |
165 | + struct wm8804_clk_cfg clk_cfg; | |
166 | + int samplerate = params_rate(params); | |
167 | + | |
168 | + if (samplerate == wm8804_samplerate) | |
169 | + return 0; | |
170 | + | |
171 | + /* clear until all clocks are setup properly */ | |
172 | + wm8804_samplerate = 0; | |
173 | + | |
174 | + snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg); | |
175 | + | |
176 | + pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n", | |
177 | + __func__, samplerate, clk_cfg.mclk_freq, | |
178 | + clk_cfg.mclk_div, clk_cfg.sysclk_freq); | |
179 | + | |
180 | + switch (samplerate) { | |
181 | + case 32000: | |
182 | + sampling_freq = 0x03; | |
183 | + break; | |
184 | + case 44100: | |
185 | + sampling_freq = 0x00; | |
186 | + break; | |
187 | + case 48000: | |
188 | + sampling_freq = 0x02; | |
189 | + break; | |
190 | + case 88200: | |
191 | + sampling_freq = 0x08; | |
192 | + break; | |
193 | + case 96000: | |
194 | + sampling_freq = 0x0a; | |
195 | + break; | |
196 | + case 176400: | |
197 | + sampling_freq = 0x0c; | |
198 | + break; | |
199 | + case 192000: | |
200 | + sampling_freq = 0x0e; | |
201 | + break; | |
202 | + default: | |
203 | + dev_err(rtd->card->dev, | |
204 | + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", | |
205 | + samplerate); | |
206 | + } | |
207 | + | |
208 | + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div); | |
209 | + snd_soc_dai_set_pll(codec_dai, 0, 0, | |
210 | + clk_cfg.sysclk_freq, clk_cfg.mclk_freq); | |
211 | + | |
212 | + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, | |
213 | + clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT); | |
214 | + if (ret < 0) { | |
215 | + dev_err(rtd->card->dev, | |
216 | + "Failed to set WM8804 SYSCLK: %d\n", ret); | |
217 | + return ret; | |
218 | + } | |
219 | + | |
220 | + wm8804_samplerate = samplerate; | |
221 | + | |
222 | + /* set sampling frequency status bits */ | |
223 | + snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, | |
224 | + sampling_freq); | |
225 | + | |
226 | + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); | |
227 | +} | |
228 | + | |
229 | +static struct snd_soc_ops snd_rpi_wm8804_ops = { | |
230 | + .hw_params = snd_rpi_wm8804_hw_params, | |
231 | +}; | |
232 | + | |
233 | +SND_SOC_DAILINK_DEFS(justboom_digi, | |
234 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
235 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
236 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
237 | + | |
238 | +static struct snd_soc_dai_link snd_justboom_digi_dai[] = { | |
239 | +{ | |
240 | + .name = "JustBoom Digi", | |
241 | + .stream_name = "JustBoom Digi HiFi", | |
242 | + SND_SOC_DAILINK_REG(justboom_digi), | |
243 | +}, | |
244 | +}; | |
245 | + | |
246 | +static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = { | |
247 | + .card_name = "snd_rpi_justboom_digi", | |
248 | + .dai = snd_justboom_digi_dai, | |
249 | +}; | |
250 | + | |
251 | +SND_SOC_DAILINK_DEFS(iqaudio_digi, | |
252 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
253 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
254 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
255 | + | |
256 | +static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = { | |
257 | +{ | |
258 | + .name = "IQAudIO Digi", | |
259 | + .stream_name = "IQAudIO Digi HiFi", | |
260 | + SND_SOC_DAILINK_REG(iqaudio_digi), | |
261 | +}, | |
262 | +}; | |
263 | + | |
264 | +static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = { | |
265 | + .card_name = "IQAudIODigi", | |
266 | + .dai = snd_iqaudio_digi_dai, | |
267 | + .card_name_dt = "wm8804-digi,card-name", | |
268 | + .dai_name_dt = "wm8804-digi,dai-name", | |
269 | + .dai_stream_name_dt = "wm8804-digi,dai-stream-name", | |
270 | +}; | |
271 | + | |
272 | +static int snd_allo_digione_probe(struct platform_device *pdev) | |
273 | +{ | |
274 | + pr_debug("%s\n", __func__); | |
275 | + | |
276 | + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) { | |
277 | + dev_err(&pdev->dev, "devm_gpiod_get() failed\n"); | |
278 | + return -EINVAL; | |
279 | + } | |
280 | + return 0; | |
281 | +} | |
282 | + | |
283 | +SND_SOC_DAILINK_DEFS(allo_digione, | |
284 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
285 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
286 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
287 | + | |
288 | +static struct snd_soc_dai_link snd_allo_digione_dai[] = { | |
289 | +{ | |
290 | + .name = "Allo DigiOne", | |
291 | + .stream_name = "Allo DigiOne HiFi", | |
292 | + SND_SOC_DAILINK_REG(allo_digione), | |
293 | +}, | |
294 | +}; | |
295 | + | |
296 | +static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = { | |
297 | + .card_name = "snd_allo_digione", | |
298 | + .dai = snd_allo_digione_dai, | |
299 | + .probe = snd_allo_digione_probe, | |
300 | +}; | |
301 | + | |
302 | +SND_SOC_DAILINK_DEFS(hifiberry_digi, | |
303 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
304 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
305 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
306 | + | |
307 | +static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = { | |
308 | +{ | |
309 | + .name = "HifiBerry Digi", | |
310 | + .stream_name = "HifiBerry Digi HiFi", | |
311 | + SND_SOC_DAILINK_REG(hifiberry_digi), | |
312 | +}, | |
313 | +}; | |
314 | + | |
315 | +static int snd_hifiberry_digi_probe(struct platform_device *pdev) | |
316 | +{ | |
317 | + pr_debug("%s\n", __func__); | |
318 | + | |
319 | + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) | |
320 | + return 0; | |
321 | + | |
322 | + snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro"; | |
323 | + snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi"; | |
324 | + return 0; | |
325 | +} | |
326 | + | |
327 | +static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = { | |
328 | + .card_name = "snd_rpi_hifiberry_digi", | |
329 | + .dai = snd_hifiberry_digi_dai, | |
330 | + .probe = snd_hifiberry_digi_probe, | |
331 | +}; | |
332 | + | |
333 | +static const struct of_device_id snd_rpi_wm8804_of_match[] = { | |
334 | + { .compatible = "justboom,justboom-digi", | |
335 | + .data = (void *) &drvdata_justboom_digi }, | |
336 | + { .compatible = "iqaudio,wm8804-digi", | |
337 | + .data = (void *) &drvdata_iqaudio_digi }, | |
338 | + { .compatible = "allo,allo-digione", | |
339 | + .data = (void *) &drvdata_allo_digione }, | |
340 | + { .compatible = "hifiberry,hifiberry-digi", | |
341 | + .data = (void *) &drvdata_hifiberry_digi }, | |
342 | + {}, | |
343 | +}; | |
344 | + | |
345 | +static struct snd_soc_card snd_rpi_wm8804 = { | |
346 | + .driver_name = "RPi-WM8804", | |
347 | + .owner = THIS_MODULE, | |
348 | + .dai_link = NULL, | |
349 | + .num_links = 1, | |
350 | +}; | |
351 | + | |
352 | +static int snd_rpi_wm8804_probe(struct platform_device *pdev) | |
353 | +{ | |
354 | + int ret = 0; | |
355 | + const struct of_device_id *of_id; | |
356 | + | |
357 | + snd_rpi_wm8804.dev = &pdev->dev; | |
358 | + of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node); | |
359 | + | |
360 | + if (pdev->dev.of_node && of_id->data) { | |
361 | + struct device_node *i2s_node; | |
362 | + struct snd_rpi_wm8804_drvdata *drvdata = | |
363 | + (struct snd_rpi_wm8804_drvdata *) of_id->data; | |
364 | + struct snd_soc_dai_link *dai = drvdata->dai; | |
365 | + | |
366 | + snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata); | |
367 | + | |
368 | + if (!dai->ops) | |
369 | + dai->ops = &snd_rpi_wm8804_ops; | |
370 | + if (!dai->codecs->dai_name) | |
371 | + dai->codecs->dai_name = "wm8804-spdif"; | |
372 | + if (!dai->codecs->name) | |
373 | + dai->codecs->name = "wm8804.1-003b"; | |
374 | + if (!dai->dai_fmt) | |
375 | + dai->dai_fmt = SND_SOC_DAIFMT_I2S | | |
376 | + SND_SOC_DAIFMT_NB_NF | | |
377 | + SND_SOC_DAIFMT_CBM_CFM; | |
378 | + | |
379 | + snd_rpi_wm8804.dai_link = dai; | |
380 | + i2s_node = of_parse_phandle(pdev->dev.of_node, | |
381 | + "i2s-controller", 0); | |
382 | + if (!i2s_node) { | |
383 | + pr_err("Failed to find i2s-controller DT node\n"); | |
384 | + return -ENODEV; | |
385 | + } | |
386 | + | |
387 | + snd_rpi_wm8804.name = drvdata->card_name; | |
388 | + | |
389 | + /* If requested by in drvdata get card & DAI names from DT */ | |
390 | + if (drvdata->card_name_dt) | |
391 | + of_property_read_string(i2s_node, | |
392 | + drvdata->card_name_dt, | |
393 | + &snd_rpi_wm8804.name); | |
394 | + | |
395 | + if (drvdata->dai_name_dt) | |
396 | + of_property_read_string(i2s_node, | |
397 | + drvdata->dai_name_dt, | |
398 | + &dai->name); | |
399 | + | |
400 | + if (drvdata->dai_stream_name_dt) | |
401 | + of_property_read_string(i2s_node, | |
402 | + drvdata->dai_stream_name_dt, | |
403 | + &dai->stream_name); | |
404 | + | |
405 | + dai->cpus->of_node = i2s_node; | |
406 | + dai->platforms->of_node = i2s_node; | |
407 | + | |
408 | + /* | |
409 | + * clk44gpio and clk48gpio are not required by all cards so | |
410 | + * don't check the error status. | |
411 | + */ | |
412 | + snd_clk44gpio = | |
413 | + devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW); | |
414 | + | |
415 | + snd_clk48gpio = | |
416 | + devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW); | |
417 | + | |
418 | + if (drvdata->probe) { | |
419 | + ret = drvdata->probe(pdev); | |
420 | + if (ret < 0) { | |
421 | + dev_err(&pdev->dev, "Custom probe failed %d\n", | |
422 | + ret); | |
423 | + return ret; | |
424 | + } | |
425 | + } | |
426 | + | |
427 | + pr_debug("%s card: %s dai: %s stream: %s\n", __func__, | |
428 | + snd_rpi_wm8804.name, | |
429 | + dai->name, dai->stream_name); | |
430 | + } | |
431 | + | |
432 | + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804); | |
433 | + if (ret && ret != -EPROBE_DEFER) | |
434 | + dev_err(&pdev->dev, "Failed to register card %d\n", ret); | |
435 | + | |
436 | + return ret; | |
437 | +} | |
438 | + | |
439 | +static struct platform_driver snd_rpi_wm8804_driver = { | |
440 | + .driver = { | |
441 | + .name = "snd-rpi-wm8804", | |
442 | + .owner = THIS_MODULE, | |
443 | + .of_match_table = snd_rpi_wm8804_of_match, | |
444 | + }, | |
445 | + .probe = snd_rpi_wm8804_probe, | |
446 | +}; | |
447 | +MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match); | |
448 | + | |
449 | +module_platform_driver(snd_rpi_wm8804_driver); | |
450 | + | |
451 | +MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>"); | |
452 | +MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards"); | |
453 | +MODULE_LICENSE("GPL v2"); | |
454 | --- linux-5.15.orig/sound/soc/bcm/rpi-simple-soundcard.c 1970-01-01 01:00:00.000000000 +0100 | |
455 | +++ linux-5.15/sound/soc/bcm/rpi-simple-soundcard.c 2021-12-23 14:16:38.688758934 +0100 | |
456 | @@ -0,0 +1,419 @@ | |
457 | +// SPDX-License-Identifier: GPL-2.0 | |
458 | +/* | |
459 | + * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard. | |
460 | + * | |
461 | + * Copyright (C) 2018 Raspberry Pi. | |
462 | + * | |
463 | + * Authors: Tim Gover <tim.gover@raspberrypi.org> | |
464 | + * | |
465 | + * Based on code: | |
466 | + * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c | |
467 | + * by Florian Meier <florian.meier@koalo.de> | |
468 | + * | |
469 | + * googlevoicehat-soundcard.c | |
470 | + * by Peter Malkin <petermalkin@google.com> | |
471 | + * | |
472 | + * adau1977-adc.c | |
473 | + * by Andrey Grodzovsky <andrey2805@gmail.com> | |
474 | + * | |
475 | + * merus-amp.c | |
476 | + * by Ariel Muszkat <ariel.muszkat@gmail.com> | |
477 | + * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com> | |
478 | + * | |
479 | + * This program is free software; you can redistribute it and/or | |
480 | + * modify it under the terms of the GNU General Public License | |
481 | + * version 2 as published by the Free Software Foundation. | |
482 | + * | |
483 | + * This program is distributed in the hope that it will be useful, but | |
484 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | |
485 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
486 | + * General Public License for more details. | |
487 | + */ | |
488 | + | |
489 | +#include <linux/module.h> | |
490 | +#include <linux/platform_device.h> | |
491 | +#include <linux/gpio/consumer.h> | |
492 | + | |
493 | +#include <sound/core.h> | |
494 | +#include <sound/pcm.h> | |
495 | +#include <sound/pcm_params.h> | |
496 | +#include <sound/soc.h> | |
497 | + | |
498 | +/* Parameters for generic RPI functions */ | |
499 | +struct snd_rpi_simple_drvdata { | |
500 | + struct snd_soc_dai_link *dai; | |
501 | + const char* card_name; | |
502 | + unsigned int fixed_bclk_ratio; | |
503 | +}; | |
504 | + | |
505 | +static struct snd_soc_card snd_rpi_simple = { | |
506 | + .driver_name = "RPi-simple", | |
507 | + .owner = THIS_MODULE, | |
508 | + .dai_link = NULL, | |
509 | + .num_links = 1, /* Only a single DAI supported at the moment */ | |
510 | +}; | |
511 | + | |
512 | +static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd) | |
513 | +{ | |
514 | + struct snd_rpi_simple_drvdata *drvdata = | |
515 | + snd_soc_card_get_drvdata(rtd->card); | |
516 | + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); | |
517 | + | |
518 | + if (drvdata->fixed_bclk_ratio > 0) | |
519 | + return snd_soc_dai_set_bclk_ratio(cpu_dai, | |
520 | + drvdata->fixed_bclk_ratio); | |
521 | + | |
522 | + return 0; | |
523 | +} | |
524 | + | |
525 | +static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd) | |
526 | +{ | |
527 | + struct snd_soc_component *dac; | |
528 | + struct gpio_desc *pdn_gpio, *rst_gpio; | |
529 | + struct snd_soc_dai *codec_dai; | |
530 | + int ret; | |
531 | + | |
532 | + snd_rpi_simple_init(rtd); | |
533 | + codec_dai = asoc_rtd_to_codec(rtd, 0); | |
534 | + | |
535 | + dac = codec_dai[0].component; | |
536 | + | |
537 | + pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn", | |
538 | + GPIOD_OUT_LOW); | |
539 | + if (IS_ERR(pdn_gpio)) { | |
540 | + ret = PTR_ERR(pdn_gpio); | |
541 | + dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret); | |
542 | + return ret; | |
543 | + } | |
544 | + | |
545 | + rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst", | |
546 | + GPIOD_OUT_LOW); | |
547 | + if (IS_ERR(rst_gpio)) { | |
548 | + ret = PTR_ERR(rst_gpio); | |
549 | + dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret); | |
550 | + return ret; | |
551 | + } | |
552 | + | |
553 | + // Set up cards - pulse power down and reset first, then | |
554 | + // set up according to datasheet | |
555 | + gpiod_set_value_cansleep(pdn_gpio, 1); | |
556 | + gpiod_set_value_cansleep(rst_gpio, 1); | |
557 | + usleep_range(1000, 10000); | |
558 | + gpiod_set_value_cansleep(pdn_gpio, 0); | |
559 | + usleep_range(20000, 30000); | |
560 | + gpiod_set_value_cansleep(rst_gpio, 0); | |
561 | + usleep_range(20000, 30000); | |
562 | + | |
563 | + // Oscillator trim | |
564 | + snd_soc_component_write(dac, 0x1b, 0); | |
565 | + usleep_range(60000, 80000); | |
566 | + | |
567 | + // MCLK at 64fs, sample rate 44.1 or 48kHz | |
568 | + snd_soc_component_write(dac, 0x00, 0x60); | |
569 | + | |
570 | + // Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772 | |
571 | + snd_soc_component_write(dac, 0x20, 0x00107772); | |
572 | + | |
573 | + // End mute | |
574 | + snd_soc_component_write(dac, 0x05, 0x00); | |
575 | + | |
576 | + return 0; | |
577 | +} | |
578 | + | |
579 | +static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream, | |
580 | + struct snd_pcm_hw_params *params) | |
581 | +{ | |
582 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
583 | + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); | |
584 | + struct snd_rpi_simple_drvdata *drvdata; | |
585 | + unsigned int sample_bits; | |
586 | + | |
587 | + drvdata = snd_soc_card_get_drvdata(rtd->card); | |
588 | + | |
589 | + if (drvdata->fixed_bclk_ratio > 0) | |
590 | + return 0; // BCLK is configured in .init | |
591 | + | |
592 | + /* The simple drivers just set the bclk_ratio to sample_bits * 2 so | |
593 | + * hard-code this for now. More complex drivers could just replace | |
594 | + * the hw_params routine. | |
595 | + */ | |
596 | + sample_bits = snd_pcm_format_physical_width(params_format(params)); | |
597 | + return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2); | |
598 | +} | |
599 | + | |
600 | +static struct snd_soc_ops snd_rpi_simple_ops = { | |
601 | + .hw_params = snd_rpi_simple_hw_params, | |
602 | +}; | |
603 | + | |
604 | +enum adau1977_clk_id { | |
605 | + ADAU1977_SYSCLK, | |
606 | +}; | |
607 | + | |
608 | +enum adau1977_sysclk_src { | |
609 | + ADAU1977_SYSCLK_SRC_MCLK, | |
610 | + ADAU1977_SYSCLK_SRC_LRCLK, | |
611 | +}; | |
612 | + | |
613 | +static int adau1977_init(struct snd_soc_pcm_runtime *rtd) | |
614 | +{ | |
615 | + int ret; | |
616 | + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); | |
617 | + | |
618 | + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); | |
619 | + if (ret < 0) | |
620 | + return ret; | |
621 | + | |
622 | + return snd_soc_component_set_sysclk(codec_dai->component, | |
623 | + ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK, | |
624 | + 11289600, SND_SOC_CLOCK_IN); | |
625 | +} | |
626 | + | |
627 | +SND_SOC_DAILINK_DEFS(adau1977, | |
628 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
629 | + DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")), | |
630 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
631 | + | |
632 | +static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = { | |
633 | + { | |
634 | + .name = "adau1977", | |
635 | + .stream_name = "ADAU1977", | |
636 | + .init = adau1977_init, | |
637 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
638 | + SND_SOC_DAIFMT_NB_NF | | |
639 | + SND_SOC_DAIFMT_CBM_CFM, | |
640 | + SND_SOC_DAILINK_REG(adau1977), | |
641 | + }, | |
642 | +}; | |
643 | + | |
644 | +static struct snd_rpi_simple_drvdata drvdata_adau1977 = { | |
645 | + .card_name = "snd_rpi_adau1977_adc", | |
646 | + .dai = snd_rpi_adau1977_dai, | |
647 | +}; | |
648 | + | |
649 | +SND_SOC_DAILINK_DEFS(gvchat, | |
650 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
651 | + DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")), | |
652 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
653 | + | |
654 | +static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = { | |
655 | +{ | |
656 | + .name = "Google voiceHAT SoundCard", | |
657 | + .stream_name = "Google voiceHAT SoundCard HiFi", | |
658 | + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | | |
659 | + SND_SOC_DAIFMT_CBS_CFS, | |
660 | + SND_SOC_DAILINK_REG(gvchat), | |
661 | +}, | |
662 | +}; | |
663 | + | |
664 | +static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = { | |
665 | + .card_name = "snd_rpi_googlevoicehat_soundcard", | |
666 | + .dai = snd_googlevoicehat_soundcard_dai, | |
667 | +}; | |
668 | + | |
669 | +SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp, | |
670 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
671 | + DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")), | |
672 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
673 | + | |
674 | +static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = { | |
675 | +{ | |
676 | + .name = "Hifiberry DAC+DSP SoundCard", | |
677 | + .stream_name = "Hifiberry DAC+DSP SoundCard HiFi", | |
678 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
679 | + SND_SOC_DAIFMT_NB_NF | | |
680 | + SND_SOC_DAIFMT_CBS_CFS, | |
681 | + SND_SOC_DAILINK_REG(hifiberry_dacplusdsp), | |
682 | +}, | |
683 | +}; | |
684 | + | |
685 | +static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = { | |
686 | + .card_name = "snd_rpi_hifiberrydacplusdsp_soundcard", | |
687 | + .dai = snd_hifiberrydacplusdsp_soundcard_dai, | |
688 | +}; | |
689 | + | |
690 | +SND_SOC_DAILINK_DEFS(hifiberry_amp, | |
691 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
692 | + DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")), | |
693 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
694 | + | |
695 | +static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = { | |
696 | + { | |
697 | + .name = "HifiBerry AMP", | |
698 | + .stream_name = "HifiBerry AMP HiFi", | |
699 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
700 | + SND_SOC_DAIFMT_NB_NF | | |
701 | + SND_SOC_DAIFMT_CBS_CFS, | |
702 | + SND_SOC_DAILINK_REG(hifiberry_amp), | |
703 | + }, | |
704 | +}; | |
705 | + | |
706 | +static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = { | |
707 | + .card_name = "snd_rpi_hifiberry_amp", | |
708 | + .dai = snd_hifiberry_amp_dai, | |
709 | + .fixed_bclk_ratio = 64, | |
710 | +}; | |
711 | + | |
712 | +SND_SOC_DAILINK_DEFS(hifiberry_dac, | |
713 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
714 | + DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")), | |
715 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
716 | + | |
717 | +static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = { | |
718 | + { | |
719 | + .name = "HifiBerry DAC", | |
720 | + .stream_name = "HifiBerry DAC HiFi", | |
721 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
722 | + SND_SOC_DAIFMT_NB_NF | | |
723 | + SND_SOC_DAIFMT_CBS_CFS, | |
724 | + SND_SOC_DAILINK_REG(hifiberry_dac), | |
725 | + }, | |
726 | +}; | |
727 | + | |
728 | +static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = { | |
729 | + .card_name = "snd_rpi_hifiberry_dac", | |
730 | + .dai = snd_hifiberry_dac_dai, | |
731 | +}; | |
732 | + | |
733 | +SND_SOC_DAILINK_DEFS(rpi_dac, | |
734 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
735 | + DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")), | |
736 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
737 | + | |
738 | +static struct snd_soc_dai_link snd_rpi_dac_dai[] = { | |
739 | +{ | |
740 | + .name = "RPi-DAC", | |
741 | + .stream_name = "RPi-DAC HiFi", | |
742 | + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | | |
743 | + SND_SOC_DAIFMT_CBS_CFS, | |
744 | + SND_SOC_DAILINK_REG(rpi_dac), | |
745 | +}, | |
746 | +}; | |
747 | + | |
748 | +static struct snd_rpi_simple_drvdata drvdata_rpi_dac = { | |
749 | + .card_name = "snd_rpi_rpi_dac", | |
750 | + .dai = snd_rpi_dac_dai, | |
751 | + .fixed_bclk_ratio = 64, | |
752 | +}; | |
753 | + | |
754 | +SND_SOC_DAILINK_DEFS(merus_amp, | |
755 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
756 | + DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020","ma120x0p-amp")), | |
757 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
758 | + | |
759 | +static struct snd_soc_dai_link snd_merus_amp_dai[] = { | |
760 | + { | |
761 | + .name = "MerusAmp", | |
762 | + .stream_name = "Merus Audio Amp", | |
763 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
764 | + SND_SOC_DAIFMT_NB_NF | | |
765 | + SND_SOC_DAIFMT_CBS_CFS, | |
766 | + SND_SOC_DAILINK_REG(merus_amp), | |
767 | + }, | |
768 | +}; | |
769 | + | |
770 | +static struct snd_rpi_simple_drvdata drvdata_merus_amp = { | |
771 | + .card_name = "snd_rpi_merus_amp", | |
772 | + .dai = snd_merus_amp_dai, | |
773 | + .fixed_bclk_ratio = 64, | |
774 | +}; | |
775 | + | |
776 | +SND_SOC_DAILINK_DEFS(pifi_mini_210, | |
777 | + DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
778 | + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")), | |
779 | + DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
780 | + | |
781 | +static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = { | |
782 | + { | |
783 | + .name = "PiFi Mini 210", | |
784 | + .stream_name = "PiFi Mini 210 HiFi", | |
785 | + .init = pifi_mini_210_init, | |
786 | + .dai_fmt = SND_SOC_DAIFMT_I2S | | |
787 | + SND_SOC_DAIFMT_NB_NF | | |
788 | + SND_SOC_DAIFMT_CBS_CFS, | |
789 | + SND_SOC_DAILINK_REG(pifi_mini_210), | |
790 | + }, | |
791 | +}; | |
792 | + | |
793 | +static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = { | |
794 | + .card_name = "snd_pifi_mini_210", | |
795 | + .dai = snd_pifi_mini_210_dai, | |
796 | + .fixed_bclk_ratio = 64, | |
797 | +}; | |
798 | + | |
799 | +static const struct of_device_id snd_rpi_simple_of_match[] = { | |
800 | + { .compatible = "adi,adau1977-adc", | |
801 | + .data = (void *) &drvdata_adau1977 }, | |
802 | + { .compatible = "googlevoicehat,googlevoicehat-soundcard", | |
803 | + .data = (void *) &drvdata_googlevoicehat }, | |
804 | + { .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard", | |
805 | + .data = (void *) &drvdata_hifiberrydacplusdsp }, | |
806 | + { .compatible = "hifiberry,hifiberry-amp", | |
807 | + .data = (void *) &drvdata_hifiberry_amp }, | |
808 | + { .compatible = "hifiberry,hifiberry-dac", | |
809 | + .data = (void *) &drvdata_hifiberry_dac }, | |
810 | + { .compatible = "rpi,rpi-dac", &drvdata_rpi_dac}, | |
811 | + { .compatible = "merus,merus-amp", | |
812 | + .data = (void *) &drvdata_merus_amp }, | |
813 | + { .compatible = "pifi,pifi-mini-210", | |
814 | + .data = (void *) &drvdata_pifi_mini_210 }, | |
815 | + {}, | |
816 | +}; | |
817 | + | |
818 | +static int snd_rpi_simple_probe(struct platform_device *pdev) | |
819 | +{ | |
820 | + int ret = 0; | |
821 | + const struct of_device_id *of_id; | |
822 | + | |
823 | + snd_rpi_simple.dev = &pdev->dev; | |
824 | + of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node); | |
825 | + | |
826 | + if (pdev->dev.of_node && of_id->data) { | |
827 | + struct device_node *i2s_node; | |
828 | + struct snd_rpi_simple_drvdata *drvdata = | |
829 | + (struct snd_rpi_simple_drvdata *) of_id->data; | |
830 | + struct snd_soc_dai_link *dai = drvdata->dai; | |
831 | + | |
832 | + snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata); | |
833 | + | |
834 | + /* More complex drivers might override individual functions */ | |
835 | + if (!dai->init) | |
836 | + dai->init = snd_rpi_simple_init; | |
837 | + if (!dai->ops) | |
838 | + dai->ops = &snd_rpi_simple_ops; | |
839 | + | |
840 | + snd_rpi_simple.name = drvdata->card_name; | |
841 | + | |
842 | + snd_rpi_simple.dai_link = dai; | |
843 | + i2s_node = of_parse_phandle(pdev->dev.of_node, | |
844 | + "i2s-controller", 0); | |
845 | + if (!i2s_node) { | |
846 | + pr_err("Failed to find i2s-controller DT node\n"); | |
847 | + return -ENODEV; | |
848 | + } | |
849 | + | |
850 | + dai->cpus->of_node = i2s_node; | |
851 | + dai->platforms->of_node = i2s_node; | |
852 | + } | |
853 | + | |
854 | + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple); | |
855 | + if (ret && ret != -EPROBE_DEFER) | |
856 | + dev_err(&pdev->dev, "Failed to register card %d\n", ret); | |
857 | + | |
858 | + return ret; | |
859 | +} | |
860 | + | |
861 | +static struct platform_driver snd_rpi_simple_driver = { | |
862 | + .driver = { | |
863 | + .name = "snd-rpi-simple", | |
864 | + .owner = THIS_MODULE, | |
865 | + .of_match_table = snd_rpi_simple_of_match, | |
866 | + }, | |
867 | + .probe = snd_rpi_simple_probe, | |
868 | +}; | |
869 | +MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match); | |
870 | + | |
871 | +module_platform_driver(snd_rpi_simple_driver); | |
872 | + | |
873 | +MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>"); | |
874 | +MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver "); | |
875 | +MODULE_LICENSE("GPL v2"); |