summaryrefslogtreecommitdiffstats
path: root/sound/hda
diff options
context:
space:
mode:
Diffstat (limited to 'sound/hda')
-rw-r--r--sound/hda/Kconfig65
-rw-r--r--sound/hda/Makefile22
-rw-r--r--sound/hda/array.c52
-rw-r--r--sound/hda/ext/Makefile4
-rw-r--r--sound/hda/ext/hdac_ext_bus.c142
-rw-r--r--sound/hda/ext/hdac_ext_controller.c393
-rw-r--r--sound/hda/ext/hdac_ext_stream.c410
-rw-r--r--sound/hda/hda_bus_type.c97
-rw-r--r--sound/hda/hdac_bus.c289
-rw-r--r--sound/hda/hdac_component.c354
-rw-r--r--sound/hda/hdac_controller.c665
-rw-r--r--sound/hda/hdac_device.c1102
-rw-r--r--sound/hda/hdac_i915.c180
-rw-r--r--sound/hda/hdac_regmap.c601
-rw-r--r--sound/hda/hdac_stream.c1009
-rw-r--r--sound/hda/hdac_sysfs.c469
-rw-r--r--sound/hda/hdmi_chmap.c850
-rw-r--r--sound/hda/intel-dsp-config.c767
-rw-r--r--sound/hda/intel-nhlt.c345
-rw-r--r--sound/hda/intel-sdw-acpi.c185
-rw-r--r--sound/hda/local.h46
-rw-r--r--sound/hda/trace.c6
-rw-r--r--sound/hda/trace.h101
23 files changed, 8154 insertions, 0 deletions
diff --git a/sound/hda/Kconfig b/sound/hda/Kconfig
new file mode 100644
index 0000000000..741179ccbd
--- /dev/null
+++ b/sound/hda/Kconfig
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_HDA_CORE
+ tristate
+ select REGMAP
+
+config SND_HDA_DSP_LOADER
+ bool
+
+config SND_HDA_ALIGNED_MMIO
+ bool
+
+config SND_HDA_COMPONENT
+ bool
+
+config SND_HDA_I915
+ bool
+ select SND_HDA_COMPONENT
+
+config SND_HDA_EXT_CORE
+ tristate
+ select SND_HDA_CORE
+
+config SND_HDA_PREALLOC_SIZE
+ int "Pre-allocated buffer size for HD-audio driver" if !SND_DMA_SGBUF
+ range 0 32768
+ default 0 if SND_DMA_SGBUF
+ default 64 if !SND_DMA_SGBUF
+ help
+ Specifies the default pre-allocated buffer-size in kB for the
+ HD-audio driver. A larger buffer (e.g. 2048) is preferred
+ for systems using PulseAudio. The default 64 is chosen just
+ for compatibility reasons.
+ On x86 systems, the default is zero as we need no preallocation.
+
+ Note that the pre-allocation size can be changed dynamically
+ via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too.
+
+config SND_INTEL_NHLT
+ bool
+ # this config should be selected only for Intel ACPI platforms.
+ # A fallback is provided so that the code compiles in all cases.
+
+config SND_INTEL_DSP_CONFIG
+ tristate
+ select SND_INTEL_NHLT if ACPI
+ select SND_INTEL_SOUNDWIRE_ACPI if ACPI
+ # this config should be selected only for Intel DSP platforms.
+ # A fallback is provided so that the code compiles in all cases.
+
+config SND_INTEL_SOUNDWIRE_ACPI
+ tristate
+
+config SND_INTEL_BYT_PREFER_SOF
+ bool "Prefer SOF driver over SST on BY/CHT platforms"
+ depends on SND_SST_ATOM_HIFI2_PLATFORM_ACPI && SND_SOC_SOF_BAYTRAIL
+ default n
+ help
+ The kernel has 2 drivers for the Low Power Engine audio-block on
+ Bay- and Cherry-Trail SoCs. The old SST driver and the new SOF
+ driver. If both drivers are enabled then the kernel will default
+ to using the old SST driver, unless told otherwise through the
+ snd_intel_dspcfg.dsp_driver module-parameter.
+
+ Set this option to Y to make the kernel default to the new SOF
+ driver instead.
diff --git a/sound/hda/Makefile b/sound/hda/Makefile
new file mode 100644
index 0000000000..78f487a635
--- /dev/null
+++ b/sound/hda/Makefile
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o \
+ hdac_regmap.o hdac_controller.o hdac_stream.o array.o hdmi_chmap.o
+
+snd-hda-core-objs += trace.o
+CFLAGS_trace.o := -I$(src)
+
+# for sync with i915 gfx driver
+snd-hda-core-$(CONFIG_SND_HDA_COMPONENT) += hdac_component.o
+snd-hda-core-$(CONFIG_SND_HDA_I915) += hdac_i915.o
+
+obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o
+
+#extended hda
+obj-$(CONFIG_SND_HDA_EXT_CORE) += ext/
+
+snd-intel-dspcfg-objs := intel-dsp-config.o
+snd-intel-dspcfg-$(CONFIG_SND_INTEL_NHLT) += intel-nhlt.o
+obj-$(CONFIG_SND_INTEL_DSP_CONFIG) += snd-intel-dspcfg.o
+
+snd-intel-sdw-acpi-objs := intel-sdw-acpi.o
+obj-$(CONFIG_SND_INTEL_SOUNDWIRE_ACPI) += snd-intel-sdw-acpi.o
diff --git a/sound/hda/array.c b/sound/hda/array.c
new file mode 100644
index 0000000000..a204dcee00
--- /dev/null
+++ b/sound/hda/array.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * generic arrays
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+
+/**
+ * snd_array_new - get a new element from the given array
+ * @array: the array object
+ *
+ * Get a new element from the given array. If it exceeds the
+ * pre-allocated array size, re-allocate the array.
+ *
+ * Returns NULL if allocation failed.
+ */
+void *snd_array_new(struct snd_array *array)
+{
+ if (snd_BUG_ON(!array->elem_size))
+ return NULL;
+ if (array->used >= array->alloced) {
+ int num = array->alloced + array->alloc_align;
+ int oldsize = array->alloced * array->elem_size;
+ int size = (num + 1) * array->elem_size;
+ void *nlist;
+ if (snd_BUG_ON(num >= 4096))
+ return NULL;
+ nlist = krealloc(array->list, size, GFP_KERNEL);
+ if (!nlist)
+ return NULL;
+ memset(nlist + oldsize, 0, size - oldsize);
+ array->list = nlist;
+ array->alloced = num;
+ }
+ return snd_array_elem(array, array->used++);
+}
+EXPORT_SYMBOL_GPL(snd_array_new);
+
+/**
+ * snd_array_free - free the given array elements
+ * @array: the array object
+ */
+void snd_array_free(struct snd_array *array)
+{
+ kfree(array->list);
+ array->used = 0;
+ array->alloced = 0;
+ array->list = NULL;
+}
+EXPORT_SYMBOL_GPL(snd_array_free);
diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile
new file mode 100644
index 0000000000..154779bdc0
--- /dev/null
+++ b/sound/hda/ext/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o
+
+obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o
diff --git a/sound/hda/ext/hdac_ext_bus.c b/sound/hda/ext/hdac_ext_bus.c
new file mode 100644
index 0000000000..6004ea1c37
--- /dev/null
+++ b/sound/hda/ext/hdac_ext_bus.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hdac-ext-bus.c - HD-audio extended core bus functions.
+ *
+ * Copyright (C) 2014-2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/hdaudio_ext.h>
+
+MODULE_DESCRIPTION("HDA extended core");
+MODULE_LICENSE("GPL v2");
+
+/**
+ * snd_hdac_ext_bus_init - initialize a HD-audio extended bus
+ * @bus: the pointer to HDAC bus object
+ * @dev: device pointer
+ * @ops: bus verb operators
+ * @ext_ops: operators used for ASoC HDA codec drivers
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_ext_bus_init(struct hdac_bus *bus, struct device *dev,
+ const struct hdac_bus_ops *ops,
+ const struct hdac_ext_bus_ops *ext_ops)
+{
+ int ret;
+
+ ret = snd_hdac_bus_init(bus, dev, ops);
+ if (ret < 0)
+ return ret;
+
+ bus->ext_ops = ext_ops;
+ /* FIXME:
+ * Currently only one bus is supported, if there is device with more
+ * buses, bus->idx should be greater than 0, but there needs to be a
+ * reliable way to always assign same number.
+ */
+ bus->idx = 0;
+ bus->cmd_dma_state = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_init);
+
+/**
+ * snd_hdac_ext_bus_exit - clean up a HD-audio extended bus
+ * @bus: the pointer to HDAC bus object
+ */
+void snd_hdac_ext_bus_exit(struct hdac_bus *bus)
+{
+ snd_hdac_bus_exit(bus);
+ WARN_ON(!list_empty(&bus->hlink_list));
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_exit);
+
+/**
+ * snd_hdac_ext_bus_device_remove - remove HD-audio extended codec base devices
+ *
+ * @bus: the pointer to HDAC bus object
+ */
+void snd_hdac_ext_bus_device_remove(struct hdac_bus *bus)
+{
+ struct hdac_device *codec, *__codec;
+ /*
+ * we need to remove all the codec devices objects created in the
+ * snd_hdac_ext_bus_device_init
+ */
+ list_for_each_entry_safe(codec, __codec, &bus->codec_list, list) {
+ snd_hdac_device_unregister(codec);
+ put_device(&codec->dev);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_remove);
+#define dev_to_hdac(dev) (container_of((dev), \
+ struct hdac_device, dev))
+
+static inline struct hdac_driver *get_hdrv(struct device *dev)
+{
+ struct hdac_driver *hdrv = drv_to_hdac_driver(dev->driver);
+ return hdrv;
+}
+
+static inline struct hdac_device *get_hdev(struct device *dev)
+{
+ struct hdac_device *hdev = dev_to_hdac_dev(dev);
+ return hdev;
+}
+
+static int hda_ext_drv_probe(struct device *dev)
+{
+ return (get_hdrv(dev))->probe(get_hdev(dev));
+}
+
+static int hdac_ext_drv_remove(struct device *dev)
+{
+ return (get_hdrv(dev))->remove(get_hdev(dev));
+}
+
+static void hdac_ext_drv_shutdown(struct device *dev)
+{
+ return (get_hdrv(dev))->shutdown(get_hdev(dev));
+}
+
+/**
+ * snd_hda_ext_driver_register - register a driver for ext hda devices
+ *
+ * @drv: ext hda driver structure
+ */
+int snd_hda_ext_driver_register(struct hdac_driver *drv)
+{
+ drv->type = HDA_DEV_ASOC;
+ drv->driver.bus = &snd_hda_bus_type;
+ /* we use default match */
+
+ if (drv->probe)
+ drv->driver.probe = hda_ext_drv_probe;
+ if (drv->remove)
+ drv->driver.remove = hdac_ext_drv_remove;
+ if (drv->shutdown)
+ drv->driver.shutdown = hdac_ext_drv_shutdown;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(snd_hda_ext_driver_register);
+
+/**
+ * snd_hda_ext_driver_unregister - unregister a driver for ext hda devices
+ *
+ * @drv: ext hda driver structure
+ */
+void snd_hda_ext_driver_unregister(struct hdac_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(snd_hda_ext_driver_unregister);
diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c
new file mode 100644
index 0000000000..6199bb60cc
--- /dev/null
+++ b/sound/hda/ext/hdac_ext_controller.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hdac-ext-controller.c - HD-audio extended controller functions.
+ *
+ * Copyright (C) 2014-2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/hda_register.h>
+#include <sound/hdaudio_ext.h>
+
+/*
+ * processing pipe helpers - these helpers are useful for dealing with HDA
+ * new capability of processing pipelines
+ */
+
+/**
+ * snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability
+ * @bus: the pointer to HDAC bus object
+ * @enable: flag to turn on/off the capability
+ */
+void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable)
+{
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "Address of PP capability is NULL");
+ return;
+ }
+
+ if (enable)
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
+ AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN);
+ else
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
+ AZX_PPCTL_GPROCEN, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable);
+
+/**
+ * snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable
+ * @bus: the pointer to HDAC bus object
+ * @enable: flag to enable/disable interrupt
+ */
+void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable)
+{
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "Address of PP capability is NULL\n");
+ return;
+ }
+
+ if (enable)
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
+ AZX_PPCTL_PIE, AZX_PPCTL_PIE);
+ else
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
+ AZX_PPCTL_PIE, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable);
+
+/*
+ * Multilink helpers - these helpers are useful for dealing with HDA
+ * new multilink capability
+ */
+
+/**
+ * snd_hdac_ext_bus_get_ml_capabilities - get multilink capability
+ * @bus: the pointer to HDAC bus object
+ *
+ * This will parse all links and read the mlink capabilities and add them
+ * in hlink_list of extended hdac bus
+ * Note: this will be freed on bus exit by driver
+ */
+int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus)
+{
+ int idx;
+ u32 link_count;
+ struct hdac_ext_link *hlink;
+
+ link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
+
+ dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
+
+ for (idx = 0; idx < link_count; idx++) {
+ hlink = kzalloc(sizeof(*hlink), GFP_KERNEL);
+ if (!hlink)
+ return -ENOMEM;
+ hlink->index = idx;
+ hlink->bus = bus;
+ hlink->ml_addr = bus->mlcap + AZX_ML_BASE +
+ (AZX_ML_INTERVAL * idx);
+ hlink->lcaps = readl(hlink->ml_addr + AZX_REG_ML_LCAP);
+ hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID);
+
+ /* since link in On, update the ref */
+ hlink->ref_count = 1;
+
+ list_add_tail(&hlink->list, &bus->hlink_list);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities);
+
+/**
+ * snd_hdac_ext_link_free_all- free hdac extended link objects
+ *
+ * @bus: the pointer to HDAC bus object
+ */
+
+void snd_hdac_ext_link_free_all(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink;
+
+ while (!list_empty(&bus->hlink_list)) {
+ hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
+ list_del(&hlink->list);
+ kfree(hlink);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_link_free_all);
+
+/**
+ * snd_hdac_ext_bus_get_hlink_by_addr - get hlink at specified address
+ * @bus: hlink's parent bus device
+ * @addr: codec device address
+ *
+ * Returns hlink object or NULL if matching hlink is not found.
+ */
+struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_addr(struct hdac_bus *bus, int addr)
+{
+ struct hdac_ext_link *hlink;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list)
+ if (hlink->lsdiid & (0x1 << addr))
+ return hlink;
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_addr);
+
+/**
+ * snd_hdac_ext_bus_get_hlink_by_name - get hlink based on codec name
+ * @bus: the pointer to HDAC bus object
+ * @codec_name: codec name
+ */
+struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_name(struct hdac_bus *bus,
+ const char *codec_name)
+{
+ int bus_idx, addr;
+
+ if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2)
+ return NULL;
+ if (bus->idx != bus_idx)
+ return NULL;
+ if (addr < 0 || addr > 31)
+ return NULL;
+
+ return snd_hdac_ext_bus_get_hlink_by_addr(bus, addr);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_name);
+
+static int check_hdac_link_power_active(struct hdac_ext_link *hlink, bool enable)
+{
+ int timeout;
+ u32 val;
+ int mask = (1 << AZX_ML_LCTL_CPA_SHIFT);
+
+ udelay(3);
+ timeout = 150;
+
+ do {
+ val = readl(hlink->ml_addr + AZX_REG_ML_LCTL);
+ if (enable) {
+ if (((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
+ return 0;
+ } else {
+ if (!((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
+ return 0;
+ }
+ udelay(3);
+ } while (--timeout);
+
+ return -EIO;
+}
+
+/**
+ * snd_hdac_ext_bus_link_power_up -power up hda link
+ * @hlink: HD-audio extended link
+ */
+int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *hlink)
+{
+ snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
+ AZX_ML_LCTL_SPA, AZX_ML_LCTL_SPA);
+
+ return check_hdac_link_power_active(hlink, true);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up);
+
+/**
+ * snd_hdac_ext_bus_link_power_down -power down hda link
+ * @hlink: HD-audio extended link
+ */
+int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *hlink)
+{
+ snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, AZX_ML_LCTL_SPA, 0);
+
+ return check_hdac_link_power_active(hlink, false);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
+
+/**
+ * snd_hdac_ext_bus_link_power_up_all -power up all hda link
+ * @bus: the pointer to HDAC bus object
+ */
+int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink = NULL;
+ int ret;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ ret = snd_hdac_ext_bus_link_power_up(hlink);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
+
+/**
+ * snd_hdac_ext_bus_link_power_down_all -power down all hda link
+ * @bus: the pointer to HDAC bus object
+ */
+int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink = NULL;
+ int ret;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ ret = snd_hdac_ext_bus_link_power_down(hlink);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all);
+
+/**
+ * snd_hdac_ext_bus_link_set_stream_id - maps stream id to link output
+ * @link: HD-audio ext link to set up
+ * @stream: stream id
+ */
+void snd_hdac_ext_bus_link_set_stream_id(struct hdac_ext_link *link,
+ int stream)
+{
+ snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_set_stream_id);
+
+/**
+ * snd_hdac_ext_bus_link_clear_stream_id - maps stream id to link output
+ * @link: HD-audio ext link to set up
+ * @stream: stream id
+ */
+void snd_hdac_ext_bus_link_clear_stream_id(struct hdac_ext_link *link,
+ int stream)
+{
+ snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_clear_stream_id);
+
+int snd_hdac_ext_bus_link_get(struct hdac_bus *bus,
+ struct hdac_ext_link *hlink)
+{
+ unsigned long codec_mask;
+ int ret = 0;
+
+ mutex_lock(&bus->lock);
+
+ /*
+ * if we move from 0 to 1, count will be 1 so power up this link
+ * as well, also check the dma status and trigger that
+ */
+ if (++hlink->ref_count == 1) {
+ if (!bus->cmd_dma_state) {
+ snd_hdac_bus_init_cmd_io(bus);
+ bus->cmd_dma_state = true;
+ }
+
+ ret = snd_hdac_ext_bus_link_power_up(hlink);
+
+ /*
+ * clear the register to invalidate all the output streams
+ */
+ snd_hdac_updatew(hlink->ml_addr, AZX_REG_ML_LOSIDV,
+ AZX_ML_LOSIDV_STREAM_MASK, 0);
+ /*
+ * wait for 521usec for codec to report status
+ * HDA spec section 4.3 - Codec Discovery
+ */
+ udelay(521);
+ codec_mask = snd_hdac_chip_readw(bus, STATESTS);
+ dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask);
+ snd_hdac_chip_writew(bus, STATESTS, codec_mask);
+ if (!bus->codec_mask)
+ bus->codec_mask = codec_mask;
+ }
+
+ mutex_unlock(&bus->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get);
+
+int snd_hdac_ext_bus_link_put(struct hdac_bus *bus,
+ struct hdac_ext_link *hlink)
+{
+ int ret = 0;
+ struct hdac_ext_link *hlink_tmp;
+ bool link_up = false;
+
+ mutex_lock(&bus->lock);
+
+ /*
+ * if we move from 1 to 0, count will be 0
+ * so power down this link as well
+ */
+ if (--hlink->ref_count == 0) {
+ ret = snd_hdac_ext_bus_link_power_down(hlink);
+
+ /*
+ * now check if all links are off, if so turn off
+ * cmd dma as well
+ */
+ list_for_each_entry(hlink_tmp, &bus->hlink_list, list) {
+ if (hlink_tmp->ref_count) {
+ link_up = true;
+ break;
+ }
+ }
+
+ if (!link_up) {
+ snd_hdac_bus_stop_cmd_io(bus);
+ bus->cmd_dma_state = false;
+ }
+ }
+
+ mutex_unlock(&bus->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put);
+
+static void hdac_ext_codec_link_up(struct hdac_device *codec)
+{
+ const char *devname = dev_name(&codec->dev);
+ struct hdac_ext_link *hlink =
+ snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
+
+ if (hlink)
+ snd_hdac_ext_bus_link_get(codec->bus, hlink);
+}
+
+static void hdac_ext_codec_link_down(struct hdac_device *codec)
+{
+ const char *devname = dev_name(&codec->dev);
+ struct hdac_ext_link *hlink =
+ snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
+
+ if (hlink)
+ snd_hdac_ext_bus_link_put(codec->bus, hlink);
+}
+
+void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable)
+{
+ struct hdac_bus *bus = codec->bus;
+ bool oldstate = test_bit(codec->addr, &bus->codec_powered);
+
+ if (enable == oldstate)
+ return;
+
+ snd_hdac_bus_link_power(codec, enable);
+
+ if (enable)
+ hdac_ext_codec_link_up(codec);
+ else
+ hdac_ext_codec_link_down(codec);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power);
diff --git a/sound/hda/ext/hdac_ext_stream.c b/sound/hda/ext/hdac_ext_stream.c
new file mode 100644
index 0000000000..11b7119cc4
--- /dev/null
+++ b/sound/hda/ext/hdac_ext_stream.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hdac-ext-stream.c - HD-audio extended stream operations.
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/hda_register.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/compress_driver.h>
+
+/**
+ * snd_hdac_ext_stream_init - initialize each stream (aka device)
+ * @bus: HD-audio core bus
+ * @hext_stream: HD-audio ext core stream object to initialize
+ * @idx: stream index number
+ * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)
+ * @tag: the tag id to assign
+ *
+ * initialize the stream, if ppcap is enabled then init those and then
+ * invoke hdac stream initialization routine
+ */
+static void snd_hdac_ext_stream_init(struct hdac_bus *bus,
+ struct hdac_ext_stream *hext_stream,
+ int idx, int direction, int tag)
+{
+ if (bus->ppcap) {
+ hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
+ AZX_PPHC_INTERVAL * idx;
+
+ hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
+ AZX_PPLC_MULTI * bus->num_streams +
+ AZX_PPLC_INTERVAL * idx;
+ }
+
+ hext_stream->decoupled = false;
+ snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);
+}
+
+/**
+ * snd_hdac_ext_stream_init_all - create and initialize the stream objects
+ * for an extended hda bus
+ * @bus: HD-audio core bus
+ * @start_idx: start index for streams
+ * @num_stream: number of streams to initialize
+ * @dir: direction of streams
+ */
+int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
+ int num_stream, int dir)
+{
+ int stream_tag = 0;
+ int i, tag, idx = start_idx;
+
+ for (i = 0; i < num_stream; i++) {
+ struct hdac_ext_stream *hext_stream =
+ kzalloc(sizeof(*hext_stream), GFP_KERNEL);
+ if (!hext_stream)
+ return -ENOMEM;
+ tag = ++stream_tag;
+ snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
+ idx++;
+ }
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
+
+/**
+ * snd_hdac_ext_stream_free_all - free hdac extended stream objects
+ *
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
+{
+ struct hdac_stream *s, *_s;
+ struct hdac_ext_stream *hext_stream;
+
+ list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
+ hext_stream = stream_to_hdac_ext_stream(s);
+ snd_hdac_ext_stream_decouple(bus, hext_stream, false);
+ list_del(&s->list);
+ kfree(hext_stream);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
+
+void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
+ struct hdac_ext_stream *hext_stream,
+ bool decouple)
+{
+ struct hdac_stream *hstream = &hext_stream->hstream;
+ u32 val;
+ int mask = AZX_PPCTL_PROCEN(hstream->index);
+
+ val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
+
+ if (decouple && !val)
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
+ else if (!decouple && val)
+ snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
+
+ hext_stream->decoupled = decouple;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
+
+/**
+ * snd_hdac_ext_stream_decouple - decouple the hdac stream
+ * @bus: HD-audio core bus
+ * @hext_stream: HD-audio ext core stream object to initialize
+ * @decouple: flag to decouple
+ */
+void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
+ struct hdac_ext_stream *hext_stream, bool decouple)
+{
+ spin_lock_irq(&bus->reg_lock);
+ snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
+
+/**
+ * snd_hdac_ext_stream_start - start a stream
+ * @hext_stream: HD-audio ext core stream to start
+ */
+void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)
+{
+ snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
+ AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);
+
+/**
+ * snd_hdac_ext_stream_clear - stop a stream DMA
+ * @hext_stream: HD-audio ext core stream to stop
+ */
+void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)
+{
+ snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);
+
+/**
+ * snd_hdac_ext_stream_reset - reset a stream
+ * @hext_stream: HD-audio ext core stream to reset
+ */
+void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)
+{
+ unsigned char val;
+ int timeout;
+
+ snd_hdac_ext_stream_clear(hext_stream);
+
+ snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
+ AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
+ udelay(3);
+ timeout = 50;
+ do {
+ val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
+ AZX_PPLCCTL_STRST;
+ if (val)
+ break;
+ udelay(3);
+ } while (--timeout);
+ val &= ~AZX_PPLCCTL_STRST;
+ writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
+ udelay(3);
+
+ timeout = 50;
+ /* waiting for hardware to report that the stream is out of reset */
+ do {
+ val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
+ if (!val)
+ break;
+ udelay(3);
+ } while (--timeout);
+
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);
+
+/**
+ * snd_hdac_ext_stream_setup - set up the SD for streaming
+ * @hext_stream: HD-audio ext core stream to set up
+ * @fmt: stream format
+ */
+int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
+{
+ struct hdac_stream *hstream = &hext_stream->hstream;
+ unsigned int val;
+
+ /* make sure the run bit is zero for SD */
+ snd_hdac_ext_stream_clear(hext_stream);
+ /* program the stream_tag */
+ val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);
+ val = (val & ~AZX_PPLCCTL_STRM_MASK) |
+ (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
+ writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
+
+ /* program the stream format */
+ writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
+
+static struct hdac_ext_stream *
+hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *hstream = NULL;
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "stream type not supported\n");
+ return NULL;
+ }
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(hstream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hext_stream = container_of(hstream,
+ struct hdac_ext_stream,
+ hstream);
+ if (hstream->direction != substream->stream)
+ continue;
+
+ /* check if link stream is available */
+ if (!hext_stream->link_locked) {
+ res = hext_stream;
+ break;
+ }
+
+ }
+ if (res) {
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+ res->link_locked = 1;
+ res->link_substream = substream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+ return res;
+}
+
+static struct hdac_ext_stream *
+hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *hstream = NULL;
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "stream type not supported\n");
+ return NULL;
+ }
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(hstream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hext_stream = container_of(hstream,
+ struct hdac_ext_stream,
+ hstream);
+ if (hstream->direction != substream->stream)
+ continue;
+
+ if (!hstream->opened) {
+ res = hext_stream;
+ break;
+ }
+ }
+ if (res) {
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+ res->hstream.opened = 1;
+ res->hstream.running = 0;
+ res->hstream.substream = substream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ return res;
+}
+
+/**
+ * snd_hdac_ext_stream_assign - assign a stream for the PCM
+ * @bus: HD-audio core bus
+ * @substream: PCM substream to assign
+ * @type: type of stream (coupled, host or link stream)
+ *
+ * This assigns the stream based on the type (coupled/host/link), for the
+ * given PCM substream, assigns it and returns the stream object
+ *
+ * coupled: Looks for an unused stream
+ * host: Looks for an unused decoupled host stream
+ * link: Looks for an unused decoupled link stream
+ *
+ * If no stream is free, returns NULL. The function tries to keep using
+ * the same stream object when it's used beforehand. when a stream is
+ * decoupled, it becomes a host stream and link stream.
+ */
+struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream,
+ int type)
+{
+ struct hdac_ext_stream *hext_stream = NULL;
+ struct hdac_stream *hstream = NULL;
+
+ switch (type) {
+ case HDAC_EXT_STREAM_TYPE_COUPLED:
+ hstream = snd_hdac_stream_assign(bus, substream);
+ if (hstream)
+ hext_stream = container_of(hstream,
+ struct hdac_ext_stream,
+ hstream);
+ return hext_stream;
+
+ case HDAC_EXT_STREAM_TYPE_HOST:
+ return hdac_ext_host_dma_stream_assign(bus, substream);
+
+ case HDAC_EXT_STREAM_TYPE_LINK:
+ return hdac_ext_link_dma_stream_assign(bus, substream);
+
+ default:
+ return NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
+
+/**
+ * snd_hdac_ext_stream_release - release the assigned stream
+ * @hext_stream: HD-audio ext core stream to release
+ * @type: type of stream (coupled, host or link stream)
+ *
+ * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
+ */
+void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
+{
+ struct hdac_bus *bus = hext_stream->hstream.bus;
+
+ switch (type) {
+ case HDAC_EXT_STREAM_TYPE_COUPLED:
+ snd_hdac_stream_release(&hext_stream->hstream);
+ break;
+
+ case HDAC_EXT_STREAM_TYPE_HOST:
+ spin_lock_irq(&bus->reg_lock);
+ /* couple link only if not in use */
+ if (!hext_stream->link_locked)
+ snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
+ snd_hdac_stream_release_locked(&hext_stream->hstream);
+ spin_unlock_irq(&bus->reg_lock);
+ break;
+
+ case HDAC_EXT_STREAM_TYPE_LINK:
+ spin_lock_irq(&bus->reg_lock);
+ /* couple host only if not in use */
+ if (!hext_stream->hstream.opened)
+ snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
+ hext_stream->link_locked = 0;
+ hext_stream->link_substream = NULL;
+ spin_unlock_irq(&bus->reg_lock);
+ break;
+
+ default:
+ dev_dbg(bus->dev, "Invalid type %d\n", type);
+ }
+
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
+
+/**
+ * snd_hdac_ext_cstream_assign - assign a host stream for compress
+ * @bus: HD-audio core bus
+ * @cstream: Compress stream to assign
+ *
+ * Assign an unused host stream for the given compress stream.
+ * If no stream is free, NULL is returned. Stream is decoupled
+ * before assignment.
+ */
+struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,
+ struct snd_compr_stream *cstream)
+{
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *hstream;
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(hstream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
+
+ if (hstream->direction != cstream->direction)
+ continue;
+
+ if (!hstream->opened) {
+ res = hext_stream;
+ break;
+ }
+ }
+
+ if (res) {
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+ res->hstream.opened = 1;
+ res->hstream.running = 0;
+ res->hstream.cstream = cstream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);
diff --git a/sound/hda/hda_bus_type.c b/sound/hda/hda_bus_type.c
new file mode 100644
index 0000000000..4cd94178df
--- /dev/null
+++ b/sound/hda/hda_bus_type.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio bus
+ */
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/export.h>
+#include <sound/hdaudio.h>
+
+MODULE_DESCRIPTION("HD-audio bus");
+MODULE_LICENSE("GPL");
+
+/**
+ * hdac_get_device_id - gets the hdac device id entry
+ * @hdev: HD-audio core device
+ * @drv: HD-audio codec driver
+ *
+ * Compares the hdac device vendor_id and revision_id to the hdac_device
+ * driver id_table and returns the matching device id entry.
+ */
+const struct hda_device_id *
+hdac_get_device_id(struct hdac_device *hdev, struct hdac_driver *drv)
+{
+ if (drv->id_table) {
+ const struct hda_device_id *id = drv->id_table;
+
+ while (id->vendor_id) {
+ if (hdev->vendor_id == id->vendor_id &&
+ (!id->rev_id || id->rev_id == hdev->revision_id))
+ return id;
+ id++;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(hdac_get_device_id);
+
+static int hdac_codec_match(struct hdac_device *dev, struct hdac_driver *drv)
+{
+ if (hdac_get_device_id(dev, drv))
+ return 1;
+ else
+ return 0;
+}
+
+static int hda_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct hdac_device *hdev = dev_to_hdac_dev(dev);
+ struct hdac_driver *hdrv = drv_to_hdac_driver(drv);
+
+ if (hdev->type != hdrv->type)
+ return 0;
+
+ /*
+ * if driver provided a match function use that otherwise we will
+ * use hdac_codec_match function
+ */
+ if (hdrv->match)
+ return hdrv->match(hdev, hdrv);
+ else
+ return hdac_codec_match(hdev, hdrv);
+ return 1;
+}
+
+static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+ char modalias[32];
+
+ snd_hdac_codec_modalias(dev_to_hdac_dev(dev), modalias,
+ sizeof(modalias));
+ if (add_uevent_var(env, "MODALIAS=%s", modalias))
+ return -ENOMEM;
+ return 0;
+}
+
+struct bus_type snd_hda_bus_type = {
+ .name = "hdaudio",
+ .match = hda_bus_match,
+ .uevent = hda_uevent,
+};
+EXPORT_SYMBOL_GPL(snd_hda_bus_type);
+
+static int __init hda_bus_init(void)
+{
+ return bus_register(&snd_hda_bus_type);
+}
+
+static void __exit hda_bus_exit(void)
+{
+ bus_unregister(&snd_hda_bus_type);
+}
+
+subsys_initcall(hda_bus_init);
+module_exit(hda_bus_exit);
diff --git a/sound/hda/hdac_bus.c b/sound/hda/hdac_bus.c
new file mode 100644
index 0000000000..d497414a55
--- /dev/null
+++ b/sound/hda/hdac_bus.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio core bus driver
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <sound/hdaudio.h>
+#include "local.h"
+#include "trace.h"
+
+static void snd_hdac_bus_process_unsol_events(struct work_struct *work);
+
+static const struct hdac_bus_ops default_ops = {
+ .command = snd_hdac_bus_send_cmd,
+ .get_response = snd_hdac_bus_get_response,
+ .link_power = snd_hdac_bus_link_power,
+};
+
+/**
+ * snd_hdac_bus_init - initialize a HD-audio bas bus
+ * @bus: the pointer to bus object
+ * @dev: device pointer
+ * @ops: bus verb operators
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
+ const struct hdac_bus_ops *ops)
+{
+ memset(bus, 0, sizeof(*bus));
+ bus->dev = dev;
+ if (ops)
+ bus->ops = ops;
+ else
+ bus->ops = &default_ops;
+ bus->dma_type = SNDRV_DMA_TYPE_DEV;
+ INIT_LIST_HEAD(&bus->stream_list);
+ INIT_LIST_HEAD(&bus->codec_list);
+ INIT_WORK(&bus->unsol_work, snd_hdac_bus_process_unsol_events);
+ spin_lock_init(&bus->reg_lock);
+ mutex_init(&bus->cmd_mutex);
+ mutex_init(&bus->lock);
+ INIT_LIST_HEAD(&bus->hlink_list);
+ init_waitqueue_head(&bus->rirb_wq);
+ bus->irq = -1;
+
+ /*
+ * Default value of '8' is as per the HD audio specification (Rev 1.0a).
+ * Following relation is used to derive STRIPE control value.
+ * For sample rate <= 48K:
+ * { ((num_channels * bits_per_sample) / number of SDOs) >= 8 }
+ * For sample rate > 48K:
+ * { ((num_channels * bits_per_sample * rate/48000) /
+ * number of SDOs) >= 8 }
+ */
+ bus->sdo_limit = 8;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_init);
+
+/**
+ * snd_hdac_bus_exit - clean up a HD-audio bas bus
+ * @bus: the pointer to bus object
+ */
+void snd_hdac_bus_exit(struct hdac_bus *bus)
+{
+ WARN_ON(!list_empty(&bus->stream_list));
+ WARN_ON(!list_empty(&bus->codec_list));
+ cancel_work_sync(&bus->unsol_work);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_exit);
+
+/**
+ * snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus
+ * @bus: bus object
+ * @addr: the HDAC device address
+ * @cmd: HD-audio encoded verb
+ * @res: pointer to store the response, NULL if performing asynchronously
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
+ unsigned int cmd, unsigned int *res)
+{
+ int err;
+
+ mutex_lock(&bus->cmd_mutex);
+ err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res);
+ mutex_unlock(&bus->cmd_mutex);
+ return err;
+}
+
+/**
+ * snd_hdac_bus_exec_verb_unlocked - unlocked version
+ * @bus: bus object
+ * @addr: the HDAC device address
+ * @cmd: HD-audio encoded verb
+ * @res: pointer to store the response, NULL if performing asynchronously
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
+ unsigned int cmd, unsigned int *res)
+{
+ unsigned int tmp;
+ int err;
+
+ if (cmd == ~0)
+ return -EINVAL;
+
+ if (res)
+ *res = -1;
+ else if (bus->sync_write)
+ res = &tmp;
+ for (;;) {
+ trace_hda_send_cmd(bus, cmd);
+ err = bus->ops->command(bus, cmd);
+ if (err != -EAGAIN)
+ break;
+ /* process pending verbs */
+ err = bus->ops->get_response(bus, addr, &tmp);
+ if (err)
+ break;
+ }
+ if (!err && res) {
+ err = bus->ops->get_response(bus, addr, res);
+ trace_hda_get_response(bus, addr, *res);
+ }
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked);
+
+/**
+ * snd_hdac_bus_queue_event - add an unsolicited event to queue
+ * @bus: the BUS
+ * @res: unsolicited event (lower 32bit of RIRB entry)
+ * @res_ex: codec addr and flags (upper 32bit or RIRB entry)
+ *
+ * Adds the given event to the queue. The events are processed in
+ * the workqueue asynchronously. Call this function in the interrupt
+ * hanlder when RIRB receives an unsolicited event.
+ */
+void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex)
+{
+ unsigned int wp;
+
+ if (!bus)
+ return;
+
+ trace_hda_unsol_event(bus, res, res_ex);
+ wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE;
+ bus->unsol_wp = wp;
+
+ wp <<= 1;
+ bus->unsol_queue[wp] = res;
+ bus->unsol_queue[wp + 1] = res_ex;
+
+ schedule_work(&bus->unsol_work);
+}
+
+/*
+ * process queued unsolicited events
+ */
+static void snd_hdac_bus_process_unsol_events(struct work_struct *work)
+{
+ struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work);
+ struct hdac_device *codec;
+ struct hdac_driver *drv;
+ unsigned int rp, caddr, res;
+
+ spin_lock_irq(&bus->reg_lock);
+ while (bus->unsol_rp != bus->unsol_wp) {
+ rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE;
+ bus->unsol_rp = rp;
+ rp <<= 1;
+ res = bus->unsol_queue[rp];
+ caddr = bus->unsol_queue[rp + 1];
+ if (!(caddr & (1 << 4))) /* no unsolicited event? */
+ continue;
+ codec = bus->caddr_tbl[caddr & 0x0f];
+ if (!codec || !codec->registered)
+ continue;
+ spin_unlock_irq(&bus->reg_lock);
+ drv = drv_to_hdac_driver(codec->dev.driver);
+ if (drv->unsol_event)
+ drv->unsol_event(codec, res);
+ spin_lock_irq(&bus->reg_lock);
+ }
+ spin_unlock_irq(&bus->reg_lock);
+}
+
+/**
+ * snd_hdac_bus_add_device - Add a codec to bus
+ * @bus: HDA core bus
+ * @codec: HDA core device to add
+ *
+ * Adds the given codec to the list in the bus. The caddr_tbl array
+ * and codec_powered bits are updated, as well.
+ * Returns zero if success, or a negative error code.
+ */
+int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec)
+{
+ if (bus->caddr_tbl[codec->addr]) {
+ dev_err(bus->dev, "address 0x%x is already occupied\n",
+ codec->addr);
+ return -EBUSY;
+ }
+
+ list_add_tail(&codec->list, &bus->codec_list);
+ bus->caddr_tbl[codec->addr] = codec;
+ set_bit(codec->addr, &bus->codec_powered);
+ bus->num_codecs++;
+ return 0;
+}
+
+/**
+ * snd_hdac_bus_remove_device - Remove a codec from bus
+ * @bus: HDA core bus
+ * @codec: HDA core device to remove
+ */
+void snd_hdac_bus_remove_device(struct hdac_bus *bus,
+ struct hdac_device *codec)
+{
+ WARN_ON(bus != codec->bus);
+ if (list_empty(&codec->list))
+ return;
+ list_del_init(&codec->list);
+ bus->caddr_tbl[codec->addr] = NULL;
+ clear_bit(codec->addr, &bus->codec_powered);
+ bus->num_codecs--;
+ flush_work(&bus->unsol_work);
+}
+
+#ifdef CONFIG_SND_HDA_ALIGNED_MMIO
+/* Helpers for aligned read/write of mmio space, for Tegra */
+unsigned int snd_hdac_aligned_read(void __iomem *addr, unsigned int mask)
+{
+ void __iomem *aligned_addr =
+ (void __iomem *)((unsigned long)(addr) & ~0x3);
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ unsigned int v;
+
+ v = readl(aligned_addr);
+ return (v >> shift) & mask;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_aligned_read);
+
+void snd_hdac_aligned_write(unsigned int val, void __iomem *addr,
+ unsigned int mask)
+{
+ void __iomem *aligned_addr =
+ (void __iomem *)((unsigned long)(addr) & ~0x3);
+ unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
+ unsigned int v;
+
+ v = readl(aligned_addr);
+ v &= ~(mask << shift);
+ v |= val << shift;
+ writel(v, aligned_addr);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_aligned_write);
+#endif /* CONFIG_SND_HDA_ALIGNED_MMIO */
+
+void snd_hdac_codec_link_up(struct hdac_device *codec)
+{
+ struct hdac_bus *bus = codec->bus;
+
+ if (bus->ops->link_power)
+ bus->ops->link_power(codec, true);
+ else
+ snd_hdac_bus_link_power(codec, true);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_codec_link_up);
+
+void snd_hdac_codec_link_down(struct hdac_device *codec)
+{
+ struct hdac_bus *bus = codec->bus;
+
+ if (bus->ops->link_power)
+ bus->ops->link_power(codec, false);
+ else
+ snd_hdac_bus_link_power(codec, false);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_codec_link_down);
diff --git a/sound/hda/hdac_component.c b/sound/hda/hdac_component.c
new file mode 100644
index 0000000000..bb37e7e0bd
--- /dev/null
+++ b/sound/hda/hdac_component.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+// hdac_component.c - routines for sync between HD-A core and DRM driver
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/component.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_component.h>
+#include <sound/hda_register.h>
+
+static void hdac_acomp_release(struct device *dev, void *res)
+{
+}
+
+static struct drm_audio_component *hdac_get_acomp(struct device *dev)
+{
+ return devres_find(dev, hdac_acomp_release, NULL, NULL);
+}
+
+/**
+ * snd_hdac_set_codec_wakeup - Enable / disable HDMI/DP codec wakeup
+ * @bus: HDA core bus
+ * @enable: enable or disable the wakeup
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function should be called during the chip reset, also called at
+ * resume for updating STATESTS register read.
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable)
+{
+ struct drm_audio_component *acomp = bus->audio_component;
+
+ if (!acomp || !acomp->ops)
+ return -ENODEV;
+
+ if (!acomp->ops->codec_wake_override)
+ return 0;
+
+ dev_dbg(bus->dev, "%s codec wakeup\n",
+ enable ? "enable" : "disable");
+
+ acomp->ops->codec_wake_override(acomp->dev, enable);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup);
+
+/**
+ * snd_hdac_display_power - Power up / down the power refcount
+ * @bus: HDA core bus
+ * @idx: HDA codec address, pass HDA_CODEC_IDX_CONTROLLER for controller
+ * @enable: power up or down
+ *
+ * This function is used by either HD-audio controller or codec driver that
+ * needs the interaction with graphics driver.
+ *
+ * This function updates the power status, and calls the get_power() and
+ * put_power() ops accordingly, toggling the codec wakeup, too.
+ */
+void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable)
+{
+ struct drm_audio_component *acomp = bus->audio_component;
+
+ dev_dbg(bus->dev, "display power %s\n",
+ enable ? "enable" : "disable");
+
+ mutex_lock(&bus->lock);
+ if (enable)
+ set_bit(idx, &bus->display_power_status);
+ else
+ clear_bit(idx, &bus->display_power_status);
+
+ if (!acomp || !acomp->ops)
+ goto unlock;
+
+ if (bus->display_power_status) {
+ if (!bus->display_power_active) {
+ unsigned long cookie = -1;
+
+ if (acomp->ops->get_power)
+ cookie = acomp->ops->get_power(acomp->dev);
+
+ snd_hdac_set_codec_wakeup(bus, true);
+ snd_hdac_set_codec_wakeup(bus, false);
+ bus->display_power_active = cookie;
+ }
+ } else {
+ if (bus->display_power_active) {
+ unsigned long cookie = bus->display_power_active;
+
+ if (acomp->ops->put_power)
+ acomp->ops->put_power(acomp->dev, cookie);
+
+ bus->display_power_active = 0;
+ }
+ }
+ unlock:
+ mutex_unlock(&bus->lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_display_power);
+
+/**
+ * snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate
+ * @codec: HDA codec
+ * @nid: the pin widget NID
+ * @dev_id: device identifier
+ * @rate: the sample rate to set
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function sets N/CTS value based on the given sample rate.
+ * Returns zero for success, or a negative error code.
+ */
+int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid,
+ int dev_id, int rate)
+{
+ struct hdac_bus *bus = codec->bus;
+ struct drm_audio_component *acomp = bus->audio_component;
+ int port, pipe;
+
+ if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate)
+ return -ENODEV;
+ port = nid;
+ if (acomp->audio_ops && acomp->audio_ops->pin2port) {
+ port = acomp->audio_ops->pin2port(codec, nid);
+ if (port < 0)
+ return -EINVAL;
+ }
+ pipe = dev_id;
+ return acomp->ops->sync_audio_rate(acomp->dev, port, pipe, rate);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate);
+
+/**
+ * snd_hdac_acomp_get_eld - Get the audio state and ELD via component
+ * @codec: HDA codec
+ * @nid: the pin widget NID
+ * @dev_id: device identifier
+ * @audio_enabled: the pointer to store the current audio state
+ * @buffer: the buffer pointer to store ELD bytes
+ * @max_bytes: the max bytes to be stored on @buffer
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function queries the current state of the audio on the given
+ * digital port and fetches the ELD bytes onto the given buffer.
+ * It returns the number of bytes for the total ELD data, zero for
+ * invalid ELD, or a negative error code.
+ *
+ * The return size is the total bytes required for the whole ELD bytes,
+ * thus it may be over @max_bytes. If it's over @max_bytes, it implies
+ * that only a part of ELD bytes have been fetched.
+ */
+int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid, int dev_id,
+ bool *audio_enabled, char *buffer, int max_bytes)
+{
+ struct hdac_bus *bus = codec->bus;
+ struct drm_audio_component *acomp = bus->audio_component;
+ int port, pipe;
+
+ if (!acomp || !acomp->ops || !acomp->ops->get_eld)
+ return -ENODEV;
+
+ port = nid;
+ if (acomp->audio_ops && acomp->audio_ops->pin2port) {
+ port = acomp->audio_ops->pin2port(codec, nid);
+ if (port < 0)
+ return -EINVAL;
+ }
+ pipe = dev_id;
+ return acomp->ops->get_eld(acomp->dev, port, pipe, audio_enabled,
+ buffer, max_bytes);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_acomp_get_eld);
+
+static int hdac_component_master_bind(struct device *dev)
+{
+ struct drm_audio_component *acomp = hdac_get_acomp(dev);
+ int ret;
+
+ if (WARN_ON(!acomp))
+ return -EINVAL;
+
+ ret = component_bind_all(dev, acomp);
+ if (ret < 0)
+ return ret;
+
+ if (WARN_ON(!(acomp->dev && acomp->ops))) {
+ ret = -EINVAL;
+ goto out_unbind;
+ }
+
+ /* pin the module to avoid dynamic unbinding, but only if given */
+ if (!try_module_get(acomp->ops->owner)) {
+ ret = -ENODEV;
+ goto out_unbind;
+ }
+
+ if (acomp->audio_ops && acomp->audio_ops->master_bind) {
+ ret = acomp->audio_ops->master_bind(dev, acomp);
+ if (ret < 0)
+ goto module_put;
+ }
+
+ complete_all(&acomp->master_bind_complete);
+ return 0;
+
+ module_put:
+ module_put(acomp->ops->owner);
+out_unbind:
+ component_unbind_all(dev, acomp);
+ complete_all(&acomp->master_bind_complete);
+
+ return ret;
+}
+
+static void hdac_component_master_unbind(struct device *dev)
+{
+ struct drm_audio_component *acomp = hdac_get_acomp(dev);
+
+ if (acomp->audio_ops && acomp->audio_ops->master_unbind)
+ acomp->audio_ops->master_unbind(dev, acomp);
+ module_put(acomp->ops->owner);
+ component_unbind_all(dev, acomp);
+ WARN_ON(acomp->ops || acomp->dev);
+}
+
+static const struct component_master_ops hdac_component_master_ops = {
+ .bind = hdac_component_master_bind,
+ .unbind = hdac_component_master_unbind,
+};
+
+/**
+ * snd_hdac_acomp_register_notifier - Register audio component ops
+ * @bus: HDA core bus
+ * @aops: audio component ops
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function sets the given ops to be called by the graphics driver.
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_acomp_register_notifier(struct hdac_bus *bus,
+ const struct drm_audio_component_audio_ops *aops)
+{
+ if (!bus->audio_component)
+ return -ENODEV;
+
+ bus->audio_component->audio_ops = aops;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_acomp_register_notifier);
+
+/**
+ * snd_hdac_acomp_init - Initialize audio component
+ * @bus: HDA core bus
+ * @aops: audio component ops
+ * @match_master: match function for finding components
+ * @extra_size: Extra bytes to allocate
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function initializes and sets up the audio component to communicate
+ * with graphics driver.
+ *
+ * Unlike snd_hdac_i915_init(), this function doesn't synchronize with the
+ * binding with the DRM component. Each caller needs to sync via master_bind
+ * audio_ops.
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_acomp_init(struct hdac_bus *bus,
+ const struct drm_audio_component_audio_ops *aops,
+ int (*match_master)(struct device *, int, void *),
+ size_t extra_size)
+{
+ struct component_match *match = NULL;
+ struct device *dev = bus->dev;
+ struct drm_audio_component *acomp;
+ int ret;
+
+ if (WARN_ON(hdac_get_acomp(dev)))
+ return -EBUSY;
+
+ acomp = devres_alloc(hdac_acomp_release, sizeof(*acomp) + extra_size,
+ GFP_KERNEL);
+ if (!acomp)
+ return -ENOMEM;
+ acomp->audio_ops = aops;
+ init_completion(&acomp->master_bind_complete);
+ bus->audio_component = acomp;
+ devres_add(dev, acomp);
+
+ component_match_add_typed(dev, &match, match_master, bus);
+ ret = component_master_add_with_match(dev, &hdac_component_master_ops,
+ match);
+ if (ret < 0)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ bus->audio_component = NULL;
+ devres_destroy(dev, hdac_acomp_release, NULL, NULL);
+ dev_info(dev, "failed to add audio component master (%d)\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_acomp_init);
+
+/**
+ * snd_hdac_acomp_exit - Finalize audio component
+ * @bus: HDA core bus
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with graphics driver.
+ *
+ * This function releases the audio component that has been used.
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_acomp_exit(struct hdac_bus *bus)
+{
+ struct device *dev = bus->dev;
+ struct drm_audio_component *acomp = bus->audio_component;
+
+ if (!acomp)
+ return 0;
+
+ if (WARN_ON(bus->display_power_active) && acomp->ops)
+ acomp->ops->put_power(acomp->dev, bus->display_power_active);
+
+ bus->display_power_active = 0;
+ bus->display_power_status = 0;
+
+ component_master_del(dev, &hdac_component_master_ops);
+
+ bus->audio_component = NULL;
+ devres_destroy(dev, hdac_acomp_release, NULL, NULL);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_acomp_exit);
diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c
new file mode 100644
index 0000000000..7f3a000fab
--- /dev/null
+++ b/sound/hda/hdac_controller.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio controller helpers
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_register.h>
+#include "local.h"
+
+/* clear CORB read pointer properly */
+static void azx_clear_corbrp(struct hdac_bus *bus)
+{
+ int timeout;
+
+ for (timeout = 1000; timeout > 0; timeout--) {
+ if (snd_hdac_chip_readw(bus, CORBRP) & AZX_CORBRP_RST)
+ break;
+ udelay(1);
+ }
+ if (timeout <= 0)
+ dev_err(bus->dev, "CORB reset timeout#1, CORBRP = %d\n",
+ snd_hdac_chip_readw(bus, CORBRP));
+
+ snd_hdac_chip_writew(bus, CORBRP, 0);
+ for (timeout = 1000; timeout > 0; timeout--) {
+ if (snd_hdac_chip_readw(bus, CORBRP) == 0)
+ break;
+ udelay(1);
+ }
+ if (timeout <= 0)
+ dev_err(bus->dev, "CORB reset timeout#2, CORBRP = %d\n",
+ snd_hdac_chip_readw(bus, CORBRP));
+}
+
+/**
+ * snd_hdac_bus_init_cmd_io - set up CORB/RIRB buffers
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
+{
+ WARN_ON_ONCE(!bus->rb.area);
+
+ spin_lock_irq(&bus->reg_lock);
+ /* CORB set up */
+ bus->corb.addr = bus->rb.addr;
+ bus->corb.buf = (__le32 *)bus->rb.area;
+ snd_hdac_chip_writel(bus, CORBLBASE, (u32)bus->corb.addr);
+ snd_hdac_chip_writel(bus, CORBUBASE, upper_32_bits(bus->corb.addr));
+
+ /* set the corb size to 256 entries (ULI requires explicitly) */
+ snd_hdac_chip_writeb(bus, CORBSIZE, 0x02);
+ /* set the corb write pointer to 0 */
+ snd_hdac_chip_writew(bus, CORBWP, 0);
+
+ /* reset the corb hw read pointer */
+ snd_hdac_chip_writew(bus, CORBRP, AZX_CORBRP_RST);
+ if (!bus->corbrp_self_clear)
+ azx_clear_corbrp(bus);
+
+ /* enable corb dma */
+ snd_hdac_chip_writeb(bus, CORBCTL, AZX_CORBCTL_RUN);
+
+ /* RIRB set up */
+ bus->rirb.addr = bus->rb.addr + 2048;
+ bus->rirb.buf = (__le32 *)(bus->rb.area + 2048);
+ bus->rirb.wp = bus->rirb.rp = 0;
+ memset(bus->rirb.cmds, 0, sizeof(bus->rirb.cmds));
+ snd_hdac_chip_writel(bus, RIRBLBASE, (u32)bus->rirb.addr);
+ snd_hdac_chip_writel(bus, RIRBUBASE, upper_32_bits(bus->rirb.addr));
+
+ /* set the rirb size to 256 entries (ULI requires explicitly) */
+ snd_hdac_chip_writeb(bus, RIRBSIZE, 0x02);
+ /* reset the rirb hw write pointer */
+ snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST);
+ /* set N=1, get RIRB response interrupt for new entry */
+ snd_hdac_chip_writew(bus, RINTCNT, 1);
+ /* enable rirb dma and response irq */
+ if (bus->not_use_interrupts)
+ snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN);
+ else
+ snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+ /* Accept unsolicited responses */
+ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io);
+
+/* wait for cmd dmas till they are stopped */
+static void hdac_wait_for_cmd_dmas(struct hdac_bus *bus)
+{
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(100);
+ while ((snd_hdac_chip_readb(bus, RIRBCTL) & AZX_RBCTL_DMA_EN)
+ && time_before(jiffies, timeout))
+ udelay(10);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+ while ((snd_hdac_chip_readb(bus, CORBCTL) & AZX_CORBCTL_RUN)
+ && time_before(jiffies, timeout))
+ udelay(10);
+}
+
+/**
+ * snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_bus_stop_cmd_io(struct hdac_bus *bus)
+{
+ spin_lock_irq(&bus->reg_lock);
+ /* disable ringbuffer DMAs */
+ snd_hdac_chip_writeb(bus, RIRBCTL, 0);
+ snd_hdac_chip_writeb(bus, CORBCTL, 0);
+ spin_unlock_irq(&bus->reg_lock);
+
+ hdac_wait_for_cmd_dmas(bus);
+
+ spin_lock_irq(&bus->reg_lock);
+ /* disable unsolicited responses */
+ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0);
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_cmd_io);
+
+static unsigned int azx_command_addr(u32 cmd)
+{
+ unsigned int addr = cmd >> 28;
+
+ if (snd_BUG_ON(addr >= HDA_MAX_CODECS))
+ addr = 0;
+ return addr;
+}
+
+/**
+ * snd_hdac_bus_send_cmd - send a command verb via CORB
+ * @bus: HD-audio core bus
+ * @val: encoded verb value to send
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val)
+{
+ unsigned int addr = azx_command_addr(val);
+ unsigned int wp, rp;
+
+ spin_lock_irq(&bus->reg_lock);
+
+ bus->last_cmd[azx_command_addr(val)] = val;
+
+ /* add command to corb */
+ wp = snd_hdac_chip_readw(bus, CORBWP);
+ if (wp == 0xffff) {
+ /* something wrong, controller likely turned to D3 */
+ spin_unlock_irq(&bus->reg_lock);
+ return -EIO;
+ }
+ wp++;
+ wp %= AZX_MAX_CORB_ENTRIES;
+
+ rp = snd_hdac_chip_readw(bus, CORBRP);
+ if (wp == rp) {
+ /* oops, it's full */
+ spin_unlock_irq(&bus->reg_lock);
+ return -EAGAIN;
+ }
+
+ bus->rirb.cmds[addr]++;
+ bus->corb.buf[wp] = cpu_to_le32(val);
+ snd_hdac_chip_writew(bus, CORBWP, wp);
+
+ spin_unlock_irq(&bus->reg_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_send_cmd);
+
+#define AZX_RIRB_EX_UNSOL_EV (1<<4)
+
+/**
+ * snd_hdac_bus_update_rirb - retrieve RIRB entries
+ * @bus: HD-audio core bus
+ *
+ * Usually called from interrupt handler.
+ * The caller needs bus->reg_lock spinlock before calling this.
+ */
+void snd_hdac_bus_update_rirb(struct hdac_bus *bus)
+{
+ unsigned int rp, wp;
+ unsigned int addr;
+ u32 res, res_ex;
+
+ wp = snd_hdac_chip_readw(bus, RIRBWP);
+ if (wp == 0xffff) {
+ /* something wrong, controller likely turned to D3 */
+ return;
+ }
+
+ if (wp == bus->rirb.wp)
+ return;
+ bus->rirb.wp = wp;
+
+ while (bus->rirb.rp != wp) {
+ bus->rirb.rp++;
+ bus->rirb.rp %= AZX_MAX_RIRB_ENTRIES;
+
+ rp = bus->rirb.rp << 1; /* an RIRB entry is 8-bytes */
+ res_ex = le32_to_cpu(bus->rirb.buf[rp + 1]);
+ res = le32_to_cpu(bus->rirb.buf[rp]);
+ addr = res_ex & 0xf;
+ if (addr >= HDA_MAX_CODECS) {
+ dev_err(bus->dev,
+ "spurious response %#x:%#x, rp = %d, wp = %d",
+ res, res_ex, bus->rirb.rp, wp);
+ snd_BUG();
+ } else if (res_ex & AZX_RIRB_EX_UNSOL_EV)
+ snd_hdac_bus_queue_event(bus, res, res_ex);
+ else if (bus->rirb.cmds[addr]) {
+ bus->rirb.res[addr] = res;
+ bus->rirb.cmds[addr]--;
+ if (!bus->rirb.cmds[addr] &&
+ waitqueue_active(&bus->rirb_wq))
+ wake_up(&bus->rirb_wq);
+ } else {
+ dev_err_ratelimited(bus->dev,
+ "spurious response %#x:%#x, last cmd=%#08x\n",
+ res, res_ex, bus->last_cmd[addr]);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_update_rirb);
+
+/**
+ * snd_hdac_bus_get_response - receive a response via RIRB
+ * @bus: HD-audio core bus
+ * @addr: codec address
+ * @res: pointer to store the value, NULL when not needed
+ *
+ * Returns zero if a value is read, or a negative error code.
+ */
+int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
+ unsigned int *res)
+{
+ unsigned long timeout;
+ unsigned long loopcounter;
+ wait_queue_entry_t wait;
+ bool warned = false;
+
+ init_wait_entry(&wait, 0);
+ timeout = jiffies + msecs_to_jiffies(1000);
+
+ for (loopcounter = 0;; loopcounter++) {
+ spin_lock_irq(&bus->reg_lock);
+ if (!bus->polling_mode)
+ prepare_to_wait(&bus->rirb_wq, &wait,
+ TASK_UNINTERRUPTIBLE);
+ if (bus->polling_mode)
+ snd_hdac_bus_update_rirb(bus);
+ if (!bus->rirb.cmds[addr]) {
+ if (res)
+ *res = bus->rirb.res[addr]; /* the last value */
+ if (!bus->polling_mode)
+ finish_wait(&bus->rirb_wq, &wait);
+ spin_unlock_irq(&bus->reg_lock);
+ return 0;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+ if (time_after(jiffies, timeout))
+ break;
+#define LOOP_COUNT_MAX 3000
+ if (!bus->polling_mode) {
+ schedule_timeout(msecs_to_jiffies(2));
+ } else if (bus->needs_damn_long_delay ||
+ loopcounter > LOOP_COUNT_MAX) {
+ if (loopcounter > LOOP_COUNT_MAX && !warned) {
+ dev_dbg_ratelimited(bus->dev,
+ "too slow response, last cmd=%#08x\n",
+ bus->last_cmd[addr]);
+ warned = true;
+ }
+ msleep(2); /* temporary workaround */
+ } else {
+ udelay(10);
+ cond_resched();
+ }
+ }
+
+ if (!bus->polling_mode)
+ finish_wait(&bus->rirb_wq, &wait);
+
+ return -EIO;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response);
+
+#define HDAC_MAX_CAPS 10
+/**
+ * snd_hdac_bus_parse_capabilities - parse capability structure
+ * @bus: the pointer to bus object
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus)
+{
+ unsigned int cur_cap;
+ unsigned int offset;
+ unsigned int counter = 0;
+
+ offset = snd_hdac_chip_readw(bus, LLCH);
+
+ /* Lets walk the linked capabilities list */
+ do {
+ cur_cap = _snd_hdac_chip_readl(bus, offset);
+
+ dev_dbg(bus->dev, "Capability version: 0x%x\n",
+ (cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF);
+
+ dev_dbg(bus->dev, "HDA capability ID: 0x%x\n",
+ (cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF);
+
+ if (cur_cap == -1) {
+ dev_dbg(bus->dev, "Invalid capability reg read\n");
+ break;
+ }
+
+ switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) {
+ case AZX_ML_CAP_ID:
+ dev_dbg(bus->dev, "Found ML capability\n");
+ bus->mlcap = bus->remap_addr + offset;
+ break;
+
+ case AZX_GTS_CAP_ID:
+ dev_dbg(bus->dev, "Found GTS capability offset=%x\n", offset);
+ bus->gtscap = bus->remap_addr + offset;
+ break;
+
+ case AZX_PP_CAP_ID:
+ /* PP capability found, the Audio DSP is present */
+ dev_dbg(bus->dev, "Found PP capability offset=%x\n", offset);
+ bus->ppcap = bus->remap_addr + offset;
+ break;
+
+ case AZX_SPB_CAP_ID:
+ /* SPIB capability found, handler function */
+ dev_dbg(bus->dev, "Found SPB capability\n");
+ bus->spbcap = bus->remap_addr + offset;
+ break;
+
+ case AZX_DRSM_CAP_ID:
+ /* DMA resume capability found, handler function */
+ dev_dbg(bus->dev, "Found DRSM capability\n");
+ bus->drsmcap = bus->remap_addr + offset;
+ break;
+
+ default:
+ dev_err(bus->dev, "Unknown capability %d\n", cur_cap);
+ cur_cap = 0;
+ break;
+ }
+
+ counter++;
+
+ if (counter > HDAC_MAX_CAPS) {
+ dev_err(bus->dev, "We exceeded HDAC capabilities!!!\n");
+ break;
+ }
+
+ /* read the offset of next capability */
+ offset = cur_cap & AZX_CAP_HDR_NXT_PTR_MASK;
+
+ } while (offset);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_parse_capabilities);
+
+/*
+ * Lowlevel interface
+ */
+
+/**
+ * snd_hdac_bus_enter_link_reset - enter link reset
+ * @bus: HD-audio core bus
+ *
+ * Enter to the link reset state.
+ */
+void snd_hdac_bus_enter_link_reset(struct hdac_bus *bus)
+{
+ unsigned long timeout;
+
+ /* reset controller */
+ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, 0);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+ while ((snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET) &&
+ time_before(jiffies, timeout))
+ usleep_range(500, 1000);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_enter_link_reset);
+
+/**
+ * snd_hdac_bus_exit_link_reset - exit link reset
+ * @bus: HD-audio core bus
+ *
+ * Exit from the link reset state.
+ */
+void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus)
+{
+ unsigned long timeout;
+
+ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+ while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout))
+ usleep_range(500, 1000);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_exit_link_reset);
+
+/* reset codec link */
+int snd_hdac_bus_reset_link(struct hdac_bus *bus, bool full_reset)
+{
+ if (!full_reset)
+ goto skip_reset;
+
+ /* clear STATESTS if not in reset */
+ if (snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET)
+ snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
+
+ /* reset controller */
+ snd_hdac_bus_enter_link_reset(bus);
+
+ /* delay for >= 100us for codec PLL to settle per spec
+ * Rev 0.9 section 5.5.1
+ */
+ usleep_range(500, 1000);
+
+ /* Bring controller out of reset */
+ snd_hdac_bus_exit_link_reset(bus);
+
+ /* Brent Chartrand said to wait >= 540us for codecs to initialize */
+ usleep_range(1000, 1200);
+
+ skip_reset:
+ /* check to see if controller is ready */
+ if (!snd_hdac_chip_readb(bus, GCTL)) {
+ dev_dbg(bus->dev, "controller not ready!\n");
+ return -EBUSY;
+ }
+
+ /* detect codecs */
+ if (!bus->codec_mask) {
+ bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
+ dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_reset_link);
+
+/* enable interrupts */
+static void azx_int_enable(struct hdac_bus *bus)
+{
+ /* enable controller CIE and GIE */
+ snd_hdac_chip_updatel(bus, INTCTL,
+ AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN,
+ AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
+}
+
+/* disable interrupts */
+static void azx_int_disable(struct hdac_bus *bus)
+{
+ struct hdac_stream *azx_dev;
+
+ /* disable interrupts in stream descriptor */
+ list_for_each_entry(azx_dev, &bus->stream_list, list)
+ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0);
+
+ /* disable SIE for all streams & disable controller CIE and GIE */
+ snd_hdac_chip_writel(bus, INTCTL, 0);
+}
+
+/* clear interrupts */
+static void azx_int_clear(struct hdac_bus *bus)
+{
+ struct hdac_stream *azx_dev;
+
+ /* clear stream status */
+ list_for_each_entry(azx_dev, &bus->stream_list, list)
+ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+
+ /* clear STATESTS */
+ snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
+
+ /* clear rirb status */
+ snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
+
+ /* clear int status */
+ snd_hdac_chip_writel(bus, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM);
+}
+
+/**
+ * snd_hdac_bus_init_chip - reset and start the controller registers
+ * @bus: HD-audio core bus
+ * @full_reset: Do full reset
+ */
+bool snd_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset)
+{
+ if (bus->chip_init)
+ return false;
+
+ /* reset controller */
+ snd_hdac_bus_reset_link(bus, full_reset);
+
+ /* clear interrupts */
+ azx_int_clear(bus);
+
+ /* initialize the codec command I/O */
+ snd_hdac_bus_init_cmd_io(bus);
+
+ /* enable interrupts after CORB/RIRB buffers are initialized above */
+ azx_int_enable(bus);
+
+ /* program the position buffer */
+ if (bus->use_posbuf && bus->posbuf.addr) {
+ snd_hdac_chip_writel(bus, DPLBASE, (u32)bus->posbuf.addr);
+ snd_hdac_chip_writel(bus, DPUBASE, upper_32_bits(bus->posbuf.addr));
+ }
+
+ bus->chip_init = true;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_init_chip);
+
+/**
+ * snd_hdac_bus_stop_chip - disable the whole IRQ and I/Os
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_bus_stop_chip(struct hdac_bus *bus)
+{
+ if (!bus->chip_init)
+ return;
+
+ /* disable interrupts */
+ azx_int_disable(bus);
+ azx_int_clear(bus);
+
+ /* disable CORB/RIRB */
+ snd_hdac_bus_stop_cmd_io(bus);
+
+ /* disable position buffer */
+ if (bus->posbuf.addr) {
+ snd_hdac_chip_writel(bus, DPLBASE, 0);
+ snd_hdac_chip_writel(bus, DPUBASE, 0);
+ }
+
+ bus->chip_init = false;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_chip);
+
+/**
+ * snd_hdac_bus_handle_stream_irq - interrupt handler for streams
+ * @bus: HD-audio core bus
+ * @status: INTSTS register value
+ * @ack: callback to be called for woken streams
+ *
+ * Returns the bits of handled streams, or zero if no stream is handled.
+ */
+int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status,
+ void (*ack)(struct hdac_bus *,
+ struct hdac_stream *))
+{
+ struct hdac_stream *azx_dev;
+ u8 sd_status;
+ int handled = 0;
+
+ list_for_each_entry(azx_dev, &bus->stream_list, list) {
+ if (status & azx_dev->sd_int_sta_mask) {
+ sd_status = snd_hdac_stream_readb(azx_dev, SD_STS);
+ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
+ handled |= 1 << azx_dev->index;
+ if ((!azx_dev->substream && !azx_dev->cstream) ||
+ !azx_dev->running || !(sd_status & SD_INT_COMPLETE))
+ continue;
+ if (ack)
+ ack(bus, azx_dev);
+ }
+ }
+ return handled;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_handle_stream_irq);
+
+/**
+ * snd_hdac_bus_alloc_stream_pages - allocate BDL and other buffers
+ * @bus: HD-audio core bus
+ *
+ * Call this after assigning the all streams.
+ * Returns zero for success, or a negative error code.
+ */
+int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus)
+{
+ struct hdac_stream *s;
+ int num_streams = 0;
+ int dma_type = bus->dma_type ? bus->dma_type : SNDRV_DMA_TYPE_DEV;
+ int err;
+
+ list_for_each_entry(s, &bus->stream_list, list) {
+ /* allocate memory for the BDL for each stream */
+ err = snd_dma_alloc_pages(dma_type, bus->dev,
+ BDL_SIZE, &s->bdl);
+ num_streams++;
+ if (err < 0)
+ return -ENOMEM;
+ }
+
+ if (WARN_ON(!num_streams))
+ return -EINVAL;
+ /* allocate memory for the position buffer */
+ err = snd_dma_alloc_pages(dma_type, bus->dev,
+ num_streams * 8, &bus->posbuf);
+ if (err < 0)
+ return -ENOMEM;
+ list_for_each_entry(s, &bus->stream_list, list)
+ s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8);
+
+ /* single page (at least 4096 bytes) must suffice for both ringbuffes */
+ return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages);
+
+/**
+ * snd_hdac_bus_free_stream_pages - release BDL and other buffers
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus)
+{
+ struct hdac_stream *s;
+
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (s->bdl.area)
+ snd_dma_free_pages(&s->bdl);
+ }
+
+ if (bus->rb.area)
+ snd_dma_free_pages(&bus->rb);
+ if (bus->posbuf.area)
+ snd_dma_free_pages(&bus->posbuf);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages);
+
+/**
+ * snd_hdac_bus_link_power - power up/down codec link
+ * @codec: HD-audio device
+ * @enable: whether to power-up the link
+ */
+void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable)
+{
+ if (enable)
+ set_bit(codec->addr, &codec->bus->codec_powered);
+ else
+ clear_bit(codec->addr, &codec->bus->codec_powered);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power);
diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c
new file mode 100644
index 0000000000..bbf7bcdb44
--- /dev/null
+++ b/sound/hda/hdac_device.c
@@ -0,0 +1,1102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio codec core device
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/pm_runtime.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_regmap.h>
+#include <sound/pcm.h>
+#include "local.h"
+
+static void setup_fg_nodes(struct hdac_device *codec);
+static int get_codec_vendor_name(struct hdac_device *codec);
+
+static void default_release(struct device *dev)
+{
+ snd_hdac_device_exit(dev_to_hdac_dev(dev));
+}
+
+/**
+ * snd_hdac_device_init - initialize the HD-audio codec base device
+ * @codec: device to initialize
+ * @bus: but to attach
+ * @name: device name string
+ * @addr: codec address
+ *
+ * Returns zero for success or a negative error code.
+ *
+ * This function increments the runtime PM counter and marks it active.
+ * The caller needs to turn it off appropriately later.
+ *
+ * The caller needs to set the device's release op properly by itself.
+ */
+int snd_hdac_device_init(struct hdac_device *codec, struct hdac_bus *bus,
+ const char *name, unsigned int addr)
+{
+ struct device *dev;
+ hda_nid_t fg;
+ int err;
+
+ dev = &codec->dev;
+ device_initialize(dev);
+ dev->parent = bus->dev;
+ dev->bus = &snd_hda_bus_type;
+ dev->release = default_release;
+ dev->groups = hdac_dev_attr_groups;
+ dev_set_name(dev, "%s", name);
+ device_enable_async_suspend(dev);
+
+ codec->bus = bus;
+ codec->addr = addr;
+ codec->type = HDA_DEV_CORE;
+ mutex_init(&codec->widget_lock);
+ mutex_init(&codec->regmap_lock);
+ pm_runtime_set_active(&codec->dev);
+ pm_runtime_get_noresume(&codec->dev);
+ atomic_set(&codec->in_pm, 0);
+
+ err = snd_hdac_bus_add_device(bus, codec);
+ if (err < 0)
+ goto error;
+
+ /* fill parameters */
+ codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
+ AC_PAR_VENDOR_ID);
+ if (codec->vendor_id == -1) {
+ /* read again, hopefully the access method was corrected
+ * in the last read...
+ */
+ codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
+ AC_PAR_VENDOR_ID);
+ }
+
+ codec->subsystem_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
+ AC_PAR_SUBSYSTEM_ID);
+ codec->revision_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
+ AC_PAR_REV_ID);
+
+ setup_fg_nodes(codec);
+ if (!codec->afg && !codec->mfg) {
+ dev_err(dev, "no AFG or MFG node found\n");
+ err = -ENODEV;
+ goto error;
+ }
+
+ fg = codec->afg ? codec->afg : codec->mfg;
+
+ err = snd_hdac_refresh_widgets(codec);
+ if (err < 0)
+ goto error;
+
+ codec->power_caps = snd_hdac_read_parm(codec, fg, AC_PAR_POWER_STATE);
+ /* reread ssid if not set by parameter */
+ if (codec->subsystem_id == -1 || codec->subsystem_id == 0)
+ snd_hdac_read(codec, fg, AC_VERB_GET_SUBSYSTEM_ID, 0,
+ &codec->subsystem_id);
+
+ err = get_codec_vendor_name(codec);
+ if (err < 0)
+ goto error;
+
+ codec->chip_name = kasprintf(GFP_KERNEL, "ID %x",
+ codec->vendor_id & 0xffff);
+ if (!codec->chip_name) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ put_device(&codec->dev);
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_init);
+
+/**
+ * snd_hdac_device_exit - clean up the HD-audio codec base device
+ * @codec: device to clean up
+ */
+void snd_hdac_device_exit(struct hdac_device *codec)
+{
+ pm_runtime_put_noidle(&codec->dev);
+ /* keep balance of runtime PM child_count in parent device */
+ pm_runtime_set_suspended(&codec->dev);
+ snd_hdac_bus_remove_device(codec->bus, codec);
+ kfree(codec->vendor_name);
+ kfree(codec->chip_name);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_exit);
+
+/**
+ * snd_hdac_device_register - register the hd-audio codec base device
+ * @codec: the device to register
+ */
+int snd_hdac_device_register(struct hdac_device *codec)
+{
+ int err;
+
+ err = device_add(&codec->dev);
+ if (err < 0)
+ return err;
+ mutex_lock(&codec->widget_lock);
+ err = hda_widget_sysfs_init(codec);
+ mutex_unlock(&codec->widget_lock);
+ if (err < 0) {
+ device_del(&codec->dev);
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_register);
+
+/**
+ * snd_hdac_device_unregister - unregister the hd-audio codec base device
+ * @codec: the device to unregister
+ */
+void snd_hdac_device_unregister(struct hdac_device *codec)
+{
+ if (device_is_registered(&codec->dev)) {
+ mutex_lock(&codec->widget_lock);
+ hda_widget_sysfs_exit(codec);
+ mutex_unlock(&codec->widget_lock);
+ device_del(&codec->dev);
+ snd_hdac_bus_remove_device(codec->bus, codec);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_unregister);
+
+/**
+ * snd_hdac_device_set_chip_name - set/update the codec name
+ * @codec: the HDAC device
+ * @name: name string to set
+ *
+ * Returns 0 if the name is set or updated, or a negative error code.
+ */
+int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name)
+{
+ char *newname;
+
+ if (!name)
+ return 0;
+ newname = kstrdup(name, GFP_KERNEL);
+ if (!newname)
+ return -ENOMEM;
+ kfree(codec->chip_name);
+ codec->chip_name = newname;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_set_chip_name);
+
+/**
+ * snd_hdac_codec_modalias - give the module alias name
+ * @codec: HDAC device
+ * @buf: string buffer to store
+ * @size: string buffer size
+ *
+ * Returns the size of string, like snprintf(), or a negative error code.
+ */
+int snd_hdac_codec_modalias(const struct hdac_device *codec, char *buf, size_t size)
+{
+ return scnprintf(buf, size, "hdaudio:v%08Xr%08Xa%02X\n",
+ codec->vendor_id, codec->revision_id, codec->type);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_codec_modalias);
+
+/**
+ * snd_hdac_make_cmd - compose a 32bit command word to be sent to the
+ * HD-audio controller
+ * @codec: the codec object
+ * @nid: NID to encode
+ * @verb: verb to encode
+ * @parm: parameter to encode
+ *
+ * Return an encoded command verb or -1 for error.
+ */
+static unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid,
+ unsigned int verb, unsigned int parm)
+{
+ u32 val, addr;
+
+ addr = codec->addr;
+ if ((addr & ~0xf) || (nid & ~0x7f) ||
+ (verb & ~0xfff) || (parm & ~0xffff)) {
+ dev_err(&codec->dev, "out of range cmd %x:%x:%x:%x\n",
+ addr, nid, verb, parm);
+ return -1;
+ }
+
+ val = addr << 28;
+ val |= (u32)nid << 20;
+ val |= verb << 8;
+ val |= parm;
+ return val;
+}
+
+/**
+ * snd_hdac_exec_verb - execute an encoded verb
+ * @codec: the codec object
+ * @cmd: encoded verb to execute
+ * @flags: optional flags, pass zero for default
+ * @res: the pointer to store the result, NULL if running async
+ *
+ * Returns zero if successful, or a negative error code.
+ *
+ * This calls the exec_verb op when set in hdac_codec. If not,
+ * call the default snd_hdac_bus_exec_verb().
+ */
+int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd,
+ unsigned int flags, unsigned int *res)
+{
+ if (codec->exec_verb)
+ return codec->exec_verb(codec, cmd, flags, res);
+ return snd_hdac_bus_exec_verb(codec->bus, codec->addr, cmd, res);
+}
+
+
+/**
+ * snd_hdac_read - execute a verb
+ * @codec: the codec object
+ * @nid: NID to execute a verb
+ * @verb: verb to execute
+ * @parm: parameter for a verb
+ * @res: the pointer to store the result, NULL if running async
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_hdac_read(struct hdac_device *codec, hda_nid_t nid,
+ unsigned int verb, unsigned int parm, unsigned int *res)
+{
+ unsigned int cmd = snd_hdac_make_cmd(codec, nid, verb, parm);
+
+ return snd_hdac_exec_verb(codec, cmd, 0, res);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_read);
+
+/**
+ * _snd_hdac_read_parm - read a parmeter
+ * @codec: the codec object
+ * @nid: NID to read a parameter
+ * @parm: parameter to read
+ * @res: pointer to store the read value
+ *
+ * This function returns zero or an error unlike snd_hdac_read_parm().
+ */
+int _snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm,
+ unsigned int *res)
+{
+ unsigned int cmd;
+
+ cmd = snd_hdac_regmap_encode_verb(nid, AC_VERB_PARAMETERS) | parm;
+ return snd_hdac_regmap_read_raw(codec, cmd, res);
+}
+EXPORT_SYMBOL_GPL(_snd_hdac_read_parm);
+
+/**
+ * snd_hdac_read_parm_uncached - read a codec parameter without caching
+ * @codec: the codec object
+ * @nid: NID to read a parameter
+ * @parm: parameter to read
+ *
+ * Returns -1 for error. If you need to distinguish the error more
+ * strictly, use snd_hdac_read() directly.
+ */
+int snd_hdac_read_parm_uncached(struct hdac_device *codec, hda_nid_t nid,
+ int parm)
+{
+ unsigned int cmd, val;
+
+ cmd = snd_hdac_regmap_encode_verb(nid, AC_VERB_PARAMETERS) | parm;
+ if (snd_hdac_regmap_read_raw_uncached(codec, cmd, &val) < 0)
+ return -1;
+ return val;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_read_parm_uncached);
+
+/**
+ * snd_hdac_override_parm - override read-only parameters
+ * @codec: the codec object
+ * @nid: NID for the parameter
+ * @parm: the parameter to change
+ * @val: the parameter value to overwrite
+ */
+int snd_hdac_override_parm(struct hdac_device *codec, hda_nid_t nid,
+ unsigned int parm, unsigned int val)
+{
+ unsigned int verb = (AC_VERB_PARAMETERS << 8) | (nid << 20) | parm;
+ int err;
+
+ if (!codec->regmap)
+ return -EINVAL;
+
+ codec->caps_overwriting = true;
+ err = snd_hdac_regmap_write_raw(codec, verb, val);
+ codec->caps_overwriting = false;
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_override_parm);
+
+/**
+ * snd_hdac_get_sub_nodes - get start NID and number of subtree nodes
+ * @codec: the codec object
+ * @nid: NID to inspect
+ * @start_id: the pointer to store the starting NID
+ *
+ * Returns the number of subtree nodes or zero if not found.
+ * This function reads parameters always without caching.
+ */
+int snd_hdac_get_sub_nodes(struct hdac_device *codec, hda_nid_t nid,
+ hda_nid_t *start_id)
+{
+ unsigned int parm;
+
+ parm = snd_hdac_read_parm_uncached(codec, nid, AC_PAR_NODE_COUNT);
+ if (parm == -1) {
+ *start_id = 0;
+ return 0;
+ }
+ *start_id = (parm >> 16) & 0x7fff;
+ return (int)(parm & 0x7fff);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_sub_nodes);
+
+/*
+ * look for an AFG and MFG nodes
+ */
+static void setup_fg_nodes(struct hdac_device *codec)
+{
+ int i, total_nodes, function_id;
+ hda_nid_t nid;
+
+ total_nodes = snd_hdac_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
+ for (i = 0; i < total_nodes; i++, nid++) {
+ function_id = snd_hdac_read_parm(codec, nid,
+ AC_PAR_FUNCTION_TYPE);
+ switch (function_id & 0xff) {
+ case AC_GRP_AUDIO_FUNCTION:
+ codec->afg = nid;
+ codec->afg_function_id = function_id & 0xff;
+ codec->afg_unsol = (function_id >> 8) & 1;
+ break;
+ case AC_GRP_MODEM_FUNCTION:
+ codec->mfg = nid;
+ codec->mfg_function_id = function_id & 0xff;
+ codec->mfg_unsol = (function_id >> 8) & 1;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * snd_hdac_refresh_widgets - Reset the widget start/end nodes
+ * @codec: the codec object
+ */
+int snd_hdac_refresh_widgets(struct hdac_device *codec)
+{
+ hda_nid_t start_nid;
+ int nums, err = 0;
+
+ /*
+ * Serialize against multiple threads trying to update the sysfs
+ * widgets array.
+ */
+ mutex_lock(&codec->widget_lock);
+ nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid);
+ if (!start_nid || nums <= 0 || nums >= 0xff) {
+ dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n",
+ codec->afg);
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = hda_widget_sysfs_reinit(codec, start_nid, nums);
+ if (err < 0)
+ goto unlock;
+
+ codec->num_nodes = nums;
+ codec->start_nid = start_nid;
+ codec->end_nid = start_nid + nums;
+unlock:
+ mutex_unlock(&codec->widget_lock);
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_refresh_widgets);
+
+/* return CONNLIST_LEN parameter of the given widget */
+static unsigned int get_num_conns(struct hdac_device *codec, hda_nid_t nid)
+{
+ unsigned int wcaps = get_wcaps(codec, nid);
+ unsigned int parm;
+
+ if (!(wcaps & AC_WCAP_CONN_LIST) &&
+ get_wcaps_type(wcaps) != AC_WID_VOL_KNB)
+ return 0;
+
+ parm = snd_hdac_read_parm(codec, nid, AC_PAR_CONNLIST_LEN);
+ if (parm == -1)
+ parm = 0;
+ return parm;
+}
+
+/**
+ * snd_hdac_get_connections - get a widget connection list
+ * @codec: the codec object
+ * @nid: NID
+ * @conn_list: the array to store the results, can be NULL
+ * @max_conns: the max size of the given array
+ *
+ * Returns the number of connected widgets, zero for no connection, or a
+ * negative error code. When the number of elements don't fit with the
+ * given array size, it returns -ENOSPC.
+ *
+ * When @conn_list is NULL, it just checks the number of connections.
+ */
+int snd_hdac_get_connections(struct hdac_device *codec, hda_nid_t nid,
+ hda_nid_t *conn_list, int max_conns)
+{
+ unsigned int parm;
+ int i, conn_len, conns, err;
+ unsigned int shift, num_elems, mask;
+ hda_nid_t prev_nid;
+ int null_count = 0;
+
+ parm = get_num_conns(codec, nid);
+ if (!parm)
+ return 0;
+
+ if (parm & AC_CLIST_LONG) {
+ /* long form */
+ shift = 16;
+ num_elems = 2;
+ } else {
+ /* short form */
+ shift = 8;
+ num_elems = 4;
+ }
+ conn_len = parm & AC_CLIST_LENGTH;
+ mask = (1 << (shift-1)) - 1;
+
+ if (!conn_len)
+ return 0; /* no connection */
+
+ if (conn_len == 1) {
+ /* single connection */
+ err = snd_hdac_read(codec, nid, AC_VERB_GET_CONNECT_LIST, 0,
+ &parm);
+ if (err < 0)
+ return err;
+ if (conn_list)
+ conn_list[0] = parm & mask;
+ return 1;
+ }
+
+ /* multi connection */
+ conns = 0;
+ prev_nid = 0;
+ for (i = 0; i < conn_len; i++) {
+ int range_val;
+ hda_nid_t val, n;
+
+ if (i % num_elems == 0) {
+ err = snd_hdac_read(codec, nid,
+ AC_VERB_GET_CONNECT_LIST, i,
+ &parm);
+ if (err < 0)
+ return -EIO;
+ }
+ range_val = !!(parm & (1 << (shift-1))); /* ranges */
+ val = parm & mask;
+ if (val == 0 && null_count++) { /* no second chance */
+ dev_dbg(&codec->dev,
+ "invalid CONNECT_LIST verb %x[%i]:%x\n",
+ nid, i, parm);
+ return 0;
+ }
+ parm >>= shift;
+ if (range_val) {
+ /* ranges between the previous and this one */
+ if (!prev_nid || prev_nid >= val) {
+ dev_warn(&codec->dev,
+ "invalid dep_range_val %x:%x\n",
+ prev_nid, val);
+ continue;
+ }
+ for (n = prev_nid + 1; n <= val; n++) {
+ if (conn_list) {
+ if (conns >= max_conns)
+ return -ENOSPC;
+ conn_list[conns] = n;
+ }
+ conns++;
+ }
+ } else {
+ if (conn_list) {
+ if (conns >= max_conns)
+ return -ENOSPC;
+ conn_list[conns] = val;
+ }
+ conns++;
+ }
+ prev_nid = val;
+ }
+ return conns;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_connections);
+
+#ifdef CONFIG_PM
+/**
+ * snd_hdac_power_up - power up the codec
+ * @codec: the codec object
+ *
+ * This function calls the runtime PM helper to power up the given codec.
+ * Unlike snd_hdac_power_up_pm(), you should call this only for the code
+ * path that isn't included in PM path. Otherwise it gets stuck.
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_hdac_power_up(struct hdac_device *codec)
+{
+ return pm_runtime_get_sync(&codec->dev);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_power_up);
+
+/**
+ * snd_hdac_power_down - power down the codec
+ * @codec: the codec object
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_hdac_power_down(struct hdac_device *codec)
+{
+ struct device *dev = &codec->dev;
+
+ pm_runtime_mark_last_busy(dev);
+ return pm_runtime_put_autosuspend(dev);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_power_down);
+
+/**
+ * snd_hdac_power_up_pm - power up the codec
+ * @codec: the codec object
+ *
+ * This function can be called in a recursive code path like init code
+ * which may be called by PM suspend/resume again. OTOH, if a power-up
+ * call must wake up the sleeper (e.g. in a kctl callback), use
+ * snd_hdac_power_up() instead.
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_hdac_power_up_pm(struct hdac_device *codec)
+{
+ if (!atomic_inc_not_zero(&codec->in_pm))
+ return snd_hdac_power_up(codec);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm);
+
+/* like snd_hdac_power_up_pm(), but only increment the pm count when
+ * already powered up. Returns -1 if not powered up, 1 if incremented
+ * or 0 if unchanged. Only used in hdac_regmap.c
+ */
+int snd_hdac_keep_power_up(struct hdac_device *codec)
+{
+ if (!atomic_inc_not_zero(&codec->in_pm)) {
+ int ret = pm_runtime_get_if_active(&codec->dev, true);
+ if (!ret)
+ return -1;
+ if (ret < 0)
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * snd_hdac_power_down_pm - power down the codec
+ * @codec: the codec object
+ *
+ * Like snd_hdac_power_up_pm(), this function is used in a recursive
+ * code path like init code which may be called by PM suspend/resume again.
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_hdac_power_down_pm(struct hdac_device *codec)
+{
+ if (atomic_dec_if_positive(&codec->in_pm) < 0)
+ return snd_hdac_power_down(codec);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm);
+#endif
+
+/* codec vendor labels */
+struct hda_vendor_id {
+ unsigned int id;
+ const char *name;
+};
+
+static const struct hda_vendor_id hda_vendor_ids[] = {
+ { 0x0014, "Loongson" },
+ { 0x1002, "ATI" },
+ { 0x1013, "Cirrus Logic" },
+ { 0x1057, "Motorola" },
+ { 0x1095, "Silicon Image" },
+ { 0x10de, "Nvidia" },
+ { 0x10ec, "Realtek" },
+ { 0x1102, "Creative" },
+ { 0x1106, "VIA" },
+ { 0x111d, "IDT" },
+ { 0x11c1, "LSI" },
+ { 0x11d4, "Analog Devices" },
+ { 0x13f6, "C-Media" },
+ { 0x14f1, "Conexant" },
+ { 0x17e8, "Chrontel" },
+ { 0x1854, "LG" },
+ { 0x19e5, "Huawei" },
+ { 0x1aec, "Wolfson Microelectronics" },
+ { 0x1af4, "QEMU" },
+ { 0x434d, "C-Media" },
+ { 0x8086, "Intel" },
+ { 0x8384, "SigmaTel" },
+ {} /* terminator */
+};
+
+/* store the codec vendor name */
+static int get_codec_vendor_name(struct hdac_device *codec)
+{
+ const struct hda_vendor_id *c;
+ u16 vendor_id = codec->vendor_id >> 16;
+
+ for (c = hda_vendor_ids; c->id; c++) {
+ if (c->id == vendor_id) {
+ codec->vendor_name = kstrdup(c->name, GFP_KERNEL);
+ return codec->vendor_name ? 0 : -ENOMEM;
+ }
+ }
+
+ codec->vendor_name = kasprintf(GFP_KERNEL, "Generic %04x", vendor_id);
+ return codec->vendor_name ? 0 : -ENOMEM;
+}
+
+/*
+ * stream formats
+ */
+struct hda_rate_tbl {
+ unsigned int hz;
+ unsigned int alsa_bits;
+ unsigned int hda_fmt;
+};
+
+/* rate = base * mult / div */
+#define HDA_RATE(base, mult, div) \
+ (AC_FMT_BASE_##base##K | (((mult) - 1) << AC_FMT_MULT_SHIFT) | \
+ (((div) - 1) << AC_FMT_DIV_SHIFT))
+
+static const struct hda_rate_tbl rate_bits[] = {
+ /* rate in Hz, ALSA rate bitmask, HDA format value */
+
+ /* autodetected value used in snd_hda_query_supported_pcm */
+ { 8000, SNDRV_PCM_RATE_8000, HDA_RATE(48, 1, 6) },
+ { 11025, SNDRV_PCM_RATE_11025, HDA_RATE(44, 1, 4) },
+ { 16000, SNDRV_PCM_RATE_16000, HDA_RATE(48, 1, 3) },
+ { 22050, SNDRV_PCM_RATE_22050, HDA_RATE(44, 1, 2) },
+ { 32000, SNDRV_PCM_RATE_32000, HDA_RATE(48, 2, 3) },
+ { 44100, SNDRV_PCM_RATE_44100, HDA_RATE(44, 1, 1) },
+ { 48000, SNDRV_PCM_RATE_48000, HDA_RATE(48, 1, 1) },
+ { 88200, SNDRV_PCM_RATE_88200, HDA_RATE(44, 2, 1) },
+ { 96000, SNDRV_PCM_RATE_96000, HDA_RATE(48, 2, 1) },
+ { 176400, SNDRV_PCM_RATE_176400, HDA_RATE(44, 4, 1) },
+ { 192000, SNDRV_PCM_RATE_192000, HDA_RATE(48, 4, 1) },
+#define AC_PAR_PCM_RATE_BITS 11
+ /* up to bits 10, 384kHZ isn't supported properly */
+
+ /* not autodetected value */
+ { 9600, SNDRV_PCM_RATE_KNOT, HDA_RATE(48, 1, 5) },
+
+ { 0 } /* terminator */
+};
+
+/**
+ * snd_hdac_calc_stream_format - calculate the format bitset
+ * @rate: the sample rate
+ * @channels: the number of channels
+ * @format: the PCM format (SNDRV_PCM_FORMAT_XXX)
+ * @maxbps: the max. bps
+ * @spdif_ctls: HD-audio SPDIF status bits (0 if irrelevant)
+ *
+ * Calculate the format bitset from the given rate, channels and th PCM format.
+ *
+ * Return zero if invalid.
+ */
+unsigned int snd_hdac_calc_stream_format(unsigned int rate,
+ unsigned int channels,
+ snd_pcm_format_t format,
+ unsigned int maxbps,
+ unsigned short spdif_ctls)
+{
+ int i;
+ unsigned int val = 0;
+
+ for (i = 0; rate_bits[i].hz; i++)
+ if (rate_bits[i].hz == rate) {
+ val = rate_bits[i].hda_fmt;
+ break;
+ }
+ if (!rate_bits[i].hz)
+ return 0;
+
+ if (channels == 0 || channels > 8)
+ return 0;
+ val |= channels - 1;
+
+ switch (snd_pcm_format_width(format)) {
+ case 8:
+ val |= AC_FMT_BITS_8;
+ break;
+ case 16:
+ val |= AC_FMT_BITS_16;
+ break;
+ case 20:
+ case 24:
+ case 32:
+ if (maxbps >= 32 || format == SNDRV_PCM_FORMAT_FLOAT_LE)
+ val |= AC_FMT_BITS_32;
+ else if (maxbps >= 24)
+ val |= AC_FMT_BITS_24;
+ else
+ val |= AC_FMT_BITS_20;
+ break;
+ default:
+ return 0;
+ }
+
+ if (spdif_ctls & AC_DIG1_NONAUDIO)
+ val |= AC_FMT_TYPE_NON_PCM;
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_calc_stream_format);
+
+static unsigned int query_pcm_param(struct hdac_device *codec, hda_nid_t nid)
+{
+ unsigned int val = 0;
+
+ if (nid != codec->afg &&
+ (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD))
+ val = snd_hdac_read_parm(codec, nid, AC_PAR_PCM);
+ if (!val || val == -1)
+ val = snd_hdac_read_parm(codec, codec->afg, AC_PAR_PCM);
+ if (!val || val == -1)
+ return 0;
+ return val;
+}
+
+static unsigned int query_stream_param(struct hdac_device *codec, hda_nid_t nid)
+{
+ unsigned int streams = snd_hdac_read_parm(codec, nid, AC_PAR_STREAM);
+
+ if (!streams || streams == -1)
+ streams = snd_hdac_read_parm(codec, codec->afg, AC_PAR_STREAM);
+ if (!streams || streams == -1)
+ return 0;
+ return streams;
+}
+
+/**
+ * snd_hdac_query_supported_pcm - query the supported PCM rates and formats
+ * @codec: the codec object
+ * @nid: NID to query
+ * @ratesp: the pointer to store the detected rate bitflags
+ * @formatsp: the pointer to store the detected formats
+ * @bpsp: the pointer to store the detected format widths
+ *
+ * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp
+ * or @bsps argument is ignored.
+ *
+ * Returns 0 if successful, otherwise a negative error code.
+ */
+int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid,
+ u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
+{
+ unsigned int i, val, wcaps;
+
+ wcaps = get_wcaps(codec, nid);
+ val = query_pcm_param(codec, nid);
+
+ if (ratesp) {
+ u32 rates = 0;
+ for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++) {
+ if (val & (1 << i))
+ rates |= rate_bits[i].alsa_bits;
+ }
+ if (rates == 0) {
+ dev_err(&codec->dev,
+ "rates == 0 (nid=0x%x, val=0x%x, ovrd=%i)\n",
+ nid, val,
+ (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0);
+ return -EIO;
+ }
+ *ratesp = rates;
+ }
+
+ if (formatsp || bpsp) {
+ u64 formats = 0;
+ unsigned int streams, bps;
+
+ streams = query_stream_param(codec, nid);
+ if (!streams)
+ return -EIO;
+
+ bps = 0;
+ if (streams & AC_SUPFMT_PCM) {
+ if (val & AC_SUPPCM_BITS_8) {
+ formats |= SNDRV_PCM_FMTBIT_U8;
+ bps = 8;
+ }
+ if (val & AC_SUPPCM_BITS_16) {
+ formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ bps = 16;
+ }
+ if (wcaps & AC_WCAP_DIGITAL) {
+ if (val & AC_SUPPCM_BITS_32)
+ formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
+ if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24))
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (val & AC_SUPPCM_BITS_24)
+ bps = 24;
+ else if (val & AC_SUPPCM_BITS_20)
+ bps = 20;
+ } else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24|
+ AC_SUPPCM_BITS_32)) {
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (val & AC_SUPPCM_BITS_32)
+ bps = 32;
+ else if (val & AC_SUPPCM_BITS_24)
+ bps = 24;
+ else if (val & AC_SUPPCM_BITS_20)
+ bps = 20;
+ }
+ }
+#if 0 /* FIXME: CS4206 doesn't work, which is the only codec supporting float */
+ if (streams & AC_SUPFMT_FLOAT32) {
+ formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+ if (!bps)
+ bps = 32;
+ }
+#endif
+ if (streams == AC_SUPFMT_AC3) {
+ /* should be exclusive */
+ /* temporary hack: we have still no proper support
+ * for the direct AC3 stream...
+ */
+ formats |= SNDRV_PCM_FMTBIT_U8;
+ bps = 8;
+ }
+ if (formats == 0) {
+ dev_err(&codec->dev,
+ "formats == 0 (nid=0x%x, val=0x%x, ovrd=%i, streams=0x%x)\n",
+ nid, val,
+ (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0,
+ streams);
+ return -EIO;
+ }
+ if (formatsp)
+ *formatsp = formats;
+ if (bpsp)
+ *bpsp = bps;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_query_supported_pcm);
+
+/**
+ * snd_hdac_is_supported_format - Check the validity of the format
+ * @codec: the codec object
+ * @nid: NID to check
+ * @format: the HD-audio format value to check
+ *
+ * Check whether the given node supports the format value.
+ *
+ * Returns true if supported, false if not.
+ */
+bool snd_hdac_is_supported_format(struct hdac_device *codec, hda_nid_t nid,
+ unsigned int format)
+{
+ int i;
+ unsigned int val = 0, rate, stream;
+
+ val = query_pcm_param(codec, nid);
+ if (!val)
+ return false;
+
+ rate = format & 0xff00;
+ for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++)
+ if (rate_bits[i].hda_fmt == rate) {
+ if (val & (1 << i))
+ break;
+ return false;
+ }
+ if (i >= AC_PAR_PCM_RATE_BITS)
+ return false;
+
+ stream = query_stream_param(codec, nid);
+ if (!stream)
+ return false;
+
+ if (stream & AC_SUPFMT_PCM) {
+ switch (format & 0xf0) {
+ case 0x00:
+ if (!(val & AC_SUPPCM_BITS_8))
+ return false;
+ break;
+ case 0x10:
+ if (!(val & AC_SUPPCM_BITS_16))
+ return false;
+ break;
+ case 0x20:
+ if (!(val & AC_SUPPCM_BITS_20))
+ return false;
+ break;
+ case 0x30:
+ if (!(val & AC_SUPPCM_BITS_24))
+ return false;
+ break;
+ case 0x40:
+ if (!(val & AC_SUPPCM_BITS_32))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ } else {
+ /* FIXME: check for float32 and AC3? */
+ }
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_is_supported_format);
+
+static unsigned int codec_read(struct hdac_device *hdac, hda_nid_t nid,
+ int flags, unsigned int verb, unsigned int parm)
+{
+ unsigned int cmd = snd_hdac_make_cmd(hdac, nid, verb, parm);
+ unsigned int res;
+
+ if (snd_hdac_exec_verb(hdac, cmd, flags, &res))
+ return -1;
+
+ return res;
+}
+
+static int codec_write(struct hdac_device *hdac, hda_nid_t nid,
+ int flags, unsigned int verb, unsigned int parm)
+{
+ unsigned int cmd = snd_hdac_make_cmd(hdac, nid, verb, parm);
+
+ return snd_hdac_exec_verb(hdac, cmd, flags, NULL);
+}
+
+/**
+ * snd_hdac_codec_read - send a command and get the response
+ * @hdac: the HDAC device
+ * @nid: NID to send the command
+ * @flags: optional bit flags
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command and read the corresponding response.
+ *
+ * Returns the obtained response value, or -1 for an error.
+ */
+int snd_hdac_codec_read(struct hdac_device *hdac, hda_nid_t nid,
+ int flags, unsigned int verb, unsigned int parm)
+{
+ return codec_read(hdac, nid, flags, verb, parm);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_codec_read);
+
+/**
+ * snd_hdac_codec_write - send a single command without waiting for response
+ * @hdac: the HDAC device
+ * @nid: NID to send the command
+ * @flags: optional bit flags
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command without waiting for response.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hdac_codec_write(struct hdac_device *hdac, hda_nid_t nid,
+ int flags, unsigned int verb, unsigned int parm)
+{
+ return codec_write(hdac, nid, flags, verb, parm);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_codec_write);
+
+/**
+ * snd_hdac_check_power_state - check whether the actual power state matches
+ * with the target state
+ *
+ * @hdac: the HDAC device
+ * @nid: NID to send the command
+ * @target_state: target state to check for
+ *
+ * Return true if state matches, false if not
+ */
+bool snd_hdac_check_power_state(struct hdac_device *hdac,
+ hda_nid_t nid, unsigned int target_state)
+{
+ unsigned int state = codec_read(hdac, nid, 0,
+ AC_VERB_GET_POWER_STATE, 0);
+
+ if (state & AC_PWRST_ERROR)
+ return true;
+ state = (state >> 4) & 0x0f;
+ return (state == target_state);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_check_power_state);
+/**
+ * snd_hdac_sync_power_state - wait until actual power state matches
+ * with the target state
+ *
+ * @codec: the HDAC device
+ * @nid: NID to send the command
+ * @power_state: target power state to wait for
+ *
+ * Return power state or PS_ERROR if codec rejects GET verb.
+ */
+unsigned int snd_hdac_sync_power_state(struct hdac_device *codec,
+ hda_nid_t nid, unsigned int power_state)
+{
+ unsigned long end_time = jiffies + msecs_to_jiffies(500);
+ unsigned int state, actual_state, count;
+
+ for (count = 0; count < 500; count++) {
+ state = snd_hdac_codec_read(codec, nid, 0,
+ AC_VERB_GET_POWER_STATE, 0);
+ if (state & AC_PWRST_ERROR) {
+ msleep(20);
+ break;
+ }
+ actual_state = (state >> 4) & 0x0f;
+ if (actual_state == power_state)
+ break;
+ if (time_after_eq(jiffies, end_time))
+ break;
+ /* wait until the codec reachs to the target state */
+ msleep(1);
+ }
+ return state;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_sync_power_state);
diff --git a/sound/hda/hdac_i915.c b/sound/hda/hdac_i915.c
new file mode 100644
index 0000000000..b428537f28
--- /dev/null
+++ b/sound/hda/hdac_i915.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * hdac_i915.c - routines for sync between HD-A core and i915 display driver
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+#include <sound/hda_register.h>
+
+/**
+ * snd_hdac_i915_set_bclk - Reprogram BCLK for HSW/BDW
+ * @bus: HDA core bus
+ *
+ * Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK
+ * depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value)
+ * are used to convert CDClk (Core Display Clock) to 24MHz BCLK:
+ * BCLK = CDCLK * M / N
+ * The values will be lost when the display power well is disabled and need to
+ * be restored to avoid abnormal playback speed.
+ *
+ * Call this function at initializing and changing power well, as well as
+ * at ELD notifier for the hotplug.
+ */
+void snd_hdac_i915_set_bclk(struct hdac_bus *bus)
+{
+ struct drm_audio_component *acomp = bus->audio_component;
+ struct pci_dev *pci = to_pci_dev(bus->dev);
+ int cdclk_freq;
+ unsigned int bclk_m, bclk_n;
+
+ if (!acomp || !acomp->ops || !acomp->ops->get_cdclk_freq)
+ return; /* only for i915 binding */
+ if (!HDA_CONTROLLER_IS_HSW(pci))
+ return; /* only HSW/BDW */
+
+ cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev);
+ switch (cdclk_freq) {
+ case 337500:
+ bclk_m = 16;
+ bclk_n = 225;
+ break;
+
+ case 450000:
+ default: /* default CDCLK 450MHz */
+ bclk_m = 4;
+ bclk_n = 75;
+ break;
+
+ case 540000:
+ bclk_m = 4;
+ bclk_n = 90;
+ break;
+
+ case 675000:
+ bclk_m = 8;
+ bclk_n = 225;
+ break;
+ }
+
+ snd_hdac_chip_writew(bus, HSW_EM4, bclk_m);
+ snd_hdac_chip_writew(bus, HSW_EM5, bclk_n);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk);
+
+/* returns true if the devices can be connected for audio */
+static bool connectivity_check(struct pci_dev *i915, struct pci_dev *hdac)
+{
+ struct pci_bus *bus_a = i915->bus, *bus_b = hdac->bus;
+
+ /* directly connected on the same bus */
+ if (bus_a == bus_b)
+ return true;
+
+ bus_a = bus_a->parent;
+ bus_b = bus_b->parent;
+
+ /* connected via parent bus (may be NULL!) */
+ if (bus_a == bus_b)
+ return true;
+
+ if (!bus_a || !bus_b)
+ return false;
+
+ /*
+ * on i915 discrete GPUs with embedded HDA audio, the two
+ * devices are connected via 2nd level PCI bridge
+ */
+ bus_a = bus_a->parent;
+ bus_b = bus_b->parent;
+ if (bus_a && bus_a == bus_b)
+ return true;
+
+ return false;
+}
+
+static int i915_component_master_match(struct device *dev, int subcomponent,
+ void *data)
+{
+ struct pci_dev *hdac_pci, *i915_pci;
+ struct hdac_bus *bus = data;
+
+ if (!dev_is_pci(dev))
+ return 0;
+
+ hdac_pci = to_pci_dev(bus->dev);
+ i915_pci = to_pci_dev(dev);
+
+ if (!strcmp(dev->driver->name, "i915") &&
+ subcomponent == I915_COMPONENT_AUDIO &&
+ connectivity_check(i915_pci, hdac_pci))
+ return 1;
+
+ return 0;
+}
+
+/* check whether Intel graphics is present and reachable */
+static int i915_gfx_present(struct pci_dev *hdac_pci)
+{
+ struct pci_dev *display_dev = NULL;
+
+ for_each_pci_dev(display_dev) {
+ if (display_dev->vendor == PCI_VENDOR_ID_INTEL &&
+ (display_dev->class >> 16) == PCI_BASE_CLASS_DISPLAY &&
+ connectivity_check(display_dev, hdac_pci)) {
+ pci_dev_put(display_dev);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * snd_hdac_i915_init - Initialize i915 audio component
+ * @bus: HDA core bus
+ *
+ * This function is supposed to be used only by a HD-audio controller
+ * driver that needs the interaction with i915 graphics.
+ *
+ * This function initializes and sets up the audio component to communicate
+ * with i915 graphics driver.
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_i915_init(struct hdac_bus *bus)
+{
+ struct drm_audio_component *acomp;
+ int err;
+
+ if (!i915_gfx_present(to_pci_dev(bus->dev)))
+ return -ENODEV;
+
+ err = snd_hdac_acomp_init(bus, NULL,
+ i915_component_master_match,
+ sizeof(struct i915_audio_component) - sizeof(*acomp));
+ if (err < 0)
+ return err;
+ acomp = bus->audio_component;
+ if (!acomp)
+ return -ENODEV;
+ if (!acomp->ops) {
+ if (!IS_ENABLED(CONFIG_MODULES) ||
+ !request_module("i915")) {
+ /* 60s timeout */
+ wait_for_completion_killable_timeout(&acomp->master_bind_complete,
+ msecs_to_jiffies(60 * 1000));
+ }
+ }
+ if (!acomp->ops) {
+ dev_info(bus->dev, "couldn't bind with audio component\n");
+ snd_hdac_acomp_exit(bus);
+ return -ENODEV;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_i915_init);
diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c
new file mode 100644
index 0000000000..97cee096a2
--- /dev/null
+++ b/sound/hda/hdac_regmap.c
@@ -0,0 +1,601 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Regmap support for HD-audio verbs
+ *
+ * A virtual register is translated to one or more hda verbs for write,
+ * vice versa for read.
+ *
+ * A few limitations:
+ * - Provided for not all verbs but only subset standard non-volatile verbs.
+ * - For reading, only AC_VERB_GET_* variants can be used.
+ * - For writing, mapped to the *corresponding* AC_VERB_SET_* variants,
+ * so can't handle asymmetric verbs for read and write
+ */
+
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/export.h>
+#include <linux/pm.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_regmap.h>
+#include "local.h"
+
+static int codec_pm_lock(struct hdac_device *codec)
+{
+ return snd_hdac_keep_power_up(codec);
+}
+
+static void codec_pm_unlock(struct hdac_device *codec, int lock)
+{
+ if (lock == 1)
+ snd_hdac_power_down_pm(codec);
+}
+
+#define get_verb(reg) (((reg) >> 8) & 0xfff)
+
+static bool hda_volatile_reg(struct device *dev, unsigned int reg)
+{
+ struct hdac_device *codec = dev_to_hdac_dev(dev);
+ unsigned int verb = get_verb(reg);
+
+ switch (verb) {
+ case AC_VERB_GET_PROC_COEF:
+ return !codec->cache_coef;
+ case AC_VERB_GET_COEF_INDEX:
+ case AC_VERB_GET_PROC_STATE:
+ case AC_VERB_GET_POWER_STATE:
+ case AC_VERB_GET_PIN_SENSE:
+ case AC_VERB_GET_HDMI_DIP_SIZE:
+ case AC_VERB_GET_HDMI_ELDD:
+ case AC_VERB_GET_HDMI_DIP_INDEX:
+ case AC_VERB_GET_HDMI_DIP_DATA:
+ case AC_VERB_GET_HDMI_DIP_XMIT:
+ case AC_VERB_GET_HDMI_CP_CTRL:
+ case AC_VERB_GET_HDMI_CHAN_SLOT:
+ case AC_VERB_GET_DEVICE_SEL:
+ case AC_VERB_GET_DEVICE_LIST: /* read-only volatile */
+ return true;
+ }
+
+ return false;
+}
+
+static bool hda_writeable_reg(struct device *dev, unsigned int reg)
+{
+ struct hdac_device *codec = dev_to_hdac_dev(dev);
+ unsigned int verb = get_verb(reg);
+ const unsigned int *v;
+ int i;
+
+ snd_array_for_each(&codec->vendor_verbs, i, v) {
+ if (verb == *v)
+ return true;
+ }
+
+ if (codec->caps_overwriting)
+ return true;
+
+ switch (verb & 0xf00) {
+ case AC_VERB_GET_STREAM_FORMAT:
+ case AC_VERB_GET_AMP_GAIN_MUTE:
+ return true;
+ case AC_VERB_GET_PROC_COEF:
+ return codec->cache_coef;
+ case 0xf00:
+ break;
+ default:
+ return false;
+ }
+
+ switch (verb) {
+ case AC_VERB_GET_CONNECT_SEL:
+ case AC_VERB_GET_SDI_SELECT:
+ case AC_VERB_GET_PIN_WIDGET_CONTROL:
+ case AC_VERB_GET_UNSOLICITED_RESPONSE: /* only as SET_UNSOLICITED_ENABLE */
+ case AC_VERB_GET_BEEP_CONTROL:
+ case AC_VERB_GET_EAPD_BTLENABLE:
+ case AC_VERB_GET_DIGI_CONVERT_1:
+ case AC_VERB_GET_DIGI_CONVERT_2: /* only for beep control */
+ case AC_VERB_GET_VOLUME_KNOB_CONTROL:
+ case AC_VERB_GET_GPIO_MASK:
+ case AC_VERB_GET_GPIO_DIRECTION:
+ case AC_VERB_GET_GPIO_DATA: /* not for volatile read */
+ case AC_VERB_GET_GPIO_WAKE_MASK:
+ case AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK:
+ case AC_VERB_GET_GPIO_STICKY_MASK:
+ return true;
+ }
+
+ return false;
+}
+
+static bool hda_readable_reg(struct device *dev, unsigned int reg)
+{
+ struct hdac_device *codec = dev_to_hdac_dev(dev);
+ unsigned int verb = get_verb(reg);
+
+ if (codec->caps_overwriting)
+ return true;
+
+ switch (verb) {
+ case AC_VERB_PARAMETERS:
+ case AC_VERB_GET_CONNECT_LIST:
+ case AC_VERB_GET_SUBSYSTEM_ID:
+ return true;
+ /* below are basically writable, but disabled for reducing unnecessary
+ * writes at sync
+ */
+ case AC_VERB_GET_CONFIG_DEFAULT: /* usually just read */
+ case AC_VERB_GET_CONV: /* managed in PCM code */
+ case AC_VERB_GET_CVT_CHAN_COUNT: /* managed in HDMI CA code */
+ return true;
+ }
+
+ return hda_writeable_reg(dev, reg);
+}
+
+/*
+ * Stereo amp pseudo register:
+ * for making easier to handle the stereo volume control, we provide a
+ * fake register to deal both left and right channels by a single
+ * (pseudo) register access. A verb consisting of SET_AMP_GAIN with
+ * *both* SET_LEFT and SET_RIGHT bits takes a 16bit value, the lower 8bit
+ * for the left and the upper 8bit for the right channel.
+ */
+static bool is_stereo_amp_verb(unsigned int reg)
+{
+ if (((reg >> 8) & 0x700) != AC_VERB_SET_AMP_GAIN_MUTE)
+ return false;
+ return (reg & (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT)) ==
+ (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
+}
+
+/* read a pseudo stereo amp register (16bit left+right) */
+static int hda_reg_read_stereo_amp(struct hdac_device *codec,
+ unsigned int reg, unsigned int *val)
+{
+ unsigned int left, right;
+ int err;
+
+ reg &= ~(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_LEFT, 0, &left);
+ if (err < 0)
+ return err;
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_RIGHT, 0, &right);
+ if (err < 0)
+ return err;
+ *val = left | (right << 8);
+ return 0;
+}
+
+/* write a pseudo stereo amp register (16bit left+right) */
+static int hda_reg_write_stereo_amp(struct hdac_device *codec,
+ unsigned int reg, unsigned int val)
+{
+ int err;
+ unsigned int verb, left, right;
+
+ verb = AC_VERB_SET_AMP_GAIN_MUTE << 8;
+ if (reg & AC_AMP_GET_OUTPUT)
+ verb |= AC_AMP_SET_OUTPUT;
+ else
+ verb |= AC_AMP_SET_INPUT | ((reg & 0xf) << 8);
+ reg = (reg & ~0xfffff) | verb;
+
+ left = val & 0xff;
+ right = (val >> 8) & 0xff;
+ if (left == right) {
+ reg |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
+ return snd_hdac_exec_verb(codec, reg | left, 0, NULL);
+ }
+
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_LEFT | left, 0, NULL);
+ if (err < 0)
+ return err;
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_RIGHT | right, 0, NULL);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/* read a pseudo coef register (16bit) */
+static int hda_reg_read_coef(struct hdac_device *codec, unsigned int reg,
+ unsigned int *val)
+{
+ unsigned int verb;
+ int err;
+
+ if (!codec->cache_coef)
+ return -EINVAL;
+ /* LSB 8bit = coef index */
+ verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
+ err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+ if (err < 0)
+ return err;
+ verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8);
+ return snd_hdac_exec_verb(codec, verb, 0, val);
+}
+
+/* write a pseudo coef register (16bit) */
+static int hda_reg_write_coef(struct hdac_device *codec, unsigned int reg,
+ unsigned int val)
+{
+ unsigned int verb;
+ int err;
+
+ if (!codec->cache_coef)
+ return -EINVAL;
+ /* LSB 8bit = coef index */
+ verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
+ err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+ if (err < 0)
+ return err;
+ verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) |
+ (val & 0xffff);
+ return snd_hdac_exec_verb(codec, verb, 0, NULL);
+}
+
+static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct hdac_device *codec = context;
+ int verb = get_verb(reg);
+ int err;
+ int pm_lock = 0;
+
+ if (verb != AC_VERB_GET_POWER_STATE) {
+ pm_lock = codec_pm_lock(codec);
+ if (pm_lock < 0)
+ return -EAGAIN;
+ }
+ reg |= (codec->addr << 28);
+ if (is_stereo_amp_verb(reg)) {
+ err = hda_reg_read_stereo_amp(codec, reg, val);
+ goto out;
+ }
+ if (verb == AC_VERB_GET_PROC_COEF) {
+ err = hda_reg_read_coef(codec, reg, val);
+ goto out;
+ }
+ if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE)
+ reg &= ~AC_AMP_FAKE_MUTE;
+
+ err = snd_hdac_exec_verb(codec, reg, 0, val);
+ if (err < 0)
+ goto out;
+ /* special handling for asymmetric reads */
+ if (verb == AC_VERB_GET_POWER_STATE) {
+ if (*val & AC_PWRST_ERROR)
+ *val = -1;
+ else /* take only the actual state */
+ *val = (*val >> 4) & 0x0f;
+ }
+ out:
+ codec_pm_unlock(codec, pm_lock);
+ return err;
+}
+
+static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct hdac_device *codec = context;
+ unsigned int verb;
+ int i, bytes, err;
+ int pm_lock = 0;
+
+ if (codec->caps_overwriting)
+ return 0;
+
+ reg &= ~0x00080000U; /* drop GET bit */
+ reg |= (codec->addr << 28);
+ verb = get_verb(reg);
+
+ if (verb != AC_VERB_SET_POWER_STATE) {
+ pm_lock = codec_pm_lock(codec);
+ if (pm_lock < 0)
+ return codec->lazy_cache ? 0 : -EAGAIN;
+ }
+
+ if (is_stereo_amp_verb(reg)) {
+ err = hda_reg_write_stereo_amp(codec, reg, val);
+ goto out;
+ }
+
+ if (verb == AC_VERB_SET_PROC_COEF) {
+ err = hda_reg_write_coef(codec, reg, val);
+ goto out;
+ }
+
+ switch (verb & 0xf00) {
+ case AC_VERB_SET_AMP_GAIN_MUTE:
+ if ((reg & AC_AMP_FAKE_MUTE) && (val & AC_AMP_MUTE))
+ val = 0;
+ verb = AC_VERB_SET_AMP_GAIN_MUTE;
+ if (reg & AC_AMP_GET_LEFT)
+ verb |= AC_AMP_SET_LEFT >> 8;
+ else
+ verb |= AC_AMP_SET_RIGHT >> 8;
+ if (reg & AC_AMP_GET_OUTPUT) {
+ verb |= AC_AMP_SET_OUTPUT >> 8;
+ } else {
+ verb |= AC_AMP_SET_INPUT >> 8;
+ verb |= reg & 0xf;
+ }
+ break;
+ }
+
+ switch (verb) {
+ case AC_VERB_SET_DIGI_CONVERT_1:
+ bytes = 2;
+ break;
+ case AC_VERB_SET_CONFIG_DEFAULT_BYTES_0:
+ bytes = 4;
+ break;
+ default:
+ bytes = 1;
+ break;
+ }
+
+ for (i = 0; i < bytes; i++) {
+ reg &= ~0xfffff;
+ reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff);
+ err = snd_hdac_exec_verb(codec, reg, 0, NULL);
+ if (err < 0)
+ goto out;
+ }
+
+ out:
+ codec_pm_unlock(codec, pm_lock);
+ return err;
+}
+
+static const struct regmap_config hda_regmap_cfg = {
+ .name = "hdaudio",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .max_register = 0xfffffff,
+ .writeable_reg = hda_writeable_reg,
+ .readable_reg = hda_readable_reg,
+ .volatile_reg = hda_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_read = hda_reg_read,
+ .reg_write = hda_reg_write,
+ .use_single_read = true,
+ .use_single_write = true,
+ .disable_locking = true,
+};
+
+/**
+ * snd_hdac_regmap_init - Initialize regmap for HDA register accesses
+ * @codec: the codec object
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_regmap_init(struct hdac_device *codec)
+{
+ struct regmap *regmap;
+
+ regmap = regmap_init(&codec->dev, NULL, codec, &hda_regmap_cfg);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ codec->regmap = regmap;
+ snd_array_init(&codec->vendor_verbs, sizeof(unsigned int), 8);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_init);
+
+/**
+ * snd_hdac_regmap_exit - Release the regmap from HDA codec
+ * @codec: the codec object
+ */
+void snd_hdac_regmap_exit(struct hdac_device *codec)
+{
+ if (codec->regmap) {
+ regmap_exit(codec->regmap);
+ codec->regmap = NULL;
+ snd_array_free(&codec->vendor_verbs);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_exit);
+
+/**
+ * snd_hdac_regmap_add_vendor_verb - add a vendor-specific verb to regmap
+ * @codec: the codec object
+ * @verb: verb to allow accessing via regmap
+ *
+ * Returns zero for success or a negative error code.
+ */
+int snd_hdac_regmap_add_vendor_verb(struct hdac_device *codec,
+ unsigned int verb)
+{
+ unsigned int *p = snd_array_new(&codec->vendor_verbs);
+
+ if (!p)
+ return -ENOMEM;
+ *p = verb | 0x800; /* set GET bit */
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_add_vendor_verb);
+
+/*
+ * helper functions
+ */
+
+/* write a pseudo-register value (w/o power sequence) */
+static int reg_raw_write(struct hdac_device *codec, unsigned int reg,
+ unsigned int val)
+{
+ int err;
+
+ mutex_lock(&codec->regmap_lock);
+ if (!codec->regmap)
+ err = hda_reg_write(codec, reg, val);
+ else
+ err = regmap_write(codec->regmap, reg, val);
+ mutex_unlock(&codec->regmap_lock);
+ return err;
+}
+
+/* a helper macro to call @func_call; retry with power-up if failed */
+#define CALL_RAW_FUNC(codec, func_call) \
+ ({ \
+ int _err = func_call; \
+ if (_err == -EAGAIN) { \
+ _err = snd_hdac_power_up_pm(codec); \
+ if (_err >= 0) \
+ _err = func_call; \
+ snd_hdac_power_down_pm(codec); \
+ } \
+ _err;})
+
+/**
+ * snd_hdac_regmap_write_raw - write a pseudo register with power mgmt
+ * @codec: the codec object
+ * @reg: pseudo register
+ * @val: value to write
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
+ unsigned int val)
+{
+ return CALL_RAW_FUNC(codec, reg_raw_write(codec, reg, val));
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_write_raw);
+
+static int reg_raw_read(struct hdac_device *codec, unsigned int reg,
+ unsigned int *val, bool uncached)
+{
+ int err;
+
+ mutex_lock(&codec->regmap_lock);
+ if (uncached || !codec->regmap)
+ err = hda_reg_read(codec, reg, val);
+ else
+ err = regmap_read(codec->regmap, reg, val);
+ mutex_unlock(&codec->regmap_lock);
+ return err;
+}
+
+static int __snd_hdac_regmap_read_raw(struct hdac_device *codec,
+ unsigned int reg, unsigned int *val,
+ bool uncached)
+{
+ return CALL_RAW_FUNC(codec, reg_raw_read(codec, reg, val, uncached));
+}
+
+/**
+ * snd_hdac_regmap_read_raw - read a pseudo register with power mgmt
+ * @codec: the codec object
+ * @reg: pseudo register
+ * @val: pointer to store the read value
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg,
+ unsigned int *val)
+{
+ return __snd_hdac_regmap_read_raw(codec, reg, val, false);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_read_raw);
+
+/* Works like snd_hdac_regmap_read_raw(), but this doesn't read from the
+ * cache but always via hda verbs.
+ */
+int snd_hdac_regmap_read_raw_uncached(struct hdac_device *codec,
+ unsigned int reg, unsigned int *val)
+{
+ return __snd_hdac_regmap_read_raw(codec, reg, val, true);
+}
+
+static int reg_raw_update(struct hdac_device *codec, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ unsigned int orig;
+ bool change;
+ int err;
+
+ mutex_lock(&codec->regmap_lock);
+ if (codec->regmap) {
+ err = regmap_update_bits_check(codec->regmap, reg, mask, val,
+ &change);
+ if (!err)
+ err = change ? 1 : 0;
+ } else {
+ err = hda_reg_read(codec, reg, &orig);
+ if (!err) {
+ val &= mask;
+ val |= orig & ~mask;
+ if (val != orig) {
+ err = hda_reg_write(codec, reg, val);
+ if (!err)
+ err = 1;
+ }
+ }
+ }
+ mutex_unlock(&codec->regmap_lock);
+ return err;
+}
+
+/**
+ * snd_hdac_regmap_update_raw - update a pseudo register with power mgmt
+ * @codec: the codec object
+ * @reg: pseudo register
+ * @mask: bit mask to update
+ * @val: value to update
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ return CALL_RAW_FUNC(codec, reg_raw_update(codec, reg, mask, val));
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw);
+
+static int reg_raw_update_once(struct hdac_device *codec, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ int err = 0;
+
+ if (!codec->regmap)
+ return reg_raw_update(codec, reg, mask, val);
+
+ mutex_lock(&codec->regmap_lock);
+ /* Discard any updates to already initialised registers. */
+ if (!regcache_reg_cached(codec->regmap, reg))
+ err = regmap_update_bits(codec->regmap, reg, mask, val);
+ mutex_unlock(&codec->regmap_lock);
+ return err;
+}
+
+/**
+ * snd_hdac_regmap_update_raw_once - initialize the register value only once
+ * @codec: the codec object
+ * @reg: pseudo register
+ * @mask: bit mask to update
+ * @val: value to update
+ *
+ * Performs the update of the register bits only once when the register
+ * hasn't been initialized yet. Used in HD-audio legacy driver.
+ * Returns zero if successful or a negative error code
+ */
+int snd_hdac_regmap_update_raw_once(struct hdac_device *codec, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ return CALL_RAW_FUNC(codec, reg_raw_update_once(codec, reg, mask, val));
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw_once);
+
+/**
+ * snd_hdac_regmap_sync - sync out the cached values for PM resume
+ * @codec: the codec object
+ */
+void snd_hdac_regmap_sync(struct hdac_device *codec)
+{
+ mutex_lock(&codec->regmap_lock);
+ if (codec->regmap)
+ regcache_sync(codec->regmap);
+ mutex_unlock(&codec->regmap_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_regmap_sync);
diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c
new file mode 100644
index 0000000000..214a068052
--- /dev/null
+++ b/sound/hda/hdac_stream.c
@@ -0,0 +1,1009 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HD-audio stream operations
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/clocksource.h>
+#include <sound/compress_driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_register.h>
+#include "trace.h"
+
+/*
+ * the hdac_stream library is intended to be used with the following
+ * transitions. The states are not formally defined in the code but loosely
+ * inspired by boolean variables. Note that the 'prepared' field is not used
+ * in this library but by the callers during the hw_params/prepare transitions
+ *
+ * |
+ * stream_init() |
+ * v
+ * +--+-------+
+ * | unused |
+ * +--+----+--+
+ * | ^
+ * stream_assign() | | stream_release()
+ * v |
+ * +--+----+--+
+ * | opened |
+ * +--+----+--+
+ * | ^
+ * stream_reset() | |
+ * stream_setup() | | stream_cleanup()
+ * v |
+ * +--+----+--+
+ * | prepared |
+ * +--+----+--+
+ * | ^
+ * stream_start() | | stream_stop()
+ * v |
+ * +--+----+--+
+ * | running |
+ * +----------+
+ */
+
+/**
+ * snd_hdac_get_stream_stripe_ctl - get stripe control value
+ * @bus: HD-audio core bus
+ * @substream: PCM substream
+ */
+int snd_hdac_get_stream_stripe_ctl(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int channels = runtime->channels,
+ rate = runtime->rate,
+ bits_per_sample = runtime->sample_bits,
+ max_sdo_lines, value, sdo_line;
+
+ /* T_AZA_GCAP_NSDO is 1:2 bitfields in GCAP */
+ max_sdo_lines = snd_hdac_chip_readl(bus, GCAP) & AZX_GCAP_NSDO;
+
+ /* following is from HD audio spec */
+ for (sdo_line = max_sdo_lines; sdo_line > 0; sdo_line >>= 1) {
+ if (rate > 48000)
+ value = (channels * bits_per_sample *
+ (rate / 48000)) / sdo_line;
+ else
+ value = (channels * bits_per_sample) / sdo_line;
+
+ if (value >= bus->sdo_limit)
+ break;
+ }
+
+ /* stripe value: 0 for 1SDO, 1 for 2SDO, 2 for 4SDO lines */
+ return sdo_line >> 1;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_stream_stripe_ctl);
+
+/**
+ * snd_hdac_stream_init - initialize each stream (aka device)
+ * @bus: HD-audio core bus
+ * @azx_dev: HD-audio core stream object to initialize
+ * @idx: stream index number
+ * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)
+ * @tag: the tag id to assign
+ *
+ * Assign the starting bdl address to each stream (device) and initialize.
+ */
+void snd_hdac_stream_init(struct hdac_bus *bus, struct hdac_stream *azx_dev,
+ int idx, int direction, int tag)
+{
+ azx_dev->bus = bus;
+ /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+ azx_dev->sd_addr = bus->remap_addr + (0x20 * idx + 0x80);
+ /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */
+ azx_dev->sd_int_sta_mask = 1 << idx;
+ azx_dev->index = idx;
+ azx_dev->direction = direction;
+ azx_dev->stream_tag = tag;
+ snd_hdac_dsp_lock_init(azx_dev);
+ list_add_tail(&azx_dev->list, &bus->stream_list);
+
+ if (bus->spbcap) {
+ azx_dev->spib_addr = bus->spbcap + AZX_SPB_BASE +
+ AZX_SPB_INTERVAL * idx +
+ AZX_SPB_SPIB;
+
+ azx_dev->fifo_addr = bus->spbcap + AZX_SPB_BASE +
+ AZX_SPB_INTERVAL * idx +
+ AZX_SPB_MAXFIFO;
+ }
+
+ if (bus->drsmcap)
+ azx_dev->dpibr_addr = bus->drsmcap + AZX_DRSM_BASE +
+ AZX_DRSM_INTERVAL * idx;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_init);
+
+/**
+ * snd_hdac_stream_start - start a stream
+ * @azx_dev: HD-audio core stream to start
+ *
+ * Start a stream, set start_wallclk and set the running flag.
+ */
+void snd_hdac_stream_start(struct hdac_stream *azx_dev)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ int stripe_ctl;
+
+ trace_snd_hdac_stream_start(bus, azx_dev);
+
+ azx_dev->start_wallclk = snd_hdac_chip_readl(bus, WALLCLK);
+
+ /* enable SIE */
+ snd_hdac_chip_updatel(bus, INTCTL,
+ 1 << azx_dev->index,
+ 1 << azx_dev->index);
+ /* set stripe control */
+ if (azx_dev->stripe) {
+ if (azx_dev->substream)
+ stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream);
+ else
+ stripe_ctl = 0;
+ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK,
+ stripe_ctl);
+ }
+ /* set DMA start and interrupt mask */
+ if (bus->access_sdnctl_in_dword)
+ snd_hdac_stream_updatel(azx_dev, SD_CTL,
+ 0, SD_CTL_DMA_START | SD_INT_MASK);
+ else
+ snd_hdac_stream_updateb(azx_dev, SD_CTL,
+ 0, SD_CTL_DMA_START | SD_INT_MASK);
+ azx_dev->running = true;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_start);
+
+/**
+ * snd_hdac_stream_clear - helper to clear stream registers and stop DMA transfers
+ * @azx_dev: HD-audio core stream to stop
+ */
+static void snd_hdac_stream_clear(struct hdac_stream *azx_dev)
+{
+ snd_hdac_stream_updateb(azx_dev, SD_CTL,
+ SD_CTL_DMA_START | SD_INT_MASK, 0);
+ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
+ if (azx_dev->stripe)
+ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0);
+ azx_dev->running = false;
+}
+
+/**
+ * snd_hdac_stream_stop - stop a stream
+ * @azx_dev: HD-audio core stream to stop
+ *
+ * Stop a stream DMA and disable stream interrupt
+ */
+void snd_hdac_stream_stop(struct hdac_stream *azx_dev)
+{
+ trace_snd_hdac_stream_stop(azx_dev->bus, azx_dev);
+
+ snd_hdac_stream_clear(azx_dev);
+ /* disable SIE */
+ snd_hdac_chip_updatel(azx_dev->bus, INTCTL, 1 << azx_dev->index, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_stop);
+
+/**
+ * snd_hdac_stop_streams - stop all streams
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_stop_streams(struct hdac_bus *bus)
+{
+ struct hdac_stream *stream;
+
+ list_for_each_entry(stream, &bus->stream_list, list)
+ snd_hdac_stream_stop(stream);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stop_streams);
+
+/**
+ * snd_hdac_stop_streams_and_chip - stop all streams and chip if running
+ * @bus: HD-audio core bus
+ */
+void snd_hdac_stop_streams_and_chip(struct hdac_bus *bus)
+{
+
+ if (bus->chip_init) {
+ snd_hdac_stop_streams(bus);
+ snd_hdac_bus_stop_chip(bus);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stop_streams_and_chip);
+
+/**
+ * snd_hdac_stream_reset - reset a stream
+ * @azx_dev: HD-audio core stream to reset
+ */
+void snd_hdac_stream_reset(struct hdac_stream *azx_dev)
+{
+ unsigned char val;
+ int dma_run_state;
+
+ snd_hdac_stream_clear(azx_dev);
+
+ dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START;
+
+ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET);
+
+ /* wait for hardware to report that the stream entered reset */
+ snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300);
+
+ if (azx_dev->bus->dma_stop_delay && dma_run_state)
+ udelay(azx_dev->bus->dma_stop_delay);
+
+ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0);
+
+ /* wait for hardware to report that the stream is out of reset */
+ snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300);
+
+ /* reset first position - may not be synced with hw at this time */
+ if (azx_dev->posbuf)
+ *azx_dev->posbuf = 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_reset);
+
+/**
+ * snd_hdac_stream_setup - set up the SD for streaming
+ * @azx_dev: HD-audio core stream to set up
+ */
+int snd_hdac_stream_setup(struct hdac_stream *azx_dev)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ struct snd_pcm_runtime *runtime;
+ unsigned int val;
+
+ if (azx_dev->substream)
+ runtime = azx_dev->substream->runtime;
+ else
+ runtime = NULL;
+ /* make sure the run bit is zero for SD */
+ snd_hdac_stream_clear(azx_dev);
+ /* program the stream_tag */
+ val = snd_hdac_stream_readl(azx_dev, SD_CTL);
+ val = (val & ~SD_CTL_STREAM_TAG_MASK) |
+ (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT);
+ if (!bus->snoop)
+ val |= SD_CTL_TRAFFIC_PRIO;
+ snd_hdac_stream_writel(azx_dev, SD_CTL, val);
+
+ /* program the length of samples in cyclic buffer */
+ snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize);
+
+ /* program the stream format */
+ /* this value needs to be the same as the one programmed */
+ snd_hdac_stream_writew(azx_dev, SD_FORMAT, azx_dev->format_val);
+
+ /* program the stream LVI (last valid index) of the BDL */
+ snd_hdac_stream_writew(azx_dev, SD_LVI, azx_dev->frags - 1);
+
+ /* program the BDL address */
+ /* lower BDL address */
+ snd_hdac_stream_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr);
+ /* upper BDL address */
+ snd_hdac_stream_writel(azx_dev, SD_BDLPU,
+ upper_32_bits(azx_dev->bdl.addr));
+
+ /* enable the position buffer */
+ if (bus->use_posbuf && bus->posbuf.addr) {
+ if (!(snd_hdac_chip_readl(bus, DPLBASE) & AZX_DPLBASE_ENABLE))
+ snd_hdac_chip_writel(bus, DPLBASE,
+ (u32)bus->posbuf.addr | AZX_DPLBASE_ENABLE);
+ }
+
+ /* set the interrupt enable bits in the descriptor control register */
+ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_INT_MASK);
+
+ azx_dev->fifo_size = snd_hdac_stream_readw(azx_dev, SD_FIFOSIZE) + 1;
+
+ /* when LPIB delay correction gives a small negative value,
+ * we ignore it; currently set the threshold statically to
+ * 64 frames
+ */
+ if (runtime && runtime->period_size > 64)
+ azx_dev->delay_negative_threshold =
+ -frames_to_bytes(runtime, 64);
+ else
+ azx_dev->delay_negative_threshold = 0;
+
+ /* wallclk has 24Mhz clock source */
+ if (runtime)
+ azx_dev->period_wallclk = (((runtime->period_size * 24000) /
+ runtime->rate) * 1000);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_setup);
+
+/**
+ * snd_hdac_stream_cleanup - cleanup a stream
+ * @azx_dev: HD-audio core stream to clean up
+ */
+void snd_hdac_stream_cleanup(struct hdac_stream *azx_dev)
+{
+ snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
+ snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0);
+ snd_hdac_stream_writel(azx_dev, SD_CTL, 0);
+ azx_dev->bufsize = 0;
+ azx_dev->period_bytes = 0;
+ azx_dev->format_val = 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_cleanup);
+
+/**
+ * snd_hdac_stream_assign - assign a stream for the PCM
+ * @bus: HD-audio core bus
+ * @substream: PCM substream to assign
+ *
+ * Look for an unused stream for the given PCM substream, assign it
+ * and return the stream object. If no stream is free, returns NULL.
+ * The function tries to keep using the same stream object when it's used
+ * beforehand. Also, when bus->reverse_assign flag is set, the last free
+ * or matching entry is returned. This is needed for some strange codecs.
+ */
+struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *azx_dev;
+ struct hdac_stream *res = NULL;
+
+ /* make a non-zero unique key for the substream */
+ int key = (substream->number << 2) | (substream->stream + 1);
+
+ if (substream->pcm)
+ key |= (substream->pcm->device << 16);
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(azx_dev, &bus->stream_list, list) {
+ if (azx_dev->direction != substream->stream)
+ continue;
+ if (azx_dev->opened)
+ continue;
+ if (azx_dev->assigned_key == key) {
+ res = azx_dev;
+ break;
+ }
+ if (!res || bus->reverse_assign)
+ res = azx_dev;
+ }
+ if (res) {
+ res->opened = 1;
+ res->running = 0;
+ res->assigned_key = key;
+ res->substream = substream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+ return res;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_assign);
+
+/**
+ * snd_hdac_stream_release_locked - release the assigned stream
+ * @azx_dev: HD-audio core stream to release
+ *
+ * Release the stream that has been assigned by snd_hdac_stream_assign().
+ * The bus->reg_lock needs to be taken at a higher level
+ */
+void snd_hdac_stream_release_locked(struct hdac_stream *azx_dev)
+{
+ azx_dev->opened = 0;
+ azx_dev->running = 0;
+ azx_dev->substream = NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_release_locked);
+
+/**
+ * snd_hdac_stream_release - release the assigned stream
+ * @azx_dev: HD-audio core stream to release
+ *
+ * Release the stream that has been assigned by snd_hdac_stream_assign().
+ */
+void snd_hdac_stream_release(struct hdac_stream *azx_dev)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+
+ spin_lock_irq(&bus->reg_lock);
+ snd_hdac_stream_release_locked(azx_dev);
+ spin_unlock_irq(&bus->reg_lock);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_release);
+
+/**
+ * snd_hdac_get_stream - return hdac_stream based on stream_tag and
+ * direction
+ *
+ * @bus: HD-audio core bus
+ * @dir: direction for the stream to be found
+ * @stream_tag: stream tag for stream to be found
+ */
+struct hdac_stream *snd_hdac_get_stream(struct hdac_bus *bus,
+ int dir, int stream_tag)
+{
+ struct hdac_stream *s;
+
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (s->direction == dir && s->stream_tag == stream_tag)
+ return s;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_stream);
+
+/*
+ * set up a BDL entry
+ */
+static int setup_bdle(struct hdac_bus *bus,
+ struct snd_dma_buffer *dmab,
+ struct hdac_stream *azx_dev, __le32 **bdlp,
+ int ofs, int size, int with_ioc)
+{
+ __le32 *bdl = *bdlp;
+
+ while (size > 0) {
+ dma_addr_t addr;
+ int chunk;
+
+ if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES)
+ return -EINVAL;
+
+ addr = snd_sgbuf_get_addr(dmab, ofs);
+ /* program the address field of the BDL entry */
+ bdl[0] = cpu_to_le32((u32)addr);
+ bdl[1] = cpu_to_le32(upper_32_bits(addr));
+ /* program the size field of the BDL entry */
+ chunk = snd_sgbuf_get_chunk_size(dmab, ofs, size);
+ /* one BDLE cannot cross 4K boundary on CTHDA chips */
+ if (bus->align_bdle_4k) {
+ u32 remain = 0x1000 - (ofs & 0xfff);
+
+ if (chunk > remain)
+ chunk = remain;
+ }
+ bdl[2] = cpu_to_le32(chunk);
+ /* program the IOC to enable interrupt
+ * only when the whole fragment is processed
+ */
+ size -= chunk;
+ bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01);
+ bdl += 4;
+ azx_dev->frags++;
+ ofs += chunk;
+ }
+ *bdlp = bdl;
+ return ofs;
+}
+
+/**
+ * snd_hdac_stream_setup_periods - set up BDL entries
+ * @azx_dev: HD-audio core stream to set up
+ *
+ * Set up the buffer descriptor table of the given stream based on the
+ * period and buffer sizes of the assigned PCM substream.
+ */
+int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ struct snd_pcm_substream *substream = azx_dev->substream;
+ struct snd_compr_stream *cstream = azx_dev->cstream;
+ struct snd_pcm_runtime *runtime = NULL;
+ struct snd_dma_buffer *dmab;
+ __le32 *bdl;
+ int i, ofs, periods, period_bytes;
+ int pos_adj, pos_align;
+
+ if (substream) {
+ runtime = substream->runtime;
+ dmab = snd_pcm_get_dma_buf(substream);
+ } else if (cstream) {
+ dmab = snd_pcm_get_dma_buf(cstream);
+ } else {
+ WARN(1, "No substream or cstream assigned\n");
+ return -EINVAL;
+ }
+
+ /* reset BDL address */
+ snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
+ snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0);
+
+ period_bytes = azx_dev->period_bytes;
+ periods = azx_dev->bufsize / period_bytes;
+
+ /* program the initial BDL entries */
+ bdl = (__le32 *)azx_dev->bdl.area;
+ ofs = 0;
+ azx_dev->frags = 0;
+
+ pos_adj = bus->bdl_pos_adj;
+ if (runtime && !azx_dev->no_period_wakeup && pos_adj > 0) {
+ pos_align = pos_adj;
+ pos_adj = DIV_ROUND_UP(pos_adj * runtime->rate, 48000);
+ if (!pos_adj)
+ pos_adj = pos_align;
+ else
+ pos_adj = roundup(pos_adj, pos_align);
+ pos_adj = frames_to_bytes(runtime, pos_adj);
+ if (pos_adj >= period_bytes) {
+ dev_warn(bus->dev, "Too big adjustment %d\n",
+ pos_adj);
+ pos_adj = 0;
+ } else {
+ ofs = setup_bdle(bus, dmab, azx_dev,
+ &bdl, ofs, pos_adj, true);
+ if (ofs < 0)
+ goto error;
+ }
+ } else
+ pos_adj = 0;
+
+ for (i = 0; i < periods; i++) {
+ if (i == periods - 1 && pos_adj)
+ ofs = setup_bdle(bus, dmab, azx_dev,
+ &bdl, ofs, period_bytes - pos_adj, 0);
+ else
+ ofs = setup_bdle(bus, dmab, azx_dev,
+ &bdl, ofs, period_bytes,
+ !azx_dev->no_period_wakeup);
+ if (ofs < 0)
+ goto error;
+ }
+ return 0;
+
+ error:
+ dev_err(bus->dev, "Too many BDL entries: buffer=%d, period=%d\n",
+ azx_dev->bufsize, period_bytes);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_setup_periods);
+
+/**
+ * snd_hdac_stream_set_params - set stream parameters
+ * @azx_dev: HD-audio core stream for which parameters are to be set
+ * @format_val: format value parameter
+ *
+ * Setup the HD-audio core stream parameters from substream of the stream
+ * and passed format value
+ */
+int snd_hdac_stream_set_params(struct hdac_stream *azx_dev,
+ unsigned int format_val)
+{
+ struct snd_pcm_substream *substream = azx_dev->substream;
+ struct snd_compr_stream *cstream = azx_dev->cstream;
+ unsigned int bufsize, period_bytes;
+ unsigned int no_period_wakeup;
+ int err;
+
+ if (substream) {
+ bufsize = snd_pcm_lib_buffer_bytes(substream);
+ period_bytes = snd_pcm_lib_period_bytes(substream);
+ no_period_wakeup = substream->runtime->no_period_wakeup;
+ } else if (cstream) {
+ bufsize = cstream->runtime->buffer_size;
+ period_bytes = cstream->runtime->fragment_size;
+ no_period_wakeup = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ if (bufsize != azx_dev->bufsize ||
+ period_bytes != azx_dev->period_bytes ||
+ format_val != azx_dev->format_val ||
+ no_period_wakeup != azx_dev->no_period_wakeup) {
+ azx_dev->bufsize = bufsize;
+ azx_dev->period_bytes = period_bytes;
+ azx_dev->format_val = format_val;
+ azx_dev->no_period_wakeup = no_period_wakeup;
+ err = snd_hdac_stream_setup_periods(azx_dev);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_set_params);
+
+static u64 azx_cc_read(const struct cyclecounter *cc)
+{
+ struct hdac_stream *azx_dev = container_of(cc, struct hdac_stream, cc);
+
+ return snd_hdac_chip_readl(azx_dev->bus, WALLCLK);
+}
+
+static void azx_timecounter_init(struct hdac_stream *azx_dev,
+ bool force, u64 last)
+{
+ struct timecounter *tc = &azx_dev->tc;
+ struct cyclecounter *cc = &azx_dev->cc;
+ u64 nsec;
+
+ cc->read = azx_cc_read;
+ cc->mask = CLOCKSOURCE_MASK(32);
+
+ /*
+ * Calculate the optimal mult/shift values. The counter wraps
+ * around after ~178.9 seconds.
+ */
+ clocks_calc_mult_shift(&cc->mult, &cc->shift, 24000000,
+ NSEC_PER_SEC, 178);
+
+ nsec = 0; /* audio time is elapsed time since trigger */
+ timecounter_init(tc, cc, nsec);
+ if (force) {
+ /*
+ * force timecounter to use predefined value,
+ * used for synchronized starts
+ */
+ tc->cycle_last = last;
+ }
+}
+
+/**
+ * snd_hdac_stream_timecounter_init - initialize time counter
+ * @azx_dev: HD-audio core stream (master stream)
+ * @streams: bit flags of streams to set up
+ *
+ * Initializes the time counter of streams marked by the bit flags (each
+ * bit corresponds to the stream index).
+ * The trigger timestamp of PCM substream assigned to the given stream is
+ * updated accordingly, too.
+ */
+void snd_hdac_stream_timecounter_init(struct hdac_stream *azx_dev,
+ unsigned int streams)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ struct snd_pcm_runtime *runtime = azx_dev->substream->runtime;
+ struct hdac_stream *s;
+ bool inited = false;
+ u64 cycle_last = 0;
+ int i = 0;
+
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (streams & (1 << i)) {
+ azx_timecounter_init(s, inited, cycle_last);
+ if (!inited) {
+ inited = true;
+ cycle_last = s->tc.cycle_last;
+ }
+ }
+ i++;
+ }
+
+ snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
+ runtime->trigger_tstamp_latched = true;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_timecounter_init);
+
+/**
+ * snd_hdac_stream_sync_trigger - turn on/off stream sync register
+ * @azx_dev: HD-audio core stream (master stream)
+ * @set: true = set, false = clear
+ * @streams: bit flags of streams to sync
+ * @reg: the stream sync register address
+ */
+void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set,
+ unsigned int streams, unsigned int reg)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ unsigned int val;
+
+ if (!reg)
+ reg = AZX_REG_SSYNC;
+ val = _snd_hdac_chip_readl(bus, reg);
+ if (set)
+ val |= streams;
+ else
+ val &= ~streams;
+ _snd_hdac_chip_writel(bus, reg, val);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger);
+
+/**
+ * snd_hdac_stream_sync - sync with start/stop trigger operation
+ * @azx_dev: HD-audio core stream (master stream)
+ * @start: true = start, false = stop
+ * @streams: bit flags of streams to sync
+ *
+ * For @start = true, wait until all FIFOs get ready.
+ * For @start = false, wait until all RUN bits are cleared.
+ */
+void snd_hdac_stream_sync(struct hdac_stream *azx_dev, bool start,
+ unsigned int streams)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ int i, nwait, timeout;
+ struct hdac_stream *s;
+
+ for (timeout = 5000; timeout; timeout--) {
+ nwait = 0;
+ i = 0;
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (!(streams & (1 << i++)))
+ continue;
+
+ if (start) {
+ /* check FIFO gets ready */
+ if (!(snd_hdac_stream_readb(s, SD_STS) &
+ SD_STS_FIFO_READY))
+ nwait++;
+ } else {
+ /* check RUN bit is cleared */
+ if (snd_hdac_stream_readb(s, SD_CTL) &
+ SD_CTL_DMA_START) {
+ nwait++;
+ /*
+ * Perform stream reset if DMA RUN
+ * bit not cleared within given timeout
+ */
+ if (timeout == 1)
+ snd_hdac_stream_reset(s);
+ }
+ }
+ }
+ if (!nwait)
+ break;
+ cpu_relax();
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_sync);
+
+/**
+ * snd_hdac_stream_spbcap_enable - enable SPIB for a stream
+ * @bus: HD-audio core bus
+ * @enable: flag to enable/disable SPIB
+ * @index: stream index for which SPIB need to be enabled
+ */
+void snd_hdac_stream_spbcap_enable(struct hdac_bus *bus,
+ bool enable, int index)
+{
+ u32 mask = 0;
+
+ if (!bus->spbcap) {
+ dev_err(bus->dev, "Address of SPB capability is NULL\n");
+ return;
+ }
+
+ mask |= (1 << index);
+
+ if (enable)
+ snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, mask);
+ else
+ snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_spbcap_enable);
+
+/**
+ * snd_hdac_stream_set_spib - sets the spib value of a stream
+ * @bus: HD-audio core bus
+ * @azx_dev: hdac_stream
+ * @value: spib value to set
+ */
+int snd_hdac_stream_set_spib(struct hdac_bus *bus,
+ struct hdac_stream *azx_dev, u32 value)
+{
+ if (!bus->spbcap) {
+ dev_err(bus->dev, "Address of SPB capability is NULL\n");
+ return -EINVAL;
+ }
+
+ writel(value, azx_dev->spib_addr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_set_spib);
+
+/**
+ * snd_hdac_stream_get_spbmaxfifo - gets the spib value of a stream
+ * @bus: HD-audio core bus
+ * @azx_dev: hdac_stream
+ *
+ * Return maxfifo for the stream
+ */
+int snd_hdac_stream_get_spbmaxfifo(struct hdac_bus *bus,
+ struct hdac_stream *azx_dev)
+{
+ if (!bus->spbcap) {
+ dev_err(bus->dev, "Address of SPB capability is NULL\n");
+ return -EINVAL;
+ }
+
+ return readl(azx_dev->fifo_addr);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_get_spbmaxfifo);
+
+/**
+ * snd_hdac_stream_drsm_enable - enable DMA resume for a stream
+ * @bus: HD-audio core bus
+ * @enable: flag to enable/disable DRSM
+ * @index: stream index for which DRSM need to be enabled
+ */
+void snd_hdac_stream_drsm_enable(struct hdac_bus *bus,
+ bool enable, int index)
+{
+ u32 mask = 0;
+
+ if (!bus->drsmcap) {
+ dev_err(bus->dev, "Address of DRSM capability is NULL\n");
+ return;
+ }
+
+ mask |= (1 << index);
+
+ if (enable)
+ snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, mask);
+ else
+ snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, 0);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_drsm_enable);
+
+/*
+ * snd_hdac_stream_wait_drsm - wait for HW to clear RSM for a stream
+ * @azx_dev: HD-audio core stream to await RSM for
+ *
+ * Returns 0 on success and -ETIMEDOUT upon a timeout.
+ */
+int snd_hdac_stream_wait_drsm(struct hdac_stream *azx_dev)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ u32 mask, reg;
+ int ret;
+
+ mask = 1 << azx_dev->index;
+
+ ret = read_poll_timeout(snd_hdac_reg_readl, reg, !(reg & mask), 250, 2000, false, bus,
+ bus->drsmcap + AZX_REG_DRSM_CTL);
+ if (ret)
+ dev_dbg(bus->dev, "polling RSM 0x%08x failed: %d\n", mask, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_wait_drsm);
+
+/**
+ * snd_hdac_stream_set_dpibr - sets the dpibr value of a stream
+ * @bus: HD-audio core bus
+ * @azx_dev: hdac_stream
+ * @value: dpib value to set
+ */
+int snd_hdac_stream_set_dpibr(struct hdac_bus *bus,
+ struct hdac_stream *azx_dev, u32 value)
+{
+ if (!bus->drsmcap) {
+ dev_err(bus->dev, "Address of DRSM capability is NULL\n");
+ return -EINVAL;
+ }
+
+ writel(value, azx_dev->dpibr_addr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_set_dpibr);
+
+/**
+ * snd_hdac_stream_set_lpib - sets the lpib value of a stream
+ * @azx_dev: hdac_stream
+ * @value: lpib value to set
+ */
+int snd_hdac_stream_set_lpib(struct hdac_stream *azx_dev, u32 value)
+{
+ snd_hdac_stream_writel(azx_dev, SD_LPIB, value);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_stream_set_lpib);
+
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+/**
+ * snd_hdac_dsp_prepare - prepare for DSP loading
+ * @azx_dev: HD-audio core stream used for DSP loading
+ * @format: HD-audio stream format
+ * @byte_size: data chunk byte size
+ * @bufp: allocated buffer
+ *
+ * Allocate the buffer for the given size and set up the given stream for
+ * DSP loading. Returns the stream tag (>= 0), or a negative error code.
+ */
+int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format,
+ unsigned int byte_size, struct snd_dma_buffer *bufp)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+ __le32 *bdl;
+ int err;
+
+ snd_hdac_dsp_lock(azx_dev);
+ spin_lock_irq(&bus->reg_lock);
+ if (azx_dev->running || azx_dev->locked) {
+ spin_unlock_irq(&bus->reg_lock);
+ err = -EBUSY;
+ goto unlock;
+ }
+ azx_dev->locked = true;
+ spin_unlock_irq(&bus->reg_lock);
+
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, bus->dev,
+ byte_size, bufp);
+ if (err < 0)
+ goto err_alloc;
+
+ azx_dev->substream = NULL;
+ azx_dev->bufsize = byte_size;
+ azx_dev->period_bytes = byte_size;
+ azx_dev->format_val = format;
+
+ snd_hdac_stream_reset(azx_dev);
+
+ /* reset BDL address */
+ snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
+ snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0);
+
+ azx_dev->frags = 0;
+ bdl = (__le32 *)azx_dev->bdl.area;
+ err = setup_bdle(bus, bufp, azx_dev, &bdl, 0, byte_size, 0);
+ if (err < 0)
+ goto error;
+
+ snd_hdac_stream_setup(azx_dev);
+ snd_hdac_dsp_unlock(azx_dev);
+ return azx_dev->stream_tag;
+
+ error:
+ snd_dma_free_pages(bufp);
+ err_alloc:
+ spin_lock_irq(&bus->reg_lock);
+ azx_dev->locked = false;
+ spin_unlock_irq(&bus->reg_lock);
+ unlock:
+ snd_hdac_dsp_unlock(azx_dev);
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_dsp_prepare);
+
+/**
+ * snd_hdac_dsp_trigger - start / stop DSP loading
+ * @azx_dev: HD-audio core stream used for DSP loading
+ * @start: trigger start or stop
+ */
+void snd_hdac_dsp_trigger(struct hdac_stream *azx_dev, bool start)
+{
+ if (start)
+ snd_hdac_stream_start(azx_dev);
+ else
+ snd_hdac_stream_stop(azx_dev);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_dsp_trigger);
+
+/**
+ * snd_hdac_dsp_cleanup - clean up the stream from DSP loading to normal
+ * @azx_dev: HD-audio core stream used for DSP loading
+ * @dmab: buffer used by DSP loading
+ */
+void snd_hdac_dsp_cleanup(struct hdac_stream *azx_dev,
+ struct snd_dma_buffer *dmab)
+{
+ struct hdac_bus *bus = azx_dev->bus;
+
+ if (!dmab->area || !azx_dev->locked)
+ return;
+
+ snd_hdac_dsp_lock(azx_dev);
+ /* reset BDL address */
+ snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
+ snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0);
+ snd_hdac_stream_writel(azx_dev, SD_CTL, 0);
+ azx_dev->bufsize = 0;
+ azx_dev->period_bytes = 0;
+ azx_dev->format_val = 0;
+
+ snd_dma_free_pages(dmab);
+ dmab->area = NULL;
+
+ spin_lock_irq(&bus->reg_lock);
+ azx_dev->locked = false;
+ spin_unlock_irq(&bus->reg_lock);
+ snd_hdac_dsp_unlock(azx_dev);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_dsp_cleanup);
+#endif /* CONFIG_SND_HDA_DSP_LOADER */
diff --git a/sound/hda/hdac_sysfs.c b/sound/hda/hdac_sysfs.c
new file mode 100644
index 0000000000..60b0a70428
--- /dev/null
+++ b/sound/hda/hdac_sysfs.c
@@ -0,0 +1,469 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * sysfs support for HD-audio core device
+ */
+
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/hdaudio.h>
+#include "local.h"
+
+struct hdac_widget_tree {
+ struct kobject *root;
+ struct kobject *afg;
+ struct kobject **nodes;
+};
+
+#define CODEC_ATTR(type) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hdac_device *codec = dev_to_hdac_dev(dev); \
+ return sysfs_emit(buf, "0x%x\n", codec->type); \
+} \
+static DEVICE_ATTR_RO(type)
+
+#define CODEC_ATTR_STR(type) \
+static ssize_t type##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct hdac_device *codec = dev_to_hdac_dev(dev); \
+ return sysfs_emit(buf, "%s\n", \
+ codec->type ? codec->type : ""); \
+} \
+static DEVICE_ATTR_RO(type)
+
+CODEC_ATTR(type);
+CODEC_ATTR(vendor_id);
+CODEC_ATTR(subsystem_id);
+CODEC_ATTR(revision_id);
+CODEC_ATTR(afg);
+CODEC_ATTR(mfg);
+CODEC_ATTR_STR(vendor_name);
+CODEC_ATTR_STR(chip_name);
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, 256);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *hdac_dev_attrs[] = {
+ &dev_attr_type.attr,
+ &dev_attr_vendor_id.attr,
+ &dev_attr_subsystem_id.attr,
+ &dev_attr_revision_id.attr,
+ &dev_attr_afg.attr,
+ &dev_attr_mfg.attr,
+ &dev_attr_vendor_name.attr,
+ &dev_attr_chip_name.attr,
+ &dev_attr_modalias.attr,
+ NULL
+};
+
+static const struct attribute_group hdac_dev_attr_group = {
+ .attrs = hdac_dev_attrs,
+};
+
+const struct attribute_group *hdac_dev_attr_groups[] = {
+ &hdac_dev_attr_group,
+ NULL
+};
+
+/*
+ * Widget tree sysfs
+ *
+ * This is a tree showing the attributes of each widget. It appears like
+ * /sys/bus/hdaudioC0D0/widgets/04/caps
+ */
+
+struct widget_attribute;
+
+struct widget_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf);
+ ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr,
+ const char *buf, size_t count);
+};
+
+static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp)
+{
+ struct device *dev = kobj_to_dev(kobj->parent->parent);
+ int nid;
+ ssize_t ret;
+
+ ret = kstrtoint(kobj->name, 16, &nid);
+ if (ret < 0)
+ return ret;
+ *codecp = dev_to_hdac_dev(dev);
+ return nid;
+}
+
+static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ struct widget_attribute *wid_attr =
+ container_of(attr, struct widget_attribute, attr);
+ struct hdac_device *codec;
+ int nid;
+
+ if (!wid_attr->show)
+ return -EIO;
+ nid = get_codec_nid(kobj, &codec);
+ if (nid < 0)
+ return nid;
+ return wid_attr->show(codec, nid, wid_attr, buf);
+}
+
+static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ struct widget_attribute *wid_attr =
+ container_of(attr, struct widget_attribute, attr);
+ struct hdac_device *codec;
+ int nid;
+
+ if (!wid_attr->store)
+ return -EIO;
+ nid = get_codec_nid(kobj, &codec);
+ if (nid < 0)
+ return nid;
+ return wid_attr->store(codec, nid, wid_attr, buf, count);
+}
+
+static const struct sysfs_ops widget_sysfs_ops = {
+ .show = widget_attr_show,
+ .store = widget_attr_store,
+};
+
+static void widget_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static const struct kobj_type widget_ktype = {
+ .release = widget_release,
+ .sysfs_ops = &widget_sysfs_ops,
+};
+
+#define WIDGET_ATTR_RO(_name) \
+ struct widget_attribute wid_attr_##_name = __ATTR_RO(_name)
+#define WIDGET_ATTR_RW(_name) \
+ struct widget_attribute wid_attr_##_name = __ATTR_RW(_name)
+
+static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0x%08x\n", get_wcaps(codec, nid));
+}
+
+static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP));
+}
+
+static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ unsigned int val;
+
+ if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+ return 0;
+ if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n", val);
+}
+
+static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid)
+{
+ if (nid == codec->afg || nid == codec->mfg)
+ return true;
+ switch (get_wcaps_type(get_wcaps(codec, nid))) {
+ case AC_WID_AUD_OUT:
+ case AC_WID_AUD_IN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (!has_pcm_cap(codec, nid))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_PCM));
+}
+
+static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (!has_pcm_cap(codec, nid))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_STREAM));
+}
+
+static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP));
+}
+
+static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP));
+}
+
+static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER))
+ return 0;
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE));
+}
+
+static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0x%08x\n",
+ snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP));
+}
+
+static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid,
+ struct widget_attribute *attr, char *buf)
+{
+ hda_nid_t list[32];
+ int i, nconns;
+ ssize_t ret = 0;
+
+ nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list));
+ if (nconns <= 0)
+ return nconns;
+ for (i = 0; i < nconns; i++)
+ ret += sysfs_emit_at(buf, ret, "%s0x%02x", i ? " " : "", list[i]);
+ ret += sysfs_emit_at(buf, ret, "\n");
+ return ret;
+}
+
+static WIDGET_ATTR_RO(caps);
+static WIDGET_ATTR_RO(pin_caps);
+static WIDGET_ATTR_RO(pin_cfg);
+static WIDGET_ATTR_RO(pcm_caps);
+static WIDGET_ATTR_RO(pcm_formats);
+static WIDGET_ATTR_RO(amp_in_caps);
+static WIDGET_ATTR_RO(amp_out_caps);
+static WIDGET_ATTR_RO(power_caps);
+static WIDGET_ATTR_RO(gpio_caps);
+static WIDGET_ATTR_RO(connections);
+
+static struct attribute *widget_node_attrs[] = {
+ &wid_attr_caps.attr,
+ &wid_attr_pin_caps.attr,
+ &wid_attr_pin_cfg.attr,
+ &wid_attr_pcm_caps.attr,
+ &wid_attr_pcm_formats.attr,
+ &wid_attr_amp_in_caps.attr,
+ &wid_attr_amp_out_caps.attr,
+ &wid_attr_power_caps.attr,
+ &wid_attr_connections.attr,
+ NULL,
+};
+
+static struct attribute *widget_afg_attrs[] = {
+ &wid_attr_pcm_caps.attr,
+ &wid_attr_pcm_formats.attr,
+ &wid_attr_amp_in_caps.attr,
+ &wid_attr_amp_out_caps.attr,
+ &wid_attr_power_caps.attr,
+ &wid_attr_gpio_caps.attr,
+ NULL,
+};
+
+static const struct attribute_group widget_node_group = {
+ .attrs = widget_node_attrs,
+};
+
+static const struct attribute_group widget_afg_group = {
+ .attrs = widget_afg_attrs,
+};
+
+static void free_widget_node(struct kobject *kobj,
+ const struct attribute_group *group)
+{
+ if (kobj) {
+ sysfs_remove_group(kobj, group);
+ kobject_put(kobj);
+ }
+}
+
+static void widget_tree_free(struct hdac_device *codec)
+{
+ struct hdac_widget_tree *tree = codec->widgets;
+ struct kobject **p;
+
+ if (!tree)
+ return;
+ free_widget_node(tree->afg, &widget_afg_group);
+ if (tree->nodes) {
+ for (p = tree->nodes; *p; p++)
+ free_widget_node(*p, &widget_node_group);
+ kfree(tree->nodes);
+ }
+ kobject_put(tree->root);
+ kfree(tree);
+ codec->widgets = NULL;
+}
+
+static int add_widget_node(struct kobject *parent, hda_nid_t nid,
+ const struct attribute_group *group,
+ struct kobject **res)
+{
+ struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
+ int err;
+
+ if (!kobj)
+ return -ENOMEM;
+ kobject_init(kobj, &widget_ktype);
+ err = kobject_add(kobj, parent, "%02x", nid);
+ if (err < 0) {
+ kobject_put(kobj);
+ return err;
+ }
+ err = sysfs_create_group(kobj, group);
+ if (err < 0) {
+ kobject_put(kobj);
+ return err;
+ }
+
+ *res = kobj;
+ return 0;
+}
+
+static int widget_tree_create(struct hdac_device *codec)
+{
+ struct hdac_widget_tree *tree;
+ int i, err;
+ hda_nid_t nid;
+
+ tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL);
+ if (!tree)
+ return -ENOMEM;
+
+ tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
+ if (!tree->root)
+ return -ENOMEM;
+
+ tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes),
+ GFP_KERNEL);
+ if (!tree->nodes)
+ return -ENOMEM;
+
+ for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
+ err = add_widget_node(tree->root, nid, &widget_node_group,
+ &tree->nodes[i]);
+ if (err < 0)
+ return err;
+ }
+
+ if (codec->afg) {
+ err = add_widget_node(tree->root, codec->afg,
+ &widget_afg_group, &tree->afg);
+ if (err < 0)
+ return err;
+ }
+
+ kobject_uevent(tree->root, KOBJ_CHANGE);
+ return 0;
+}
+
+/* call with codec->widget_lock held */
+int hda_widget_sysfs_init(struct hdac_device *codec)
+{
+ int err;
+
+ if (codec->widgets)
+ return 0; /* already created */
+
+ err = widget_tree_create(codec);
+ if (err < 0) {
+ widget_tree_free(codec);
+ return err;
+ }
+
+ return 0;
+}
+
+/* call with codec->widget_lock held */
+void hda_widget_sysfs_exit(struct hdac_device *codec)
+{
+ widget_tree_free(codec);
+}
+
+/* call with codec->widget_lock held */
+int hda_widget_sysfs_reinit(struct hdac_device *codec,
+ hda_nid_t start_nid, int num_nodes)
+{
+ struct hdac_widget_tree *tree;
+ hda_nid_t end_nid = start_nid + num_nodes;
+ hda_nid_t nid;
+ int i;
+
+ if (!codec->widgets)
+ return 0;
+
+ tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL);
+ if (!tree)
+ return -ENOMEM;
+
+ tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL);
+ if (!tree->nodes) {
+ kfree(tree);
+ return -ENOMEM;
+ }
+
+ /* prune non-existing nodes */
+ for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
+ if (nid < start_nid || nid >= end_nid)
+ free_widget_node(codec->widgets->nodes[i],
+ &widget_node_group);
+ }
+
+ /* add new nodes */
+ for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) {
+ if (nid < codec->start_nid || nid >= codec->end_nid)
+ add_widget_node(tree->root, nid, &widget_node_group,
+ &tree->nodes[i]);
+ else
+ tree->nodes[i] =
+ codec->widgets->nodes[nid - codec->start_nid];
+ }
+
+ /* replace with the new tree */
+ kfree(codec->widgets->nodes);
+ kfree(codec->widgets);
+ codec->widgets = tree;
+
+ kobject_uevent(tree->root, KOBJ_CHANGE);
+ return 0;
+}
diff --git a/sound/hda/hdmi_chmap.c b/sound/hda/hdmi_chmap.c
new file mode 100644
index 0000000000..5d8e1d944b
--- /dev/null
+++ b/sound/hda/hdmi_chmap.c
@@ -0,0 +1,850 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HDMI Channel map support helpers
+ */
+
+#include <linux/module.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/hda_chmap.h>
+
+/*
+ * CEA speaker placement:
+ *
+ * FLH FCH FRH
+ * FLW FL FLC FC FRC FR FRW
+ *
+ * LFE
+ * TC
+ *
+ * RL RLC RC RRC RR
+ *
+ * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to
+ * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC.
+ */
+enum cea_speaker_placement {
+ FL = (1 << 0), /* Front Left */
+ FC = (1 << 1), /* Front Center */
+ FR = (1 << 2), /* Front Right */
+ FLC = (1 << 3), /* Front Left Center */
+ FRC = (1 << 4), /* Front Right Center */
+ RL = (1 << 5), /* Rear Left */
+ RC = (1 << 6), /* Rear Center */
+ RR = (1 << 7), /* Rear Right */
+ RLC = (1 << 8), /* Rear Left Center */
+ RRC = (1 << 9), /* Rear Right Center */
+ LFE = (1 << 10), /* Low Frequency Effect */
+ FLW = (1 << 11), /* Front Left Wide */
+ FRW = (1 << 12), /* Front Right Wide */
+ FLH = (1 << 13), /* Front Left High */
+ FCH = (1 << 14), /* Front Center High */
+ FRH = (1 << 15), /* Front Right High */
+ TC = (1 << 16), /* Top Center */
+};
+
+static const char * const cea_speaker_allocation_names[] = {
+ /* 0 */ "FL/FR",
+ /* 1 */ "LFE",
+ /* 2 */ "FC",
+ /* 3 */ "RL/RR",
+ /* 4 */ "RC",
+ /* 5 */ "FLC/FRC",
+ /* 6 */ "RLC/RRC",
+ /* 7 */ "FLW/FRW",
+ /* 8 */ "FLH/FRH",
+ /* 9 */ "TC",
+ /* 10 */ "FCH",
+};
+
+/*
+ * ELD SA bits in the CEA Speaker Allocation data block
+ */
+static const int eld_speaker_allocation_bits[] = {
+ [0] = FL | FR,
+ [1] = LFE,
+ [2] = FC,
+ [3] = RL | RR,
+ [4] = RC,
+ [5] = FLC | FRC,
+ [6] = RLC | RRC,
+ /* the following are not defined in ELD yet */
+ [7] = FLW | FRW,
+ [8] = FLH | FRH,
+ [9] = TC,
+ [10] = FCH,
+};
+
+/*
+ * ALSA sequence is:
+ *
+ * surround40 surround41 surround50 surround51 surround71
+ * ch0 front left = = = =
+ * ch1 front right = = = =
+ * ch2 rear left = = = =
+ * ch3 rear right = = = =
+ * ch4 LFE center center center
+ * ch5 LFE LFE
+ * ch6 side left
+ * ch7 side right
+ *
+ * surround71 = {FL, FR, RLC, RRC, FC, LFE, RL, RR}
+ */
+static int hdmi_channel_mapping[0x32][8] = {
+ /* stereo */
+ [0x00] = { 0x00, 0x11, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
+ /* 2.1 */
+ [0x01] = { 0x00, 0x11, 0x22, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
+ /* Dolby Surround */
+ [0x02] = { 0x00, 0x11, 0x23, 0xf2, 0xf4, 0xf5, 0xf6, 0xf7 },
+ /* surround40 */
+ [0x08] = { 0x00, 0x11, 0x24, 0x35, 0xf3, 0xf2, 0xf6, 0xf7 },
+ /* 4ch */
+ [0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 },
+ /* surround41 */
+ [0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 },
+ /* surround50 */
+ [0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 },
+ /* surround51 */
+ [0x0b] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0x52, 0xf6, 0xf7 },
+ /* 7.1 */
+ [0x13] = { 0x00, 0x11, 0x26, 0x37, 0x43, 0x52, 0x64, 0x75 },
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct hdac_cea_channel_speaker_allocation channel_allocations[] = {
+/* channel: 7 6 5 4 3 2 1 0 */
+{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } },
+ /* 2.1 */
+{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } },
+ /* Dolby Surround */
+{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } },
+ /* surround40 */
+{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } },
+ /* surround41 */
+{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } },
+ /* surround50 */
+{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } },
+ /* surround51 */
+{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } },
+ /* 6.1 */
+{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } },
+ /* surround71 */
+{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } },
+
+{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } },
+{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } },
+{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } },
+{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } },
+{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } },
+{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } },
+{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } },
+{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } },
+{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } },
+{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } },
+{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } },
+{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } },
+{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } },
+{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL } },
+{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL } },
+{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL } },
+{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL } },
+{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL } },
+};
+
+static int hdmi_pin_set_slot_channel(struct hdac_device *codec,
+ hda_nid_t pin_nid, int asp_slot, int channel)
+{
+ return snd_hdac_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_HDMI_CHAN_SLOT,
+ (channel << 4) | asp_slot);
+}
+
+static int hdmi_pin_get_slot_channel(struct hdac_device *codec,
+ hda_nid_t pin_nid, int asp_slot)
+{
+ return (snd_hdac_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_CHAN_SLOT,
+ asp_slot) & 0xf0) >> 4;
+}
+
+static int hdmi_get_channel_count(struct hdac_device *codec, hda_nid_t cvt_nid)
+{
+ return 1 + snd_hdac_codec_read(codec, cvt_nid, 0,
+ AC_VERB_GET_CVT_CHAN_COUNT, 0);
+}
+
+static void hdmi_set_channel_count(struct hdac_device *codec,
+ hda_nid_t cvt_nid, int chs)
+{
+ if (chs != hdmi_get_channel_count(codec, cvt_nid))
+ snd_hdac_codec_write(codec, cvt_nid, 0,
+ AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
+}
+
+/*
+ * Channel mapping routines
+ */
+
+/*
+ * Compute derived values in channel_allocations[].
+ */
+static void init_channel_allocations(void)
+{
+ int i, j;
+ struct hdac_cea_channel_speaker_allocation *p;
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ p = channel_allocations + i;
+ p->channels = 0;
+ p->spk_mask = 0;
+ for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+ if (p->speakers[j]) {
+ p->channels++;
+ p->spk_mask |= p->speakers[j];
+ }
+ }
+}
+
+static int get_channel_allocation_order(int ca)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ if (channel_allocations[i].ca_index == ca)
+ break;
+ }
+ return i;
+}
+
+void snd_hdac_print_channel_allocation(int spk_alloc, char *buf, int buflen)
+{
+ int i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) {
+ if (spk_alloc & (1 << i))
+ j += scnprintf(buf + j, buflen - j, " %s",
+ cea_speaker_allocation_names[i]);
+ }
+ buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_GPL(snd_hdac_print_channel_allocation);
+
+/*
+ * The transformation takes two steps:
+ *
+ * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask
+ * spk_mask => (channel_allocations[]) => ai->CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation_spk_alloc_blk(struct hdac_device *codec,
+ int spk_alloc, int channels)
+{
+ int i;
+ int ca = 0;
+ int spk_mask = 0;
+ char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+
+ /*
+ * CA defaults to 0 for basic stereo audio
+ */
+ if (channels <= 2)
+ return 0;
+
+ /*
+ * expand ELD's speaker allocation mask
+ *
+ * ELD tells the speaker mask in a compact(paired) form,
+ * expand ELD's notions to match the ones used by Audio InfoFrame.
+ */
+ for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
+ if (spk_alloc & (1 << i))
+ spk_mask |= eld_speaker_allocation_bits[i];
+ }
+
+ /* search for the first working match in the CA table */
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ if (channels == channel_allocations[i].channels &&
+ (spk_mask & channel_allocations[i].spk_mask) ==
+ channel_allocations[i].spk_mask) {
+ ca = channel_allocations[i].ca_index;
+ break;
+ }
+ }
+
+ if (!ca) {
+ /*
+ * if there was no match, select the regular ALSA channel
+ * allocation with the matching number of channels
+ */
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ if (channels == channel_allocations[i].channels) {
+ ca = channel_allocations[i].ca_index;
+ break;
+ }
+ }
+ }
+
+ snd_hdac_print_channel_allocation(spk_alloc, buf, sizeof(buf));
+ dev_dbg(&codec->dev, "HDMI: select CA 0x%x for %d-channel allocation: %s\n",
+ ca, channels, buf);
+
+ return ca;
+}
+
+static void hdmi_debug_channel_mapping(struct hdac_chmap *chmap,
+ hda_nid_t pin_nid)
+{
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ int i;
+ int channel;
+
+ for (i = 0; i < 8; i++) {
+ channel = chmap->ops.pin_get_slot_channel(
+ chmap->hdac, pin_nid, i);
+ dev_dbg(&chmap->hdac->dev, "HDMI: ASP channel %d => slot %d\n",
+ channel, i);
+ }
+#endif
+}
+
+static void hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap,
+ hda_nid_t pin_nid,
+ bool non_pcm,
+ int ca)
+{
+ struct hdac_cea_channel_speaker_allocation *ch_alloc;
+ int i;
+ int err;
+ int order;
+ int non_pcm_mapping[8];
+
+ order = get_channel_allocation_order(ca);
+ ch_alloc = &channel_allocations[order];
+
+ if (hdmi_channel_mapping[ca][1] == 0) {
+ int hdmi_slot = 0;
+ /* fill actual channel mappings in ALSA channel (i) order */
+ for (i = 0; i < ch_alloc->channels; i++) {
+ while (!WARN_ON(hdmi_slot >= 8) &&
+ !ch_alloc->speakers[7 - hdmi_slot])
+ hdmi_slot++; /* skip zero slots */
+
+ hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++;
+ }
+ /* fill the rest of the slots with ALSA channel 0xf */
+ for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++)
+ if (!ch_alloc->speakers[7 - hdmi_slot])
+ hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot;
+ }
+
+ if (non_pcm) {
+ for (i = 0; i < ch_alloc->channels; i++)
+ non_pcm_mapping[i] = (i << 4) | i;
+ for (; i < 8; i++)
+ non_pcm_mapping[i] = (0xf << 4) | i;
+ }
+
+ for (i = 0; i < 8; i++) {
+ int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i];
+ int hdmi_slot = slotsetup & 0x0f;
+ int channel = (slotsetup & 0xf0) >> 4;
+
+ err = chmap->ops.pin_set_slot_channel(chmap->hdac,
+ pin_nid, hdmi_slot, channel);
+ if (err) {
+ dev_dbg(&chmap->hdac->dev, "HDMI: channel mapping failed\n");
+ break;
+ }
+ }
+}
+
+struct channel_map_table {
+ unsigned char map; /* ALSA API channel map position */
+ int spk_mask; /* speaker position bit mask */
+};
+
+static struct channel_map_table map_tables[] = {
+ { SNDRV_CHMAP_FL, FL },
+ { SNDRV_CHMAP_FR, FR },
+ { SNDRV_CHMAP_RL, RL },
+ { SNDRV_CHMAP_RR, RR },
+ { SNDRV_CHMAP_LFE, LFE },
+ { SNDRV_CHMAP_FC, FC },
+ { SNDRV_CHMAP_RLC, RLC },
+ { SNDRV_CHMAP_RRC, RRC },
+ { SNDRV_CHMAP_RC, RC },
+ { SNDRV_CHMAP_FLC, FLC },
+ { SNDRV_CHMAP_FRC, FRC },
+ { SNDRV_CHMAP_TFL, FLH },
+ { SNDRV_CHMAP_TFR, FRH },
+ { SNDRV_CHMAP_FLW, FLW },
+ { SNDRV_CHMAP_FRW, FRW },
+ { SNDRV_CHMAP_TC, TC },
+ { SNDRV_CHMAP_TFC, FCH },
+ {} /* terminator */
+};
+
+/* from ALSA API channel position to speaker bit mask */
+int snd_hdac_chmap_to_spk_mask(unsigned char c)
+{
+ struct channel_map_table *t = map_tables;
+
+ for (; t->map; t++) {
+ if (t->map == c)
+ return t->spk_mask;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_chmap_to_spk_mask);
+
+/* from ALSA API channel position to CEA slot */
+static int to_cea_slot(int ordered_ca, unsigned char pos)
+{
+ int mask = snd_hdac_chmap_to_spk_mask(pos);
+ int i;
+
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (ordered_ca >= ARRAY_SIZE(channel_allocations))
+ return -1;
+
+ if (mask) {
+ for (i = 0; i < 8; i++) {
+ if (channel_allocations[ordered_ca].speakers[7 - i] == mask)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/* from speaker bit mask to ALSA API channel position */
+int snd_hdac_spk_to_chmap(int spk)
+{
+ struct channel_map_table *t = map_tables;
+
+ for (; t->map; t++) {
+ if (t->spk_mask == spk)
+ return t->map;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap);
+
+/* from CEA slot to ALSA API channel position */
+static int from_cea_slot(int ordered_ca, unsigned char slot)
+{
+ int mask;
+
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (slot >= 8)
+ return 0;
+
+ mask = channel_allocations[ordered_ca].speakers[7 - slot];
+
+ return snd_hdac_spk_to_chmap(mask);
+}
+
+/* get the CA index corresponding to the given ALSA API channel map */
+static int hdmi_manual_channel_allocation(int chs, unsigned char *map)
+{
+ int i, spks = 0, spk_mask = 0;
+
+ for (i = 0; i < chs; i++) {
+ int mask = snd_hdac_chmap_to_spk_mask(map[i]);
+
+ if (mask) {
+ spk_mask |= mask;
+ spks++;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ if ((chs == channel_allocations[i].channels ||
+ spks == channel_allocations[i].channels) &&
+ (spk_mask & channel_allocations[i].spk_mask) ==
+ channel_allocations[i].spk_mask)
+ return channel_allocations[i].ca_index;
+ }
+ return -1;
+}
+
+/* set up the channel slots for the given ALSA API channel map */
+static int hdmi_manual_setup_channel_mapping(struct hdac_chmap *chmap,
+ hda_nid_t pin_nid,
+ int chs, unsigned char *map,
+ int ca)
+{
+ int ordered_ca = get_channel_allocation_order(ca);
+ int alsa_pos, hdmi_slot;
+ int assignments[8] = {[0 ... 7] = 0xf};
+
+ for (alsa_pos = 0; alsa_pos < chs; alsa_pos++) {
+
+ hdmi_slot = to_cea_slot(ordered_ca, map[alsa_pos]);
+
+ if (hdmi_slot < 0)
+ continue; /* unassigned channel */
+
+ assignments[hdmi_slot] = alsa_pos;
+ }
+
+ for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) {
+ int err;
+
+ err = chmap->ops.pin_set_slot_channel(chmap->hdac,
+ pin_nid, hdmi_slot, assignments[hdmi_slot]);
+ if (err)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* store ALSA API channel map from the current default map */
+static void hdmi_setup_fake_chmap(unsigned char *map, int ca)
+{
+ int i;
+ int ordered_ca = get_channel_allocation_order(ca);
+
+ for (i = 0; i < 8; i++) {
+ if (ordered_ca < ARRAY_SIZE(channel_allocations) &&
+ i < channel_allocations[ordered_ca].channels)
+ map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f);
+ else
+ map[i] = 0;
+ }
+}
+
+void snd_hdac_setup_channel_mapping(struct hdac_chmap *chmap,
+ hda_nid_t pin_nid, bool non_pcm, int ca,
+ int channels, unsigned char *map,
+ bool chmap_set)
+{
+ if (!non_pcm && chmap_set) {
+ hdmi_manual_setup_channel_mapping(chmap, pin_nid,
+ channels, map, ca);
+ } else {
+ hdmi_std_setup_channel_mapping(chmap, pin_nid, non_pcm, ca);
+ hdmi_setup_fake_chmap(map, ca);
+ }
+
+ hdmi_debug_channel_mapping(chmap, pin_nid);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_setup_channel_mapping);
+
+int snd_hdac_get_active_channels(int ca)
+{
+ int ordered_ca = get_channel_allocation_order(ca);
+
+ /* Add sanity check to pass klockwork check.
+ * This should never happen.
+ */
+ if (ordered_ca >= ARRAY_SIZE(channel_allocations))
+ ordered_ca = 0;
+
+ return channel_allocations[ordered_ca].channels;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_active_channels);
+
+struct hdac_cea_channel_speaker_allocation *snd_hdac_get_ch_alloc_from_ca(int ca)
+{
+ return &channel_allocations[get_channel_allocation_order(ca)];
+}
+EXPORT_SYMBOL_GPL(snd_hdac_get_ch_alloc_from_ca);
+
+int snd_hdac_channel_allocation(struct hdac_device *hdac, int spk_alloc,
+ int channels, bool chmap_set, bool non_pcm, unsigned char *map)
+{
+ int ca;
+
+ if (!non_pcm && chmap_set)
+ ca = hdmi_manual_channel_allocation(channels, map);
+ else
+ ca = hdmi_channel_allocation_spk_alloc_blk(hdac,
+ spk_alloc, channels);
+
+ if (ca < 0)
+ ca = 0;
+
+ return ca;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_channel_allocation);
+
+/*
+ * ALSA API channel-map control callbacks
+ */
+static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct hdac_chmap *chmap = info->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chmap->channels_max;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+ return 0;
+}
+
+static int hdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap,
+ struct hdac_cea_channel_speaker_allocation *cap, int channels)
+{
+ /* If the speaker allocation matches the channel count, it is OK.*/
+ if (cap->channels != channels)
+ return -1;
+
+ /* all channels are remappable freely */
+ return SNDRV_CTL_TLVT_CHMAP_VAR;
+}
+
+static void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
+ struct hdac_cea_channel_speaker_allocation *cap,
+ unsigned int *chmap, int channels)
+{
+ int count = 0;
+ int c;
+
+ for (c = 7; c >= 0; c--) {
+ int spk = cap->speakers[c];
+
+ if (!spk)
+ continue;
+
+ chmap[count++] = snd_hdac_spk_to_chmap(spk);
+ }
+
+ WARN_ON(count != channels);
+}
+
+static int spk_mask_from_spk_alloc(int spk_alloc)
+{
+ int i;
+ int spk_mask = eld_speaker_allocation_bits[0];
+
+ for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
+ if (spk_alloc & (1 << i))
+ spk_mask |= eld_speaker_allocation_bits[i];
+ }
+
+ return spk_mask;
+}
+
+static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *tlv)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct hdac_chmap *chmap = info->private_data;
+ int pcm_idx = kcontrol->private_value;
+ unsigned int __user *dst;
+ int chs, count = 0;
+ unsigned long max_chs;
+ int type;
+ int spk_alloc, spk_mask;
+
+ if (size < 8)
+ return -ENOMEM;
+ if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+ return -EFAULT;
+ size -= 8;
+ dst = tlv + 2;
+
+ spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx);
+ spk_mask = spk_mask_from_spk_alloc(spk_alloc);
+
+ max_chs = hweight_long(spk_mask);
+
+ for (chs = 2; chs <= max_chs; chs++) {
+ int i;
+ struct hdac_cea_channel_speaker_allocation *cap;
+
+ cap = channel_allocations;
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
+ int chs_bytes = chs * 4;
+ unsigned int tlv_chmap[8];
+
+ if (cap->channels != chs)
+ continue;
+
+ if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
+ continue;
+
+ type = chmap->ops.chmap_cea_alloc_validate_get_type(
+ chmap, cap, chs);
+ if (type < 0)
+ return -ENODEV;
+ if (size < 8)
+ return -ENOMEM;
+
+ if (put_user(type, dst) ||
+ put_user(chs_bytes, dst + 1))
+ return -EFAULT;
+
+ dst += 2;
+ size -= 8;
+ count += 8;
+
+ if (size < chs_bytes)
+ return -ENOMEM;
+
+ size -= chs_bytes;
+ count += chs_bytes;
+ chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap,
+ tlv_chmap, chs);
+
+ if (copy_to_user(dst, tlv_chmap, chs_bytes))
+ return -EFAULT;
+ dst += chs;
+ }
+ }
+
+ if (put_user(count, tlv + 1))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct hdac_chmap *chmap = info->private_data;
+ int pcm_idx = kcontrol->private_value;
+ unsigned char pcm_chmap[8];
+ int i;
+
+ memset(pcm_chmap, 0, sizeof(pcm_chmap));
+ chmap->ops.get_chmap(chmap->hdac, pcm_idx, pcm_chmap);
+
+ for (i = 0; i < ARRAY_SIZE(pcm_chmap); i++)
+ ucontrol->value.integer.value[i] = pcm_chmap[i];
+
+ return 0;
+}
+
+static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct hdac_chmap *hchmap = info->private_data;
+ int pcm_idx = kcontrol->private_value;
+ unsigned int ctl_idx;
+ struct snd_pcm_substream *substream;
+ unsigned char chmap[8], per_pin_chmap[8];
+ int i, err, ca, prepared = 0;
+
+ /* No monitor is connected in dyn_pcm_assign.
+ * It's invalid to setup the chmap
+ */
+ if (!hchmap->ops.is_pcm_attached(hchmap->hdac, pcm_idx))
+ return 0;
+
+ ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ substream = snd_pcm_chmap_substream(info, ctl_idx);
+ if (!substream || !substream->runtime)
+ return 0; /* just for avoiding error from alsactl restore */
+ switch (substream->runtime->state) {
+ case SNDRV_PCM_STATE_OPEN:
+ case SNDRV_PCM_STATE_SETUP:
+ break;
+ case SNDRV_PCM_STATE_PREPARED:
+ prepared = 1;
+ break;
+ default:
+ return -EBUSY;
+ }
+ memset(chmap, 0, sizeof(chmap));
+ for (i = 0; i < ARRAY_SIZE(chmap); i++)
+ chmap[i] = ucontrol->value.integer.value[i];
+
+ hchmap->ops.get_chmap(hchmap->hdac, pcm_idx, per_pin_chmap);
+ if (!memcmp(chmap, per_pin_chmap, sizeof(chmap)))
+ return 0;
+ ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
+ if (ca < 0)
+ return -EINVAL;
+ if (hchmap->ops.chmap_validate) {
+ err = hchmap->ops.chmap_validate(hchmap, ca,
+ ARRAY_SIZE(chmap), chmap);
+ if (err)
+ return err;
+ }
+
+ hchmap->ops.set_chmap(hchmap->hdac, pcm_idx, chmap, prepared);
+
+ return 0;
+}
+
+static const struct hdac_chmap_ops chmap_ops = {
+ .chmap_cea_alloc_validate_get_type = hdmi_chmap_cea_alloc_validate_get_type,
+ .cea_alloc_to_tlv_chmap = hdmi_cea_alloc_to_tlv_chmap,
+ .pin_get_slot_channel = hdmi_pin_get_slot_channel,
+ .pin_set_slot_channel = hdmi_pin_set_slot_channel,
+ .set_channel_count = hdmi_set_channel_count,
+};
+
+void snd_hdac_register_chmap_ops(struct hdac_device *hdac,
+ struct hdac_chmap *chmap)
+{
+ chmap->ops = chmap_ops;
+ chmap->hdac = hdac;
+ init_channel_allocations();
+}
+EXPORT_SYMBOL_GPL(snd_hdac_register_chmap_ops);
+
+int snd_hdac_add_chmap_ctls(struct snd_pcm *pcm, int pcm_idx,
+ struct hdac_chmap *hchmap)
+{
+ struct snd_pcm_chmap *chmap;
+ struct snd_kcontrol *kctl;
+ int err, i;
+
+ err = snd_pcm_add_chmap_ctls(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ NULL, 0, pcm_idx, &chmap);
+ if (err < 0)
+ return err;
+ /* override handlers */
+ chmap->private_data = hchmap;
+ kctl = chmap->kctl;
+ for (i = 0; i < kctl->count; i++)
+ kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+ kctl->info = hdmi_chmap_ctl_info;
+ kctl->get = hdmi_chmap_ctl_get;
+ kctl->put = hdmi_chmap_ctl_put;
+ kctl->tlv.c = hdmi_chmap_ctl_tlv;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_hdac_add_chmap_ctls);
diff --git a/sound/hda/intel-dsp-config.c b/sound/hda/intel-dsp-config.c
new file mode 100644
index 0000000000..756fa0aa69
--- /dev/null
+++ b/sound/hda/intel-dsp-config.c
@@ -0,0 +1,767 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019 Jaroslav Kysela <perex@perex.cz>
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <sound/core.h>
+#include <sound/intel-dsp-config.h>
+#include <sound/intel-nhlt.h>
+#include <sound/soc-acpi.h>
+
+static int dsp_driver;
+
+module_param(dsp_driver, int, 0444);
+MODULE_PARM_DESC(dsp_driver, "Force the DSP driver for Intel DSP (0=auto, 1=legacy, 2=SST, 3=SOF)");
+
+#define FLAG_SST BIT(0)
+#define FLAG_SOF BIT(1)
+#define FLAG_SST_ONLY_IF_DMIC BIT(15)
+#define FLAG_SOF_ONLY_IF_DMIC BIT(16)
+#define FLAG_SOF_ONLY_IF_SOUNDWIRE BIT(17)
+
+#define FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE (FLAG_SOF_ONLY_IF_DMIC | \
+ FLAG_SOF_ONLY_IF_SOUNDWIRE)
+
+struct config_entry {
+ u32 flags;
+ u16 device;
+ u8 acpi_hid[ACPI_ID_LEN];
+ const struct dmi_system_id *dmi_table;
+ const struct snd_soc_acpi_codecs *codec_hid;
+};
+
+static const struct snd_soc_acpi_codecs __maybe_unused essx_83x6 = {
+ .num_codecs = 3,
+ .codecs = { "ESSX8316", "ESSX8326", "ESSX8336"},
+};
+
+/*
+ * configuration table
+ * - the order of similar PCI ID entries is important!
+ * - the first successful match will win
+ */
+static const struct config_entry config_table[] = {
+/* Merrifield */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_SST_TNG,
+ },
+#endif
+/*
+ * Apollolake (Broxton-P)
+ * the legacy HDAudio driver is used except on Up Squared (SOF) and
+ * Chromebooks (SST), as well as devices based on the ES8336 codec
+ */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_APL,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Up Squared",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_APL,
+ .codec_hid = &essx_83x6,
+ },
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_APL)
+ {
+ .flags = FLAG_SST,
+ .device = PCI_DEVICE_ID_INTEL_HDA_APL,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+#endif
+/*
+ * Skylake and Kabylake use legacy HDAudio driver except for Google
+ * Chromebooks (SST)
+ */
+
+/* Sunrise Point-LP */
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKL)
+ {
+ .flags = FLAG_SST,
+ .device = PCI_DEVICE_ID_INTEL_HDA_SKL_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SST | FLAG_SST_ONLY_IF_DMIC,
+ .device = PCI_DEVICE_ID_INTEL_HDA_SKL_LP,
+ },
+#endif
+/* Kabylake-LP */
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_KBL)
+ {
+ .flags = FLAG_SST,
+ .device = PCI_DEVICE_ID_INTEL_HDA_KBL_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SST | FLAG_SST_ONLY_IF_DMIC,
+ .device = PCI_DEVICE_ID_INTEL_HDA_KBL_LP,
+ },
+#endif
+
+/*
+ * Geminilake uses legacy HDAudio driver except for Google
+ * Chromebooks and devices based on the ES8336 codec
+ */
+/* Geminilake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_GML,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_GML,
+ .codec_hid = &essx_83x6,
+ },
+#endif
+
+/*
+ * CoffeeLake, CannonLake, CometLake, IceLake, TigerLake, AlderLake,
+ * RaptorLake use legacy HDAudio driver except for Google Chromebooks
+ * and when DMICs are present. Two cases are required since Coreboot
+ * does not expose NHLT tables.
+ *
+ * When the Chromebook quirk is not present, it's based on information
+ * that no such device exists. When the quirk is present, it could be
+ * either based on product information or a placeholder.
+ */
+
+/* Cannonlake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {
+ .ident = "UP-WHL",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
+ },
+#endif
+
+/* Coffelake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CNL_H,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CNL_H,
+ },
+#endif
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
+/* Cometlake-LP */
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "09C6")
+ },
+ },
+ {
+ /* early version of SKU 09C6 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0983")
+ },
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
+ },
+/* Cometlake-H */
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "098F"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0990"),
+ },
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
+ },
+#endif
+
+/* Icelake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
+ },
+#endif
+
+/* Jasper Lake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {
+ .ident = "Google firmware",
+ .matches = {
+ DMI_MATCH(DMI_BIOS_VERSION, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
+ .device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
+ },
+#endif
+
+/* Tigerlake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE)
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {
+ .ident = "UPX-TGL",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_TGL_H,
+ },
+#endif
+
+/* Elkhart Lake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE)
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
+ .device = PCI_DEVICE_ID_INTEL_HDA_EHL_0,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
+ .device = PCI_DEVICE_ID_INTEL_HDA_EHL_3,
+ },
+#endif
+
+/* Alder Lake / Raptor Lake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_ALDERLAKE)
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_S,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_S,
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_PX,
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_PS,
+ .codec_hid = &essx_83x6,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_PS,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_M,
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_N,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_ADL_N,
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_0,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_0,
+ },
+ {
+ .flags = FLAG_SOF,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_1,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {}
+ }
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_1,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_M,
+ },
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_RPL_PX,
+ },
+#endif
+
+/* Meteor Lake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_METEORLAKE)
+ /* Meteorlake-P */
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_MTL,
+ },
+#endif
+
+/* Lunar Lake */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_LUNARLAKE)
+ /* Lunarlake-P */
+ {
+ .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
+ .device = PCI_DEVICE_ID_INTEL_HDA_LNL_P,
+ },
+#endif
+};
+
+static const struct config_entry *snd_intel_dsp_find_config
+ (struct pci_dev *pci, const struct config_entry *table, u32 len)
+{
+ u16 device;
+
+ device = pci->device;
+ for (; len > 0; len--, table++) {
+ if (table->device != device)
+ continue;
+ if (table->dmi_table && !dmi_check_system(table->dmi_table))
+ continue;
+ if (table->codec_hid) {
+ int i;
+
+ for (i = 0; i < table->codec_hid->num_codecs; i++)
+ if (acpi_dev_present(table->codec_hid->codecs[i], NULL, -1))
+ break;
+ if (i == table->codec_hid->num_codecs)
+ continue;
+ }
+ return table;
+ }
+ return NULL;
+}
+
+static int snd_intel_dsp_check_dmic(struct pci_dev *pci)
+{
+ struct nhlt_acpi_table *nhlt;
+ int ret = 0;
+
+ nhlt = intel_nhlt_init(&pci->dev);
+ if (nhlt) {
+ if (intel_nhlt_has_endpoint_type(nhlt, NHLT_LINK_DMIC))
+ ret = 1;
+ intel_nhlt_free(nhlt);
+ }
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
+static int snd_intel_dsp_check_soundwire(struct pci_dev *pci)
+{
+ struct sdw_intel_acpi_info info;
+ acpi_handle handle;
+ int ret;
+
+ handle = ACPI_HANDLE(&pci->dev);
+
+ ret = sdw_intel_acpi_scan(handle, &info);
+ if (ret < 0)
+ return ret;
+
+ return info.link_mask;
+}
+#else
+static int snd_intel_dsp_check_soundwire(struct pci_dev *pci)
+{
+ return 0;
+}
+#endif
+
+int snd_intel_dsp_driver_probe(struct pci_dev *pci)
+{
+ const struct config_entry *cfg;
+
+ /* Intel vendor only */
+ if (pci->vendor != PCI_VENDOR_ID_INTEL)
+ return SND_INTEL_DSP_DRIVER_ANY;
+
+ /*
+ * Legacy devices don't have a PCI-based DSP and use HDaudio
+ * for HDMI/DP support, ignore kernel parameter
+ */
+ switch (pci->device) {
+ case PCI_DEVICE_ID_INTEL_HDA_BDW:
+ case PCI_DEVICE_ID_INTEL_HDA_HSW_0:
+ case PCI_DEVICE_ID_INTEL_HDA_HSW_2:
+ case PCI_DEVICE_ID_INTEL_HDA_HSW_3:
+ case PCI_DEVICE_ID_INTEL_HDA_BYT:
+ case PCI_DEVICE_ID_INTEL_HDA_BSW:
+ return SND_INTEL_DSP_DRIVER_ANY;
+ }
+
+ if (dsp_driver > 0 && dsp_driver <= SND_INTEL_DSP_DRIVER_LAST)
+ return dsp_driver;
+
+ /*
+ * detect DSP by checking class/subclass/prog-id information
+ * class=04 subclass 03 prog-if 00: no DSP, use legacy driver
+ * class=04 subclass 01 prog-if 00: DSP is present
+ * (and may be required e.g. for DMIC or SSP support)
+ * class=04 subclass 03 prog-if 80: use DSP or legacy mode
+ */
+ if (pci->class == 0x040300)
+ return SND_INTEL_DSP_DRIVER_LEGACY;
+ if (pci->class != 0x040100 && pci->class != 0x040380) {
+ dev_err(&pci->dev, "Unknown PCI class/subclass/prog-if information (0x%06x) found, selecting HDAudio legacy driver\n", pci->class);
+ return SND_INTEL_DSP_DRIVER_LEGACY;
+ }
+
+ dev_info(&pci->dev, "DSP detected with PCI class/subclass/prog-if info 0x%06x\n", pci->class);
+
+ /* find the configuration for the specific device */
+ cfg = snd_intel_dsp_find_config(pci, config_table, ARRAY_SIZE(config_table));
+ if (!cfg)
+ return SND_INTEL_DSP_DRIVER_ANY;
+
+ if (cfg->flags & FLAG_SOF) {
+ if (cfg->flags & FLAG_SOF_ONLY_IF_SOUNDWIRE &&
+ snd_intel_dsp_check_soundwire(pci) > 0) {
+ dev_info(&pci->dev, "SoundWire enabled on CannonLake+ platform, using SOF driver\n");
+ return SND_INTEL_DSP_DRIVER_SOF;
+ }
+ if (cfg->flags & FLAG_SOF_ONLY_IF_DMIC &&
+ snd_intel_dsp_check_dmic(pci)) {
+ dev_info(&pci->dev, "Digital mics found on Skylake+ platform, using SOF driver\n");
+ return SND_INTEL_DSP_DRIVER_SOF;
+ }
+ if (!(cfg->flags & FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE))
+ return SND_INTEL_DSP_DRIVER_SOF;
+ }
+
+
+ if (cfg->flags & FLAG_SST) {
+ if (cfg->flags & FLAG_SST_ONLY_IF_DMIC) {
+ if (snd_intel_dsp_check_dmic(pci)) {
+ dev_info(&pci->dev, "Digital mics found on Skylake+ platform, using SST driver\n");
+ return SND_INTEL_DSP_DRIVER_SST;
+ }
+ } else {
+ return SND_INTEL_DSP_DRIVER_SST;
+ }
+ }
+
+ return SND_INTEL_DSP_DRIVER_LEGACY;
+}
+EXPORT_SYMBOL_GPL(snd_intel_dsp_driver_probe);
+
+/* Should we default to SOF or SST for BYT/CHT ? */
+#if IS_ENABLED(CONFIG_SND_INTEL_BYT_PREFER_SOF) || \
+ !IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI)
+#define FLAG_SST_OR_SOF_BYT FLAG_SOF
+#else
+#define FLAG_SST_OR_SOF_BYT FLAG_SST
+#endif
+
+/*
+ * configuration table
+ * - the order of similar ACPI ID entries is important!
+ * - the first successful match will win
+ */
+static const struct config_entry acpi_config_table[] = {
+#if IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI) || \
+ IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* BayTrail */
+ {
+ .flags = FLAG_SST_OR_SOF_BYT,
+ .acpi_hid = "80860F28",
+ },
+/* CherryTrail */
+ {
+ .flags = FLAG_SST_OR_SOF_BYT,
+ .acpi_hid = "808622A8",
+ },
+#endif
+/* Broadwell */
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CATPT)
+ {
+ .flags = FLAG_SST,
+ .acpi_hid = "INT3438"
+ },
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
+ {
+ .flags = FLAG_SOF,
+ .acpi_hid = "INT3438"
+ },
+#endif
+/* Haswell - not supported by SOF but added for consistency */
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CATPT)
+ {
+ .flags = FLAG_SST,
+ .acpi_hid = "INT33C8"
+ },
+#endif
+};
+
+static const struct config_entry *snd_intel_acpi_dsp_find_config(const u8 acpi_hid[ACPI_ID_LEN],
+ const struct config_entry *table,
+ u32 len)
+{
+ for (; len > 0; len--, table++) {
+ if (memcmp(table->acpi_hid, acpi_hid, ACPI_ID_LEN))
+ continue;
+ if (table->dmi_table && !dmi_check_system(table->dmi_table))
+ continue;
+ return table;
+ }
+ return NULL;
+}
+
+int snd_intel_acpi_dsp_driver_probe(struct device *dev, const u8 acpi_hid[ACPI_ID_LEN])
+{
+ const struct config_entry *cfg;
+
+ if (dsp_driver > SND_INTEL_DSP_DRIVER_LEGACY && dsp_driver <= SND_INTEL_DSP_DRIVER_LAST)
+ return dsp_driver;
+
+ if (dsp_driver == SND_INTEL_DSP_DRIVER_LEGACY) {
+ dev_warn(dev, "dsp_driver parameter %d not supported, using automatic detection\n",
+ SND_INTEL_DSP_DRIVER_LEGACY);
+ }
+
+ /* find the configuration for the specific device */
+ cfg = snd_intel_acpi_dsp_find_config(acpi_hid, acpi_config_table,
+ ARRAY_SIZE(acpi_config_table));
+ if (!cfg)
+ return SND_INTEL_DSP_DRIVER_ANY;
+
+ if (cfg->flags & FLAG_SST)
+ return SND_INTEL_DSP_DRIVER_SST;
+
+ if (cfg->flags & FLAG_SOF)
+ return SND_INTEL_DSP_DRIVER_SOF;
+
+ return SND_INTEL_DSP_DRIVER_SST;
+}
+EXPORT_SYMBOL_GPL(snd_intel_acpi_dsp_driver_probe);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel DSP config driver");
+MODULE_IMPORT_NS(SND_INTEL_SOUNDWIRE_ACPI);
diff --git a/sound/hda/intel-nhlt.c b/sound/hda/intel-nhlt.c
new file mode 100644
index 0000000000..696a958d93
--- /dev/null
+++ b/sound/hda/intel-nhlt.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2015-2019 Intel Corporation
+
+#include <linux/acpi.h>
+#include <sound/intel-nhlt.h>
+
+struct nhlt_acpi_table *intel_nhlt_init(struct device *dev)
+{
+ struct nhlt_acpi_table *nhlt;
+ acpi_status status;
+
+ status = acpi_get_table(ACPI_SIG_NHLT, 0,
+ (struct acpi_table_header **)&nhlt);
+ if (ACPI_FAILURE(status)) {
+ dev_warn(dev, "NHLT table not found\n");
+ return NULL;
+ }
+
+ return nhlt;
+}
+EXPORT_SYMBOL_GPL(intel_nhlt_init);
+
+void intel_nhlt_free(struct nhlt_acpi_table *nhlt)
+{
+ acpi_put_table((struct acpi_table_header *)nhlt);
+}
+EXPORT_SYMBOL_GPL(intel_nhlt_free);
+
+int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt)
+{
+ struct nhlt_endpoint *epnt;
+ struct nhlt_dmic_array_config *cfg;
+ struct nhlt_vendor_dmic_array_config *cfg_vendor;
+ struct nhlt_fmt *fmt_configs;
+ unsigned int dmic_geo = 0;
+ u16 max_ch = 0;
+ u8 i, j;
+
+ if (!nhlt)
+ return 0;
+
+ if (nhlt->header.length <= sizeof(struct acpi_table_header)) {
+ dev_warn(dev, "Invalid DMIC description table\n");
+ return 0;
+ }
+
+ for (j = 0, epnt = nhlt->desc; j < nhlt->endpoint_count; j++,
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length)) {
+
+ if (epnt->linktype != NHLT_LINK_DMIC)
+ continue;
+
+ cfg = (struct nhlt_dmic_array_config *)(epnt->config.caps);
+ fmt_configs = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
+
+ /* find max number of channels based on format_configuration */
+ if (fmt_configs->fmt_count) {
+ struct nhlt_fmt_cfg *fmt_cfg = fmt_configs->fmt_config;
+
+ dev_dbg(dev, "found %d format definitions\n",
+ fmt_configs->fmt_count);
+
+ for (i = 0; i < fmt_configs->fmt_count; i++) {
+ struct wav_fmt_ext *fmt_ext;
+
+ fmt_ext = &fmt_cfg->fmt_ext;
+
+ if (fmt_ext->fmt.channels > max_ch)
+ max_ch = fmt_ext->fmt.channels;
+
+ /* Move to the next nhlt_fmt_cfg */
+ fmt_cfg = (struct nhlt_fmt_cfg *)(fmt_cfg->config.caps +
+ fmt_cfg->config.size);
+ }
+ dev_dbg(dev, "max channels found %d\n", max_ch);
+ } else {
+ dev_dbg(dev, "No format information found\n");
+ }
+
+ if (cfg->device_config.config_type != NHLT_CONFIG_TYPE_MIC_ARRAY) {
+ dmic_geo = max_ch;
+ } else {
+ switch (cfg->array_type) {
+ case NHLT_MIC_ARRAY_2CH_SMALL:
+ case NHLT_MIC_ARRAY_2CH_BIG:
+ dmic_geo = MIC_ARRAY_2CH;
+ break;
+
+ case NHLT_MIC_ARRAY_4CH_1ST_GEOM:
+ case NHLT_MIC_ARRAY_4CH_L_SHAPED:
+ case NHLT_MIC_ARRAY_4CH_2ND_GEOM:
+ dmic_geo = MIC_ARRAY_4CH;
+ break;
+ case NHLT_MIC_ARRAY_VENDOR_DEFINED:
+ cfg_vendor = (struct nhlt_vendor_dmic_array_config *)cfg;
+ dmic_geo = cfg_vendor->nb_mics;
+ break;
+ default:
+ dev_warn(dev, "%s: undefined DMIC array_type 0x%0x\n",
+ __func__, cfg->array_type);
+ }
+
+ if (dmic_geo > 0) {
+ dev_dbg(dev, "Array with %d dmics\n", dmic_geo);
+ }
+ if (max_ch > dmic_geo) {
+ dev_dbg(dev, "max channels %d exceed dmic number %d\n",
+ max_ch, dmic_geo);
+ }
+ }
+ }
+
+ dev_dbg(dev, "dmic number %d max_ch %d\n", dmic_geo, max_ch);
+
+ return dmic_geo;
+}
+EXPORT_SYMBOL_GPL(intel_nhlt_get_dmic_geo);
+
+bool intel_nhlt_has_endpoint_type(struct nhlt_acpi_table *nhlt, u8 link_type)
+{
+ struct nhlt_endpoint *epnt;
+ int i;
+
+ if (!nhlt)
+ return false;
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+ for (i = 0; i < nhlt->endpoint_count; i++) {
+ if (epnt->linktype == link_type)
+ return true;
+
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+ return false;
+}
+EXPORT_SYMBOL(intel_nhlt_has_endpoint_type);
+
+int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 device_type)
+{
+ struct nhlt_endpoint *epnt;
+ int ssp_mask = 0;
+ int i;
+
+ if (!nhlt || (device_type != NHLT_DEVICE_BT && device_type != NHLT_DEVICE_I2S))
+ return 0;
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+ for (i = 0; i < nhlt->endpoint_count; i++) {
+ if (epnt->linktype == NHLT_LINK_SSP && epnt->device_type == device_type) {
+ /* for SSP the virtual bus id is the SSP port */
+ ssp_mask |= BIT(epnt->virtual_bus_id);
+ }
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+
+ return ssp_mask;
+}
+EXPORT_SYMBOL(intel_nhlt_ssp_endpoint_mask);
+
+#define SSP_BLOB_V1_0_SIZE 84
+#define SSP_BLOB_V1_0_MDIVC_OFFSET 19 /* offset in u32 */
+
+#define SSP_BLOB_V1_5_SIZE 96
+#define SSP_BLOB_V1_5_MDIVC_OFFSET 21 /* offset in u32 */
+#define SSP_BLOB_VER_1_5 0xEE000105
+
+#define SSP_BLOB_V2_0_SIZE 88
+#define SSP_BLOB_V2_0_MDIVC_OFFSET 20 /* offset in u32 */
+#define SSP_BLOB_VER_2_0 0xEE000200
+
+int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num)
+{
+ struct nhlt_endpoint *epnt;
+ struct nhlt_fmt *fmt;
+ struct nhlt_fmt_cfg *cfg;
+ int mclk_mask = 0;
+ int i, j;
+
+ if (!nhlt)
+ return 0;
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+ for (i = 0; i < nhlt->endpoint_count; i++) {
+
+ /* we only care about endpoints connected to an audio codec over SSP */
+ if (epnt->linktype == NHLT_LINK_SSP &&
+ epnt->device_type == NHLT_DEVICE_I2S &&
+ epnt->virtual_bus_id == ssp_num) {
+
+ fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
+ cfg = fmt->fmt_config;
+
+ /*
+ * In theory all formats should use the same MCLK but it doesn't hurt to
+ * double-check that the configuration is consistent
+ */
+ for (j = 0; j < fmt->fmt_count; j++) {
+ u32 *blob;
+ int mdivc_offset;
+ int size;
+
+ /* first check we have enough data to read the blob type */
+ if (cfg->config.size < 8)
+ return -EINVAL;
+
+ blob = (u32 *)cfg->config.caps;
+
+ if (blob[1] == SSP_BLOB_VER_2_0) {
+ mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET;
+ size = SSP_BLOB_V2_0_SIZE;
+ } else if (blob[1] == SSP_BLOB_VER_1_5) {
+ mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET;
+ size = SSP_BLOB_V1_5_SIZE;
+ } else {
+ mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET;
+ size = SSP_BLOB_V1_0_SIZE;
+ }
+
+ /* make sure we have enough data for the fixed part of the blob */
+ if (cfg->config.size < size)
+ return -EINVAL;
+
+ mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0);
+
+ cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size);
+ }
+ }
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+
+ /* make sure only one MCLK is used */
+ if (hweight_long(mclk_mask) != 1)
+ return -EINVAL;
+
+ return mclk_mask;
+}
+EXPORT_SYMBOL(intel_nhlt_ssp_mclk_mask);
+
+static struct nhlt_specific_cfg *
+nhlt_get_specific_cfg(struct device *dev, struct nhlt_fmt *fmt, u8 num_ch,
+ u32 rate, u8 vbps, u8 bps, bool ignore_vbps)
+{
+ struct nhlt_fmt_cfg *cfg = fmt->fmt_config;
+ struct wav_fmt *wfmt;
+ u16 _bps, _vbps;
+ int i;
+
+ dev_dbg(dev, "Endpoint format count=%d\n", fmt->fmt_count);
+
+ for (i = 0; i < fmt->fmt_count; i++) {
+ wfmt = &cfg->fmt_ext.fmt;
+ _bps = wfmt->bits_per_sample;
+ _vbps = cfg->fmt_ext.sample.valid_bits_per_sample;
+
+ dev_dbg(dev, "Endpoint format: ch=%d fmt=%d/%d rate=%d\n",
+ wfmt->channels, _vbps, _bps, wfmt->samples_per_sec);
+
+ /*
+ * When looking for exact match of configuration ignore the vbps
+ * from NHLT table when ignore_vbps is true
+ */
+ if (wfmt->channels == num_ch && wfmt->samples_per_sec == rate &&
+ (ignore_vbps || vbps == _vbps) && bps == _bps)
+ return &cfg->config;
+
+ cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size);
+ }
+
+ return NULL;
+}
+
+static bool nhlt_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt,
+ u32 bus_id, u8 link_type, u8 dir, u8 dev_type)
+{
+ dev_dbg(dev, "Endpoint: vbus_id=%d link_type=%d dir=%d dev_type = %d\n",
+ epnt->virtual_bus_id, epnt->linktype,
+ epnt->direction, epnt->device_type);
+
+ if ((epnt->virtual_bus_id != bus_id) ||
+ (epnt->linktype != link_type) ||
+ (epnt->direction != dir))
+ return false;
+
+ /* link of type DMIC bypasses device_type check */
+ return epnt->linktype == NHLT_LINK_DMIC ||
+ epnt->device_type == dev_type;
+}
+
+struct nhlt_specific_cfg *
+intel_nhlt_get_endpoint_blob(struct device *dev, struct nhlt_acpi_table *nhlt,
+ u32 bus_id, u8 link_type, u8 vbps, u8 bps,
+ u8 num_ch, u32 rate, u8 dir, u8 dev_type)
+{
+ struct nhlt_specific_cfg *cfg;
+ struct nhlt_endpoint *epnt;
+ bool ignore_vbps = false;
+ struct nhlt_fmt *fmt;
+ int i;
+
+ if (!nhlt)
+ return NULL;
+
+ dev_dbg(dev, "Looking for configuration:\n");
+ dev_dbg(dev, " vbus_id=%d link_type=%d dir=%d, dev_type=%d\n",
+ bus_id, link_type, dir, dev_type);
+ if (link_type == NHLT_LINK_DMIC && bps == 32 && (vbps == 24 || vbps == 32)) {
+ /*
+ * The DMIC hardware supports only one type of 32 bits sample
+ * size, which is 24 bit sampling on the MSB side and bits[1:0]
+ * are used for indicating the channel number.
+ * It has been observed that some NHLT tables have the vbps
+ * specified as 32 while some uses 24.
+ * The format these variations describe are identical, the
+ * hardware is configured and behaves the same way.
+ * Note: when the samples assumed to be vbps=32 then the 'noise'
+ * introduced by the lower two bits (channel number) have no
+ * real life implication on audio quality.
+ */
+ dev_dbg(dev,
+ " ch=%d fmt=%d rate=%d (vbps is ignored for DMIC 32bit format)\n",
+ num_ch, bps, rate);
+ ignore_vbps = true;
+ } else {
+ dev_dbg(dev, " ch=%d fmt=%d/%d rate=%d\n", num_ch, vbps, bps, rate);
+ }
+ dev_dbg(dev, "Endpoint count=%d\n", nhlt->endpoint_count);
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+
+ for (i = 0; i < nhlt->endpoint_count; i++) {
+ if (nhlt_check_ep_match(dev, epnt, bus_id, link_type, dir, dev_type)) {
+ fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
+
+ cfg = nhlt_get_specific_cfg(dev, fmt, num_ch, rate,
+ vbps, bps, ignore_vbps);
+ if (cfg)
+ return cfg;
+ }
+
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(intel_nhlt_get_endpoint_blob);
diff --git a/sound/hda/intel-sdw-acpi.c b/sound/hda/intel-sdw-acpi.c
new file mode 100644
index 0000000000..b57d72ea45
--- /dev/null
+++ b/sound/hda/intel-sdw-acpi.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-2021 Intel Corporation.
+
+/*
+ * SDW Intel ACPI scan helpers
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/fwnode.h>
+#include <linux/module.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <linux/string.h>
+
+#define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */
+#define SDW_MAX_LINKS 4
+
+static int ctrl_link_mask;
+module_param_named(sdw_link_mask, ctrl_link_mask, int, 0444);
+MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)");
+
+static bool is_link_enabled(struct fwnode_handle *fw_node, u8 idx)
+{
+ struct fwnode_handle *link;
+ char name[32];
+ u32 quirk_mask = 0;
+
+ /* Find master handle */
+ snprintf(name, sizeof(name),
+ "mipi-sdw-link-%hhu-subproperties", idx);
+
+ link = fwnode_get_named_child_node(fw_node, name);
+ if (!link)
+ return false;
+
+ fwnode_property_read_u32(link,
+ "intel-quirk-mask",
+ &quirk_mask);
+
+ if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
+ return false;
+
+ return true;
+}
+
+static int
+sdw_intel_scan_controller(struct sdw_intel_acpi_info *info)
+{
+ struct acpi_device *adev = acpi_fetch_acpi_dev(info->handle);
+ u8 count, i;
+ int ret;
+
+ if (!adev)
+ return -EINVAL;
+
+ /* Found controller, find links supported */
+ count = 0;
+ ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev),
+ "mipi-sdw-master-count", &count, 1);
+
+ /*
+ * In theory we could check the number of links supported in
+ * hardware, but in that step we cannot assume SoundWire IP is
+ * powered.
+ *
+ * In addition, if the BIOS doesn't even provide this
+ * 'master-count' property then all the inits based on link
+ * masks will fail as well.
+ *
+ * We will check the hardware capabilities in the startup() step
+ */
+
+ if (ret) {
+ dev_err(&adev->dev,
+ "Failed to read mipi-sdw-master-count: %d\n", ret);
+ return -EINVAL;
+ }
+
+ /* Check count is within bounds */
+ if (count > SDW_MAX_LINKS) {
+ dev_err(&adev->dev, "Link count %d exceeds max %d\n",
+ count, SDW_MAX_LINKS);
+ return -EINVAL;
+ }
+
+ if (!count) {
+ dev_warn(&adev->dev, "No SoundWire links detected\n");
+ return -EINVAL;
+ }
+ dev_dbg(&adev->dev, "ACPI reports %d SDW Link devices\n", count);
+
+ info->count = count;
+ info->link_mask = 0;
+
+ for (i = 0; i < count; i++) {
+ if (ctrl_link_mask && !(ctrl_link_mask & BIT(i))) {
+ dev_dbg(&adev->dev,
+ "Link %d masked, will not be enabled\n", i);
+ continue;
+ }
+
+ if (!is_link_enabled(acpi_fwnode_handle(adev), i)) {
+ dev_dbg(&adev->dev,
+ "Link %d not selected in firmware\n", i);
+ continue;
+ }
+
+ info->link_mask |= BIT(i);
+ }
+
+ return 0;
+}
+
+static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
+ void *cdata, void **return_value)
+{
+ struct sdw_intel_acpi_info *info = cdata;
+ acpi_status status;
+ u64 adr;
+
+ status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr);
+ if (ACPI_FAILURE(status))
+ return AE_OK; /* keep going */
+
+ if (!acpi_fetch_acpi_dev(handle)) {
+ pr_err("%s: Couldn't find ACPI handle\n", __func__);
+ return AE_NOT_FOUND;
+ }
+
+ /*
+ * On some Intel platforms, multiple children of the HDAS
+ * device can be found, but only one of them is the SoundWire
+ * controller. The SNDW device is always exposed with
+ * Name(_ADR, 0x40000000), with bits 31..28 representing the
+ * SoundWire link so filter accordingly
+ */
+ if (FIELD_GET(GENMASK(31, 28), adr) != SDW_LINK_TYPE)
+ return AE_OK; /* keep going */
+
+ /* found the correct SoundWire controller */
+ info->handle = handle;
+
+ /* device found, stop namespace walk */
+ return AE_CTRL_TERMINATE;
+}
+
+/**
+ * sdw_intel_acpi_scan() - SoundWire Intel init routine
+ * @parent_handle: ACPI parent handle
+ * @info: description of what firmware/DSDT tables expose
+ *
+ * This scans the namespace and queries firmware to figure out which
+ * links to enable. A follow-up use of sdw_intel_probe() and
+ * sdw_intel_startup() is required for creation of devices and bus
+ * startup
+ */
+int sdw_intel_acpi_scan(acpi_handle *parent_handle,
+ struct sdw_intel_acpi_info *info)
+{
+ acpi_status status;
+
+ info->handle = NULL;
+ /*
+ * In the HDAS ACPI scope, 'SNDW' may be either the child of
+ * 'HDAS' or the grandchild of 'HDAS'. So let's go through
+ * the ACPI from 'HDAS' at max depth of 2 to find the 'SNDW'
+ * device.
+ */
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE,
+ parent_handle, 2,
+ sdw_intel_acpi_cb,
+ NULL, info, NULL);
+ if (ACPI_FAILURE(status) || info->handle == NULL)
+ return -ENODEV;
+
+ return sdw_intel_scan_controller(info);
+}
+EXPORT_SYMBOL_NS(sdw_intel_acpi_scan, SND_INTEL_SOUNDWIRE_ACPI);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Intel Soundwire ACPI helpers");
diff --git a/sound/hda/local.h b/sound/hda/local.h
new file mode 100644
index 0000000000..896ba142e8
--- /dev/null
+++ b/sound/hda/local.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Local helper macros and functions for HD-audio core drivers
+ */
+
+#ifndef __HDAC_LOCAL_H
+#define __HDAC_LOCAL_H
+
+#define get_wcaps(codec, nid) \
+ snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP)
+
+/* get the widget type from widget capability bits */
+static inline int get_wcaps_type(unsigned int wcaps)
+{
+ if (!wcaps)
+ return -1; /* invalid type */
+ return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+}
+
+static inline unsigned int get_wcaps_channels(u32 wcaps)
+{
+ unsigned int chans;
+
+ chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
+ chans = (chans + 1) * 2;
+
+ return chans;
+}
+
+extern const struct attribute_group *hdac_dev_attr_groups[];
+int hda_widget_sysfs_init(struct hdac_device *codec);
+int hda_widget_sysfs_reinit(struct hdac_device *codec, hda_nid_t start_nid,
+ int num_nodes);
+void hda_widget_sysfs_exit(struct hdac_device *codec);
+
+int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec);
+void snd_hdac_bus_remove_device(struct hdac_bus *bus,
+ struct hdac_device *codec);
+void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex);
+int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
+ unsigned int cmd, unsigned int *res);
+
+int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd,
+ unsigned int flags, unsigned int *res);
+
+#endif /* __HDAC_LOCAL_H */
diff --git a/sound/hda/trace.c b/sound/hda/trace.c
new file mode 100644
index 0000000000..ca2d6bd945
--- /dev/null
+++ b/sound/hda/trace.c
@@ -0,0 +1,6 @@
+/*
+ * tracepoint definitions for HD-audio core drivers
+ */
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/sound/hda/trace.h b/sound/hda/trace.h
new file mode 100644
index 0000000000..2cc493434a
--- /dev/null
+++ b/sound/hda/trace.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM hda
+
+#if !defined(__HDAC_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define __HDAC_TRACE_H
+
+#include <linux/tracepoint.h>
+#include <linux/device.h>
+#include <sound/hdaudio.h>
+
+#ifndef HDAC_MSG_MAX
+#define HDAC_MSG_MAX 500
+#endif
+
+struct hdac_bus;
+struct hdac_codec;
+
+TRACE_EVENT(hda_send_cmd,
+ TP_PROTO(struct hdac_bus *bus, unsigned int cmd),
+ TP_ARGS(bus, cmd),
+ TP_STRUCT__entry(
+ __string(name, dev_name((bus)->dev))
+ __field(u32, cmd)
+ ),
+ TP_fast_assign(
+ __assign_str(name, dev_name((bus)->dev));
+ __entry->cmd = cmd;
+ ),
+ TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->cmd >> 28, __entry->cmd)
+);
+
+TRACE_EVENT(hda_get_response,
+ TP_PROTO(struct hdac_bus *bus, unsigned int addr, unsigned int res),
+ TP_ARGS(bus, addr, res),
+ TP_STRUCT__entry(
+ __string(name, dev_name((bus)->dev))
+ __field(u32, addr)
+ __field(u32, res)
+ ),
+ TP_fast_assign(
+ __assign_str(name, dev_name((bus)->dev));
+ __entry->addr = addr;
+ __entry->res = res;
+ ),
+ TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->addr, __entry->res)
+);
+
+TRACE_EVENT(hda_unsol_event,
+ TP_PROTO(struct hdac_bus *bus, u32 res, u32 res_ex),
+ TP_ARGS(bus, res, res_ex),
+ TP_STRUCT__entry(
+ __string(name, dev_name((bus)->dev))
+ __field(u32, res)
+ __field(u32, res_ex)
+ ),
+ TP_fast_assign(
+ __assign_str(name, dev_name((bus)->dev));
+ __entry->res = res;
+ __entry->res_ex = res_ex;
+ ),
+ TP_printk("[%s:%d] res=0x%08x, res_ex=0x%08x", __get_str(name),
+ __entry->res_ex & 0x0f, __entry->res, __entry->res_ex)
+);
+
+DECLARE_EVENT_CLASS(hdac_stream,
+ TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
+
+ TP_ARGS(bus, azx_dev),
+
+ TP_STRUCT__entry(
+ __field(unsigned char, stream_tag)
+ ),
+
+ TP_fast_assign(
+ __entry->stream_tag = (azx_dev)->stream_tag;
+ ),
+
+ TP_printk("stream_tag: %d", __entry->stream_tag)
+);
+
+DEFINE_EVENT(hdac_stream, snd_hdac_stream_start,
+ TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
+ TP_ARGS(bus, azx_dev)
+);
+
+DEFINE_EVENT(hdac_stream, snd_hdac_stream_stop,
+ TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
+ TP_ARGS(bus, azx_dev)
+);
+
+#endif /* __HDAC_TRACE_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#include <trace/define_trace.h>