brintos

brintos / linux-shallow public Read only

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