1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright (c) 2018-2020, Intel Corporation
4 // sof-wm8804.c - ASoC machine driver for Up and Up2 board
5 // based on WM8804/Hifiberry Digi+
8 #include <linux/acpi.h>
10 #include <linux/gpio/consumer.h>
11 #include <linux/gpio/machine.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/slab.h>
15 #include <sound/pcm.h>
16 #include <sound/pcm_params.h>
17 #include <sound/soc.h>
18 #include <sound/soc-acpi.h>
19 #include "../../codecs/wm8804.h"
21 struct sof_card_private
{
22 struct gpio_desc
*gpio_44
;
23 struct gpio_desc
*gpio_48
;
27 #define SOF_WM8804_UP2_QUIRK BIT(0)
29 static unsigned long sof_wm8804_quirk
;
31 static int sof_wm8804_quirk_cb(const struct dmi_system_id
*id
)
33 sof_wm8804_quirk
= (unsigned long)id
->driver_data
;
37 static const struct dmi_system_id sof_wm8804_quirk_table
[] = {
39 .callback
= sof_wm8804_quirk_cb
,
41 DMI_MATCH(DMI_SYS_VENDOR
, "AAEON"),
42 DMI_MATCH(DMI_PRODUCT_NAME
, "UP-APL01"),
44 .driver_data
= (void *)SOF_WM8804_UP2_QUIRK
,
49 static int sof_wm8804_hw_params(struct snd_pcm_substream
*substream
,
50 struct snd_pcm_hw_params
*params
)
52 struct snd_soc_pcm_runtime
*rtd
= asoc_substream_to_rtd(substream
);
53 struct sof_card_private
*ctx
= snd_soc_card_get_drvdata(rtd
->card
);
54 struct snd_soc_dai
*codec_dai
= asoc_rtd_to_codec(rtd
, 0);
55 struct snd_soc_component
*codec
= codec_dai
->component
;
56 const int sysclk
= 27000000; /* This is fixed on this board */
64 samplerate
= params_rate(params
);
65 if (samplerate
== ctx
->sample_rate
)
70 if (samplerate
<= 96000) {
71 mclk_freq
= samplerate
* 256;
72 mclk_div
= WM8804_MCLKDIV_256FS
;
74 mclk_freq
= samplerate
* 128;
75 mclk_div
= WM8804_MCLKDIV_128FS
;
101 dev_err(rtd
->card
->dev
,
102 "unsupported samplerate %d\n", samplerate
);
106 if (samplerate
% 16000)
107 clk_44
= true; /* use 44.1 kHz root frequency */
111 if (!(IS_ERR_OR_NULL(ctx
->gpio_44
) ||
112 IS_ERR_OR_NULL(ctx
->gpio_48
))) {
114 * ensure both GPIOs are LOW first, then drive the
115 * relevant one to HIGH
118 gpiod_set_value_cansleep(ctx
->gpio_48
, !clk_44
);
119 gpiod_set_value_cansleep(ctx
->gpio_44
, clk_44
);
121 gpiod_set_value_cansleep(ctx
->gpio_44
, clk_44
);
122 gpiod_set_value_cansleep(ctx
->gpio_48
, !clk_44
);
126 snd_soc_dai_set_clkdiv(codec_dai
, WM8804_MCLK_DIV
, mclk_div
);
127 snd_soc_dai_set_pll(codec_dai
, 0, 0, sysclk
, mclk_freq
);
129 ret
= snd_soc_dai_set_sysclk(codec_dai
, WM8804_TX_CLKSRC_PLL
,
130 sysclk
, SND_SOC_CLOCK_OUT
);
132 dev_err(rtd
->card
->dev
,
133 "Failed to set WM8804 SYSCLK: %d\n", ret
);
137 /* set sampling frequency status bits */
138 snd_soc_component_update_bits(codec
, WM8804_SPDTX4
, 0x0f,
141 ctx
->sample_rate
= samplerate
;
146 /* machine stream operations */
147 static struct snd_soc_ops sof_wm8804_ops
= {
148 .hw_params
= sof_wm8804_hw_params
,
151 SND_SOC_DAILINK_DEF(ssp5_pin
,
152 DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));
154 SND_SOC_DAILINK_DEF(ssp5_codec
,
155 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif")));
157 SND_SOC_DAILINK_DEF(platform
,
158 DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
160 static struct snd_soc_dai_link dailink
[] = {
163 .name
= "SSP5-Codec",
169 .ops
= &sof_wm8804_ops
,
170 SND_SOC_DAILINK_REG(ssp5_pin
, ssp5_codec
, platform
),
175 static struct snd_soc_card sof_wm8804_card
= {
176 .name
= "wm8804", /* sof- prefix added automatically */
177 .owner
= THIS_MODULE
,
179 .num_links
= ARRAY_SIZE(dailink
),
182 /* i2c-<HID>:00 with HID being 8 chars */
183 static char codec_name
[SND_ACPI_I2C_ID_LEN
];
186 * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to
187 * select the clock source. On the Up2 board, this means
188 * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404.
190 * Using the ACPI device name is not very nice, but since we only use
191 * the value for the Up2 board there is no risk of conflict with other
195 static struct gpiod_lookup_table up2_gpios_table
= {
196 /* .dev_id is set during probe */
198 GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH
),
199 GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH
),
204 static int sof_wm8804_probe(struct platform_device
*pdev
)
206 struct snd_soc_card
*card
;
207 struct snd_soc_acpi_mach
*mach
;
208 struct sof_card_private
*ctx
;
209 struct acpi_device
*adev
;
214 ctx
= devm_kzalloc(&pdev
->dev
, sizeof(*ctx
), GFP_KERNEL
);
218 mach
= pdev
->dev
.platform_data
;
219 card
= &sof_wm8804_card
;
220 card
->dev
= &pdev
->dev
;
222 dmi_check_system(sof_wm8804_quirk_table
);
224 if (sof_wm8804_quirk
& SOF_WM8804_UP2_QUIRK
) {
225 up2_gpios_table
.dev_id
= dev_name(&pdev
->dev
);
226 gpiod_add_lookup_table(&up2_gpios_table
);
229 * The gpios are required for specific boards with
230 * local oscillators, and optional in other cases.
231 * Since we can't identify when they are needed, use
232 * the GPIO as non-optional
235 ctx
->gpio_44
= devm_gpiod_get(&pdev
->dev
, "BCM-GPIO5",
237 if (IS_ERR(ctx
->gpio_44
)) {
238 ret
= PTR_ERR(ctx
->gpio_44
);
240 "could not get BCM-GPIO5: %d\n",
245 ctx
->gpio_48
= devm_gpiod_get(&pdev
->dev
, "BCM-GPIO6",
247 if (IS_ERR(ctx
->gpio_48
)) {
248 ret
= PTR_ERR(ctx
->gpio_48
);
250 "could not get BCM-GPIO6: %d\n",
256 /* fix index of codec dai */
257 for (i
= 0; i
< ARRAY_SIZE(dailink
); i
++) {
258 if (!strcmp(dailink
[i
].codecs
->name
, "i2c-1AEC8804:00")) {
264 /* fixup codec name based on HID */
265 adev
= acpi_dev_get_first_match_dev(mach
->id
, NULL
, -1);
267 snprintf(codec_name
, sizeof(codec_name
),
268 "%s%s", "i2c-", acpi_dev_name(adev
));
269 put_device(&adev
->dev
);
270 dailink
[dai_index
].codecs
->name
= codec_name
;
273 snd_soc_card_set_drvdata(card
, ctx
);
275 return devm_snd_soc_register_card(&pdev
->dev
, card
);
278 static int sof_wm8804_remove(struct platform_device
*pdev
)
280 if (sof_wm8804_quirk
& SOF_WM8804_UP2_QUIRK
)
281 gpiod_remove_lookup_table(&up2_gpios_table
);
285 static struct platform_driver sof_wm8804_driver
= {
287 .name
= "sof-wm8804",
288 .pm
= &snd_soc_pm_ops
,
290 .probe
= sof_wm8804_probe
,
291 .remove
= sof_wm8804_remove
,
293 module_platform_driver(sof_wm8804_driver
);
295 MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver");
296 MODULE_AUTHOR("Pierre-Louis Bossart");
297 MODULE_LICENSE("GPL v2");
298 MODULE_ALIAS("platform:sof-wm8804");