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