From nobody Wed Dec 17 03:02:27 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2CD31EE49A0 for ; Thu, 24 Aug 2023 21:02:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243448AbjHXVCG (ORCPT ); Thu, 24 Aug 2023 17:02:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36240 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S243186AbjHXVBu (ORCPT ); Thu, 24 Aug 2023 17:01:50 -0400 Received: from mail.mutex.one (mail.mutex.one [62.77.152.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6833119BF for ; Thu, 24 Aug 2023 14:01:46 -0700 (PDT) Received: from localhost (localhost.localdomain [127.0.0.1]) by mail.mutex.one (Postfix) with ESMTP id 1DE8C16C004F; Fri, 25 Aug 2023 00:01:45 +0300 (EEST) X-Virus-Scanned: Debian amavisd-new at mail.mutex.one Received: from mail.mutex.one ([127.0.0.1]) by localhost (mail.mutex.one [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id kRtxKl6rltLD; Fri, 25 Aug 2023 00:01:42 +0300 (EEST) From: Marian Postevca DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=mutex.one; s=default; t=1692910902; bh=P7L+WFsGFeGb2fXdmTAEAs1ICRK+t1ZJBH5ILqxJWP0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kFRv/pjFlKrKRr9HxZKCqBG3pDMoLWmfAMmUQrJ77c1DyuDbeZuqrIJje7dLudqYL f6fzdnxJbSpPZyAfZWLASWEVwDNMfdRidxX5cRN6cN22Ca6vOqMtKdl1oyi27Kj1e6 wtlZmtOK98TVygoEpsSgYlZm50AgAhZs//VTkLDY= To: Takashi Iwai , Liam Girdwood , Mark Brown , Jaroslav Kysela Cc: alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org, Marian Postevca Subject: [PATCH v2 4/4] ASoC: amd: acp: Add machine driver that enables sound for systems with a ES8336 codec Date: Fri, 25 Aug 2023 00:01:35 +0300 Message-ID: <20230824210135.19303-5-posteuca@mutex.one> In-Reply-To: <20230824210135.19303-1-posteuca@mutex.one> References: <20230824210135.19303-1-posteuca@mutex.one> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" This commit enables sound for a line of Huawei laptops that use the ES8336 codec which is connected to the ACP3X module. Signed-off-by: Marian Postevca --- sound/soc/amd/acp-config.c | 70 +++ sound/soc/amd/acp/Makefile | 2 +- sound/soc/amd/acp/acp-legacy-mach.c | 102 +++- sound/soc/amd/acp/acp-mach-common.c | 8 + sound/soc/amd/acp/acp-mach.h | 2 + sound/soc/amd/acp/acp-renoir.c | 4 + sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 449 ++++++++++++++++++ sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h | 12 + 8 files changed, 637 insertions(+), 12 deletions(-) create mode 100644 sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c create mode 100644 sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index f27c27580009..a58d646d28f6 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -61,6 +61,76 @@ static const struct config_entry config_table[] =3D { {} }, }, + { + .flags =3D FLAG_AMD_LEGACY, + .device =3D ACP_PCI_DEV_ID, + .dmi_table =3D (const struct dmi_system_id []) { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXXW"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + }, + {} + }, + }, + { + .flags =3D FLAG_AMD_LEGACY, + .device =3D ACP_PCI_DEV_ID, + .dmi_table =3D (const struct dmi_system_id []) { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + }, + {} + }, + }, + { + .flags =3D FLAG_AMD_LEGACY, + .device =3D ACP_PCI_DEV_ID, + .dmi_table =3D (const struct dmi_system_id []) { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BOM-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + }, + {} + }, + }, + { + .flags =3D FLAG_AMD_LEGACY, + .device =3D ACP_PCI_DEV_ID, + .dmi_table =3D (const struct dmi_system_id []) { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1020"), + }, + }, + {} + }, + }, + { + .flags =3D FLAG_AMD_LEGACY, + .device =3D ACP_PCI_DEV_ID, + .dmi_table =3D (const struct dmi_system_id []) { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1040"), + }, + }, + {} + }, + }, }; =20 int snd_amd_acp_find_config(struct pci_dev *pci) diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile index 4e65fdbc8dca..dc70691bc293 100644 --- a/sound/soc/amd/acp/Makefile +++ b/sound/soc/amd/acp/Makefile @@ -17,7 +17,7 @@ snd-acp-rembrandt-objs :=3D acp-rembrandt.o =20 #machine specific driver snd-acp-mach-objs :=3D acp-mach-common.o -snd-acp-legacy-mach-objs :=3D acp-legacy-mach.o +snd-acp-legacy-mach-objs :=3D acp-legacy-mach.o acp3x-es83xx/acp3x-es8= 3xx.o snd-acp-sof-mach-objs :=3D acp-sof-mach.o =20 obj-$(CONFIG_SND_SOC_AMD_ACP_PCM) +=3D snd-acp-pcm.o diff --git a/sound/soc/amd/acp/acp-legacy-mach.c b/sound/soc/amd/acp/acp-le= gacy-mach.c index 6d57d17ddfd7..1ab3edffe0ce 100644 --- a/sound/soc/amd/acp/acp-legacy-mach.c +++ b/sound/soc/amd/acp/acp-legacy-mach.c @@ -20,6 +20,7 @@ #include =20 #include "acp-mach.h" +#include "acp3x-es83xx/acp3x-es83xx.h" =20 static struct acp_card_drvdata rt5682_rt1019_data =3D { .hs_cpu_id =3D I2S_SP, @@ -51,6 +52,14 @@ static struct acp_card_drvdata rt5682s_rt1019_data =3D { .tdm_mode =3D false, }; =20 +static struct acp_card_drvdata es83xx_rn_data =3D { + .hs_cpu_id =3D I2S_SP, + .dmic_cpu_id =3D DMIC, + .hs_codec_id =3D ES83XX, + .dmic_codec_id =3D DMIC, + .platform =3D RENOIR, +}; + static struct acp_card_drvdata max_nau8825_data =3D { .hs_cpu_id =3D I2S_HS, .amp_cpu_id =3D I2S_HS, @@ -75,6 +84,39 @@ static struct acp_card_drvdata rt5682s_rt1019_rmb_data = =3D { .tdm_mode =3D false, }; =20 +static bool acp_asoc_init_ops(struct acp_card_drvdata *priv) +{ + bool has_ops =3D false; + + if (priv->hs_codec_id =3D=3D ES83XX) { + has_ops =3D true; + acp3x_es83xx_init_ops(&priv->ops); + } + return has_ops; +} + +static int acp_asoc_suspend_pre(struct snd_soc_card *card) +{ + int ret; + + ret =3D acp_ops_suspend_pre(card); + if (ret =3D=3D 1) + return 0; + else + return ret; +} + +static int acp_asoc_resume_post(struct snd_soc_card *card) +{ + int ret; + + ret =3D acp_ops_resume_post(card); + if (ret =3D=3D 1) + return 0; + else + return ret; +} + static int acp_asoc_probe(struct platform_device *pdev) { struct snd_soc_card *card =3D NULL; @@ -83,35 +125,68 @@ static int acp_asoc_probe(struct platform_device *pdev) struct acp_card_drvdata *acp_card_drvdata; int ret; =20 - if (!pdev->id_entry) - return -EINVAL; + if (!pdev->id_entry) { + ret =3D -EINVAL; + goto out; + } =20 card =3D devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); - if (!card) - return -ENOMEM; + if (!card) { + ret =3D -ENOMEM; + goto out; + } =20 + card->drvdata =3D (struct acp_card_drvdata *)pdev->id_entry->driver_data; + acp_card_drvdata =3D card->drvdata; + acp_card_drvdata->acpi_mach =3D (struct snd_soc_acpi_mach *)pdev->dev.pla= tform_data; card->dev =3D dev; card->owner =3D THIS_MODULE; card->name =3D pdev->id_entry->name; - card->drvdata =3D (struct acp_card_drvdata *)pdev->id_entry->driver_data; - /* Widgets and controls added per-codec in acp-mach-common.c */ =20 - acp_card_drvdata =3D card->drvdata; + acp_asoc_init_ops(card->drvdata); + + /* If widgets and controls are not set in specific callback, + * they will be added per-codec in acp-mach-common.c + */ + ret =3D acp_ops_configure_widgets(card); + if (ret < 0) { + dev_err(&pdev->dev, + "Cannot configure widgets for card (%s): %d\n", + card->name, ret); + goto out; + } + card->suspend_pre =3D acp_asoc_suspend_pre; + card->resume_post =3D acp_asoc_resume_post; + + ret =3D acp_ops_probe(card); + if (ret < 0) { + dev_err(&pdev->dev, + "Cannot probe card (%s): %d\n", + card->name, ret); + goto out; + } + dmi_id =3D dmi_first_match(acp_quirk_table); if (dmi_id && dmi_id->driver_data) acp_card_drvdata->tdm_mode =3D dmi_id->driver_data; =20 - acp_legacy_dai_links_create(card); + ret =3D acp_legacy_dai_links_create(card); + if (ret) { + dev_err(&pdev->dev, + "Cannot create dai links for card (%s): %d\n", + card->name, ret); + goto out; + } =20 ret =3D devm_snd_soc_register_card(&pdev->dev, card); if (ret) { dev_err(&pdev->dev, "devm_snd_soc_register_card(%s) failed: %d\n", card->name, ret); - return ret; + goto out; } - - return 0; +out: + return ret; } =20 static const struct platform_device_id board_ids[] =3D { @@ -127,6 +202,10 @@ static const struct platform_device_id board_ids[] =3D= { .name =3D "acp3xalc5682s1019", .driver_data =3D (kernel_ulong_t)&rt5682s_rt1019_data, }, + { + .name =3D "acp3x-es83xx", + .driver_data =3D (kernel_ulong_t)&es83xx_rn_data, + }, { .name =3D "rmb-nau8825-max", .driver_data =3D (kernel_ulong_t)&max_nau8825_data, @@ -153,6 +232,7 @@ MODULE_DESCRIPTION("ACP chrome audio support"); MODULE_ALIAS("platform:acp3xalc56821019"); MODULE_ALIAS("platform:acp3xalc5682sm98360"); MODULE_ALIAS("platform:acp3xalc5682s1019"); +MODULE_ALIAS("platform:acp3x-es83xx"); MODULE_ALIAS("platform:rmb-nau8825-max"); MODULE_ALIAS("platform:rmb-rt5682s-rt1019"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-ma= ch-common.c index a06af82b8056..8f968c12e54a 100644 --- a/sound/soc/amd/acp/acp-mach-common.c +++ b/sound/soc/amd/acp/acp-mach-common.c @@ -1513,6 +1513,7 @@ int acp_legacy_dai_links_create(struct snd_soc_card *= card) struct device *dev =3D card->dev; struct acp_card_drvdata *drv_data =3D card->drvdata; int i =3D 0, num_links =3D 0; + int rc; =20 if (drv_data->hs_cpu_id) num_links++; @@ -1551,6 +1552,13 @@ int acp_legacy_dai_links_create(struct snd_soc_card = *card) links[i].init =3D acp_card_rt5682s_init; links[i].ops =3D &acp_card_rt5682s_ops; } + if (drv_data->hs_codec_id =3D=3D ES83XX) { + rc =3D acp_ops_configure_link(card, &links[i]); + if (rc !=3D 0) { + dev_err(dev, "Failed to configure link for ES83XX: %d\n", rc); + return rc; + } + } i++; } =20 diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h index 31f38ec4b1d1..b0a3f6bd172f 100644 --- a/sound/soc/amd/acp/acp-mach.h +++ b/sound/soc/amd/acp/acp-mach.h @@ -47,6 +47,7 @@ enum codec_endpoints { NAU8825, NAU8821, MAX98388, + ES83XX, }; =20 enum platform_end_point { @@ -74,6 +75,7 @@ struct acp_card_drvdata { struct clk *wclk; struct clk *bclk; struct acp_mach_ops ops; + struct snd_soc_acpi_mach *acpi_mach; void *mach_priv; bool soc_mclk; bool tdm_mode; diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c index 54235cad9cc9..b15cbdf7fa9b 100644 --- a/sound/soc/amd/acp/acp-renoir.c +++ b/sound/soc/amd/acp/acp-renoir.c @@ -69,6 +69,10 @@ static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_mac= hines[] =3D { .id =3D "AMDI1019", .drv_name =3D "renoir-acp", }, + { + .id =3D "ESSX8336", + .drv_name =3D "acp3x-es83xx", + }, {}, }; =20 diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/= acp/acp3x-es83xx/acp3x-es83xx.c new file mode 100644 index 000000000000..b4e0ea515352 --- /dev/null +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Machine driver for AMD ACP Audio engine using ES8336 codec. +// +// Copyright 2023 Marian Postevca +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../acp-mach.h" + +#define get_mach_priv(card) ((struct acp3x_es83xx_private *)((acp_get_drvd= ata(card))->mach_priv)) + +#define DUAL_CHANNEL 2 + +#define ES83XX_ENABLE_DMIC BIT(4) +#define ES83XX_48_MHZ_MCLK BIT(5) + +struct acp3x_es83xx_private { + bool speaker_on; + bool headphone_on; + unsigned long quirk; + struct snd_soc_component *codec; + struct device *codec_dev; + struct gpio_desc *gpio_speakers, *gpio_headphone; + struct acpi_gpio_params enable_spk_gpio, enable_hp_gpio; + struct acpi_gpio_mapping gpio_mapping[3]; + struct snd_soc_dapm_route mic_map[2]; +}; + +static const unsigned int channels[] =3D { + DUAL_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels =3D { + .count =3D ARRAY_SIZE(channels), + .list =3D channels, + .mask =3D 0, +}; + +#define ES83xx_12288_KHZ_MCLK_FREQ (48000 * 256) +#define ES83xx_48_MHZ_MCLK_FREQ (48000 * 1000) + +static int acp3x_es83xx_headphone_power_event(struct snd_soc_dapm_widget *= w, + struct snd_kcontrol *kcontrol, int event); +static int acp3x_es83xx_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +static void acp3x_es83xx_set_gpios_values(struct acp3x_es83xx_private *pri= v, + bool speaker, bool headphone) +{ + gpiod_set_value_cansleep(priv->gpio_speakers, speaker); + gpiod_set_value_cansleep(priv->gpio_headphone, headphone); +} + +static int acp3x_es83xx_codec_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct acp3x_es83xx_private *priv; + unsigned int freq; + int ret; + + runtime =3D substream->runtime; + rtd =3D asoc_substream_to_rtd(substream); + codec_dai =3D asoc_rtd_to_codec(rtd, 0); + priv =3D get_mach_priv(rtd->card); + + if (priv->quirk & ES83XX_48_MHZ_MCLK) { + dev_dbg(priv->codec_dev, "using a 48Mhz MCLK\n"); + freq =3D ES83xx_48_MHZ_MCLK_FREQ; + } else { + dev_dbg(priv->codec_dev, "using a 12.288Mhz MCLK\n"); + freq =3D ES83xx_12288_KHZ_MCLK_FREQ; + } + + ret =3D snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + runtime->hw.channels_max =3D DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + runtime->hw.formats =3D SNDRV_PCM_FMTBIT_S32_LE; + + return 0; +} + +static struct snd_soc_jack es83xx_jack; + +static struct snd_soc_jack_pin es83xx_jack_pins[] =3D { + { + .pin =3D "Headphone", + .mask =3D SND_JACK_HEADPHONE, + }, + { + .pin =3D "Headset Mic", + .mask =3D SND_JACK_MICROPHONE, + }, +}; + +static const struct snd_soc_dapm_widget acp3x_es83xx_widgets[] =3D { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + + SND_SOC_DAPM_SUPPLY("Headphone Power", SND_SOC_NOPM, 0, 0, + acp3x_es83xx_headphone_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, + acp3x_es83xx_speaker_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_route acp3x_es83xx_audio_map[] =3D { + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Headphone", NULL, "Headphone Power"}, + + /* + * There is no separate speaker output instead the speakers are muxed to + * the HP outputs. The mux is controlled Speaker and/or headphone switch. + */ + {"Speaker", NULL, "HPOL"}, + {"Speaker", NULL, "HPOR"}, + {"Speaker", NULL, "Speaker Power"}, +}; + + +static const struct snd_kcontrol_new acp3x_es83xx_controls[] =3D { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static int acp3x_es83xx_configure_widgets(struct snd_soc_card *card) +{ + card->dapm_widgets =3D acp3x_es83xx_widgets; + card->num_dapm_widgets =3D ARRAY_SIZE(acp3x_es83xx_widgets); + card->controls =3D acp3x_es83xx_controls; + card->num_controls =3D ARRAY_SIZE(acp3x_es83xx_controls); + card->dapm_routes =3D acp3x_es83xx_audio_map; + card->num_dapm_routes =3D ARRAY_SIZE(acp3x_es83xx_audio_map); + + return 0; +} + +static int acp3x_es83xx_headphone_power_event(struct snd_soc_dapm_widget *= w, + struct snd_kcontrol *kcontrol, int event) +{ + struct acp3x_es83xx_private *priv =3D get_mach_priv(w->dapm->card); + + dev_dbg(priv->codec_dev, "headphone power event =3D %d\n", event); + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->headphone_on =3D true; + else + priv->headphone_on =3D false; + + acp3x_es83xx_set_gpios_values(priv, priv->speaker_on, priv->headphone_on); + + return 0; +} + +static int acp3x_es83xx_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct acp3x_es83xx_private *priv =3D get_mach_priv(w->dapm->card); + + dev_dbg(priv->codec_dev, "speaker power event: %d\n", event); + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->speaker_on =3D true; + else + priv->speaker_on =3D false; + + acp3x_es83xx_set_gpios_values(priv, priv->speaker_on, priv->headphone_on); + + return 0; +} + +static int acp3x_es83xx_suspend_pre(struct snd_soc_card *card) +{ + struct acp3x_es83xx_private *priv =3D get_mach_priv(card); + + /* We need to disable the jack in the machine driver suspend + * callback so that the CODEC suspend callback actually gets + * called. Without doing it, the CODEC suspend/resume + * callbacks do not get called if headphones are plugged in. + * This is because plugging in headphones keeps some supplies + * active, this in turn means that the lowest bias level + * that the CODEC can go to is SND_SOC_BIAS_STANDBY. + * If components do not set idle_bias_on to true then + * their suspend/resume callbacks do not get called. + */ + dev_dbg(priv->codec_dev, "card suspend\n"); + snd_soc_component_set_jack(priv->codec, NULL, NULL); + return 0; +} + +static int acp3x_es83xx_resume_post(struct snd_soc_card *card) +{ + struct acp3x_es83xx_private *priv =3D get_mach_priv(card); + + /* We disabled jack detection in suspend callback, + * enable it back. + */ + dev_dbg(priv->codec_dev, "card resume\n"); + snd_soc_component_set_jack(priv->codec, &es83xx_jack, NULL); + return 0; +} + +static int acp3x_es83xx_configure_gpios(struct acp3x_es83xx_private *priv) +{ + int ret =3D 0; + + priv->enable_spk_gpio.crs_entry_index =3D 0; + priv->enable_hp_gpio.crs_entry_index =3D 1; + + priv->enable_spk_gpio.active_low =3D false; + priv->enable_hp_gpio.active_low =3D false; + + priv->gpio_mapping[0].name =3D "speakers-enable-gpios"; + priv->gpio_mapping[0].data =3D &priv->enable_spk_gpio; + priv->gpio_mapping[0].size =3D 1; + priv->gpio_mapping[0].quirks =3D ACPI_GPIO_QUIRK_ONLY_GPIOIO; + + priv->gpio_mapping[1].name =3D "headphone-enable-gpios"; + priv->gpio_mapping[1].data =3D &priv->enable_hp_gpio; + priv->gpio_mapping[1].size =3D 1; + priv->gpio_mapping[1].quirks =3D ACPI_GPIO_QUIRK_ONLY_GPIOIO; + + dev_info(priv->codec_dev, "speaker gpio %d active %s, headphone gpio %d a= ctive %s\n", + priv->enable_spk_gpio.crs_entry_index, + priv->enable_spk_gpio.active_low ? "low" : "high", + priv->enable_hp_gpio.crs_entry_index, + priv->enable_hp_gpio.active_low ? "low" : "high"); + return ret; +} + +static int acp3x_es83xx_configure_mics(struct acp3x_es83xx_private *priv) +{ + int num_routes =3D 0; + int i; + + if (!(priv->quirk & ES83XX_ENABLE_DMIC)) { + priv->mic_map[num_routes].sink =3D "MIC1"; + priv->mic_map[num_routes].source =3D "Internal Mic"; + num_routes++; + } + + priv->mic_map[num_routes].sink =3D "MIC2"; + priv->mic_map[num_routes].source =3D "Headset Mic"; + num_routes++; + + for (i =3D 0; i < num_routes; i++) + dev_info(priv->codec_dev, "%s is %s\n", + priv->mic_map[i].source, priv->mic_map[i].sink); + + return num_routes; +} + +static int acp3x_es83xx_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *codec =3D asoc_rtd_to_codec(runtime, 0)->compon= ent; + struct snd_soc_card *card =3D runtime->card; + struct acp3x_es83xx_private *priv =3D get_mach_priv(card); + int ret =3D 0; + int num_routes; + + ret =3D snd_soc_card_jack_new_pins(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &es83xx_jack, es83xx_jack_pins, + ARRAY_SIZE(es83xx_jack_pins)); + if (ret) { + dev_err(card->dev, "jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(es83xx_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + + snd_soc_component_set_jack(codec, &es83xx_jack, NULL); + + priv->codec =3D codec; + acp3x_es83xx_configure_gpios(priv); + + ret =3D devm_acpi_dev_add_driver_gpios(priv->codec_dev, priv->gpio_mappin= g); + if (ret) + dev_warn(priv->codec_dev, "failed to add speaker gpio\n"); + + priv->gpio_speakers =3D gpiod_get_optional(priv->codec_dev, "speakers-ena= ble", + priv->enable_spk_gpio.active_low ? GPIOD_OUT_LOW : GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpio_speakers)) { + dev_err(priv->codec_dev, "could not get speakers-enable GPIO\n"); + return PTR_ERR(priv->gpio_speakers); + } + + priv->gpio_headphone =3D gpiod_get_optional(priv->codec_dev, "headphone-e= nable", + priv->enable_hp_gpio.active_low ? GPIOD_OUT_LOW : GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpio_headphone)) { + dev_err(priv->codec_dev, "could not get headphone-enable GPIO\n"); + return PTR_ERR(priv->gpio_headphone); + } + + num_routes =3D acp3x_es83xx_configure_mics(priv); + if (num_routes > 0) { + ret =3D snd_soc_dapm_add_routes(&card->dapm, priv->mic_map, num_routes); + if (ret !=3D 0) + device_remove_software_node(priv->codec_dev); + } + + return ret; +} + +static const struct snd_soc_ops acp3x_es83xx_ops =3D { + .startup =3D acp3x_es83xx_codec_startup, +}; + + +SND_SOC_DAILINK_DEF(codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi"))); + +static const struct dmi_system_id acp3x_es83xx_dmi_table[] =3D { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXXW"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + .driver_data =3D (void *)(ES83XX_ENABLE_DMIC), + }, + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + .driver_data =3D (void *)(ES83XX_ENABLE_DMIC), + }, + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BOM-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"), + }, + .driver_data =3D (void *)(ES83XX_ENABLE_DMIC|ES83XX_48_MHZ_MCLK), + }, + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1020"), + }, + .driver_data =3D (void *)(ES83XX_ENABLE_DMIC), + }, + { + .matches =3D { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1040"), + }, + .driver_data =3D (void *)(ES83XX_ENABLE_DMIC), + }, + {} +}; + +static int acp3x_es83xx_configure_link(struct snd_soc_card *card, struct s= nd_soc_dai_link *link) +{ + link->codecs =3D codec; + link->num_codecs =3D ARRAY_SIZE(codec); + link->init =3D acp3x_es83xx_init; + link->ops =3D &acp3x_es83xx_ops; + link->dai_fmt =3D SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; + + return 0; +} + +static int acp3x_es83xx_probe(struct snd_soc_card *card) +{ + int ret =3D 0; + struct device *dev =3D card->dev; + const struct dmi_system_id *dmi_id; + + dmi_id =3D dmi_first_match(acp3x_es83xx_dmi_table); + if (dmi_id && dmi_id->driver_data) { + struct acp3x_es83xx_private *priv; + struct acp_card_drvdata *acp_drvdata; + struct acpi_device *adev; + struct device *codec_dev; + + acp_drvdata =3D (struct acp_card_drvdata *)card->drvdata; + + dev_info(dev, "matched DMI table with this system, trying to register so= und card\n"); + + adev =3D acpi_dev_get_first_match_dev(acp_drvdata->acpi_mach->id, NULL, = -1); + if (!adev) { + dev_err(dev, "Error cannot find '%s' dev\n", acp_drvdata->acpi_mach->id= ); + return -ENXIO; + } + + codec_dev =3D acpi_get_first_physical_node(adev); + acpi_dev_put(adev); + if (!codec_dev) { + dev_warn(dev, "Error cannot find codec device, will defer probe\n"); + return -EPROBE_DEFER; + } + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + put_device(codec_dev); + return -ENOMEM; + } + + priv->codec_dev =3D codec_dev; + priv->quirk =3D (unsigned long)dmi_id->driver_data; + acp_drvdata->mach_priv =3D priv; + dev_info(dev, "successfully probed the sound card\n"); + } else { + ret =3D -ENODEV; + dev_warn(dev, "this system has a ES83xx codec defined in ACPI, but the d= river doesn't have this system registered in DMI table\n"); + } + return ret; +} + + +void acp3x_es83xx_init_ops(struct acp_mach_ops *ops) +{ + ops->probe =3D acp3x_es83xx_probe; + ops->configure_widgets =3D acp3x_es83xx_configure_widgets; + ops->configure_link =3D acp3x_es83xx_configure_link; + ops->suspend_pre =3D acp3x_es83xx_suspend_pre; + ops->resume_post =3D acp3x_es83xx_resume_post; +} diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h b/sound/soc/amd/= acp/acp3x-es83xx/acp3x-es83xx.h new file mode 100644 index 000000000000..03551ffdd9da --- /dev/null +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2023 Marian Postevca + */ + +#ifndef __ACP3X_ES83XX_H +#define __ACP3X_ES83XX_H + +void acp3x_es83xx_init_ops(struct acp_mach_ops *ops); + +#endif + --=20 2.41.0