--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Machine driver for AMD ACP Audio engine using ES8336 codec.
+//
+// Copyright 2023 Marian Postevca <posteuca@mutex.one>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include "../acp-mach.h"
+
+#define get_mach_priv(card) ((struct acp3x_es83xx_private *)((acp_get_drvdata(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[] = {
+       DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+       .count = ARRAY_SIZE(channels),
+       .list = channels,
+       .mask = 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 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 = substream->runtime;
+       rtd = asoc_substream_to_rtd(substream);
+       codec_dai = asoc_rtd_to_codec(rtd, 0);
+       priv = get_mach_priv(rtd->card);
+
+       if (priv->quirk & ES83XX_48_MHZ_MCLK) {
+               dev_dbg(priv->codec_dev, "using a 48Mhz MCLK\n");
+               freq = ES83xx_48_MHZ_MCLK_FREQ;
+       } else {
+               dev_dbg(priv->codec_dev, "using a 12.288Mhz MCLK\n");
+               freq = ES83xx_12288_KHZ_MCLK_FREQ;
+       }
+
+       ret = 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 = DUAL_CHANNEL;
+       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+                                  &constraints_channels);
+
+       return 0;
+}
+
+static struct snd_soc_jack es83xx_jack;
+
+static struct snd_soc_jack_pin es83xx_jack_pins[] = {
+       {
+               .pin    = "Headphone",
+               .mask   = SND_JACK_HEADPHONE,
+       },
+       {
+               .pin    = "Headset Mic",
+               .mask   = SND_JACK_MICROPHONE,
+       },
+};
+
+static const struct snd_soc_dapm_widget acp3x_es83xx_widgets[] = {
+       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[] = {
+       {"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[] = {
+       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 = acp3x_es83xx_widgets;
+       card->num_dapm_widgets = ARRAY_SIZE(acp3x_es83xx_widgets);
+       card->controls = acp3x_es83xx_controls;
+       card->num_controls = ARRAY_SIZE(acp3x_es83xx_controls);
+       card->dapm_routes = acp3x_es83xx_audio_map;
+       card->num_dapm_routes = 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 = get_mach_priv(w->dapm->card);
+
+       dev_dbg(priv->codec_dev, "headphone power event = %d\n", event);
+       if (SND_SOC_DAPM_EVENT_ON(event))
+               priv->headphone_on = true;
+       else
+               priv->headphone_on = false;
+
+       gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_on);
+       gpiod_set_value_cansleep(priv->gpio_headphone, 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 = 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 = true;
+       else
+               priv->speaker_on = false;
+
+       gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_on);
+       gpiod_set_value_cansleep(priv->gpio_headphone, priv->headphone_on);
+
+       return 0;
+}
+
+static int acp3x_es83xx_suspend_pre(struct snd_soc_card *card)
+{
+       struct acp3x_es83xx_private *priv = 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 = 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 = 0;
+
+       priv->enable_spk_gpio.crs_entry_index = 0;
+       priv->enable_hp_gpio.crs_entry_index = 1;
+
+       priv->enable_spk_gpio.active_low = false;
+       priv->enable_hp_gpio.active_low = false;
+
+       priv->gpio_mapping[0].name = "speakers-enable-gpios";
+       priv->gpio_mapping[0].data = &priv->enable_spk_gpio;
+       priv->gpio_mapping[0].size = 1;
+       priv->gpio_mapping[0].quirks = ACPI_GPIO_QUIRK_ONLY_GPIOIO;
+
+       priv->gpio_mapping[1].name = "headphone-enable-gpios";
+       priv->gpio_mapping[1].data = &priv->enable_hp_gpio;
+       priv->gpio_mapping[1].size = 1;
+       priv->gpio_mapping[1].quirks = ACPI_GPIO_QUIRK_ONLY_GPIOIO;
+
+       dev_info(priv->codec_dev, "speaker gpio %d active %s, headphone gpio %d active %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 = 0;
+       int i;
+
+       if (!(priv->quirk & ES83XX_ENABLE_DMIC)) {
+               priv->mic_map[num_routes].sink = "MIC1";
+               priv->mic_map[num_routes].source = "Internal Mic";
+               num_routes++;
+       }
+
+       priv->mic_map[num_routes].sink = "MIC2";
+       priv->mic_map[num_routes].source = "Headset Mic";
+       num_routes++;
+
+       for (i = 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 = asoc_rtd_to_codec(runtime, 0)->component;
+       struct snd_soc_card *card = runtime->card;
+       struct acp3x_es83xx_private *priv = get_mach_priv(card);
+       int ret = 0;
+       int num_routes;
+
+       ret = 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 = codec;
+       acp3x_es83xx_configure_gpios(priv);
+
+       ret = devm_acpi_dev_add_driver_gpios(priv->codec_dev, priv->gpio_mapping);
+       if (ret)
+               dev_warn(priv->codec_dev, "failed to add speaker gpio\n");
+
+       priv->gpio_speakers = gpiod_get_optional(priv->codec_dev, "speakers-enable",
+                               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 = gpiod_get_optional(priv->codec_dev, "headphone-enable",
+                               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 = acp3x_es83xx_configure_mics(priv);
+       if (num_routes > 0) {
+               ret = snd_soc_dapm_add_routes(&card->dapm, priv->mic_map, num_routes);
+               if (ret != 0)
+                       device_remove_software_node(priv->codec_dev);
+       }
+
+       return ret;
+}
+
+static const struct snd_soc_ops acp3x_es83xx_ops = {
+       .startup = 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[] = {
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXXW"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+               },
+               .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+       },
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXX9"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+               },
+               .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+       },
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BOM-WXX9"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+               },
+               .driver_data = (void *)(ES83XX_ENABLE_DMIC|ES83XX_48_MHZ_MCLK),
+       },
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1020"),
+               },
+               .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+       },
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1040"),
+               },
+               .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+       },
+       {}
+};
+
+static int acp3x_es83xx_configure_link(struct snd_soc_card *card, struct snd_soc_dai_link *link)
+{
+       link->codecs = codec;
+       link->num_codecs = ARRAY_SIZE(codec);
+       link->init = acp3x_es83xx_init;
+       link->ops = &acp3x_es83xx_ops;
+       link->dai_fmt = 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 = 0;
+       struct device *dev = card->dev;
+       const struct dmi_system_id *dmi_id;
+
+       dmi_id = 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 = (struct acp_card_drvdata *)card->drvdata;
+
+               dev_info(dev, "matched DMI table with this system, trying to register sound card\n");
+
+               adev = 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 = 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 = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+               if (!priv) {
+                       put_device(codec_dev);
+                       return -ENOMEM;
+               }
+
+               priv->codec_dev = codec_dev;
+               priv->quirk = (unsigned long)dmi_id->driver_data;
+               acp_drvdata->mach_priv = priv;
+               dev_info(dev, "successfully probed the sound card\n");
+       } else {
+               ret = -ENODEV;
+               dev_warn(dev, "this system has a ES83xx codec defined in ACPI, but the driver doesn't have this system registered in DMI table\n");
+       }
+       return ret;
+}
+
+
+void acp3x_es83xx_init_ops(struct acp_mach_ops *ops)
+{
+       ops->probe = acp3x_es83xx_probe;
+       ops->configure_widgets = acp3x_es83xx_configure_widgets;
+       ops->configure_link = acp3x_es83xx_configure_link;
+       ops->suspend_pre = acp3x_es83xx_suspend_pre;
+       ops->resume_post = acp3x_es83xx_resume_post;
+}