162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci// Copyright (c) 2018-2020, Intel Corporation 362306a36Sopenharmony_ci// 462306a36Sopenharmony_ci// sof-wm8804.c - ASoC machine driver for Up and Up2 board 562306a36Sopenharmony_ci// based on WM8804/Hifiberry Digi+ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/acpi.h> 962306a36Sopenharmony_ci#include <linux/dmi.h> 1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1162306a36Sopenharmony_ci#include <linux/gpio/machine.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <sound/pcm.h> 1662306a36Sopenharmony_ci#include <sound/pcm_params.h> 1762306a36Sopenharmony_ci#include <sound/soc.h> 1862306a36Sopenharmony_ci#include <sound/soc-acpi.h> 1962306a36Sopenharmony_ci#include "../../codecs/wm8804.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct sof_card_private { 2262306a36Sopenharmony_ci struct gpio_desc *gpio_44; 2362306a36Sopenharmony_ci struct gpio_desc *gpio_48; 2462306a36Sopenharmony_ci int sample_rate; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define SOF_WM8804_UP2_QUIRK BIT(0) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic unsigned long sof_wm8804_quirk; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic int sof_wm8804_quirk_cb(const struct dmi_system_id *id) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci sof_wm8804_quirk = (unsigned long)id->driver_data; 3462306a36Sopenharmony_ci return 1; 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic const struct dmi_system_id sof_wm8804_quirk_table[] = { 3862306a36Sopenharmony_ci { 3962306a36Sopenharmony_ci .callback = sof_wm8804_quirk_cb, 4062306a36Sopenharmony_ci .matches = { 4162306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), 4262306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"), 4362306a36Sopenharmony_ci }, 4462306a36Sopenharmony_ci .driver_data = (void *)SOF_WM8804_UP2_QUIRK, 4562306a36Sopenharmony_ci }, 4662306a36Sopenharmony_ci {} 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic int sof_wm8804_hw_params(struct snd_pcm_substream *substream, 5062306a36Sopenharmony_ci struct snd_pcm_hw_params *params) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 5362306a36Sopenharmony_ci struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); 5462306a36Sopenharmony_ci struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 5562306a36Sopenharmony_ci struct snd_soc_component *codec = codec_dai->component; 5662306a36Sopenharmony_ci const int sysclk = 27000000; /* This is fixed on this board */ 5762306a36Sopenharmony_ci int samplerate; 5862306a36Sopenharmony_ci long mclk_freq; 5962306a36Sopenharmony_ci int mclk_div; 6062306a36Sopenharmony_ci int sampling_freq; 6162306a36Sopenharmony_ci bool clk_44; 6262306a36Sopenharmony_ci int ret; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci samplerate = params_rate(params); 6562306a36Sopenharmony_ci if (samplerate == ctx->sample_rate) 6662306a36Sopenharmony_ci return 0; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci ctx->sample_rate = 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (samplerate <= 96000) { 7162306a36Sopenharmony_ci mclk_freq = samplerate * 256; 7262306a36Sopenharmony_ci mclk_div = WM8804_MCLKDIV_256FS; 7362306a36Sopenharmony_ci } else { 7462306a36Sopenharmony_ci mclk_freq = samplerate * 128; 7562306a36Sopenharmony_ci mclk_div = WM8804_MCLKDIV_128FS; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci switch (samplerate) { 7962306a36Sopenharmony_ci case 32000: 8062306a36Sopenharmony_ci sampling_freq = 0x03; 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci case 44100: 8362306a36Sopenharmony_ci sampling_freq = 0x00; 8462306a36Sopenharmony_ci break; 8562306a36Sopenharmony_ci case 48000: 8662306a36Sopenharmony_ci sampling_freq = 0x02; 8762306a36Sopenharmony_ci break; 8862306a36Sopenharmony_ci case 88200: 8962306a36Sopenharmony_ci sampling_freq = 0x08; 9062306a36Sopenharmony_ci break; 9162306a36Sopenharmony_ci case 96000: 9262306a36Sopenharmony_ci sampling_freq = 0x0a; 9362306a36Sopenharmony_ci break; 9462306a36Sopenharmony_ci case 176400: 9562306a36Sopenharmony_ci sampling_freq = 0x0c; 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci case 192000: 9862306a36Sopenharmony_ci sampling_freq = 0x0e; 9962306a36Sopenharmony_ci break; 10062306a36Sopenharmony_ci default: 10162306a36Sopenharmony_ci dev_err(rtd->card->dev, 10262306a36Sopenharmony_ci "unsupported samplerate %d\n", samplerate); 10362306a36Sopenharmony_ci return -EINVAL; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (samplerate % 16000) 10762306a36Sopenharmony_ci clk_44 = true; /* use 44.1 kHz root frequency */ 10862306a36Sopenharmony_ci else 10962306a36Sopenharmony_ci clk_44 = false; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (!(IS_ERR_OR_NULL(ctx->gpio_44) || 11262306a36Sopenharmony_ci IS_ERR_OR_NULL(ctx->gpio_48))) { 11362306a36Sopenharmony_ci /* 11462306a36Sopenharmony_ci * ensure both GPIOs are LOW first, then drive the 11562306a36Sopenharmony_ci * relevant one to HIGH 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_ci if (clk_44) { 11862306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 11962306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 12062306a36Sopenharmony_ci } else { 12162306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_44, clk_44); 12262306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->gpio_48, !clk_44); 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); 12762306a36Sopenharmony_ci ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); 12862306a36Sopenharmony_ci if (ret < 0) { 12962306a36Sopenharmony_ci dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n"); 13062306a36Sopenharmony_ci return ret; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, 13462306a36Sopenharmony_ci sysclk, SND_SOC_CLOCK_OUT); 13562306a36Sopenharmony_ci if (ret < 0) { 13662306a36Sopenharmony_ci dev_err(rtd->card->dev, 13762306a36Sopenharmony_ci "Failed to set WM8804 SYSCLK: %d\n", ret); 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci /* set sampling frequency status bits */ 14262306a36Sopenharmony_ci snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f, 14362306a36Sopenharmony_ci sampling_freq); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci ctx->sample_rate = samplerate; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci return 0; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/* machine stream operations */ 15162306a36Sopenharmony_cistatic struct snd_soc_ops sof_wm8804_ops = { 15262306a36Sopenharmony_ci .hw_params = sof_wm8804_hw_params, 15362306a36Sopenharmony_ci}; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ciSND_SOC_DAILINK_DEF(ssp5_pin, 15662306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin"))); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ciSND_SOC_DAILINK_DEF(ssp5_codec, 15962306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif"))); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ciSND_SOC_DAILINK_DEF(platform, 16262306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0"))); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic struct snd_soc_dai_link dailink[] = { 16562306a36Sopenharmony_ci /* back ends */ 16662306a36Sopenharmony_ci { 16762306a36Sopenharmony_ci .name = "SSP5-Codec", 16862306a36Sopenharmony_ci .id = 0, 16962306a36Sopenharmony_ci .no_pcm = 1, 17062306a36Sopenharmony_ci .dpcm_playback = 1, 17162306a36Sopenharmony_ci .dpcm_capture = 1, 17262306a36Sopenharmony_ci .ops = &sof_wm8804_ops, 17362306a36Sopenharmony_ci SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform), 17462306a36Sopenharmony_ci }, 17562306a36Sopenharmony_ci}; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci/* SoC card */ 17862306a36Sopenharmony_cistatic struct snd_soc_card sof_wm8804_card = { 17962306a36Sopenharmony_ci .name = "wm8804", /* sof- prefix added automatically */ 18062306a36Sopenharmony_ci .owner = THIS_MODULE, 18162306a36Sopenharmony_ci .dai_link = dailink, 18262306a36Sopenharmony_ci .num_links = ARRAY_SIZE(dailink), 18362306a36Sopenharmony_ci}; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* i2c-<HID>:00 with HID being 8 chars */ 18662306a36Sopenharmony_cistatic char codec_name[SND_ACPI_I2C_ID_LEN]; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* 18962306a36Sopenharmony_ci * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to 19062306a36Sopenharmony_ci * select the clock source. On the Up2 board, this means 19162306a36Sopenharmony_ci * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404. 19262306a36Sopenharmony_ci * 19362306a36Sopenharmony_ci * Using the ACPI device name is not very nice, but since we only use 19462306a36Sopenharmony_ci * the value for the Up2 board there is no risk of conflict with other 19562306a36Sopenharmony_ci * platforms. 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic struct gpiod_lookup_table up2_gpios_table = { 19962306a36Sopenharmony_ci /* .dev_id is set during probe */ 20062306a36Sopenharmony_ci .table = { 20162306a36Sopenharmony_ci GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH), 20262306a36Sopenharmony_ci GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH), 20362306a36Sopenharmony_ci { }, 20462306a36Sopenharmony_ci }, 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int sof_wm8804_probe(struct platform_device *pdev) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct snd_soc_card *card; 21062306a36Sopenharmony_ci struct snd_soc_acpi_mach *mach; 21162306a36Sopenharmony_ci struct sof_card_private *ctx; 21262306a36Sopenharmony_ci struct acpi_device *adev; 21362306a36Sopenharmony_ci int dai_index = 0; 21462306a36Sopenharmony_ci int ret; 21562306a36Sopenharmony_ci int i; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); 21862306a36Sopenharmony_ci if (!ctx) 21962306a36Sopenharmony_ci return -ENOMEM; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci mach = pdev->dev.platform_data; 22262306a36Sopenharmony_ci card = &sof_wm8804_card; 22362306a36Sopenharmony_ci card->dev = &pdev->dev; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci dmi_check_system(sof_wm8804_quirk_table); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) { 22862306a36Sopenharmony_ci up2_gpios_table.dev_id = dev_name(&pdev->dev); 22962306a36Sopenharmony_ci gpiod_add_lookup_table(&up2_gpios_table); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* 23262306a36Sopenharmony_ci * The gpios are required for specific boards with 23362306a36Sopenharmony_ci * local oscillators, and optional in other cases. 23462306a36Sopenharmony_ci * Since we can't identify when they are needed, use 23562306a36Sopenharmony_ci * the GPIO as non-optional 23662306a36Sopenharmony_ci */ 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5", 23962306a36Sopenharmony_ci GPIOD_OUT_LOW); 24062306a36Sopenharmony_ci if (IS_ERR(ctx->gpio_44)) { 24162306a36Sopenharmony_ci ret = PTR_ERR(ctx->gpio_44); 24262306a36Sopenharmony_ci dev_err(&pdev->dev, 24362306a36Sopenharmony_ci "could not get BCM-GPIO5: %d\n", 24462306a36Sopenharmony_ci ret); 24562306a36Sopenharmony_ci return ret; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6", 24962306a36Sopenharmony_ci GPIOD_OUT_LOW); 25062306a36Sopenharmony_ci if (IS_ERR(ctx->gpio_48)) { 25162306a36Sopenharmony_ci ret = PTR_ERR(ctx->gpio_48); 25262306a36Sopenharmony_ci dev_err(&pdev->dev, 25362306a36Sopenharmony_ci "could not get BCM-GPIO6: %d\n", 25462306a36Sopenharmony_ci ret); 25562306a36Sopenharmony_ci return ret; 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* fix index of codec dai */ 26062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dailink); i++) { 26162306a36Sopenharmony_ci if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) { 26262306a36Sopenharmony_ci dai_index = i; 26362306a36Sopenharmony_ci break; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* fixup codec name based on HID */ 26862306a36Sopenharmony_ci adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); 26962306a36Sopenharmony_ci if (adev) { 27062306a36Sopenharmony_ci snprintf(codec_name, sizeof(codec_name), 27162306a36Sopenharmony_ci "%s%s", "i2c-", acpi_dev_name(adev)); 27262306a36Sopenharmony_ci dailink[dai_index].codecs->name = codec_name; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci acpi_dev_put(adev); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci snd_soc_card_set_drvdata(card, ctx); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci return devm_snd_soc_register_card(&pdev->dev, card); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic void sof_wm8804_remove(struct platform_device *pdev) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) 28462306a36Sopenharmony_ci gpiod_remove_lookup_table(&up2_gpios_table); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic struct platform_driver sof_wm8804_driver = { 28862306a36Sopenharmony_ci .driver = { 28962306a36Sopenharmony_ci .name = "sof-wm8804", 29062306a36Sopenharmony_ci .pm = &snd_soc_pm_ops, 29162306a36Sopenharmony_ci }, 29262306a36Sopenharmony_ci .probe = sof_wm8804_probe, 29362306a36Sopenharmony_ci .remove_new = sof_wm8804_remove, 29462306a36Sopenharmony_ci}; 29562306a36Sopenharmony_cimodule_platform_driver(sof_wm8804_driver); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ciMODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver"); 29862306a36Sopenharmony_ciMODULE_AUTHOR("Pierre-Louis Bossart"); 29962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 30062306a36Sopenharmony_ciMODULE_ALIAS("platform:sof-wm8804"); 301