sound: x86: link: Add sound support

Add sound support for link, using the HDA codec implementation.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig
index 0dca6ff..4b893f0 100644
--- a/drivers/sound/Kconfig
+++ b/drivers/sound/Kconfig
@@ -49,6 +49,16 @@
 	  Azalia which was the development code-name. It requires setup
 	  information in the device tree (see intel-hda.txt).
 
+config SOUND_IVYBRIDGE
+	bool "Intel Ivybridge sound support"
+	depends on SOUND
+	select SOUND_INTEL_HDA
+	help
+	  Enable sound output on supported Intel Ivybridge-based boards. This
+	  driver uses Intel's High-definition Audio (HDA) architecture,
+	  sometimes called Azalia. The audio codec is detected using a
+	  semi-automatic mechanism.
+
 config SOUND_MAX98088
 	bool "Support Maxim max98088 audio codec"
 	depends on I2S
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index 2e21a0d..771c116 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -16,3 +16,4 @@
 obj-$(CONFIG_SOUND_MAX98090)	+= max98090.o maxim_codec.o
 obj-$(CONFIG_SOUND_MAX98095)	+= max98095.o maxim_codec.o
 obj-$(CONFIG_SOUND_INTEL_HDA)	+= hda_codec.o
+obj-$(CONFIG_SOUND_IVYBRIDGE)	+= ivybridge_sound.o
diff --git a/drivers/sound/ivybridge_sound.c b/drivers/sound/ivybridge_sound.c
new file mode 100644
index 0000000..101046f
--- /dev/null
+++ b/drivers/sound/ivybridge_sound.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel HDA audio (Azalia) for ivybridge
+ *
+ * Originally from coreboot file bd82x6x/azalia.c
+ *
+ * Copyright (C) 2008 Advanced Micro Devices, Inc.
+ * Copyright (C) 2008-2009 coresystems GmbH
+ * Copyright (C) 2011 The ChromiumOS Authors.
+ * Copyright 2018 Google LLC
+ */
+
+#define LOG_CATEGORY UCLASS_SOUND
+
+#include <common.h>
+#include <dm.h>
+#include <hda_codec.h>
+#include <pch.h>
+#include <sound.h>
+
+static int bd82x6x_azalia_probe(struct udevice *dev)
+{
+	struct pci_child_platdata *plat;
+	struct hda_codec_priv *priv;
+	struct udevice *pch;
+	u32 codec_mask;
+	int conf;
+	int ret;
+
+	/* Only init after relocation */
+	if (!(gd->flags & GD_FLG_RELOC))
+		return 0;
+
+	ret = hda_codec_init(dev);
+	if (ret) {
+		log_debug("Cannot set up HDA codec (err=%d)\n", ret);
+		return ret;
+	}
+	priv = dev_get_priv(dev);
+
+	ret = uclass_first_device_err(UCLASS_PCH, &pch);
+	log_debug("PCH %p %s\n", pch, pch->name);
+	if (ret)
+		return ret;
+
+	conf = pch_ioctl(pch, PCH_REQ_HDA_CONFIG, NULL, 0);
+	log_debug("conf = %x\n", conf);
+	if (conf >= 0) {
+		dm_pci_clrset_config32(dev, 0x120, 7 << 24 | 0xfe,
+				       1 << 24 | /* 2 << 24 for server */
+				       conf);
+
+		dm_pci_clrset_config16(dev, 0x78, 0, 1 << 1);
+	} else {
+		log_debug("V1CTL disabled\n");
+	}
+	dm_pci_clrset_config32(dev, 0x114, 0xfe, 0);
+
+	/* Set VCi enable bit */
+	dm_pci_clrset_config32(dev, 0x120, 0, 1U << 31);
+
+	/* Enable HDMI codec */
+	dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 1);
+	dm_pci_clrset_config8(dev, 0x43, 0, 1 << 6);
+
+	/* Additional programming steps */
+	dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 13);
+	dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 10);
+	dm_pci_clrset_config32(dev, 0xd0, 1U << 31, 0);
+
+	/* Additional step on Panther Point */
+	plat = dev_get_parent_platdata(dev);
+	if (plat->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA)
+		dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 17);
+
+	dm_pci_write_config8(dev, 0x3c, 0xa); /* unused? */
+
+	/* Audio Control: Select Azalia mode */
+	dm_pci_clrset_config8(dev, 0x40, 0, 1);
+	dm_pci_clrset_config8(dev, 0x4d, 1 << 7, 0); /* Docking not supported */
+	codec_mask = hda_codec_detect(priv->regs);
+	log_debug("codec_mask = %02x\n", codec_mask);
+
+	if (codec_mask) {
+		ret = hda_codecs_init(dev, priv->regs, codec_mask);
+		if (ret) {
+			log_err("Codec init failed (err=%d)\n", ret);
+			return ret;
+		}
+	}
+
+	/* Enable dynamic clock gating */
+	dm_pci_clrset_config8(dev, 0x43, 7, BIT(2) | BIT(0));
+
+	ret = hda_codec_finish_init(dev);
+	if (ret) {
+		log_debug("Cannot set up HDA codec (err=%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int bd82x6x_azalia_setup(struct udevice *dev)
+{
+	return 0;
+}
+
+int bd82x6x_azalia_start_beep(struct udevice *dev, int frequency_hz)
+{
+	return hda_codec_start_beep(dev, frequency_hz);
+}
+
+int bd82x6x_azalia_stop_beep(struct udevice *dev)
+{
+	return hda_codec_stop_beep(dev);
+}
+
+static const struct sound_ops bd82x6x_azalia_ops = {
+	.setup		= bd82x6x_azalia_setup,
+	.start_beep	= bd82x6x_azalia_start_beep,
+	.stop_beep	= bd82x6x_azalia_stop_beep,
+};
+
+static const struct udevice_id bd82x6x_azalia_ids[] = {
+	{ .compatible = "intel,hd-audio" },
+	{ }
+};
+
+U_BOOT_DRIVER(bd82x6x_azalia_drv) = {
+	.name		= "bd82x6x-hda",
+	.id		= UCLASS_SOUND,
+	.of_match	= bd82x6x_azalia_ids,
+	.probe		= bd82x6x_azalia_probe,
+	.ops		= &bd82x6x_azalia_ops,
+	.priv_auto_alloc_size	= sizeof(struct hda_codec_priv),
+};