1f139546fSPierre-Louis Bossart // SPDX-License-Identifier: GPL-2.0-only 2f139546fSPierre-Louis Bossart // Copyright (c) 2018-2020, Intel Corporation 3f139546fSPierre-Louis Bossart // 4f139546fSPierre-Louis Bossart // sof-wm8804.c - ASoC machine driver for Up and Up2 board 5f139546fSPierre-Louis Bossart // based on WM8804/Hifiberry Digi+ 6f139546fSPierre-Louis Bossart 7f139546fSPierre-Louis Bossart 8f139546fSPierre-Louis Bossart #include <linux/acpi.h> 9f139546fSPierre-Louis Bossart #include <linux/dmi.h> 10f139546fSPierre-Louis Bossart #include <linux/gpio/consumer.h> 11f139546fSPierre-Louis Bossart #include <linux/gpio/machine.h> 12f139546fSPierre-Louis Bossart #include <linux/module.h> 13f139546fSPierre-Louis Bossart #include <linux/platform_device.h> 14f139546fSPierre-Louis Bossart #include <linux/slab.h> 15f139546fSPierre-Louis Bossart #include <sound/pcm.h> 16f139546fSPierre-Louis Bossart #include <sound/pcm_params.h> 17f139546fSPierre-Louis Bossart #include <sound/soc.h> 18f139546fSPierre-Louis Bossart #include <sound/soc-acpi.h> 19f139546fSPierre-Louis Bossart #include "../../codecs/wm8804.h" 20f139546fSPierre-Louis Bossart 21f139546fSPierre-Louis Bossart struct sof_card_private { 22f139546fSPierre-Louis Bossart struct gpio_desc *gpio_44; 23f139546fSPierre-Louis Bossart struct gpio_desc *gpio_48; 24f139546fSPierre-Louis Bossart int sample_rate; 25f139546fSPierre-Louis Bossart }; 26f139546fSPierre-Louis Bossart 27f139546fSPierre-Louis Bossart #define SOF_WM8804_UP2_QUIRK BIT(0) 28f139546fSPierre-Louis Bossart 29f139546fSPierre-Louis Bossart static unsigned long sof_wm8804_quirk; 30f139546fSPierre-Louis Bossart 31f139546fSPierre-Louis Bossart static int sof_wm8804_quirk_cb(const struct dmi_system_id *id) 32f139546fSPierre-Louis Bossart { 33f139546fSPierre-Louis Bossart sof_wm8804_quirk = (unsigned long)id->driver_data; 34f139546fSPierre-Louis Bossart return 1; 35f139546fSPierre-Louis Bossart } 36f139546fSPierre-Louis Bossart 37f139546fSPierre-Louis Bossart static const struct dmi_system_id sof_wm8804_quirk_table[] = { 38f139546fSPierre-Louis Bossart { 39f139546fSPierre-Louis Bossart .callback = sof_wm8804_quirk_cb, 40f139546fSPierre-Louis Bossart .matches = { 41f139546fSPierre-Louis Bossart DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), 42f139546fSPierre-Louis Bossart DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"), 43f139546fSPierre-Louis Bossart }, 44f139546fSPierre-Louis Bossart .driver_data = (void *)SOF_WM8804_UP2_QUIRK, 45f139546fSPierre-Louis Bossart }, 46f139546fSPierre-Louis Bossart {} 47f139546fSPierre-Louis Bossart }; 48f139546fSPierre-Louis Bossart 49f139546fSPierre-Louis Bossart static int sof_wm8804_hw_params(struct snd_pcm_substream *substream, 50f139546fSPierre-Louis Bossart struct snd_pcm_hw_params *params) 51f139546fSPierre-Louis Bossart { 52a2c1125eSKuninori Morimoto struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 53f139546fSPierre-Louis Bossart struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); 54a2c1125eSKuninori Morimoto struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 55f139546fSPierre-Louis Bossart struct snd_soc_component *codec = codec_dai->component; 56f139546fSPierre-Louis Bossart const int sysclk = 27000000; /* This is fixed on this board */ 57f139546fSPierre-Louis Bossart int samplerate; 58f139546fSPierre-Louis Bossart long mclk_freq; 59f139546fSPierre-Louis Bossart int mclk_div; 60f139546fSPierre-Louis Bossart int sampling_freq; 61f139546fSPierre-Louis Bossart bool clk_44; 62f139546fSPierre-Louis Bossart int ret; 63f139546fSPierre-Louis Bossart 64f139546fSPierre-Louis Bossart samplerate = params_rate(params); 65f139546fSPierre-Louis Bossart if (samplerate == ctx->sample_rate) 66f139546fSPierre-Louis Bossart return 0; 67f139546fSPierre-Louis Bossart 68f139546fSPierre-Louis Bossart ctx->sample_rate = 0; 69f139546fSPierre-Louis Bossart 70f139546fSPierre-Louis Bossart if (samplerate <= 96000) { 71f139546fSPierre-Louis Bossart mclk_freq = samplerate * 256; 72f139546fSPierre-Louis Bossart mclk_div = WM8804_MCLKDIV_256FS; 73f139546fSPierre-Louis Bossart } else { 74f139546fSPierre-Louis Bossart mclk_freq = samplerate * 128; 75f139546fSPierre-Louis Bossart mclk_div = WM8804_MCLKDIV_128FS; 76f139546fSPierre-Louis Bossart } 77f139546fSPierre-Louis Bossart 78f139546fSPierre-Louis Bossart switch (samplerate) { 79f139546fSPierre-Louis Bossart case 32000: 80f139546fSPierre-Louis Bossart sampling_freq = 0x03; 81f139546fSPierre-Louis Bossart break; 82f139546fSPierre-Louis Bossart case 44100: 83f139546fSPierre-Louis Bossart sampling_freq = 0x00; 84f139546fSPierre-Louis Bossart break; 85f139546fSPierre-Louis Bossart case 48000: 86f139546fSPierre-Louis Bossart sampling_freq = 0x02; 87f139546fSPierre-Louis Bossart break; 88f139546fSPierre-Louis Bossart case 88200: 89f139546fSPierre-Louis Bossart sampling_freq = 0x08; 90f139546fSPierre-Louis Bossart break; 91f139546fSPierre-Louis Bossart case 96000: 92f139546fSPierre-Louis Bossart sampling_freq = 0x0a; 93f139546fSPierre-Louis Bossart break; 94f139546fSPierre-Louis Bossart case 176400: 95f139546fSPierre-Louis Bossart sampling_freq = 0x0c; 96f139546fSPierre-Louis Bossart break; 97f139546fSPierre-Louis Bossart case 192000: 98f139546fSPierre-Louis Bossart sampling_freq = 0x0e; 99f139546fSPierre-Louis Bossart break; 100f139546fSPierre-Louis Bossart default: 101f139546fSPierre-Louis Bossart dev_err(rtd->card->dev, 102f139546fSPierre-Louis Bossart "unsupported samplerate %d\n", samplerate); 103f139546fSPierre-Louis Bossart return -EINVAL; 104f139546fSPierre-Louis Bossart } 105f139546fSPierre-Louis Bossart 106f139546fSPierre-Louis Bossart if (samplerate % 16000) 107f139546fSPierre-Louis Bossart clk_44 = true; /* use 44.1 kHz root frequency */ 108f139546fSPierre-Louis Bossart else 109f139546fSPierre-Louis Bossart clk_44 = false; 110f139546fSPierre-Louis Bossart 111f139546fSPierre-Louis Bossart if (!(IS_ERR_OR_NULL(ctx->gpio_44) || 112f139546fSPierre-Louis Bossart IS_ERR_OR_NULL(ctx->gpio_48))) { 113f139546fSPierre-Louis Bossart /* 114f139546fSPierre-Louis Bossart * ensure both GPIOs are LOW first, then drive the 115f139546fSPierre-Louis Bossart * relevant one to HIGH 116f139546fSPierre-Louis Bossart */ 117f139546fSPierre-Louis Bossart if (clk_44) { 118f139546fSPierre-Louis Bossart gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 119f139546fSPierre-Louis Bossart gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 120f139546fSPierre-Louis Bossart } else { 121f139546fSPierre-Louis Bossart gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 122f139546fSPierre-Louis Bossart gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 123f139546fSPierre-Louis Bossart } 124f139546fSPierre-Louis Bossart } 125f139546fSPierre-Louis Bossart 126f139546fSPierre-Louis Bossart snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); 1271730ef62SColin Ian King ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); 1281730ef62SColin Ian King if (ret < 0) { 1291730ef62SColin Ian King dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n"); 1301730ef62SColin Ian King return ret; 1311730ef62SColin Ian King } 132f139546fSPierre-Louis Bossart 133f139546fSPierre-Louis Bossart ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, 134f139546fSPierre-Louis Bossart sysclk, SND_SOC_CLOCK_OUT); 135f139546fSPierre-Louis Bossart if (ret < 0) { 136f139546fSPierre-Louis Bossart dev_err(rtd->card->dev, 137f139546fSPierre-Louis Bossart "Failed to set WM8804 SYSCLK: %d\n", ret); 138f139546fSPierre-Louis Bossart return ret; 139f139546fSPierre-Louis Bossart } 140f139546fSPierre-Louis Bossart 141f139546fSPierre-Louis Bossart /* set sampling frequency status bits */ 142f139546fSPierre-Louis Bossart snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f, 143f139546fSPierre-Louis Bossart sampling_freq); 144f139546fSPierre-Louis Bossart 145f139546fSPierre-Louis Bossart ctx->sample_rate = samplerate; 146f139546fSPierre-Louis Bossart 147f139546fSPierre-Louis Bossart return 0; 148f139546fSPierre-Louis Bossart } 149f139546fSPierre-Louis Bossart 150f139546fSPierre-Louis Bossart /* machine stream operations */ 151*ed258130SChristophe JAILLET static const struct snd_soc_ops sof_wm8804_ops = { 152f139546fSPierre-Louis Bossart .hw_params = sof_wm8804_hw_params, 153f139546fSPierre-Louis Bossart }; 154f139546fSPierre-Louis Bossart 155f139546fSPierre-Louis Bossart SND_SOC_DAILINK_DEF(ssp5_pin, 156f139546fSPierre-Louis Bossart DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin"))); 157f139546fSPierre-Louis Bossart 158f139546fSPierre-Louis Bossart SND_SOC_DAILINK_DEF(ssp5_codec, 159f139546fSPierre-Louis Bossart DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif"))); 160f139546fSPierre-Louis Bossart 161f139546fSPierre-Louis Bossart SND_SOC_DAILINK_DEF(platform, 162f139546fSPierre-Louis Bossart DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0"))); 163f139546fSPierre-Louis Bossart 164f139546fSPierre-Louis Bossart static struct snd_soc_dai_link dailink[] = { 165f139546fSPierre-Louis Bossart /* back ends */ 166f139546fSPierre-Louis Bossart { 167f139546fSPierre-Louis Bossart .name = "SSP5-Codec", 168f139546fSPierre-Louis Bossart .id = 0, 169f139546fSPierre-Louis Bossart .no_pcm = 1, 170f139546fSPierre-Louis Bossart .dpcm_playback = 1, 171f139546fSPierre-Louis Bossart .dpcm_capture = 1, 172f139546fSPierre-Louis Bossart .ops = &sof_wm8804_ops, 173f139546fSPierre-Louis Bossart SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform), 174f139546fSPierre-Louis Bossart }, 175f139546fSPierre-Louis Bossart }; 176f139546fSPierre-Louis Bossart 177f139546fSPierre-Louis Bossart /* SoC card */ 178f139546fSPierre-Louis Bossart static struct snd_soc_card sof_wm8804_card = { 179f139546fSPierre-Louis Bossart .name = "wm8804", /* sof- prefix added automatically */ 180f139546fSPierre-Louis Bossart .owner = THIS_MODULE, 181f139546fSPierre-Louis Bossart .dai_link = dailink, 182f139546fSPierre-Louis Bossart .num_links = ARRAY_SIZE(dailink), 183f139546fSPierre-Louis Bossart }; 184f139546fSPierre-Louis Bossart 185f139546fSPierre-Louis Bossart /* i2c-<HID>:00 with HID being 8 chars */ 186f139546fSPierre-Louis Bossart static char codec_name[SND_ACPI_I2C_ID_LEN]; 187f139546fSPierre-Louis Bossart 188f139546fSPierre-Louis Bossart /* 189f139546fSPierre-Louis Bossart * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to 190f139546fSPierre-Louis Bossart * select the clock source. On the Up2 board, this means 191f139546fSPierre-Louis Bossart * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404. 192f139546fSPierre-Louis Bossart * 193f139546fSPierre-Louis Bossart * Using the ACPI device name is not very nice, but since we only use 194f139546fSPierre-Louis Bossart * the value for the Up2 board there is no risk of conflict with other 195f139546fSPierre-Louis Bossart * platforms. 196f139546fSPierre-Louis Bossart */ 197f139546fSPierre-Louis Bossart 198f139546fSPierre-Louis Bossart static struct gpiod_lookup_table up2_gpios_table = { 199f139546fSPierre-Louis Bossart /* .dev_id is set during probe */ 200f139546fSPierre-Louis Bossart .table = { 201f139546fSPierre-Louis Bossart GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH), 202f139546fSPierre-Louis Bossart GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH), 203f139546fSPierre-Louis Bossart { }, 204f139546fSPierre-Louis Bossart }, 205f139546fSPierre-Louis Bossart }; 206f139546fSPierre-Louis Bossart 207f139546fSPierre-Louis Bossart static int sof_wm8804_probe(struct platform_device *pdev) 208f139546fSPierre-Louis Bossart { 209f139546fSPierre-Louis Bossart struct snd_soc_card *card; 210f139546fSPierre-Louis Bossart struct snd_soc_acpi_mach *mach; 211f139546fSPierre-Louis Bossart struct sof_card_private *ctx; 212f139546fSPierre-Louis Bossart struct acpi_device *adev; 213f139546fSPierre-Louis Bossart int dai_index = 0; 214f139546fSPierre-Louis Bossart int ret; 215f139546fSPierre-Louis Bossart int i; 216f139546fSPierre-Louis Bossart 217f139546fSPierre-Louis Bossart ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); 218f139546fSPierre-Louis Bossart if (!ctx) 219f139546fSPierre-Louis Bossart return -ENOMEM; 220f139546fSPierre-Louis Bossart 221f139546fSPierre-Louis Bossart mach = pdev->dev.platform_data; 222f139546fSPierre-Louis Bossart card = &sof_wm8804_card; 223f139546fSPierre-Louis Bossart card->dev = &pdev->dev; 224f139546fSPierre-Louis Bossart 225f139546fSPierre-Louis Bossart dmi_check_system(sof_wm8804_quirk_table); 226f139546fSPierre-Louis Bossart 227f139546fSPierre-Louis Bossart if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) { 228f139546fSPierre-Louis Bossart up2_gpios_table.dev_id = dev_name(&pdev->dev); 229f139546fSPierre-Louis Bossart gpiod_add_lookup_table(&up2_gpios_table); 230f139546fSPierre-Louis Bossart 231f139546fSPierre-Louis Bossart /* 232f139546fSPierre-Louis Bossart * The gpios are required for specific boards with 233f139546fSPierre-Louis Bossart * local oscillators, and optional in other cases. 234f139546fSPierre-Louis Bossart * Since we can't identify when they are needed, use 235f139546fSPierre-Louis Bossart * the GPIO as non-optional 236f139546fSPierre-Louis Bossart */ 237f139546fSPierre-Louis Bossart 238f139546fSPierre-Louis Bossart ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5", 239f139546fSPierre-Louis Bossart GPIOD_OUT_LOW); 240f139546fSPierre-Louis Bossart if (IS_ERR(ctx->gpio_44)) { 241f139546fSPierre-Louis Bossart ret = PTR_ERR(ctx->gpio_44); 242f139546fSPierre-Louis Bossart dev_err(&pdev->dev, 243f139546fSPierre-Louis Bossart "could not get BCM-GPIO5: %d\n", 244f139546fSPierre-Louis Bossart ret); 245f139546fSPierre-Louis Bossart return ret; 246f139546fSPierre-Louis Bossart } 247f139546fSPierre-Louis Bossart 248f139546fSPierre-Louis Bossart ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6", 249f139546fSPierre-Louis Bossart GPIOD_OUT_LOW); 250f139546fSPierre-Louis Bossart if (IS_ERR(ctx->gpio_48)) { 251f139546fSPierre-Louis Bossart ret = PTR_ERR(ctx->gpio_48); 252f139546fSPierre-Louis Bossart dev_err(&pdev->dev, 253f139546fSPierre-Louis Bossart "could not get BCM-GPIO6: %d\n", 254f139546fSPierre-Louis Bossart ret); 255f139546fSPierre-Louis Bossart return ret; 256f139546fSPierre-Louis Bossart } 257f139546fSPierre-Louis Bossart } 258f139546fSPierre-Louis Bossart 259f139546fSPierre-Louis Bossart /* fix index of codec dai */ 260f139546fSPierre-Louis Bossart for (i = 0; i < ARRAY_SIZE(dailink); i++) { 261f139546fSPierre-Louis Bossart if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) { 262f139546fSPierre-Louis Bossart dai_index = i; 263f139546fSPierre-Louis Bossart break; 264f139546fSPierre-Louis Bossart } 265f139546fSPierre-Louis Bossart } 266f139546fSPierre-Louis Bossart 267f139546fSPierre-Louis Bossart /* fixup codec name based on HID */ 268f139546fSPierre-Louis Bossart adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); 269f139546fSPierre-Louis Bossart if (adev) { 270f139546fSPierre-Louis Bossart snprintf(codec_name, sizeof(codec_name), 271f139546fSPierre-Louis Bossart "%s%s", "i2c-", acpi_dev_name(adev)); 272f139546fSPierre-Louis Bossart dailink[dai_index].codecs->name = codec_name; 273f139546fSPierre-Louis Bossart } 274892dbe0eSAndy Shevchenko acpi_dev_put(adev); 275f139546fSPierre-Louis Bossart 276f139546fSPierre-Louis Bossart snd_soc_card_set_drvdata(card, ctx); 277f139546fSPierre-Louis Bossart 278f139546fSPierre-Louis Bossart return devm_snd_soc_register_card(&pdev->dev, card); 279f139546fSPierre-Louis Bossart } 280f139546fSPierre-Louis Bossart 281674dd2c5SUwe Kleine-König static void sof_wm8804_remove(struct platform_device *pdev) 282f139546fSPierre-Louis Bossart { 283f139546fSPierre-Louis Bossart if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) 284f139546fSPierre-Louis Bossart gpiod_remove_lookup_table(&up2_gpios_table); 285f139546fSPierre-Louis Bossart } 286f139546fSPierre-Louis Bossart 287f139546fSPierre-Louis Bossart static struct platform_driver sof_wm8804_driver = { 288f139546fSPierre-Louis Bossart .driver = { 289f139546fSPierre-Louis Bossart .name = "sof-wm8804", 290f139546fSPierre-Louis Bossart .pm = &snd_soc_pm_ops, 291f139546fSPierre-Louis Bossart }, 292f139546fSPierre-Louis Bossart .probe = sof_wm8804_probe, 293674dd2c5SUwe Kleine-König .remove_new = sof_wm8804_remove, 294f139546fSPierre-Louis Bossart }; 295f139546fSPierre-Louis Bossart module_platform_driver(sof_wm8804_driver); 296f139546fSPierre-Louis Bossart 297f139546fSPierre-Louis Bossart MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver"); 298f139546fSPierre-Louis Bossart MODULE_AUTHOR("Pierre-Louis Bossart"); 299f139546fSPierre-Louis Bossart MODULE_LICENSE("GPL v2"); 300f139546fSPierre-Louis Bossart MODULE_ALIAS("platform:sof-wm8804"); 301