18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci// Copyright (c) 2018-2020, Intel Corporation 38c2ecf20Sopenharmony_ci// 48c2ecf20Sopenharmony_ci// sof-wm8804.c - ASoC machine driver for Up and Up2 board 58c2ecf20Sopenharmony_ci// based on WM8804/Hifiberry Digi+ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/acpi.h> 98c2ecf20Sopenharmony_ci#include <linux/dmi.h> 108c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 118c2ecf20Sopenharmony_ci#include <linux/gpio/machine.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <sound/pcm.h> 168c2ecf20Sopenharmony_ci#include <sound/pcm_params.h> 178c2ecf20Sopenharmony_ci#include <sound/soc.h> 188c2ecf20Sopenharmony_ci#include <sound/soc-acpi.h> 198c2ecf20Sopenharmony_ci#include "../../codecs/wm8804.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct sof_card_private { 228c2ecf20Sopenharmony_ci struct gpio_desc *gpio_44; 238c2ecf20Sopenharmony_ci struct gpio_desc *gpio_48; 248c2ecf20Sopenharmony_ci int sample_rate; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define SOF_WM8804_UP2_QUIRK BIT(0) 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic unsigned long sof_wm8804_quirk; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int sof_wm8804_quirk_cb(const struct dmi_system_id *id) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci sof_wm8804_quirk = (unsigned long)id->driver_data; 348c2ecf20Sopenharmony_ci return 1; 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic const struct dmi_system_id sof_wm8804_quirk_table[] = { 388c2ecf20Sopenharmony_ci { 398c2ecf20Sopenharmony_ci .callback = sof_wm8804_quirk_cb, 408c2ecf20Sopenharmony_ci .matches = { 418c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), 428c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"), 438c2ecf20Sopenharmony_ci }, 448c2ecf20Sopenharmony_ci .driver_data = (void *)SOF_WM8804_UP2_QUIRK, 458c2ecf20Sopenharmony_ci }, 468c2ecf20Sopenharmony_ci {} 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic int sof_wm8804_hw_params(struct snd_pcm_substream *substream, 508c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 538c2ecf20Sopenharmony_ci struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); 548c2ecf20Sopenharmony_ci struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 558c2ecf20Sopenharmony_ci struct snd_soc_component *codec = codec_dai->component; 568c2ecf20Sopenharmony_ci const int sysclk = 27000000; /* This is fixed on this board */ 578c2ecf20Sopenharmony_ci int samplerate; 588c2ecf20Sopenharmony_ci long mclk_freq; 598c2ecf20Sopenharmony_ci int mclk_div; 608c2ecf20Sopenharmony_ci int sampling_freq; 618c2ecf20Sopenharmony_ci bool clk_44; 628c2ecf20Sopenharmony_ci int ret; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci samplerate = params_rate(params); 658c2ecf20Sopenharmony_ci if (samplerate == ctx->sample_rate) 668c2ecf20Sopenharmony_ci return 0; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci ctx->sample_rate = 0; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci if (samplerate <= 96000) { 718c2ecf20Sopenharmony_ci mclk_freq = samplerate * 256; 728c2ecf20Sopenharmony_ci mclk_div = WM8804_MCLKDIV_256FS; 738c2ecf20Sopenharmony_ci } else { 748c2ecf20Sopenharmony_ci mclk_freq = samplerate * 128; 758c2ecf20Sopenharmony_ci mclk_div = WM8804_MCLKDIV_128FS; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci switch (samplerate) { 798c2ecf20Sopenharmony_ci case 32000: 808c2ecf20Sopenharmony_ci sampling_freq = 0x03; 818c2ecf20Sopenharmony_ci break; 828c2ecf20Sopenharmony_ci case 44100: 838c2ecf20Sopenharmony_ci sampling_freq = 0x00; 848c2ecf20Sopenharmony_ci break; 858c2ecf20Sopenharmony_ci case 48000: 868c2ecf20Sopenharmony_ci sampling_freq = 0x02; 878c2ecf20Sopenharmony_ci break; 888c2ecf20Sopenharmony_ci case 88200: 898c2ecf20Sopenharmony_ci sampling_freq = 0x08; 908c2ecf20Sopenharmony_ci break; 918c2ecf20Sopenharmony_ci case 96000: 928c2ecf20Sopenharmony_ci sampling_freq = 0x0a; 938c2ecf20Sopenharmony_ci break; 948c2ecf20Sopenharmony_ci case 176400: 958c2ecf20Sopenharmony_ci sampling_freq = 0x0c; 968c2ecf20Sopenharmony_ci break; 978c2ecf20Sopenharmony_ci case 192000: 988c2ecf20Sopenharmony_ci sampling_freq = 0x0e; 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci default: 1018c2ecf20Sopenharmony_ci dev_err(rtd->card->dev, 1028c2ecf20Sopenharmony_ci "unsupported samplerate %d\n", samplerate); 1038c2ecf20Sopenharmony_ci return -EINVAL; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (samplerate % 16000) 1078c2ecf20Sopenharmony_ci clk_44 = true; /* use 44.1 kHz root frequency */ 1088c2ecf20Sopenharmony_ci else 1098c2ecf20Sopenharmony_ci clk_44 = false; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (!(IS_ERR_OR_NULL(ctx->gpio_44) || 1128c2ecf20Sopenharmony_ci IS_ERR_OR_NULL(ctx->gpio_48))) { 1138c2ecf20Sopenharmony_ci /* 1148c2ecf20Sopenharmony_ci * ensure both GPIOs are LOW first, then drive the 1158c2ecf20Sopenharmony_ci * relevant one to HIGH 1168c2ecf20Sopenharmony_ci */ 1178c2ecf20Sopenharmony_ci if (clk_44) { 1188c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 1198c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 1208c2ecf20Sopenharmony_ci } else { 1218c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 1228c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); 1278c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); 1288c2ecf20Sopenharmony_ci if (ret < 0) { 1298c2ecf20Sopenharmony_ci dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n"); 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, 1348c2ecf20Sopenharmony_ci sysclk, SND_SOC_CLOCK_OUT); 1358c2ecf20Sopenharmony_ci if (ret < 0) { 1368c2ecf20Sopenharmony_ci dev_err(rtd->card->dev, 1378c2ecf20Sopenharmony_ci "Failed to set WM8804 SYSCLK: %d\n", ret); 1388c2ecf20Sopenharmony_ci return ret; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci /* set sampling frequency status bits */ 1428c2ecf20Sopenharmony_ci snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f, 1438c2ecf20Sopenharmony_ci sampling_freq); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci ctx->sample_rate = samplerate; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci return 0; 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci/* machine stream operations */ 1518c2ecf20Sopenharmony_cistatic struct snd_soc_ops sof_wm8804_ops = { 1528c2ecf20Sopenharmony_ci .hw_params = sof_wm8804_hw_params, 1538c2ecf20Sopenharmony_ci}; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEF(ssp5_pin, 1568c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin"))); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEF(ssp5_codec, 1598c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif"))); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ciSND_SOC_DAILINK_DEF(platform, 1628c2ecf20Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0"))); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic struct snd_soc_dai_link dailink[] = { 1658c2ecf20Sopenharmony_ci /* back ends */ 1668c2ecf20Sopenharmony_ci { 1678c2ecf20Sopenharmony_ci .name = "SSP5-Codec", 1688c2ecf20Sopenharmony_ci .id = 0, 1698c2ecf20Sopenharmony_ci .no_pcm = 1, 1708c2ecf20Sopenharmony_ci .nonatomic = true, 1718c2ecf20Sopenharmony_ci .dpcm_playback = 1, 1728c2ecf20Sopenharmony_ci .dpcm_capture = 1, 1738c2ecf20Sopenharmony_ci .ops = &sof_wm8804_ops, 1748c2ecf20Sopenharmony_ci SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform), 1758c2ecf20Sopenharmony_ci }, 1768c2ecf20Sopenharmony_ci}; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci/* SoC card */ 1798c2ecf20Sopenharmony_cistatic struct snd_soc_card sof_wm8804_card = { 1808c2ecf20Sopenharmony_ci .name = "wm8804", /* sof- prefix added automatically */ 1818c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1828c2ecf20Sopenharmony_ci .dai_link = dailink, 1838c2ecf20Sopenharmony_ci .num_links = ARRAY_SIZE(dailink), 1848c2ecf20Sopenharmony_ci}; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* i2c-<HID>:00 with HID being 8 chars */ 1878c2ecf20Sopenharmony_cistatic char codec_name[SND_ACPI_I2C_ID_LEN]; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci/* 1908c2ecf20Sopenharmony_ci * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to 1918c2ecf20Sopenharmony_ci * select the clock source. On the Up2 board, this means 1928c2ecf20Sopenharmony_ci * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404. 1938c2ecf20Sopenharmony_ci * 1948c2ecf20Sopenharmony_ci * Using the ACPI device name is not very nice, but since we only use 1958c2ecf20Sopenharmony_ci * the value for the Up2 board there is no risk of conflict with other 1968c2ecf20Sopenharmony_ci * platforms. 1978c2ecf20Sopenharmony_ci */ 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic struct gpiod_lookup_table up2_gpios_table = { 2008c2ecf20Sopenharmony_ci /* .dev_id is set during probe */ 2018c2ecf20Sopenharmony_ci .table = { 2028c2ecf20Sopenharmony_ci GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH), 2038c2ecf20Sopenharmony_ci GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH), 2048c2ecf20Sopenharmony_ci { }, 2058c2ecf20Sopenharmony_ci }, 2068c2ecf20Sopenharmony_ci}; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic int sof_wm8804_probe(struct platform_device *pdev) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci struct snd_soc_card *card; 2118c2ecf20Sopenharmony_ci struct snd_soc_acpi_mach *mach; 2128c2ecf20Sopenharmony_ci struct sof_card_private *ctx; 2138c2ecf20Sopenharmony_ci struct acpi_device *adev; 2148c2ecf20Sopenharmony_ci int dai_index = 0; 2158c2ecf20Sopenharmony_ci int ret; 2168c2ecf20Sopenharmony_ci int i; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); 2198c2ecf20Sopenharmony_ci if (!ctx) 2208c2ecf20Sopenharmony_ci return -ENOMEM; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci mach = pdev->dev.platform_data; 2238c2ecf20Sopenharmony_ci card = &sof_wm8804_card; 2248c2ecf20Sopenharmony_ci card->dev = &pdev->dev; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci dmi_check_system(sof_wm8804_quirk_table); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) { 2298c2ecf20Sopenharmony_ci up2_gpios_table.dev_id = dev_name(&pdev->dev); 2308c2ecf20Sopenharmony_ci gpiod_add_lookup_table(&up2_gpios_table); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* 2338c2ecf20Sopenharmony_ci * The gpios are required for specific boards with 2348c2ecf20Sopenharmony_ci * local oscillators, and optional in other cases. 2358c2ecf20Sopenharmony_ci * Since we can't identify when they are needed, use 2368c2ecf20Sopenharmony_ci * the GPIO as non-optional 2378c2ecf20Sopenharmony_ci */ 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5", 2408c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 2418c2ecf20Sopenharmony_ci if (IS_ERR(ctx->gpio_44)) { 2428c2ecf20Sopenharmony_ci ret = PTR_ERR(ctx->gpio_44); 2438c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 2448c2ecf20Sopenharmony_ci "could not get BCM-GPIO5: %d\n", 2458c2ecf20Sopenharmony_ci ret); 2468c2ecf20Sopenharmony_ci return ret; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6", 2508c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 2518c2ecf20Sopenharmony_ci if (IS_ERR(ctx->gpio_48)) { 2528c2ecf20Sopenharmony_ci ret = PTR_ERR(ctx->gpio_48); 2538c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 2548c2ecf20Sopenharmony_ci "could not get BCM-GPIO6: %d\n", 2558c2ecf20Sopenharmony_ci ret); 2568c2ecf20Sopenharmony_ci return ret; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* fix index of codec dai */ 2618c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dailink); i++) { 2628c2ecf20Sopenharmony_ci if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) { 2638c2ecf20Sopenharmony_ci dai_index = i; 2648c2ecf20Sopenharmony_ci break; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci /* fixup codec name based on HID */ 2698c2ecf20Sopenharmony_ci adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); 2708c2ecf20Sopenharmony_ci if (adev) { 2718c2ecf20Sopenharmony_ci snprintf(codec_name, sizeof(codec_name), 2728c2ecf20Sopenharmony_ci "%s%s", "i2c-", acpi_dev_name(adev)); 2738c2ecf20Sopenharmony_ci put_device(&adev->dev); 2748c2ecf20Sopenharmony_ci dailink[dai_index].codecs->name = codec_name; 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci snd_soc_card_set_drvdata(card, ctx); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return devm_snd_soc_register_card(&pdev->dev, card); 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_cistatic int sof_wm8804_remove(struct platform_device *pdev) 2838c2ecf20Sopenharmony_ci{ 2848c2ecf20Sopenharmony_ci if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) 2858c2ecf20Sopenharmony_ci gpiod_remove_lookup_table(&up2_gpios_table); 2868c2ecf20Sopenharmony_ci return 0; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic struct platform_driver sof_wm8804_driver = { 2908c2ecf20Sopenharmony_ci .driver = { 2918c2ecf20Sopenharmony_ci .name = "sof-wm8804", 2928c2ecf20Sopenharmony_ci .pm = &snd_soc_pm_ops, 2938c2ecf20Sopenharmony_ci }, 2948c2ecf20Sopenharmony_ci .probe = sof_wm8804_probe, 2958c2ecf20Sopenharmony_ci .remove = sof_wm8804_remove, 2968c2ecf20Sopenharmony_ci}; 2978c2ecf20Sopenharmony_cimodule_platform_driver(sof_wm8804_driver); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver"); 3008c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pierre-Louis Bossart"); 3018c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3028c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:sof-wm8804"); 303