summaryrefslogtreecommitdiffstats
path: root/sound/soc/intel
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/intel')
-rw-r--r--sound/soc/intel/Kconfig208
-rw-r--r--sound/soc/intel/Makefile12
-rw-r--r--sound/soc/intel/atom/Makefile9
-rw-r--r--sound/soc/intel/atom/sst-atom-controls.c1583
-rw-r--r--sound/soc/intel/atom/sst-atom-controls.h875
-rw-r--r--sound/soc/intel/atom/sst-mfld-dsp.h525
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform-compress.c271
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform-pcm.c815
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform.h178
-rw-r--r--sound/soc/intel/atom/sst/Makefile8
-rw-r--r--sound/soc/intel/atom/sst/sst.c574
-rw-r--r--sound/soc/intel/atom/sst/sst.h533
-rw-r--r--sound/soc/intel/atom/sst/sst_acpi.c364
-rw-r--r--sound/soc/intel/atom/sst/sst_drv_interface.c717
-rw-r--r--sound/soc/intel/atom/sst/sst_ipc.c375
-rw-r--r--sound/soc/intel/atom/sst/sst_loader.c454
-rw-r--r--sound/soc/intel/atom/sst/sst_pci.c201
-rw-r--r--sound/soc/intel/atom/sst/sst_pvt.c406
-rw-r--r--sound/soc/intel/atom/sst/sst_stream.c471
-rw-r--r--sound/soc/intel/boards/Kconfig563
-rw-r--r--sound/soc/intel/boards/Makefile74
-rw-r--r--sound/soc/intel/boards/bdw-rt5650.c331
-rw-r--r--sound/soc/intel/boards/bdw-rt5677.c458
-rw-r--r--sound/soc/intel/boards/broadwell.c330
-rw-r--r--sound/soc/intel/boards/bxt_da7219_max98357a.c870
-rw-r--r--sound/soc/intel/boards/bxt_rt298.c669
-rw-r--r--sound/soc/intel/boards/bytcht_cx2072x.c284
-rw-r--r--sound/soc/intel/boards/bytcht_da7213.c296
-rw-r--r--sound/soc/intel/boards/bytcht_es8316.c637
-rw-r--r--sound/soc/intel/boards/bytcht_nocodec.c198
-rw-r--r--sound/soc/intel/boards/bytcr_rt5640.c1524
-rw-r--r--sound/soc/intel/boards/bytcr_rt5651.c1135
-rw-r--r--sound/soc/intel/boards/cht_bsw_max98090_ti.c642
-rw-r--r--sound/soc/intel/boards/cht_bsw_nau8824.c308
-rw-r--r--sound/soc/intel/boards/cht_bsw_rt5645.c708
-rw-r--r--sound/soc/intel/boards/cht_bsw_rt5672.c487
-rw-r--r--sound/soc/intel/boards/cml_rt1011_rt5682.c596
-rw-r--r--sound/soc/intel/boards/ehl_rt5660.c323
-rw-r--r--sound/soc/intel/boards/glk_rt5682_max98357a.c644
-rw-r--r--sound/soc/intel/boards/haswell.c202
-rw-r--r--sound/soc/intel/boards/hda_dsp_common.c86
-rw-r--r--sound/soc/intel/boards/hda_dsp_common.h29
-rw-r--r--sound/soc/intel/boards/kbl_da7219_max98357a.c619
-rw-r--r--sound/soc/intel/boards/kbl_da7219_max98927.c1154
-rw-r--r--sound/soc/intel/boards/kbl_rt5660.c567
-rw-r--r--sound/soc/intel/boards/kbl_rt5663_max98927.c1054
-rw-r--r--sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c856
-rw-r--r--sound/soc/intel/boards/skl_hda_dsp_common.c174
-rw-r--r--sound/soc/intel/boards/skl_hda_dsp_common.h66
-rw-r--r--sound/soc/intel/boards/skl_hda_dsp_generic.c260
-rw-r--r--sound/soc/intel/boards/skl_nau88l25_max98357a.c693
-rw-r--r--sound/soc/intel/boards/skl_nau88l25_ssm4567.c741
-rw-r--r--sound/soc/intel/boards/skl_rt286.c569
-rw-r--r--sound/soc/intel/boards/sof_da7219_max98373.c460
-rw-r--r--sound/soc/intel/boards/sof_maxim_common.c126
-rw-r--r--sound/soc/intel/boards/sof_maxim_common.h27
-rw-r--r--sound/soc/intel/boards/sof_pcm512x.c448
-rw-r--r--sound/soc/intel/boards/sof_rt5682.c904
-rw-r--r--sound/soc/intel/boards/sof_sdw.c1343
-rw-r--r--sound/soc/intel/boards/sof_sdw_common.h151
-rw-r--r--sound/soc/intel/boards/sof_sdw_dmic.c43
-rw-r--r--sound/soc/intel/boards/sof_sdw_hdmi.c96
-rw-r--r--sound/soc/intel/boards/sof_sdw_max98373.c122
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt1308.c157
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt1316.c119
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt5682.c129
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt700.c128
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt711.c174
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt711_sdca.c174
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt715.c42
-rw-r--r--sound/soc/intel/boards/sof_sdw_rt715_sdca.c42
-rw-r--r--sound/soc/intel/boards/sof_wm8804.c302
-rw-r--r--sound/soc/intel/catpt/Makefile6
-rw-r--r--sound/soc/intel/catpt/core.h188
-rw-r--r--sound/soc/intel/catpt/device.c355
-rw-r--r--sound/soc/intel/catpt/dsp.c591
-rw-r--r--sound/soc/intel/catpt/ipc.c298
-rw-r--r--sound/soc/intel/catpt/loader.c671
-rw-r--r--sound/soc/intel/catpt/messages.c313
-rw-r--r--sound/soc/intel/catpt/messages.h401
-rw-r--r--sound/soc/intel/catpt/pcm.c1184
-rw-r--r--sound/soc/intel/catpt/registers.h178
-rw-r--r--sound/soc/intel/catpt/sysfs.c55
-rw-r--r--sound/soc/intel/catpt/trace.h83
-rw-r--r--sound/soc/intel/common/Makefile15
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-bxt-match.c90
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-byt-match.c238
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-cfl-match.c23
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-cht-match.c201
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-cml-match.c301
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-cnl-match.c68
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-ehl-match.c25
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-glk-match.c48
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-hda-match.c40
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c58
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-icl-match.c190
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-jsl-match.c68
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-kbl-match.c133
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-skl-match.c47
-rw-r--r--sound/soc/intel/common/soc-acpi-intel-tgl-match.c403
-rw-r--r--sound/soc/intel/common/soc-intel-quirks.h98
-rw-r--r--sound/soc/intel/common/sst-dsp-priv.h101
-rw-r--r--sound/soc/intel/common/sst-dsp.c250
-rw-r--r--sound/soc/intel/common/sst-dsp.h61
-rw-r--r--sound/soc/intel/common/sst-ipc.c294
-rw-r--r--sound/soc/intel/common/sst-ipc.h86
-rw-r--r--sound/soc/intel/keembay/Makefile4
-rw-r--r--sound/soc/intel/keembay/kmb_platform.c739
-rw-r--r--sound/soc/intel/keembay/kmb_platform.h146
-rw-r--r--sound/soc/intel/skylake/Makefile15
-rw-r--r--sound/soc/intel/skylake/bxt-sst.c629
-rw-r--r--sound/soc/intel/skylake/cnl-sst-dsp.c266
-rw-r--r--sound/soc/intel/skylake/cnl-sst-dsp.h103
-rw-r--r--sound/soc/intel/skylake/cnl-sst.c508
-rw-r--r--sound/soc/intel/skylake/skl-debug.c248
-rw-r--r--sound/soc/intel/skylake/skl-i2s.h87
-rw-r--r--sound/soc/intel/skylake/skl-messages.c1387
-rw-r--r--sound/soc/intel/skylake/skl-nhlt.c371
-rw-r--r--sound/soc/intel/skylake/skl-pcm.c1515
-rw-r--r--sound/soc/intel/skylake/skl-ssp-clk.c430
-rw-r--r--sound/soc/intel/skylake/skl-ssp-clk.h108
-rw-r--r--sound/soc/intel/skylake/skl-sst-cldma.c368
-rw-r--r--sound/soc/intel/skylake/skl-sst-cldma.h243
-rw-r--r--sound/soc/intel/skylake/skl-sst-dsp.c462
-rw-r--r--sound/soc/intel/skylake/skl-sst-dsp.h256
-rw-r--r--sound/soc/intel/skylake/skl-sst-ipc.c1071
-rw-r--r--sound/soc/intel/skylake/skl-sst-ipc.h169
-rw-r--r--sound/soc/intel/skylake/skl-sst-utils.c425
-rw-r--r--sound/soc/intel/skylake/skl-sst.c599
-rw-r--r--sound/soc/intel/skylake/skl-topology.c3777
-rw-r--r--sound/soc/intel/skylake/skl-topology.h505
-rw-r--r--sound/soc/intel/skylake/skl.c1199
-rw-r--r--sound/soc/intel/skylake/skl.h211
133 files changed, 54715 insertions, 0 deletions
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig
new file mode 100644
index 000000000..c1bf69a0b
--- /dev/null
+++ b/sound/soc/intel/Kconfig
@@ -0,0 +1,208 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_SOC_INTEL_SST_TOPLEVEL
+ bool "Intel ASoC SST drivers"
+ default y
+ depends on X86 || COMPILE_TEST
+ select SND_SOC_INTEL_MACH
+ help
+ Intel ASoC SST Platform Drivers. If you have a Intel machine that
+ has an audio controller with a DSP and I2S or DMIC port, then
+ enable this option by saying Y
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Intel SST drivers.
+
+if SND_SOC_INTEL_SST_TOPLEVEL
+
+config SND_SOC_INTEL_SST
+ tristate
+
+config SND_SOC_INTEL_CATPT
+ tristate "Haswell and Broadwell"
+ depends on ACPI || COMPILE_TEST
+ depends on DMADEVICES && SND_DMA_SGBUF
+ select DW_DMAC_CORE
+ select SND_SOC_ACPI_INTEL_MATCH
+ help
+ Enable support for Intel(R) Haswell and Broadwell platforms
+ with I2S codec present. This is a recommended option.
+ Say Y or m if you have such device.
+ If unsure, say N.
+
+config SND_SOC_INTEL_HASWELL
+ tristate
+ select SND_SOC_INTEL_CATPT
+
+config SND_SST_ATOM_HIFI2_PLATFORM
+ tristate
+ select SND_SOC_COMPRESS
+
+config SND_SST_ATOM_HIFI2_PLATFORM_PCI
+ tristate "PCI HiFi2 (Merrifield) Platforms"
+ depends on X86 && PCI
+ select SND_SST_ATOM_HIFI2_PLATFORM
+ help
+ If you have a Intel Merrifield/Edison platform, then
+ enable this option by saying Y or m. Distros will typically not
+ enable this option: while Merrifield/Edison can run a mainline
+ kernel with limited functionality it will require a firmware file
+ which is not in the standard firmware tree
+
+config SND_SST_ATOM_HIFI2_PLATFORM_ACPI
+ tristate "ACPI HiFi2 (Baytrail, Cherrytrail) Platforms"
+ default ACPI
+ depends on X86 && ACPI && PCI
+ select SND_SST_ATOM_HIFI2_PLATFORM
+ select SND_SOC_ACPI_INTEL_MATCH
+ select IOSF_MBI
+ help
+ If you have a Intel Baytrail or Cherrytrail platform with an I2S
+ codec, then enable this option by saying Y or m. This is a
+ recommended option
+ This option is mutually exclusive with the SOF support on
+ Baytrail/Cherrytrail. If you want to enable SOF on
+ Baytrail/Cherrytrail, you need to deselect this option first.
+
+config SND_SOC_INTEL_SKYLAKE
+ tristate "All Skylake/SST Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKL
+ select SND_SOC_INTEL_APL
+ select SND_SOC_INTEL_KBL
+ select SND_SOC_INTEL_GLK
+ select SND_SOC_INTEL_CNL
+ select SND_SOC_INTEL_CFL
+ help
+ This is a backwards-compatible option to select all devices
+ supported by the Intel SST/Skylake driver. This option is no
+ longer recommended and will be deprecated when the SOF
+ driver is introduced. Distributions should explicitly
+ select which platform uses this driver.
+
+config SND_SOC_INTEL_SKL
+ tristate "Skylake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel Skylake platform with the DSP enabled
+ in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_APL
+ tristate "Broxton/ApolloLake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel Broxton/ApolloLake platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_KBL
+ tristate "Kabylake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel Kabylake platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_GLK
+ tristate "GeminiLake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel GeminiLake platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_CNL
+ tristate "CannonLake/WhiskyLake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel CNL/WHL platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_CFL
+ tristate "CoffeeLake Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel CoffeeLake platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_CML_H
+ tristate "CometLake-H Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel CometLake-H platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_CML_LP
+ tristate "CometLake-LP Platforms"
+ depends on PCI && ACPI
+ depends on COMMON_CLK
+ select SND_SOC_INTEL_SKYLAKE_FAMILY
+ help
+ If you have a Intel CometLake-LP platform with the DSP
+ enabled in the BIOS then enable this option by saying Y or m.
+
+config SND_SOC_INTEL_SKYLAKE_FAMILY
+ tristate
+ select SND_SOC_INTEL_SKYLAKE_COMMON
+
+if SND_SOC_INTEL_SKYLAKE_FAMILY
+
+config SND_SOC_INTEL_SKYLAKE_SSP_CLK
+ tristate
+
+config SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC
+ bool "HDAudio codec support"
+ help
+ If you have Intel Skylake or Kabylake with HDAudio codec
+ and DMIC present then enable this option by saying Y.
+
+config SND_SOC_INTEL_SKYLAKE_COMMON
+ tristate
+ select SND_HDA_EXT_CORE
+ select SND_HDA_DSP_LOADER
+ select SND_SOC_TOPOLOGY
+ select SND_SOC_INTEL_SST
+ select SND_SOC_HDAC_HDA if SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC
+ select SND_SOC_ACPI_INTEL_MATCH
+ select SND_INTEL_DSP_CONFIG
+ help
+ If you have a Intel Skylake/Broxton/ApolloLake/KabyLake/
+ GeminiLake or CannonLake platform with the DSP enabled in the BIOS
+ then enable this option by saying Y or m.
+
+endif ## SND_SOC_INTEL_SKYLAKE_FAMILY
+
+endif ## SND_SOC_INTEL_SST_TOPLEVEL
+
+if SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL
+
+config SND_SOC_ACPI_INTEL_MATCH
+ tristate
+ select SND_SOC_ACPI if ACPI
+ # this option controls the compilation of ACPI matching tables and
+ # helpers and is not meant to be selected by the user.
+
+endif ## SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL
+
+config SND_SOC_INTEL_KEEMBAY
+ tristate "Keembay Platforms"
+ depends on ARCH_KEEMBAY || COMPILE_TEST
+ depends on COMMON_CLK
+ help
+ If you have a Intel Keembay platform then enable this option
+ by saying Y or m.
+
+# ASoC codec drivers
+source "sound/soc/intel/boards/Kconfig"
diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile
new file mode 100644
index 000000000..7c5038803
--- /dev/null
+++ b/sound/soc/intel/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Core support
+obj-$(CONFIG_SND_SOC) += common/
+
+# Platform Support
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += atom/
+obj-$(CONFIG_SND_SOC_INTEL_CATPT) += catpt/
+obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_COMMON) += skylake/
+obj-$(CONFIG_SND_SOC_INTEL_KEEMBAY) += keembay/
+
+# Machine support
+obj-$(CONFIG_SND_SOC) += boards/
diff --git a/sound/soc/intel/atom/Makefile b/sound/soc/intel/atom/Makefile
new file mode 100644
index 000000000..c66f03f5d
--- /dev/null
+++ b/sound/soc/intel/atom/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-soc-sst-atom-hifi2-platform-objs := sst-mfld-platform-pcm.o \
+ sst-mfld-platform-compress.o \
+ sst-atom-controls.o
+
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-soc-sst-atom-hifi2-platform.o
+
+# DSP driver
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += sst/
diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c
new file mode 100644
index 000000000..6b5a34a15
--- /dev/null
+++ b/sound/soc/intel/atom/sst-atom-controls.c
@@ -0,0 +1,1583 @@
+// SPDX-License-Identifier: GPL-2.0-only
+ /*
+ * sst-atom-controls.c - Intel MID Platform driver DPCM ALSA controls for Mrfld
+ *
+ * Copyright (C) 2013-14 Intel Corp
+ * Author: Omair Mohammed Abdullah <omair.m.abdullah@intel.com>
+ * Vinod Koul <vinod.koul@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active
+ * we forward the settings and parameters, rest we keep the values in
+ * driver and forward when DAPM enables them
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include "sst-mfld-platform.h"
+#include "sst-atom-controls.h"
+
+static int sst_fill_byte_control(struct sst_data *drv,
+ u8 ipc_msg, u8 block,
+ u8 task_id, u8 pipe_id,
+ u16 len, void *cmd_data)
+{
+ struct snd_sst_bytes_v2 *byte_data = drv->byte_stream;
+
+ byte_data->type = SST_CMD_BYTES_SET;
+ byte_data->ipc_msg = ipc_msg;
+ byte_data->block = block;
+ byte_data->task_id = task_id;
+ byte_data->pipe_id = pipe_id;
+
+ if (len > SST_MAX_BIN_BYTES - sizeof(*byte_data)) {
+ dev_err(&drv->pdev->dev, "command length too big (%u)", len);
+ return -EINVAL;
+ }
+ byte_data->len = len;
+ memcpy(byte_data->bytes, cmd_data, len);
+ print_hex_dump_bytes("writing to lpe: ", DUMP_PREFIX_OFFSET,
+ byte_data, len + sizeof(*byte_data));
+ return 0;
+}
+
+static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv,
+ u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id,
+ void *cmd_data, u16 len)
+{
+ int ret = 0;
+
+ WARN_ON(!mutex_is_locked(&drv->lock));
+
+ ret = sst_fill_byte_control(drv, ipc_msg,
+ block, task_id, pipe_id, len, cmd_data);
+ if (ret < 0)
+ return ret;
+ return sst->ops->send_byte_stream(sst->dev, drv->byte_stream);
+}
+
+/**
+ * sst_fill_and_send_cmd - generate the IPC message and send it to the FW
+ * @drv: sst_data
+ * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS)
+ * @block: block index
+ * @task_id: task index
+ * @pipe_id: pipe index
+ * @cmd_data: the IPC payload
+ * @len: length of data to be sent
+ */
+static int sst_fill_and_send_cmd(struct sst_data *drv,
+ u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id,
+ void *cmd_data, u16 len)
+{
+ int ret;
+
+ mutex_lock(&drv->lock);
+ ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block,
+ task_id, pipe_id, cmd_data, len);
+ mutex_unlock(&drv->lock);
+
+ return ret;
+}
+
+/*
+ * tx map value is a bitfield where each bit represents a FW channel
+ *
+ * 3 2 1 0 # 0 = codec0, 1 = codec1
+ * RLRLRLRL # 3, 4 = reserved
+ *
+ * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R
+ */
+static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = {
+ 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */
+};
+
+/*
+ * rx map value is a bitfield where each bit represents a slot
+ *
+ * 76543210 # 0 = slot 0, 1 = slot 1
+ *
+ * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2
+ */
+static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = {
+ 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */
+};
+
+/*
+ * NOTE: this is invoked with lock held
+ */
+static int sst_send_slot_map(struct sst_data *drv)
+{
+ struct sst_param_sba_ssp_slot_map cmd;
+
+ SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
+ cmd.header.command_id = SBA_SET_SSP_SLOT_MAP;
+ cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map)
+ - sizeof(struct sst_dsp_header);
+
+ cmd.param_id = SBA_SET_SSP_SLOT_MAP;
+ cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map)
+ + sizeof(cmd.ssp_index);
+ cmd.ssp_index = SSP_CODEC;
+
+ memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map));
+ memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map));
+
+ return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS,
+ SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+}
+
+static int sst_slot_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct sst_enum *e = (struct sst_enum *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = e->max;
+
+ if (uinfo->value.enumerated.item > e->max - 1)
+ uinfo->value.enumerated.item = e->max - 1;
+ strcpy(uinfo->value.enumerated.name,
+ e->texts[uinfo->value.enumerated.item]);
+
+ return 0;
+}
+
+/**
+ * sst_slot_get - get the status of the interleaver/deinterleaver control
+ * @kcontrol: control pointer
+ * @ucontrol: User data
+ * Searches the map where the control status is stored, and gets the
+ * channel/slot which is currently set for this enumerated control. Since it is
+ * an enumerated control, there is only one possible value.
+ */
+static int sst_slot_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sst_enum *e = (void *)kcontrol->private_value;
+ struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+ unsigned int ctl_no = e->reg;
+ unsigned int is_tx = e->tx;
+ unsigned int val, mux;
+ u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map;
+
+ mutex_lock(&drv->lock);
+ val = 1 << ctl_no;
+ /* search which slot/channel has this bit set - there should be only one */
+ for (mux = e->max; mux > 0; mux--)
+ if (map[mux - 1] & val)
+ break;
+
+ ucontrol->value.enumerated.item[0] = mux;
+ mutex_unlock(&drv->lock);
+
+ dev_dbg(c->dev, "%s - %s map = %#x\n",
+ is_tx ? "tx channel" : "rx slot",
+ e->texts[mux], mux ? map[mux - 1] : -1);
+ return 0;
+}
+
+/* sst_check_and_send_slot_map - helper for checking power state and sending
+ * slot map cmd
+ *
+ * called with lock held
+ */
+static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol)
+{
+ struct sst_enum *e = (void *)kcontrol->private_value;
+ int ret = 0;
+
+ if (e->w && e->w->power)
+ ret = sst_send_slot_map(drv);
+ else if (!e->w)
+ dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n",
+ kcontrol->id.name);
+ return ret;
+}
+
+/**
+ * sst_slot_put - set the status of interleaver/deinterleaver control
+ * @kcontrol: control pointer
+ * @ucontrol: User data
+ * (de)interleaver controls are defined in opposite sense to be user-friendly
+ *
+ * Instead of the enum value being the value written to the register, it is the
+ * register address; and the kcontrol number (register num) is the value written
+ * to the register. This is so that there can be only one value for each
+ * slot/channel since there is only one control for each slot/channel.
+ *
+ * This means that whenever an enum is set, we need to clear the bit
+ * for that kcontrol_no for all the interleaver OR deinterleaver registers
+ */
+static int sst_slot_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+ struct sst_enum *e = (void *)kcontrol->private_value;
+ int i, ret = 0;
+ unsigned int ctl_no = e->reg;
+ unsigned int is_tx = e->tx;
+ unsigned int slot_channel_no;
+ unsigned int val, mux;
+ u8 *map;
+
+ map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map;
+
+ val = 1 << ctl_no;
+ mux = ucontrol->value.enumerated.item[0];
+ if (mux > e->max - 1)
+ return -EINVAL;
+
+ mutex_lock(&drv->lock);
+ /* first clear all registers of this bit */
+ for (i = 0; i < e->max; i++)
+ map[i] &= ~val;
+
+ if (mux == 0) {
+ /* kctl set to 'none' and we reset the bits so send IPC */
+ ret = sst_check_and_send_slot_map(drv, kcontrol);
+
+ mutex_unlock(&drv->lock);
+ return ret;
+ }
+
+ /* offset by one to take "None" into account */
+ slot_channel_no = mux - 1;
+ map[slot_channel_no] |= val;
+
+ dev_dbg(c->dev, "%s %s map = %#x\n",
+ is_tx ? "tx channel" : "rx slot",
+ e->texts[mux], map[slot_channel_no]);
+
+ ret = sst_check_and_send_slot_map(drv, kcontrol);
+
+ mutex_unlock(&drv->lock);
+ return ret;
+}
+
+static int sst_send_algo_cmd(struct sst_data *drv,
+ struct sst_algo_control *bc)
+{
+ int len, ret = 0;
+ struct sst_cmd_set_params *cmd;
+
+ /*bc->max includes sizeof algos + length field*/
+ len = sizeof(cmd->dst) + sizeof(cmd->command_id) + bc->max;
+
+ cmd = kzalloc(len, GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+
+ SST_FILL_DESTINATION(2, cmd->dst, bc->pipe_id, bc->module_id);
+ cmd->command_id = bc->cmd_id;
+ memcpy(cmd->params, bc->params, bc->max);
+
+ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS,
+ SST_FLAG_BLOCKED, bc->task_id, 0, cmd, len);
+ kfree(cmd);
+ return ret;
+}
+
+/**
+ * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe
+ * @drv: sst_data
+ * @pipe: string identifier
+ * @ids: list of algorithms
+ * The algos which are in each pipeline are sent to the firmware one by one
+ *
+ * Called with lock held
+ */
+static int sst_find_and_send_pipe_algo(struct sst_data *drv,
+ const char *pipe, struct sst_ids *ids)
+{
+ int ret = 0;
+ struct sst_algo_control *bc;
+ struct sst_module *algo;
+
+ dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe);
+
+ list_for_each_entry(algo, &ids->algo_list, node) {
+ bc = (void *)algo->kctl->private_value;
+
+ dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n",
+ algo->kctl->id.name, pipe);
+ ret = sst_send_algo_cmd(drv, bc);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct sst_algo_control *bc = (void *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = bc->max;
+
+ return 0;
+}
+
+static int sst_algo_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sst_algo_control *bc = (void *)kcontrol->private_value;
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+
+ switch (bc->type) {
+ case SST_ALGO_PARAMS:
+ memcpy(ucontrol->value.bytes.data, bc->params, bc->max);
+ break;
+ default:
+ dev_err(component->dev, "Invalid Input- algo type:%d\n",
+ bc->type);
+ return -EINVAL;
+
+ }
+ return 0;
+}
+
+static int sst_algo_control_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt);
+ struct sst_algo_control *bc = (void *)kcontrol->private_value;
+
+ dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name);
+ mutex_lock(&drv->lock);
+ switch (bc->type) {
+ case SST_ALGO_PARAMS:
+ memcpy(bc->params, ucontrol->value.bytes.data, bc->max);
+ break;
+ default:
+ mutex_unlock(&drv->lock);
+ dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n",
+ bc->type);
+ return -EINVAL;
+ }
+ /*if pipe is enabled, need to send the algo params from here*/
+ if (bc->w && bc->w->power)
+ ret = sst_send_algo_cmd(drv, bc);
+ mutex_unlock(&drv->lock);
+
+ return ret;
+}
+
+static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = mc->stereo ? 2 : 1;
+ uinfo->value.integer.min = mc->min;
+ uinfo->value.integer.max = mc->max;
+
+ return 0;
+}
+
+/**
+ * sst_send_gain_cmd - send the gain algorithm IPC to the FW
+ * @drv: sst_data
+ * @gv:the stored value of gain (also contains rampduration)
+ * @task_id: task index
+ * @loc_id: location/position index
+ * @module_id: module index
+ * @mute: flag that indicates whether this was called from the
+ * digital_mute callback or directly. If called from the
+ * digital_mute callback, module will be muted/unmuted based on this
+ * flag. The flag is always 0 if called directly.
+ *
+ * Called with sst_data.lock held
+ *
+ * The user-set gain value is sent only if the user-controllable 'mute' control
+ * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is
+ * sent.
+ */
+static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv,
+ u16 task_id, u16 loc_id, u16 module_id, int mute)
+{
+ struct sst_cmd_set_gain_dual cmd;
+
+ dev_dbg(&drv->pdev->dev, "Enter\n");
+
+ cmd.header.command_id = MMX_SET_GAIN;
+ SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
+ cmd.gain_cell_num = 1;
+
+ if (mute || gv->mute) {
+ cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE;
+ cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE;
+ } else {
+ cmd.cell_gains[0].cell_gain_left = gv->l_gain;
+ cmd.cell_gains[0].cell_gain_right = gv->r_gain;
+ }
+
+ SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest,
+ loc_id, module_id);
+ cmd.cell_gains[0].gain_time_constant = gv->ramp_duration;
+
+ cmd.header.length = sizeof(struct sst_cmd_set_gain_dual)
+ - sizeof(struct sst_dsp_header);
+
+ /* we are with lock held, so call the unlocked api to send */
+ return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS,
+ SST_FLAG_BLOCKED, task_id, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+}
+
+static int sst_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
+ struct sst_gain_value *gv = mc->gain_val;
+
+ switch (mc->type) {
+ case SST_GAIN_TLV:
+ ucontrol->value.integer.value[0] = gv->l_gain;
+ ucontrol->value.integer.value[1] = gv->r_gain;
+ break;
+
+ case SST_GAIN_MUTE:
+ ucontrol->value.integer.value[0] = gv->mute ? 0 : 1;
+ break;
+
+ case SST_GAIN_RAMP_DURATION:
+ ucontrol->value.integer.value[0] = gv->ramp_duration;
+ break;
+
+ default:
+ dev_err(component->dev, "Invalid Input- gain type:%d\n",
+ mc->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sst_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt);
+ struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value;
+ struct sst_gain_value *gv = mc->gain_val;
+
+ mutex_lock(&drv->lock);
+
+ switch (mc->type) {
+ case SST_GAIN_TLV:
+ gv->l_gain = ucontrol->value.integer.value[0];
+ gv->r_gain = ucontrol->value.integer.value[1];
+ dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n",
+ mc->pname, gv->l_gain, gv->r_gain);
+ break;
+
+ case SST_GAIN_MUTE:
+ gv->mute = !ucontrol->value.integer.value[0];
+ dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute);
+ break;
+
+ case SST_GAIN_RAMP_DURATION:
+ gv->ramp_duration = ucontrol->value.integer.value[0];
+ dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n",
+ mc->pname, gv->ramp_duration);
+ break;
+
+ default:
+ mutex_unlock(&drv->lock);
+ dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n",
+ mc->type);
+ return -EINVAL;
+ }
+
+ if (mc->w && mc->w->power)
+ ret = sst_send_gain_cmd(drv, gv, mc->task_id,
+ mc->pipe_id | mc->instance_id, mc->module_id, 0);
+ mutex_unlock(&drv->lock);
+
+ return ret;
+}
+
+static int sst_set_pipe_gain(struct sst_ids *ids,
+ struct sst_data *drv, int mute);
+
+static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol)
+{
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+ struct sst_ids *ids = w->priv;
+
+ mutex_lock(&drv->lock);
+ sst_find_and_send_pipe_algo(drv, w->name, ids);
+ sst_set_pipe_gain(ids, drv, 0);
+ mutex_unlock(&drv->lock);
+
+ return 0;
+}
+
+static int sst_generic_modules_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ return sst_send_pipe_module_params(w, k);
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0);
+
+/* Look up table to convert MIXER SW bit regs to SWM inputs */
+static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = {
+ [SST_IP_MODEM] = SST_SWM_IN_MODEM,
+ [SST_IP_CODEC0] = SST_SWM_IN_CODEC0,
+ [SST_IP_CODEC1] = SST_SWM_IN_CODEC1,
+ [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP,
+ [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1,
+ [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2,
+ [SST_IP_PCM0] = SST_SWM_IN_PCM0,
+ [SST_IP_PCM1] = SST_SWM_IN_PCM1,
+ [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0,
+ [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1,
+ [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2,
+ [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3,
+};
+
+/**
+ * fill_swm_input - fill in the SWM input ids given the register
+ * @cmpnt: ASoC component
+ * @swm_input: array of swm_input_ids
+ * @reg: the register value is a bit-field inicated which mixer inputs are ON.
+ *
+ * Use the lookup table to get the input-id and fill it in the
+ * structure.
+ */
+static int fill_swm_input(struct snd_soc_component *cmpnt,
+ struct swm_input_ids *swm_input, unsigned int reg)
+{
+ uint i, is_set, nb_inputs = 0;
+ u16 input_loc_id;
+
+ dev_dbg(cmpnt->dev, "reg: %#x\n", reg);
+ for (i = 0; i < SST_SWM_INPUT_COUNT; i++) {
+ is_set = reg & BIT(i);
+ if (!is_set)
+ continue;
+
+ input_loc_id = swm_mixer_input_ids[i];
+ SST_FILL_DESTINATION(2, swm_input->input_id,
+ input_loc_id, SST_DEFAULT_MODULE_ID);
+ nb_inputs++;
+ swm_input++;
+ dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n",
+ input_loc_id, nb_inputs);
+
+ if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) {
+ dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached");
+ break;
+ }
+ }
+ return nb_inputs;
+}
+
+
+/*
+ * called with lock held
+ */
+static int sst_set_pipe_gain(struct sst_ids *ids,
+ struct sst_data *drv, int mute)
+{
+ int ret = 0;
+ struct sst_gain_mixer_control *mc;
+ struct sst_gain_value *gv;
+ struct sst_module *gain;
+
+ list_for_each_entry(gain, &ids->gain_list, node) {
+ struct snd_kcontrol *kctl = gain->kctl;
+
+ dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name);
+ mc = (void *)kctl->private_value;
+ gv = mc->gain_val;
+
+ ret = sst_send_gain_cmd(drv, gv, mc->task_id,
+ mc->pipe_id | mc->instance_id, mc->module_id, mute);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct sst_cmd_set_swm cmd;
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt);
+ struct sst_ids *ids = w->priv;
+ bool set_mixer = false;
+ struct soc_mixer_control *mc;
+ int val = 0;
+ int i = 0;
+
+ dev_dbg(cmpnt->dev, "widget = %s\n", w->name);
+ /*
+ * Identify which mixer input is on and send the bitmap of the
+ * inputs as an IPC to the DSP.
+ */
+ for (i = 0; i < w->num_kcontrols; i++) {
+ if (dapm_kcontrol_get_value(w->kcontrols[i])) {
+ mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value;
+ val |= 1 << mc->shift;
+ }
+ }
+ dev_dbg(cmpnt->dev, "val = %#x\n", val);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ case SND_SOC_DAPM_POST_PMD:
+ set_mixer = true;
+ break;
+ case SND_SOC_DAPM_POST_REG:
+ if (w->power)
+ set_mixer = true;
+ break;
+ default:
+ set_mixer = false;
+ }
+
+ if (!set_mixer)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event) ||
+ event == SND_SOC_DAPM_POST_REG)
+ cmd.switch_state = SST_SWM_ON;
+ else
+ cmd.switch_state = SST_SWM_OFF;
+
+ SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
+ /* MMX_SET_SWM == SBA_SET_SWM */
+ cmd.header.command_id = SBA_SET_SWM;
+
+ SST_FILL_DESTINATION(2, cmd.output_id,
+ ids->location_id, SST_DEFAULT_MODULE_ID);
+ cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val);
+ cmd.header.length = offsetof(struct sst_cmd_set_swm, input)
+ - sizeof(struct sst_dsp_header)
+ + (cmd.nb_inputs * sizeof(cmd.input[0]));
+
+ return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
+ ids->task_id, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+}
+
+/* SBA mixers - 16 inputs */
+#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \
+ static const struct snd_kcontrol_new kctl_name[] = { \
+ SOC_DAPM_SINGLE("modem_in Switch", SND_SOC_NOPM, SST_IP_MODEM, 1, 0), \
+ SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \
+ SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \
+ SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \
+ SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \
+ SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \
+ SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \
+ SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \
+ }
+
+#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \
+ { mix_name, "modem_in Switch", "modem_in" }, \
+ { mix_name, "codec_in0 Switch", "codec_in0" }, \
+ { mix_name, "codec_in1 Switch", "codec_in1" }, \
+ { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \
+ { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \
+ { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \
+ { mix_name, "pcm0_in Switch", "pcm0_in" }, \
+ { mix_name, "pcm1_in Switch", "pcm1_in" }
+
+#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \
+ static const struct snd_kcontrol_new kctl_name[] = { \
+ SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \
+ SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \
+ SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \
+ SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \
+ }
+
+SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls);
+SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls);
+
+/* 18 SBA mixers */
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(__maybe_unused sst_mix_voip_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_modem_controls);
+
+/*
+ * sst_handle_vb_timer - Start/Stop the DSP scheduler
+ *
+ * The DSP expects first cmd to be SBA_VB_START, so at first startup send
+ * that.
+ * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that.
+ *
+ * Do refcount internally so that we send command only at first start
+ * and last end. Since SST driver does its own ref count, invoke sst's
+ * power ops always!
+ */
+int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable)
+{
+ int ret = 0;
+ struct sst_cmd_generic cmd;
+ struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
+ static int timer_usage;
+
+ if (enable)
+ cmd.header.command_id = SBA_VB_START;
+ else
+ cmd.header.command_id = SBA_IDLE;
+ dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage);
+
+ SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
+ cmd.header.length = 0;
+
+ if (enable) {
+ ret = sst->ops->power(sst->dev, true);
+ if (ret < 0)
+ return ret;
+ }
+
+ mutex_lock(&drv->lock);
+ if (enable)
+ timer_usage++;
+ else
+ timer_usage--;
+
+ /*
+ * Send the command only if this call is the first enable or last
+ * disable
+ */
+ if ((enable && (timer_usage == 1)) ||
+ (!enable && (timer_usage == 0))) {
+ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD,
+ SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+ if (ret && enable) {
+ timer_usage--;
+ enable = false;
+ }
+ }
+ mutex_unlock(&drv->lock);
+
+ if (!enable)
+ sst->ops->power(sst->dev, false);
+ return ret;
+}
+
+int sst_fill_ssp_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct sst_data *ctx = snd_soc_dai_get_drvdata(dai);
+
+ ctx->ssp_cmd.nb_slots = slots;
+ ctx->ssp_cmd.active_tx_slot_map = tx_mask;
+ ctx->ssp_cmd.active_rx_slot_map = rx_mask;
+ ctx->ssp_cmd.nb_bits_per_slots = slot_width;
+
+ return 0;
+}
+
+static int sst_get_frame_sync_polarity(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ int format;
+
+ format = fmt & SND_SOC_DAIFMT_INV_MASK;
+ dev_dbg(dai->dev, "Enter:%s, format=%x\n", __func__, format);
+
+ switch (format) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_IB_NF:
+ return SSP_FS_ACTIVE_HIGH;
+ case SND_SOC_DAIFMT_NB_IF:
+ case SND_SOC_DAIFMT_IB_IF:
+ return SSP_FS_ACTIVE_LOW;
+ default:
+ dev_err(dai->dev, "Invalid frame sync polarity %d\n", format);
+ }
+
+ return -EINVAL;
+}
+
+static int sst_get_ssp_mode(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ int format;
+
+ format = (fmt & SND_SOC_DAIFMT_MASTER_MASK);
+ dev_dbg(dai->dev, "Enter:%s, format=%x\n", __func__, format);
+
+ switch (format) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ return SSP_MODE_MASTER;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ return SSP_MODE_SLAVE;
+ default:
+ dev_err(dai->dev, "Invalid ssp protocol: %d\n", format);
+ }
+
+ return -EINVAL;
+}
+
+
+int sst_fill_ssp_config(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ unsigned int mode;
+ int fs_polarity;
+ struct sst_data *ctx = snd_soc_dai_get_drvdata(dai);
+
+ mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ switch (mode) {
+ case SND_SOC_DAIFMT_DSP_B:
+ ctx->ssp_cmd.ssp_protocol = SSP_MODE_PCM;
+ ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NETWORK << 1);
+ ctx->ssp_cmd.start_delay = 0;
+ ctx->ssp_cmd.data_polarity = 1;
+ ctx->ssp_cmd.frame_sync_width = 1;
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A:
+ ctx->ssp_cmd.ssp_protocol = SSP_MODE_PCM;
+ ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NETWORK << 1);
+ ctx->ssp_cmd.start_delay = 1;
+ ctx->ssp_cmd.data_polarity = 1;
+ ctx->ssp_cmd.frame_sync_width = 1;
+ break;
+
+ case SND_SOC_DAIFMT_I2S:
+ ctx->ssp_cmd.ssp_protocol = SSP_MODE_I2S;
+ ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NORMAL << 1);
+ ctx->ssp_cmd.start_delay = 1;
+ ctx->ssp_cmd.data_polarity = 0;
+ ctx->ssp_cmd.frame_sync_width = ctx->ssp_cmd.nb_bits_per_slots;
+ break;
+
+ case SND_SOC_DAIFMT_LEFT_J:
+ ctx->ssp_cmd.ssp_protocol = SSP_MODE_I2S;
+ ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NORMAL << 1);
+ ctx->ssp_cmd.start_delay = 0;
+ ctx->ssp_cmd.data_polarity = 0;
+ ctx->ssp_cmd.frame_sync_width = ctx->ssp_cmd.nb_bits_per_slots;
+ break;
+
+ default:
+ dev_dbg(dai->dev, "using default ssp configs\n");
+ }
+
+ fs_polarity = sst_get_frame_sync_polarity(dai, fmt);
+ if (fs_polarity < 0)
+ return fs_polarity;
+
+ ctx->ssp_cmd.frame_sync_polarity = fs_polarity;
+
+ return 0;
+}
+
+/*
+ * sst_ssp_config - contains SSP configuration for media UC
+ * this can be overwritten by set_dai_xxx APIs
+ */
+static const struct sst_ssp_config sst_ssp_configs = {
+ .ssp_id = SSP_CODEC,
+ .bits_per_slot = 24,
+ .slots = 4,
+ .ssp_mode = SSP_MODE_MASTER,
+ .pcm_mode = SSP_PCM_MODE_NETWORK,
+ .duplex = SSP_DUPLEX,
+ .ssp_protocol = SSP_MODE_PCM,
+ .fs_width = 1,
+ .fs_frequency = SSP_FS_48_KHZ,
+ .active_slot_map = 0xF,
+ .start_delay = 0,
+ .frame_sync_polarity = SSP_FS_ACTIVE_HIGH,
+ .data_polarity = 1,
+};
+
+void sst_fill_ssp_defaults(struct snd_soc_dai *dai)
+{
+ const struct sst_ssp_config *config;
+ struct sst_data *ctx = snd_soc_dai_get_drvdata(dai);
+
+ config = &sst_ssp_configs;
+
+ ctx->ssp_cmd.selection = config->ssp_id;
+ ctx->ssp_cmd.nb_bits_per_slots = config->bits_per_slot;
+ ctx->ssp_cmd.nb_slots = config->slots;
+ ctx->ssp_cmd.mode = config->ssp_mode | (config->pcm_mode << 1);
+ ctx->ssp_cmd.duplex = config->duplex;
+ ctx->ssp_cmd.active_tx_slot_map = config->active_slot_map;
+ ctx->ssp_cmd.active_rx_slot_map = config->active_slot_map;
+ ctx->ssp_cmd.frame_sync_frequency = config->fs_frequency;
+ ctx->ssp_cmd.frame_sync_polarity = config->frame_sync_polarity;
+ ctx->ssp_cmd.data_polarity = config->data_polarity;
+ ctx->ssp_cmd.frame_sync_width = config->fs_width;
+ ctx->ssp_cmd.ssp_protocol = config->ssp_protocol;
+ ctx->ssp_cmd.start_delay = config->start_delay;
+ ctx->ssp_cmd.reserved1 = ctx->ssp_cmd.reserved2 = 0xFF;
+}
+
+int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable)
+{
+ struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
+ int ssp_id;
+
+ dev_dbg(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id);
+
+ if (strcmp(id, "ssp0-port") == 0)
+ ssp_id = SSP_MODEM;
+ else if (strcmp(id, "ssp2-port") == 0)
+ ssp_id = SSP_CODEC;
+ else {
+ dev_dbg(dai->dev, "port %s is not supported\n", id);
+ return -1;
+ }
+
+ SST_FILL_DEFAULT_DESTINATION(drv->ssp_cmd.header.dst);
+ drv->ssp_cmd.header.command_id = SBA_HW_SET_SSP;
+ drv->ssp_cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp)
+ - sizeof(struct sst_dsp_header);
+
+ drv->ssp_cmd.selection = ssp_id;
+ dev_dbg(dai->dev, "ssp_id: %u\n", ssp_id);
+
+ if (enable)
+ drv->ssp_cmd.switch_state = SST_SWITCH_ON;
+ else
+ drv->ssp_cmd.switch_state = SST_SWITCH_OFF;
+
+ return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
+ SST_TASK_SBA, 0, &drv->ssp_cmd,
+ sizeof(drv->ssp_cmd.header) + drv->ssp_cmd.header.length);
+}
+
+static int sst_set_be_modules(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int ret = 0;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+
+ dev_dbg(c->dev, "Enter: widget=%s\n", w->name);
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ mutex_lock(&drv->lock);
+ ret = sst_send_slot_map(drv);
+ mutex_unlock(&drv->lock);
+ if (ret)
+ return ret;
+ ret = sst_send_pipe_module_params(w, k);
+ }
+ return ret;
+}
+
+static int sst_set_media_path(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int ret = 0;
+ struct sst_cmd_set_media_path cmd;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+ struct sst_ids *ids = w->priv;
+
+ dev_dbg(c->dev, "widget=%s\n", w->name);
+ dev_dbg(c->dev, "task=%u, location=%#x\n",
+ ids->task_id, ids->location_id);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ cmd.switch_state = SST_PATH_ON;
+ else
+ cmd.switch_state = SST_PATH_OFF;
+
+ SST_FILL_DESTINATION(2, cmd.header.dst,
+ ids->location_id, SST_DEFAULT_MODULE_ID);
+
+ /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */
+ cmd.header.command_id = MMX_SET_MEDIA_PATH;
+ cmd.header.length = sizeof(struct sst_cmd_set_media_path)
+ - sizeof(struct sst_dsp_header);
+
+ ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
+ ids->task_id, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+ if (ret)
+ return ret;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ret = sst_send_pipe_module_params(w, k);
+ return ret;
+}
+
+static int sst_set_media_loop(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int ret = 0;
+ struct sst_cmd_sba_set_media_loop_map cmd;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct sst_data *drv = snd_soc_component_get_drvdata(c);
+ struct sst_ids *ids = w->priv;
+
+ dev_dbg(c->dev, "Enter:widget=%s\n", w->name);
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ cmd.switch_state = SST_SWITCH_ON;
+ else
+ cmd.switch_state = SST_SWITCH_OFF;
+
+ SST_FILL_DESTINATION(2, cmd.header.dst,
+ ids->location_id, SST_DEFAULT_MODULE_ID);
+
+ cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP;
+ cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map)
+ - sizeof(struct sst_dsp_header);
+ cmd.param.part.cfg.rate = 2; /* 48khz */
+
+ cmd.param.part.cfg.format = ids->format; /* stereo/Mono */
+ cmd.param.part.cfg.s_length = 1; /* 24bit left justified */
+ cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */
+
+ ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
+ SST_TASK_SBA, 0, &cmd,
+ sizeof(cmd.header) + cmd.header.length);
+ if (ret)
+ return ret;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ret = sst_send_pipe_module_params(w, k);
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget sst_dapm_widgets[] = {
+ SST_AIF_IN("modem_in", sst_set_be_modules),
+ SST_AIF_IN("codec_in0", sst_set_be_modules),
+ SST_AIF_IN("codec_in1", sst_set_be_modules),
+ SST_AIF_OUT("modem_out", sst_set_be_modules),
+ SST_AIF_OUT("codec_out0", sst_set_be_modules),
+ SST_AIF_OUT("codec_out1", sst_set_be_modules),
+
+ /* Media Paths */
+ /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */
+ SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event),
+ SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL),
+ SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path),
+ SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL),
+ SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path),
+ SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path),
+
+ /* SBA PCM Paths */
+ SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path),
+ SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path),
+ SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path),
+ SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path),
+ SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path),
+
+ /* SBA Loops */
+ SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL),
+ SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL),
+ SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL),
+ SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_STEREO, sst_set_media_loop),
+ SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_STEREO, sst_set_media_loop),
+ SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop),
+
+ /* Media Mixers */
+ SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0,
+ sst_mix_media0_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1,
+ sst_mix_media1_controls, sst_swm_mixer_event),
+
+ /* SBA PCM mixers */
+ SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0,
+ sst_mix_pcm0_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1,
+ sst_mix_pcm1_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2,
+ sst_mix_pcm2_controls, sst_swm_mixer_event),
+
+ /* SBA Loop mixers */
+ SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP,
+ sst_mix_sprot_l0_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1,
+ sst_mix_media_l1_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2,
+ sst_mix_media_l2_controls, sst_swm_mixer_event),
+
+ /* SBA Backend mixers */
+ SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0,
+ sst_mix_codec0_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1,
+ sst_mix_codec1_controls, sst_swm_mixer_event),
+ SST_SWM_MIXER("modem_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MODEM,
+ sst_mix_modem_controls, sst_swm_mixer_event),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ {"media0_in", NULL, "Compress Playback"},
+ {"media1_in", NULL, "Headset Playback"},
+ {"media2_in", NULL, "pcm0_out"},
+ {"media3_in", NULL, "Deepbuffer Playback"},
+
+ {"media0_out mix 0", "media0_in Switch", "media0_in"},
+ {"media0_out mix 0", "media1_in Switch", "media1_in"},
+ {"media0_out mix 0", "media2_in Switch", "media2_in"},
+ {"media0_out mix 0", "media3_in Switch", "media3_in"},
+ {"media1_out mix 0", "media0_in Switch", "media0_in"},
+ {"media1_out mix 0", "media1_in Switch", "media1_in"},
+ {"media1_out mix 0", "media2_in Switch", "media2_in"},
+ {"media1_out mix 0", "media3_in Switch", "media3_in"},
+
+ {"media0_out", NULL, "media0_out mix 0"},
+ {"media1_out", NULL, "media1_out mix 0"},
+ {"pcm0_in", NULL, "media0_out"},
+ {"pcm1_in", NULL, "media1_out"},
+
+ {"Headset Capture", NULL, "pcm1_out"},
+ {"Headset Capture", NULL, "pcm2_out"},
+ {"pcm0_out", NULL, "pcm0_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"),
+ {"pcm1_out", NULL, "pcm1_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"),
+ {"pcm2_out", NULL, "pcm2_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"),
+
+ {"media_loop1_in", NULL, "media_loop1_out"},
+ {"media_loop1_out", NULL, "media_loop1_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"),
+ {"media_loop2_in", NULL, "media_loop2_out"},
+ {"media_loop2_out", NULL, "media_loop2_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"),
+ {"sprot_loop_in", NULL, "sprot_loop_out"},
+ {"sprot_loop_out", NULL, "sprot_loop_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"),
+
+ {"codec_out0", NULL, "codec_out0 mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"),
+ {"codec_out1", NULL, "codec_out1 mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"),
+ {"modem_out", NULL, "modem_out mix 0"},
+ SST_SBA_MIXER_GRAPH_MAP("modem_out mix 0"),
+
+
+};
+static const char * const slot_names[] = {
+ "none",
+ "slot 0", "slot 1", "slot 2", "slot 3",
+ "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */
+};
+
+static const char * const channel_names[] = {
+ "none",
+ "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1",
+ "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */
+};
+
+#define SST_INTERLEAVER(xpname, slot_name, slotno) \
+ SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \
+ channel_names, sst_slot_get, sst_slot_put)
+
+#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \
+ SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \
+ slot_names, sst_slot_get, sst_slot_put)
+
+static const struct snd_kcontrol_new sst_slot_controls[] = {
+ SST_INTERLEAVER("codec_out", "slot 0", 0),
+ SST_INTERLEAVER("codec_out", "slot 1", 1),
+ SST_INTERLEAVER("codec_out", "slot 2", 2),
+ SST_INTERLEAVER("codec_out", "slot 3", 3),
+ SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0),
+ SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1),
+ SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2),
+ SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3),
+};
+
+/* Gain helper with min/max set */
+#define SST_GAIN(name, path_id, task_id, instance, gain_var) \
+ SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \
+ SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \
+ sst_gain_get, sst_gain_put, \
+ SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \
+ sst_gain_tlv_common, gain_var)
+
+#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \
+ SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \
+ SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \
+ sst_gain_get, sst_gain_put, \
+ SST_MODULE_ID_VOLUME, path_id, instance, task_id, \
+ sst_gain_tlv_common, gain_var)
+
+static struct sst_gain_value sst_gains[];
+
+static const struct snd_kcontrol_new sst_gain_controls[] = {
+ SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]),
+ SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]),
+ SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]),
+ SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]),
+
+ SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]),
+ SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]),
+ SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]),
+ SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]),
+
+ SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]),
+ SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]),
+ SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]),
+ SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]),
+ SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]),
+ SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]),
+ SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]),
+ SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]),
+ SST_GAIN("modem_in", SST_PATH_INDEX_MODEM_IN, SST_TASK_SBA, 0, &sst_gains[16]),
+ SST_GAIN("modem_out", SST_PATH_INDEX_MODEM_OUT, SST_TASK_SBA, 0, &sst_gains[17]),
+
+};
+
+#define SST_GAIN_NUM_CONTROLS 3
+/* the SST_GAIN macro above will create three alsa controls for each
+ * instance invoked, gain, mute and ramp duration, which use the same gain
+ * cell sst_gain to keep track of data
+ * To calculate number of gain cell instances we need to device by 3 in
+ * below caulcation for gain cell memory.
+ * This gets rid of static number and issues while adding new controls
+ */
+static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS];
+
+static const struct snd_kcontrol_new sst_algo_controls[] = {
+ SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24,
+ SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR),
+ SST_ALGO_KCONTROL_BYTES("media_loop1_out", "iir", 300, SST_MODULE_ID_IIR_24,
+ SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR),
+ SST_ALGO_KCONTROL_BYTES("media_loop1_out", "mdrp", 286, SST_MODULE_ID_MDRP,
+ SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP),
+ SST_ALGO_KCONTROL_BYTES("media_loop2_out", "fir", 272, SST_MODULE_ID_FIR_24,
+ SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR),
+ SST_ALGO_KCONTROL_BYTES("media_loop2_out", "iir", 300, SST_MODULE_ID_IIR_24,
+ SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR),
+ SST_ALGO_KCONTROL_BYTES("media_loop2_out", "mdrp", 286, SST_MODULE_ID_MDRP,
+ SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP),
+ SST_ALGO_KCONTROL_BYTES("sprot_loop_out", "lpro", 192, SST_MODULE_ID_SPROT,
+ SST_PATH_INDEX_SPROT_LOOP_OUT, 0, SST_TASK_SBA, SBA_VB_LPRO),
+ SST_ALGO_KCONTROL_BYTES("codec_in0", "dcr", 52, SST_MODULE_ID_FILT_DCR,
+ SST_PATH_INDEX_CODEC_IN0, 0, SST_TASK_SBA, SBA_VB_SET_IIR),
+ SST_ALGO_KCONTROL_BYTES("codec_in1", "dcr", 52, SST_MODULE_ID_FILT_DCR,
+ SST_PATH_INDEX_CODEC_IN1, 0, SST_TASK_SBA, SBA_VB_SET_IIR),
+
+};
+
+static int sst_algo_control_init(struct device *dev)
+{
+ int i = 0;
+ struct sst_algo_control *bc;
+ /*allocate space to cache the algo parameters in the driver*/
+ for (i = 0; i < ARRAY_SIZE(sst_algo_controls); i++) {
+ bc = (struct sst_algo_control *)sst_algo_controls[i].private_value;
+ bc->params = devm_kzalloc(dev, bc->max, GFP_KERNEL);
+ if (bc->params == NULL)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w)
+{
+ switch (w->id) {
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_mixer:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * sst_send_pipe_gains - send gains for the front-end DAIs
+ * @dai: front-end dai
+ * @stream: direction
+ * @mute: boolean indicating mute status
+ *
+ * The gains in the pipes connected to the front-ends are muted/unmuted
+ * automatically via the digital_mute() DAPM callback. This function sends the
+ * gains for the front-end pipes.
+ */
+int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute)
+{
+ struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_path *p = NULL;
+
+ dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dev_dbg(dai->dev, "Stream name=%s\n",
+ dai->playback_widget->name);
+ w = dai->playback_widget;
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (p->connected && !p->connected(w, p->sink))
+ continue;
+
+ if (p->connect && p->sink->power &&
+ is_sst_dapm_widget(p->sink)) {
+ struct sst_ids *ids = p->sink->priv;
+
+ dev_dbg(dai->dev, "send gains for widget=%s\n",
+ p->sink->name);
+ mutex_lock(&drv->lock);
+ sst_set_pipe_gain(ids, drv, mute);
+ mutex_unlock(&drv->lock);
+ }
+ }
+ } else {
+ dev_dbg(dai->dev, "Stream name=%s\n",
+ dai->capture_widget->name);
+ w = dai->capture_widget;
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ if (p->connected && !p->connected(w, p->source))
+ continue;
+
+ if (p->connect && p->source->power &&
+ is_sst_dapm_widget(p->source)) {
+ struct sst_ids *ids = p->source->priv;
+
+ dev_dbg(dai->dev, "send gain for widget=%s\n",
+ p->source->name);
+ mutex_lock(&drv->lock);
+ sst_set_pipe_gain(ids, drv, mute);
+ mutex_unlock(&drv->lock);
+ }
+ }
+ }
+ return 0;
+}
+
+/**
+ * sst_fill_module_list - populate the list of modules/gains for a pipe
+ * @kctl: kcontrol pointer
+ * @w: dapm widget
+ * @type: widget type
+ *
+ * Fills the widget pointer in the kcontrol private data, and also fills the
+ * kcontrol pointer in the widget private data.
+ *
+ * Widget pointer is used to send the algo/gain in the .put() handler if the
+ * widget is powerd on.
+ *
+ * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF
+ * event handler. Each widget (pipe) has multiple algos stored in the algo_list.
+ */
+static int sst_fill_module_list(struct snd_kcontrol *kctl,
+ struct snd_soc_dapm_widget *w, int type)
+{
+ struct sst_module *module = NULL;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct sst_ids *ids = w->priv;
+ int ret = 0;
+
+ module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL);
+ if (!module)
+ return -ENOMEM;
+
+ if (type == SST_MODULE_GAIN) {
+ struct sst_gain_mixer_control *mc = (void *)kctl->private_value;
+
+ mc->w = w;
+ module->kctl = kctl;
+ list_add_tail(&module->node, &ids->gain_list);
+ } else if (type == SST_MODULE_ALGO) {
+ struct sst_algo_control *bc = (void *)kctl->private_value;
+
+ bc->w = w;
+ module->kctl = kctl;
+ list_add_tail(&module->node, &ids->algo_list);
+ } else {
+ dev_err(c->dev, "invoked for unknown type %d module %s",
+ type, kctl->id.name);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * sst_fill_widget_module_info - fill list of gains/algos for the pipe
+ * @w: pipe modeled as a DAPM widget
+ * @component: ASoC component
+ *
+ * Fill the list of gains/algos for the widget by looking at all the card
+ * controls and comparing the name of the widget with the first part of control
+ * name. First part of control name contains the pipe name (widget name).
+ */
+static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w,
+ struct snd_soc_component *component)
+{
+ struct snd_kcontrol *kctl;
+ int index, ret = 0;
+ struct snd_card *card = component->card->snd_card;
+ char *idx;
+
+ down_read(&card->controls_rwsem);
+
+ list_for_each_entry(kctl, &card->controls, list) {
+ idx = strchr(kctl->id.name, ' ');
+ if (idx == NULL)
+ continue;
+ index = idx - (char*)kctl->id.name;
+ if (strncmp(kctl->id.name, w->name, index))
+ continue;
+
+ if (strstr(kctl->id.name, "Volume"))
+ ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN);
+
+ else if (strstr(kctl->id.name, "params"))
+ ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO);
+
+ else if (strstr(kctl->id.name, "Switch") &&
+ strstr(kctl->id.name, "Gain")) {
+ struct sst_gain_mixer_control *mc =
+ (void *)kctl->private_value;
+
+ mc->w = w;
+
+ } else if (strstr(kctl->id.name, "interleaver")) {
+ struct sst_enum *e = (void *)kctl->private_value;
+
+ e->w = w;
+
+ } else if (strstr(kctl->id.name, "deinterleaver")) {
+ struct sst_enum *e = (void *)kctl->private_value;
+
+ e->w = w;
+ }
+
+ if (ret < 0) {
+ up_read(&card->controls_rwsem);
+ return ret;
+ }
+ }
+
+ up_read(&card->controls_rwsem);
+ return 0;
+}
+
+/**
+ * sst_fill_linked_widgets - fill the parent pointer for the linked widget
+ * @component: ASoC component
+ * @ids: sst_ids array
+ */
+static void sst_fill_linked_widgets(struct snd_soc_component *component,
+ struct sst_ids *ids)
+{
+ struct snd_soc_dapm_widget *w;
+ unsigned int len = strlen(ids->parent_wname);
+
+ list_for_each_entry(w, &component->card->widgets, list) {
+ if (!strncmp(ids->parent_wname, w->name, len)) {
+ ids->parent_w = w;
+ break;
+ }
+ }
+}
+
+/**
+ * sst_map_modules_to_pipe - fill algo/gains list for all pipes
+ * @component: ASoC component
+ */
+static int sst_map_modules_to_pipe(struct snd_soc_component *component)
+{
+ struct snd_soc_dapm_widget *w;
+ int ret = 0;
+
+ list_for_each_entry(w, &component->card->widgets, list) {
+ if (is_sst_dapm_widget(w) && (w->priv)) {
+ struct sst_ids *ids = w->priv;
+
+ dev_dbg(component->dev, "widget type=%d name=%s\n",
+ w->id, w->name);
+ INIT_LIST_HEAD(&ids->algo_list);
+ INIT_LIST_HEAD(&ids->gain_list);
+ ret = sst_fill_widget_module_info(w, component);
+
+ if (ret < 0)
+ return ret;
+
+ /* fill linked widgets */
+ if (ids->parent_wname != NULL)
+ sst_fill_linked_widgets(component, ids);
+ }
+ }
+ return 0;
+}
+
+int sst_dsp_init_v2_dpcm(struct snd_soc_component *component)
+{
+ int i, ret = 0;
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ struct sst_data *drv = snd_soc_component_get_drvdata(component);
+ unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3;
+
+ drv->byte_stream = devm_kzalloc(component->dev,
+ SST_MAX_BIN_BYTES, GFP_KERNEL);
+ if (!drv->byte_stream)
+ return -ENOMEM;
+
+ snd_soc_dapm_new_controls(dapm, sst_dapm_widgets,
+ ARRAY_SIZE(sst_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon,
+ ARRAY_SIZE(intercon));
+ snd_soc_dapm_new_widgets(dapm->card);
+
+ for (i = 0; i < gains; i++) {
+ sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT;
+ sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT;
+ sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT;
+ sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT;
+ }
+
+ ret = snd_soc_add_component_controls(component, sst_gain_controls,
+ ARRAY_SIZE(sst_gain_controls));
+ if (ret)
+ return ret;
+
+ /* Initialize algo control params */
+ ret = sst_algo_control_init(component->dev);
+ if (ret)
+ return ret;
+ ret = snd_soc_add_component_controls(component, sst_algo_controls,
+ ARRAY_SIZE(sst_algo_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_add_component_controls(component, sst_slot_controls,
+ ARRAY_SIZE(sst_slot_controls));
+ if (ret)
+ return ret;
+
+ ret = sst_map_modules_to_pipe(component);
+
+ return ret;
+}
diff --git a/sound/soc/intel/atom/sst-atom-controls.h b/sound/soc/intel/atom/sst-atom-controls.h
new file mode 100644
index 000000000..620b48d2a
--- /dev/null
+++ b/sound/soc/intel/atom/sst-atom-controls.h
@@ -0,0 +1,875 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * sst-atom-controls.h - Intel MID Platform driver header file
+ *
+ * Copyright (C) 2013-14 Intel Corp
+ * Author: Ramesh Babu <ramesh.babu.koul@intel.com>
+ * Omair M Abdullah <omair.m.abdullah@intel.com>
+ * Samreen Nilofer <samreen.nilofer@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SST_ATOM_CONTROLS_H__
+#define __SST_ATOM_CONTROLS_H__
+
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+enum {
+ MERR_DPCM_AUDIO = 0,
+ MERR_DPCM_DEEP_BUFFER,
+ MERR_DPCM_COMPR,
+};
+
+/* define a bit for each mixer input */
+#define SST_MIX_IP(x) (x)
+
+#define SST_IP_MODEM SST_MIX_IP(0)
+#define SST_IP_BT SST_MIX_IP(1)
+#define SST_IP_CODEC0 SST_MIX_IP(2)
+#define SST_IP_CODEC1 SST_MIX_IP(3)
+#define SST_IP_LOOP0 SST_MIX_IP(4)
+#define SST_IP_LOOP1 SST_MIX_IP(5)
+#define SST_IP_LOOP2 SST_MIX_IP(6)
+#define SST_IP_PROBE SST_MIX_IP(7)
+#define SST_IP_VOIP SST_MIX_IP(12)
+#define SST_IP_PCM0 SST_MIX_IP(13)
+#define SST_IP_PCM1 SST_MIX_IP(14)
+#define SST_IP_MEDIA0 SST_MIX_IP(17)
+#define SST_IP_MEDIA1 SST_MIX_IP(18)
+#define SST_IP_MEDIA2 SST_MIX_IP(19)
+#define SST_IP_MEDIA3 SST_MIX_IP(20)
+
+#define SST_IP_LAST SST_IP_MEDIA3
+
+#define SST_SWM_INPUT_COUNT (SST_IP_LAST + 1)
+#define SST_CMD_SWM_MAX_INPUTS 6
+
+#define SST_PATH_ID_SHIFT 8
+#define SST_DEFAULT_LOCATION_ID 0xFFFF
+#define SST_DEFAULT_CELL_NBR 0xFF
+#define SST_DEFAULT_MODULE_ID 0xFFFF
+
+/*
+ * Audio DSP Path Ids. Specified by the audio DSP FW
+ */
+enum sst_path_index {
+ SST_PATH_INDEX_MODEM_OUT = (0x00 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_CODEC_OUT0 = (0x02 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_CODEC_OUT1 = (0x03 << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_SPROT_LOOP_OUT = (0x04 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA_LOOP1_OUT = (0x05 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA_LOOP2_OUT = (0x06 << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_VOIP_OUT = (0x0C << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_PCM0_OUT = (0x0D << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_PCM1_OUT = (0x0E << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_PCM2_OUT = (0x0F << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_MEDIA0_OUT = (0x12 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA1_OUT = (0x13 << SST_PATH_ID_SHIFT),
+
+
+ /* Start of input paths */
+ SST_PATH_INDEX_MODEM_IN = (0x80 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_CODEC_IN0 = (0x82 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_CODEC_IN1 = (0x83 << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_SPROT_LOOP_IN = (0x84 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA_LOOP1_IN = (0x85 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA_LOOP2_IN = (0x86 << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_VOIP_IN = (0x8C << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_PCM0_IN = (0x8D << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_PCM1_IN = (0x8E << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_MEDIA0_IN = (0x8F << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA1_IN = (0x90 << SST_PATH_ID_SHIFT),
+ SST_PATH_INDEX_MEDIA2_IN = (0x91 << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_MEDIA3_IN = (0x9C << SST_PATH_ID_SHIFT),
+
+ SST_PATH_INDEX_RESERVED = (0xFF << SST_PATH_ID_SHIFT),
+};
+
+/*
+ * path IDs
+ */
+enum sst_swm_inputs {
+ SST_SWM_IN_MODEM = (SST_PATH_INDEX_MODEM_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_CODEC0 = (SST_PATH_INDEX_CODEC_IN0 | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_CODEC1 = (SST_PATH_INDEX_CODEC_IN1 | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_VOIP = (SST_PATH_INDEX_VOIP_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_PCM0 = (SST_PATH_INDEX_PCM0_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_PCM1 = (SST_PATH_INDEX_PCM1_IN | SST_DEFAULT_CELL_NBR),
+ SST_SWM_IN_MEDIA0 = (SST_PATH_INDEX_MEDIA0_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_IN_MEDIA1 = (SST_PATH_INDEX_MEDIA1_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_IN_MEDIA2 = (SST_PATH_INDEX_MEDIA2_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_IN_MEDIA3 = (SST_PATH_INDEX_MEDIA3_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_IN_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR)
+};
+
+/*
+ * path IDs
+ */
+enum sst_swm_outputs {
+ SST_SWM_OUT_MODEM = (SST_PATH_INDEX_MODEM_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_CODEC0 = (SST_PATH_INDEX_CODEC_OUT0 | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_CODEC1 = (SST_PATH_INDEX_CODEC_OUT1 | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_VOIP = (SST_PATH_INDEX_VOIP_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_PCM0 = (SST_PATH_INDEX_PCM0_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_PCM1 = (SST_PATH_INDEX_PCM1_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_PCM2 = (SST_PATH_INDEX_PCM2_OUT | SST_DEFAULT_CELL_NBR),
+ SST_SWM_OUT_MEDIA0 = (SST_PATH_INDEX_MEDIA0_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_OUT_MEDIA1 = (SST_PATH_INDEX_MEDIA1_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */
+ SST_SWM_OUT_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR),
+};
+
+enum sst_ipc_msg {
+ SST_IPC_IA_CMD = 1,
+ SST_IPC_IA_SET_PARAMS,
+ SST_IPC_IA_GET_PARAMS,
+};
+
+enum sst_cmd_type {
+ SST_CMD_BYTES_SET = 1,
+ SST_CMD_BYTES_GET = 2,
+};
+
+enum sst_task {
+ SST_TASK_SBA = 1,
+ SST_TASK_MMX = 3,
+};
+
+enum sst_type {
+ SST_TYPE_CMD = 1,
+ SST_TYPE_PARAMS,
+};
+
+enum sst_flag {
+ SST_FLAG_BLOCKED = 1,
+ SST_FLAG_NONBLOCK,
+};
+
+/*
+ * Enumeration for indexing the gain cells in VB_SET_GAIN DSP command
+ */
+enum sst_gain_index {
+ /* GAIN IDs for SB task start here */
+ SST_GAIN_INDEX_CODEC_OUT0,
+ SST_GAIN_INDEX_CODEC_OUT1,
+ SST_GAIN_INDEX_CODEC_IN0,
+ SST_GAIN_INDEX_CODEC_IN1,
+
+ SST_GAIN_INDEX_SPROT_LOOP_OUT,
+ SST_GAIN_INDEX_MEDIA_LOOP1_OUT,
+ SST_GAIN_INDEX_MEDIA_LOOP2_OUT,
+
+ SST_GAIN_INDEX_PCM0_IN_LEFT,
+ SST_GAIN_INDEX_PCM0_IN_RIGHT,
+
+ SST_GAIN_INDEX_PCM1_OUT_LEFT,
+ SST_GAIN_INDEX_PCM1_OUT_RIGHT,
+ SST_GAIN_INDEX_PCM1_IN_LEFT,
+ SST_GAIN_INDEX_PCM1_IN_RIGHT,
+ SST_GAIN_INDEX_PCM2_OUT_LEFT,
+
+ SST_GAIN_INDEX_PCM2_OUT_RIGHT,
+ SST_GAIN_INDEX_VOIP_OUT,
+ SST_GAIN_INDEX_VOIP_IN,
+
+ /* Gain IDs for MMX task start here */
+ SST_GAIN_INDEX_MEDIA0_IN_LEFT,
+ SST_GAIN_INDEX_MEDIA0_IN_RIGHT,
+ SST_GAIN_INDEX_MEDIA1_IN_LEFT,
+ SST_GAIN_INDEX_MEDIA1_IN_RIGHT,
+
+ SST_GAIN_INDEX_MEDIA2_IN_LEFT,
+ SST_GAIN_INDEX_MEDIA2_IN_RIGHT,
+
+ SST_GAIN_INDEX_GAIN_END
+};
+
+/*
+ * Audio DSP module IDs specified by FW spec
+ * TODO: Update with all modules
+ */
+enum sst_module_id {
+ SST_MODULE_ID_PCM = 0x0001,
+ SST_MODULE_ID_MP3 = 0x0002,
+ SST_MODULE_ID_MP24 = 0x0003,
+ SST_MODULE_ID_AAC = 0x0004,
+ SST_MODULE_ID_AACP = 0x0005,
+ SST_MODULE_ID_EAACP = 0x0006,
+ SST_MODULE_ID_WMA9 = 0x0007,
+ SST_MODULE_ID_WMA10 = 0x0008,
+ SST_MODULE_ID_WMA10P = 0x0009,
+ SST_MODULE_ID_RA = 0x000A,
+ SST_MODULE_ID_DDAC3 = 0x000B,
+ SST_MODULE_ID_TRUE_HD = 0x000C,
+ SST_MODULE_ID_HD_PLUS = 0x000D,
+
+ SST_MODULE_ID_SRC = 0x0064,
+ SST_MODULE_ID_DOWNMIX = 0x0066,
+ SST_MODULE_ID_GAIN_CELL = 0x0067,
+ SST_MODULE_ID_SPROT = 0x006D,
+ SST_MODULE_ID_BASS_BOOST = 0x006E,
+ SST_MODULE_ID_STEREO_WDNG = 0x006F,
+ SST_MODULE_ID_AV_REMOVAL = 0x0070,
+ SST_MODULE_ID_MIC_EQ = 0x0071,
+ SST_MODULE_ID_SPL = 0x0072,
+ SST_MODULE_ID_ALGO_VTSV = 0x0073,
+ SST_MODULE_ID_NR = 0x0076,
+ SST_MODULE_ID_BWX = 0x0077,
+ SST_MODULE_ID_DRP = 0x0078,
+ SST_MODULE_ID_MDRP = 0x0079,
+
+ SST_MODULE_ID_ANA = 0x007A,
+ SST_MODULE_ID_AEC = 0x007B,
+ SST_MODULE_ID_NR_SNS = 0x007C,
+ SST_MODULE_ID_SER = 0x007D,
+ SST_MODULE_ID_AGC = 0x007E,
+
+ SST_MODULE_ID_CNI = 0x007F,
+ SST_MODULE_ID_CONTEXT_ALGO_AWARE = 0x0080,
+ SST_MODULE_ID_FIR_24 = 0x0081,
+ SST_MODULE_ID_IIR_24 = 0x0082,
+
+ SST_MODULE_ID_ASRC = 0x0083,
+ SST_MODULE_ID_TONE_GEN = 0x0084,
+ SST_MODULE_ID_BMF = 0x0086,
+ SST_MODULE_ID_EDL = 0x0087,
+ SST_MODULE_ID_GLC = 0x0088,
+
+ SST_MODULE_ID_FIR_16 = 0x0089,
+ SST_MODULE_ID_IIR_16 = 0x008A,
+ SST_MODULE_ID_DNR = 0x008B,
+
+ SST_MODULE_ID_VIRTUALIZER = 0x008C,
+ SST_MODULE_ID_VISUALIZATION = 0x008D,
+ SST_MODULE_ID_LOUDNESS_OPTIMIZER = 0x008E,
+ SST_MODULE_ID_REVERBERATION = 0x008F,
+
+ SST_MODULE_ID_CNI_TX = 0x0090,
+ SST_MODULE_ID_REF_LINE = 0x0091,
+ SST_MODULE_ID_VOLUME = 0x0092,
+ SST_MODULE_ID_FILT_DCR = 0x0094,
+ SST_MODULE_ID_SLV = 0x009A,
+ SST_MODULE_ID_NLF = 0x009B,
+ SST_MODULE_ID_TNR = 0x009C,
+ SST_MODULE_ID_WNR = 0x009D,
+
+ SST_MODULE_ID_LOG = 0xFF00,
+
+ SST_MODULE_ID_TASK = 0xFFFF,
+};
+
+enum sst_cmd {
+ SBA_IDLE = 14,
+ SBA_VB_SET_SPEECH_PATH = 26,
+ MMX_SET_GAIN = 33,
+ SBA_VB_SET_GAIN = 33,
+ FBA_VB_RX_CNI = 35,
+ MMX_SET_GAIN_TIMECONST = 36,
+ SBA_VB_SET_TIMECONST = 36,
+ SBA_VB_START = 85,
+ SBA_SET_SWM = 114,
+ SBA_SET_MDRP = 116,
+ SBA_HW_SET_SSP = 117,
+ SBA_SET_MEDIA_LOOP_MAP = 118,
+ SBA_SET_MEDIA_PATH = 119,
+ MMX_SET_MEDIA_PATH = 119,
+ SBA_VB_LPRO = 126,
+ SBA_VB_SET_FIR = 128,
+ SBA_VB_SET_IIR = 129,
+ SBA_SET_SSP_SLOT_MAP = 130,
+};
+
+enum sst_dsp_switch {
+ SST_SWITCH_OFF = 0,
+ SST_SWITCH_ON = 3,
+};
+
+enum sst_path_switch {
+ SST_PATH_OFF = 0,
+ SST_PATH_ON = 1,
+};
+
+enum sst_swm_state {
+ SST_SWM_OFF = 0,
+ SST_SWM_ON = 3,
+};
+
+#define SST_FILL_LOCATION_IDS(dst, cell_idx, pipe_id) do { \
+ dst.location_id.p.cell_nbr_idx = (cell_idx); \
+ dst.location_id.p.path_id = (pipe_id); \
+ } while (0)
+#define SST_FILL_LOCATION_ID(dst, loc_id) (\
+ dst.location_id.f = (loc_id))
+#define SST_FILL_MODULE_ID(dst, mod_id) (\
+ dst.module_id = (mod_id))
+
+#define SST_FILL_DESTINATION1(dst, id) do { \
+ SST_FILL_LOCATION_ID(dst, (id) & 0xFFFF); \
+ SST_FILL_MODULE_ID(dst, ((id) & 0xFFFF0000) >> 16); \
+ } while (0)
+#define SST_FILL_DESTINATION2(dst, loc_id, mod_id) do { \
+ SST_FILL_LOCATION_ID(dst, loc_id); \
+ SST_FILL_MODULE_ID(dst, mod_id); \
+ } while (0)
+#define SST_FILL_DESTINATION3(dst, cell_idx, path_id, mod_id) do { \
+ SST_FILL_LOCATION_IDS(dst, cell_idx, path_id); \
+ SST_FILL_MODULE_ID(dst, mod_id); \
+ } while (0)
+
+#define SST_FILL_DESTINATION(level, dst, ...) \
+ SST_FILL_DESTINATION##level(dst, __VA_ARGS__)
+#define SST_FILL_DEFAULT_DESTINATION(dst) \
+ SST_FILL_DESTINATION(2, dst, SST_DEFAULT_LOCATION_ID, SST_DEFAULT_MODULE_ID)
+
+struct sst_destination_id {
+ union sst_location_id {
+ struct {
+ u8 cell_nbr_idx; /* module index */
+ u8 path_id; /* pipe_id */
+ } __packed p; /* part */
+ u16 f; /* full */
+ } __packed location_id;
+ u16 module_id;
+} __packed;
+struct sst_dsp_header {
+ struct sst_destination_id dst;
+ u16 command_id;
+ u16 length;
+} __packed;
+
+/*
+ *
+ * Common Commands
+ *
+ */
+struct sst_cmd_generic {
+ struct sst_dsp_header header;
+} __packed;
+
+struct swm_input_ids {
+ struct sst_destination_id input_id;
+} __packed;
+
+struct sst_cmd_set_swm {
+ struct sst_dsp_header header;
+ struct sst_destination_id output_id;
+ u16 switch_state;
+ u16 nb_inputs;
+ struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS];
+} __packed;
+
+struct sst_cmd_set_media_path {
+ struct sst_dsp_header header;
+ u16 switch_state;
+} __packed;
+
+struct pcm_cfg {
+ u8 s_length:2;
+ u8 rate:3;
+ u8 format:3;
+} __packed;
+
+struct sst_cmd_set_speech_path {
+ struct sst_dsp_header header;
+ u16 switch_state;
+ struct {
+ u16 rsvd:8;
+ struct pcm_cfg cfg;
+ } config;
+} __packed;
+
+struct gain_cell {
+ struct sst_destination_id dest;
+ s16 cell_gain_left;
+ s16 cell_gain_right;
+ u16 gain_time_constant;
+} __packed;
+
+#define NUM_GAIN_CELLS 1
+struct sst_cmd_set_gain_dual {
+ struct sst_dsp_header header;
+ u16 gain_cell_num;
+ struct gain_cell cell_gains[NUM_GAIN_CELLS];
+} __packed;
+struct sst_cmd_set_params {
+ struct sst_destination_id dst;
+ u16 command_id;
+ char params[];
+} __packed;
+
+
+struct sst_cmd_sba_vb_start {
+ struct sst_dsp_header header;
+} __packed;
+
+union sba_media_loop_params {
+ struct {
+ u16 rsvd:8;
+ struct pcm_cfg cfg;
+ } part;
+ u16 full;
+} __packed;
+
+struct sst_cmd_sba_set_media_loop_map {
+ struct sst_dsp_header header;
+ u16 switch_state;
+ union sba_media_loop_params param;
+ u16 map;
+} __packed;
+
+struct sst_cmd_tone_stop {
+ struct sst_dsp_header header;
+ u16 switch_state;
+} __packed;
+
+enum sst_ssp_mode {
+ SSP_MODE_MASTER = 0,
+ SSP_MODE_SLAVE = 1,
+};
+
+enum sst_ssp_pcm_mode {
+ SSP_PCM_MODE_NORMAL = 0,
+ SSP_PCM_MODE_NETWORK = 1,
+};
+
+enum sst_ssp_duplex {
+ SSP_DUPLEX = 0,
+ SSP_RX = 1,
+ SSP_TX = 2,
+};
+
+enum sst_ssp_fs_frequency {
+ SSP_FS_8_KHZ = 0,
+ SSP_FS_16_KHZ = 1,
+ SSP_FS_44_1_KHZ = 2,
+ SSP_FS_48_KHZ = 3,
+};
+
+enum sst_ssp_fs_polarity {
+ SSP_FS_ACTIVE_LOW = 0,
+ SSP_FS_ACTIVE_HIGH = 1,
+};
+
+enum sst_ssp_protocol {
+ SSP_MODE_PCM = 0,
+ SSP_MODE_I2S = 1,
+};
+
+enum sst_ssp_port_id {
+ SSP_MODEM = 0,
+ SSP_BT = 1,
+ SSP_FM = 2,
+ SSP_CODEC = 3,
+};
+
+struct sst_cmd_sba_hw_set_ssp {
+ struct sst_dsp_header header;
+ u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */
+
+ u16 switch_state;
+
+ u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */
+ u16 nb_slots:4; /* 0-8: slots per frame */
+ u16 mode:3; /* 0:Master, 1: Slave */
+ u16 duplex:3;
+
+ u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */
+ u16 reserved1:8;
+
+ u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */
+ u16 reserved2:8;
+
+ u16 frame_sync_frequency;
+
+ u16 frame_sync_polarity:8;
+ u16 data_polarity:8;
+
+ u16 frame_sync_width; /* 1 to N clocks */
+ u16 ssp_protocol:8;
+ u16 start_delay:8; /* Start delay in terms of clock ticks */
+} __packed;
+
+#define SST_MAX_TDM_SLOTS 8
+
+struct sst_param_sba_ssp_slot_map {
+ struct sst_dsp_header header;
+
+ u16 param_id;
+ u16 param_len;
+ u16 ssp_index;
+
+ u8 rx_slot_map[SST_MAX_TDM_SLOTS];
+ u8 tx_slot_map[SST_MAX_TDM_SLOTS];
+} __packed;
+
+enum {
+ SST_PROBE_EXTRACTOR = 0,
+ SST_PROBE_INJECTOR = 1,
+};
+
+/**** widget defines *****/
+
+#define SST_MODULE_GAIN 1
+#define SST_MODULE_ALGO 2
+
+#define SST_FMT_MONO 0
+#define SST_FMT_STEREO 3
+
+/* physical SSP numbers */
+enum {
+ SST_SSP0 = 0,
+ SST_SSP1,
+ SST_SSP2,
+ SST_SSP_LAST = SST_SSP2,
+};
+
+#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */
+#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */
+#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */
+
+struct sst_module {
+ struct snd_kcontrol *kctl;
+ struct list_head node;
+};
+
+struct sst_ssp_config {
+ u8 ssp_id;
+ u8 bits_per_slot;
+ u8 slots;
+ u8 ssp_mode;
+ u8 pcm_mode;
+ u8 duplex;
+ u8 ssp_protocol;
+ u8 fs_frequency;
+ u8 active_slot_map;
+ u8 start_delay;
+ u16 fs_width;
+ u8 frame_sync_polarity;
+ u8 data_polarity;
+};
+
+struct sst_ssp_cfg {
+ const u8 ssp_number;
+ const int *mux_shift;
+ const int (*domain_shift)[SST_MAX_SSP_MUX];
+ const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS];
+};
+
+struct sst_ids {
+ u16 location_id;
+ u16 module_id;
+ u8 task_id;
+ u8 format;
+ u8 reg;
+ const char *parent_wname;
+ struct snd_soc_dapm_widget *parent_w;
+ struct list_head algo_list;
+ struct list_head gain_list;
+ const struct sst_pcm_format *pcm_fmt;
+};
+
+
+#define SST_AIF_IN(wname, wevent) \
+{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \
+ .reg = SND_SOC_NOPM, .shift = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+ .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
+}
+
+#define SST_AIF_OUT(wname, wevent) \
+{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \
+ .reg = SND_SOC_NOPM, .shift = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+ .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
+}
+
+#define SST_INPUT(wname, wevent) \
+{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \
+ .reg = SND_SOC_NOPM, .shift = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+ .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
+}
+
+#define SST_OUTPUT(wname, wevent) \
+{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \
+ .reg = SND_SOC_NOPM, .shift = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+ .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \
+}
+
+#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \
+{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \
+ .reg = SND_SOC_NOPM, .shift = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+ .priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\
+ .pcm_fmt = wformat, } \
+}
+
+#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
+ .kcontrol_news = NULL, .num_kcontrols = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = wflags, \
+ .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \
+}
+
+#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
+ .kcontrol_news = NULL, .num_kcontrols = 0, \
+ .on_val = 1, .off_val = 0, \
+ .event = wevent, .event_flags = wflags, \
+ .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
+ .parent_wname = linked_wname} \
+}
+
+#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
+ .kcontrol_news = NULL, .num_kcontrols = 0, \
+ .event = wevent, .event_flags = wflags, \
+ .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
+ .format = wformat,} \
+}
+
+/* output is triggered before input */
+#define SST_PATH_INPUT(name, task_id, loc_id, event) \
+ SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
+
+#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \
+ SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
+
+#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \
+ SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
+
+#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \
+ SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
+
+#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \
+ SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)
+
+
+#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \
+{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \
+ .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\
+ .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \
+ SND_SOC_DAPM_POST_REG, \
+ .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \
+ .reg = wreg } \
+}
+
+enum sst_gain_kcontrol_type {
+ SST_GAIN_TLV,
+ SST_GAIN_MUTE,
+ SST_GAIN_RAMP_DURATION,
+};
+
+struct sst_gain_mixer_control {
+ bool stereo;
+ enum sst_gain_kcontrol_type type;
+ struct sst_gain_value *gain_val;
+ int max;
+ int min;
+ u16 instance_id;
+ u16 module_id;
+ u16 pipe_id;
+ u16 task_id;
+ char pname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ struct snd_soc_dapm_widget *w;
+};
+
+struct sst_gain_value {
+ u16 ramp_duration;
+ s16 l_gain;
+ s16 r_gain;
+ bool mute;
+};
+#define SST_GAIN_VOLUME_DEFAULT (-1440)
+#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */
+#define SST_GAIN_MUTE_DEFAULT true
+
+#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \
+ xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \
+ xmin, xmax, xpname) \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = sst_gain_ctl_info,\
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct sst_gain_mixer_control) \
+ { .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \
+ .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
+ .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
+
+#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \
+ xmod, xpipe, xinstance, xtask, xtype, xgain_val, \
+ xmin, xmax, xpname) \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = sst_gain_ctl_info, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct sst_gain_mixer_control) \
+ { .stereo = false, .max = xmax, .min = xmin, .type = xtype, \
+ .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
+ .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
+
+#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\
+ xmod, xpipe, xinstance, xtask, xgain_val, xpname) \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_bool_ext, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct sst_gain_mixer_control) \
+ { .stereo = false, .type = SST_GAIN_MUTE, \
+ .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\
+ .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname}
+#define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \
+ xpname " " xmname " " #xinstance " " xtype
+
+#define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \
+ xpname " " xmname " " #xinstance " " xtype " " xsubmodule
+
+/*
+ * 3 Controls for each Gain module
+ * e.g. - pcm0_in Gain 0 Volume
+ * - pcm0_in Gain 0 Ramp Delay
+ * - pcm0_in Gain 0 Switch
+ */
+#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \
+ xhandler_get, xhandler_put, \
+ xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \
+ { SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \
+ xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \
+ xgain_val, xmin_tc, xmax_tc, xpname) }, \
+ { SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \
+ xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \
+ xgain_val, xpname) } ,\
+ { SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \
+ xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \
+ xgain_val, xmin_gain, xmax_gain, xpname) }
+
+#define SST_GAIN_TC_MIN 5
+#define SST_GAIN_TC_MAX 5000
+#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */
+#define SST_GAIN_MAX_VALUE 360
+
+enum sst_algo_kcontrol_type {
+ SST_ALGO_PARAMS,
+ SST_ALGO_BYPASS,
+};
+
+struct sst_algo_control {
+ enum sst_algo_kcontrol_type type;
+ int max;
+ u16 module_id;
+ u16 pipe_id;
+ u16 task_id;
+ u16 cmd_id;
+ bool bypass;
+ unsigned char *params;
+ struct snd_soc_dapm_widget *w;
+};
+
+/* size of the control = size of params + size of length field */
+#define SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, xmod, xtask, xcmd) \
+ (struct sst_algo_control){ \
+ .max = xcount + sizeof(u16), .type = xtype, .module_id = xmod, \
+ .pipe_id = xpipe, .task_id = xtask, .cmd_id = xcmd, \
+ }
+
+#define SST_ALGO_KCONTROL(xname, xcount, xmod, xpipe, \
+ xtask, xcmd, xtype, xinfo, xget, xput) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .info = xinfo, .get = xget, .put = xput, \
+ .private_value = (unsigned long)& \
+ SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, \
+ xmod, xtask, xcmd), \
+}
+
+#define SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, \
+ xpipe, xinstance, xtask, xcmd) \
+ SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "params"), \
+ xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \
+ sst_algo_bytes_ctl_info, \
+ sst_algo_control_get, sst_algo_control_set)
+
+#define SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask) \
+ SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "bypass"), \
+ 0, xmod, xpipe, xtask, 0, SST_ALGO_BYPASS, \
+ snd_soc_info_bool_ext, \
+ sst_algo_control_get, sst_algo_control_set)
+
+#define SST_ALGO_BYPASS_PARAMS(xpname, xmname, xcount, xmod, xpipe, \
+ xinstance, xtask, xcmd) \
+ SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask), \
+ SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, xpipe, xinstance, xtask, xcmd)
+
+#define SST_COMBO_ALGO_KCONTROL_BYTES(xpname, xmname, xsubmod, xcount, xmod, \
+ xpipe, xinstance, xtask, xcmd) \
+ SST_ALGO_KCONTROL(SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, "params", \
+ xsubmod), \
+ xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \
+ sst_algo_bytes_ctl_info, \
+ sst_algo_control_get, sst_algo_control_set)
+
+
+struct sst_enum {
+ bool tx;
+ unsigned short reg;
+ unsigned int max;
+ const char * const *texts;
+ struct snd_soc_dapm_widget *w;
+};
+
+/* only 4 slots/channels supported atm */
+#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \
+ (struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, }
+
+#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \
+ xpname " " xmname " " s_ch_name
+
+#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \
+ .info = sst_slot_enum_info, \
+ .get = xget, .put = xput, \
+ .private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \
+}
+
+#define SST_MUX_CTL_NAME(xpname, xinstance) \
+ xpname " " #xinstance
+
+#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \
+ (struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts)
+
+#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \
+ SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \
+ SST_SSP_MUX_ENUM(xreg, xshift, xtexts))
+
+int sst_fill_ssp_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width);
+int sst_fill_ssp_config(struct snd_soc_dai *dai, unsigned int fmt);
+void sst_fill_ssp_defaults(struct snd_soc_dai *dai);
+
+#endif
diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h
new file mode 100644
index 000000000..5795f98e0
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-dsp.h
@@ -0,0 +1,525 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __SST_MFLD_DSP_H__
+#define __SST_MFLD_DSP_H__
+/*
+ * sst_mfld_dsp.h - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul@linux.intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define SST_MAX_BIN_BYTES 1024
+
+#define MAX_DBG_RW_BYTES 80
+#define MAX_NUM_SCATTER_BUFFERS 8
+#define MAX_LOOP_BACK_DWORDS 8
+/* IPC base address and mailbox, timestamp offsets */
+#define SST_MAILBOX_SIZE 0x0400
+#define SST_MAILBOX_SEND 0x0000
+#define SST_TIME_STAMP 0x1800
+#define SST_TIME_STAMP_MRFLD 0x800
+#define SST_RESERVED_OFFSET 0x1A00
+#define SST_SCU_LPE_MAILBOX 0x1000
+#define SST_LPE_SCU_MAILBOX 0x1400
+#define SST_SCU_LPE_LOG_BUF (SST_SCU_LPE_MAILBOX+16)
+#define PROCESS_MSG 0x80
+
+/* Message ID's for IPC messages */
+/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */
+
+/* I2L Firmware/Codec Download msgs */
+#define IPC_IA_PREP_LIB_DNLD 0x01
+#define IPC_IA_LIB_DNLD_CMPLT 0x02
+#define IPC_IA_GET_FW_VERSION 0x04
+#define IPC_IA_GET_FW_BUILD_INF 0x05
+#define IPC_IA_GET_FW_INFO 0x06
+#define IPC_IA_GET_FW_CTXT 0x07
+#define IPC_IA_SET_FW_CTXT 0x08
+#define IPC_IA_PREPARE_SHUTDOWN 0x31
+/* I2L Codec Config/control msgs */
+#define IPC_PREP_D3 0x10
+#define IPC_IA_SET_CODEC_PARAMS 0x10
+#define IPC_IA_GET_CODEC_PARAMS 0x11
+#define IPC_IA_SET_PPP_PARAMS 0x12
+#define IPC_IA_GET_PPP_PARAMS 0x13
+#define IPC_SST_PERIOD_ELAPSED_MRFLD 0xA
+#define IPC_IA_ALG_PARAMS 0x1A
+#define IPC_IA_TUNING_PARAMS 0x1B
+#define IPC_IA_SET_RUNTIME_PARAMS 0x1C
+#define IPC_IA_SET_PARAMS 0x1
+#define IPC_IA_GET_PARAMS 0x2
+
+#define IPC_EFFECTS_CREATE 0xE
+#define IPC_EFFECTS_DESTROY 0xF
+
+/* I2L Stream config/control msgs */
+#define IPC_IA_ALLOC_STREAM_MRFLD 0x2
+#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */
+#define IPC_IA_FREE_STREAM_MRFLD 0x03
+#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */
+#define IPC_IA_SET_STREAM_PARAMS 0x22
+#define IPC_IA_SET_STREAM_PARAMS_MRFLD 0x12
+#define IPC_IA_GET_STREAM_PARAMS 0x23
+#define IPC_IA_PAUSE_STREAM 0x24
+#define IPC_IA_PAUSE_STREAM_MRFLD 0x4
+#define IPC_IA_RESUME_STREAM 0x25
+#define IPC_IA_RESUME_STREAM_MRFLD 0x5
+#define IPC_IA_DROP_STREAM 0x26
+#define IPC_IA_DROP_STREAM_MRFLD 0x07
+#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */
+#define IPC_IA_DRAIN_STREAM_MRFLD 0x8
+#define IPC_IA_CONTROL_ROUTING 0x29
+#define IPC_IA_VTSV_UPDATE_MODULES 0x20
+#define IPC_IA_VTSV_DETECTED 0x21
+
+#define IPC_IA_START_STREAM_MRFLD 0X06
+#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */
+
+#define IPC_IA_SET_GAIN_MRFLD 0x21
+/* Debug msgs */
+#define IPC_IA_DBG_MEM_READ 0x40
+#define IPC_IA_DBG_MEM_WRITE 0x41
+#define IPC_IA_DBG_LOOP_BACK 0x42
+#define IPC_IA_DBG_LOG_ENABLE 0x45
+#define IPC_IA_DBG_SET_PROBE_PARAMS 0x47
+
+/* L2I Firmware/Codec Download msgs */
+#define IPC_IA_FW_INIT_CMPLT 0x81
+#define IPC_IA_FW_INIT_CMPLT_MRFLD 0x01
+#define IPC_IA_FW_ASYNC_ERR_MRFLD 0x11
+
+/* L2I Codec Config/control msgs */
+#define IPC_SST_FRAGMENT_ELPASED 0x90 /* Request IA more data */
+
+#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */
+#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */
+#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */
+#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */
+#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */
+#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */
+
+#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occurred */
+/* L2S messages */
+#define IPC_SC_DDR_LINK_UP 0xC0
+#define IPC_SC_DDR_LINK_DOWN 0xC1
+#define IPC_SC_SET_LPECLK_REQ 0xC2
+#define IPC_SC_SSP_BIT_BANG 0xC3
+
+/* L2I Error reporting msgs */
+#define IPC_IA_MEM_ALLOC_FAIL 0xE0
+#define IPC_IA_PROC_ERR 0xE1 /* error in processing a
+ stream can be used by playback and
+ capture modules */
+
+/* L2I Debug msgs */
+#define IPC_IA_PRINT_STRING 0xF0
+
+/* Buffer under-run */
+#define IPC_IA_BUF_UNDER_RUN_MRFLD 0x0B
+
+/* Mrfld specific defines:
+ * For asynchronous messages(INIT_CMPLT, PERIOD_ELAPSED, ASYNC_ERROR)
+ * received from FW, the format is:
+ * - IPC High: pvt_id is set to zero. Always short message.
+ * - msg_id is in lower 16-bits of IPC low payload.
+ * - pipe_id is in higher 16-bits of IPC low payload for period_elapsed.
+ * - error id is in higher 16-bits of IPC low payload for async errors.
+ */
+#define SST_ASYNC_DRV_ID 0
+
+/* Command Response or Acknowledge message to any IPC message will have
+ * same message ID and stream ID information which is sent.
+ * There is no specific Ack message ID. The data field is used as response
+ * meaning.
+ */
+enum ackData {
+ IPC_ACK_SUCCESS = 0,
+ IPC_ACK_FAILURE,
+};
+
+enum ipc_ia_msg_id {
+ IPC_CMD = 1, /*!< Task Control message ID */
+ IPC_SET_PARAMS = 2,/*!< Task Set param message ID */
+ IPC_GET_PARAMS = 3, /*!< Task Get param message ID */
+ IPC_INVALID = 0xFF, /*!<Task Get param message ID */
+};
+
+enum sst_codec_types {
+ /* AUDIO/MUSIC CODEC Type Definitions */
+ SST_CODEC_TYPE_UNKNOWN = 0,
+ SST_CODEC_TYPE_PCM, /* Pass through Audio codec */
+ SST_CODEC_TYPE_MP3,
+ SST_CODEC_TYPE_MP24,
+ SST_CODEC_TYPE_AAC,
+ SST_CODEC_TYPE_AACP,
+ SST_CODEC_TYPE_eAACP,
+};
+
+enum stream_type {
+ SST_STREAM_TYPE_NONE = 0,
+ SST_STREAM_TYPE_MUSIC = 1,
+};
+
+enum sst_error_codes {
+ /* Error code,response to msgId: Description */
+ /* Common error codes */
+ SST_SUCCESS = 0, /* Success */
+ SST_ERR_INVALID_STREAM_ID = 1,
+ SST_ERR_INVALID_MSG_ID = 2,
+ SST_ERR_INVALID_STREAM_OP = 3,
+ SST_ERR_INVALID_PARAMS = 4,
+ SST_ERR_INVALID_CODEC = 5,
+ SST_ERR_INVALID_MEDIA_TYPE = 6,
+ SST_ERR_STREAM_ERR = 7,
+
+ SST_ERR_STREAM_IN_USE = 15,
+};
+
+struct ipc_dsp_hdr {
+ u16 mod_index_id:8; /*!< DSP Command ID specific to tasks */
+ u16 pipe_id:8; /*!< instance of the module in the pipeline */
+ u16 mod_id; /*!< Pipe_id */
+ u16 cmd_id; /*!< Module ID = lpe_algo_types_t */
+ u16 length; /*!< Length of the payload only */
+} __packed;
+
+union ipc_header_high {
+ struct {
+ u32 msg_id:8; /* Message ID - Max 256 Message Types */
+ u32 task_id:4; /* Task ID associated with this comand */
+ u32 drv_id:4; /* Identifier for the driver to track*/
+ u32 rsvd1:8; /* Reserved */
+ u32 result:4; /* Reserved */
+ u32 res_rqd:1; /* Response rqd */
+ u32 large:1; /* Large Message if large = 1 */
+ u32 done:1; /* bit 30 - Done bit */
+ u32 busy:1; /* bit 31 - busy bit*/
+ } part;
+ u32 full;
+} __packed;
+/* IPC header */
+union ipc_header_mrfld {
+ struct {
+ u32 header_low_payload;
+ union ipc_header_high header_high;
+ } p;
+ u64 full;
+} __packed;
+/* CAUTION NOTE: All IPC message body must be multiple of 32 bits.*/
+
+/* IPC Header */
+union ipc_header {
+ struct {
+ u32 msg_id:8; /* Message ID - Max 256 Message Types */
+ u32 str_id:5;
+ u32 large:1; /* Large Message if large = 1 */
+ u32 reserved:2; /* Reserved for future use */
+ u32 data:14; /* Ack/Info for msg, size of msg in Mailbox */
+ u32 done:1; /* bit 30 */
+ u32 busy:1; /* bit 31 */
+ } part;
+ u32 full;
+} __packed;
+
+/* Firmware build info */
+struct sst_fw_build_info {
+ unsigned char date[16]; /* Firmware build date */
+ unsigned char time[16]; /* Firmware build time */
+} __packed;
+
+/* Firmware Version info */
+struct snd_sst_fw_version {
+ u8 build; /* build number*/
+ u8 minor; /* minor number*/
+ u8 major; /* major number*/
+ u8 type; /* build type */
+};
+
+struct ipc_header_fw_init {
+ struct snd_sst_fw_version fw_version;/* Firmware version details */
+ struct sst_fw_build_info build_info;
+ u16 result; /* Fw init result */
+ u8 module_id; /* Module ID in case of error */
+ u8 debug_info; /* Debug info from Module ID in case of fail */
+} __packed;
+
+struct snd_sst_tstamp {
+ u64 ring_buffer_counter; /* PB/CP: Bytes copied from/to DDR. */
+ u64 hardware_counter; /* PB/CP: Bytes DMAed to/from SSP. */
+ u64 frames_decoded;
+ u64 bytes_decoded;
+ u64 bytes_copied;
+ u32 sampling_frequency;
+ u32 channel_peak[8];
+} __packed;
+
+/* Stream type params struture for Alloc stream */
+struct snd_sst_str_type {
+ u8 codec_type; /* Codec type */
+ u8 str_type; /* 1 = voice 2 = music */
+ u8 operation; /* Playback or Capture */
+ u8 protected_str; /* 0=Non DRM, 1=DRM */
+ u8 time_slots;
+ u8 reserved; /* Reserved */
+ u16 result; /* Result used for acknowledgment */
+} __packed;
+
+/* Library info structure */
+struct module_info {
+ u32 lib_version;
+ u32 lib_type;/*TBD- KLOCKWORK u8 lib_type;*/
+ u32 media_type;
+ u8 lib_name[12];
+ u32 lib_caps;
+ unsigned char b_date[16]; /* Lib build date */
+ unsigned char b_time[16]; /* Lib build time */
+} __packed;
+
+/* Library slot info */
+struct lib_slot_info {
+ u8 slot_num; /* 1 or 2 */
+ u8 reserved1;
+ u16 reserved2;
+ u32 iram_size; /* slot size in IRAM */
+ u32 dram_size; /* slot size in DRAM */
+ u32 iram_offset; /* starting offset of slot in IRAM */
+ u32 dram_offset; /* starting offset of slot in DRAM */
+} __packed;
+
+struct snd_ppp_mixer_params {
+ __u32 type; /*Type of the parameter */
+ __u32 size;
+ __u32 input_stream_bitmap; /*Input stream Bit Map*/
+} __packed;
+
+struct snd_sst_lib_download {
+ struct module_info lib_info; /* library info type, capabilities etc */
+ struct lib_slot_info slot_info; /* slot info to be downloaded */
+ u32 mod_entry_pt;
+};
+
+struct snd_sst_lib_download_info {
+ struct snd_sst_lib_download dload_lib;
+ u16 result; /* Result used for acknowledgment */
+ u8 pvt_id; /* Private ID */
+ u8 reserved; /* for alignment */
+};
+struct snd_pcm_params {
+ u8 num_chan; /* 1=Mono, 2=Stereo */
+ u8 pcm_wd_sz; /* 16/24 - bit*/
+ u8 use_offload_path; /* 0-PCM using period elpased & ALSA interfaces
+ 1-PCM stream via compressed interface */
+ u8 reserved2;
+ u32 sfreq; /* Sampling rate in Hz */
+ u8 channel_map[8];
+} __packed;
+
+/* MP3 Music Parameters Message */
+struct snd_mp3_params {
+ u8 num_chan; /* 1=Mono, 2=Stereo */
+ u8 pcm_wd_sz; /* 16/24 - bit*/
+ u8 crc_check; /* crc_check - disable (0) or enable (1) */
+ u8 reserved1; /* unused*/
+ u16 reserved2; /* Unused */
+} __packed;
+
+#define AAC_BIT_STREAM_ADTS 0
+#define AAC_BIT_STREAM_ADIF 1
+#define AAC_BIT_STREAM_RAW 2
+
+/* AAC Music Parameters Message */
+struct snd_aac_params {
+ u8 num_chan; /* 1=Mono, 2=Stereo*/
+ u8 pcm_wd_sz; /* 16/24 - bit*/
+ u8 bdownsample; /*SBR downsampling 0 - disable 1 -enabled AAC+ only */
+ u8 bs_format; /* input bit stream format adts=0, adif=1, raw=2 */
+ u16 reser2;
+ u32 externalsr; /*sampling rate of basic AAC raw bit stream*/
+ u8 sbr_signalling;/*disable/enable/set automode the SBR tool.AAC+*/
+ u8 reser1;
+ u16 reser3;
+} __packed;
+
+/* WMA Music Parameters Message */
+struct snd_wma_params {
+ u8 num_chan; /* 1=Mono, 2=Stereo */
+ u8 pcm_wd_sz; /* 16/24 - bit*/
+ u16 reserved1;
+ u32 brate; /* Use the hard coded value. */
+ u32 sfreq; /* Sampling freq eg. 8000, 441000, 48000 */
+ u32 channel_mask; /* Channel Mask */
+ u16 format_tag; /* Format Tag */
+ u16 block_align; /* packet size */
+ u16 wma_encode_opt;/* Encoder option */
+ u8 op_align; /* op align 0- 16 bit, 1- MSB, 2 LSB */
+ u8 reserved; /* reserved */
+} __packed;
+
+/* Codec params struture */
+union snd_sst_codec_params {
+ struct snd_pcm_params pcm_params;
+ struct snd_mp3_params mp3_params;
+ struct snd_aac_params aac_params;
+ struct snd_wma_params wma_params;
+} __packed;
+
+/* Address and size info of a frame buffer */
+struct sst_address_info {
+ u32 addr; /* Address at IA */
+ u32 size; /* Size of the buffer */
+};
+
+struct snd_sst_alloc_params_ext {
+ __u16 sg_count;
+ __u16 reserved;
+ __u32 frag_size; /*Number of samples after which period elapsed
+ message is sent valid only if path = 0*/
+ struct sst_address_info ring_buf_info[8];
+};
+
+struct snd_sst_stream_params {
+ union snd_sst_codec_params uc;
+} __packed;
+
+struct snd_sst_params {
+ u32 result;
+ u32 stream_id;
+ u8 codec;
+ u8 ops;
+ u8 stream_type;
+ u8 device_type;
+ u8 task;
+ struct snd_sst_stream_params sparams;
+ struct snd_sst_alloc_params_ext aparams;
+};
+
+struct snd_sst_alloc_mrfld {
+ u16 codec_type;
+ u8 operation;
+ u8 sg_count;
+ struct sst_address_info ring_buf_info[8];
+ u32 frag_size;
+ u32 ts;
+ struct snd_sst_stream_params codec_params;
+} __packed;
+
+/* Alloc stream params structure */
+struct snd_sst_alloc_params {
+ struct snd_sst_str_type str_type;
+ struct snd_sst_stream_params stream_params;
+ struct snd_sst_alloc_params_ext alloc_params;
+} __packed;
+
+/* Alloc stream response message */
+struct snd_sst_alloc_response {
+ struct snd_sst_str_type str_type; /* Stream type for allocation */
+ struct snd_sst_lib_download lib_dnld; /* Valid only for codec dnld */
+};
+
+/* Drop response */
+struct snd_sst_drop_response {
+ u32 result;
+ u32 bytes;
+};
+
+struct snd_sst_async_msg {
+ u32 msg_id; /* Async msg id */
+ u32 payload[0];
+};
+
+struct snd_sst_async_err_msg {
+ u32 fw_resp; /* Firmware Result */
+ u32 lib_resp; /*Library result */
+} __packed;
+
+struct snd_sst_vol {
+ u32 stream_id;
+ s32 volume;
+ u32 ramp_duration;
+ u32 ramp_type; /* Ramp type, default=0 */
+};
+
+/* Gain library parameters for mrfld
+ * based on DSP command spec v0.82
+ */
+struct snd_sst_gain_v2 {
+ u16 gain_cell_num; /* num of gain cells to modify*/
+ u8 cell_nbr_idx; /* instance index*/
+ u8 cell_path_idx; /* pipe-id */
+ u16 module_id; /*module id */
+ u16 left_cell_gain; /* left gain value in dB*/
+ u16 right_cell_gain; /* right gain value in dB*/
+ u16 gain_time_const; /* gain time constant*/
+} __packed;
+
+struct snd_sst_mute {
+ u32 stream_id;
+ u32 mute;
+};
+
+struct snd_sst_runtime_params {
+ u8 type;
+ u8 str_id;
+ u8 size;
+ u8 rsvd;
+ void *addr;
+} __packed;
+
+enum stream_param_type {
+ SST_SET_TIME_SLOT = 0,
+ SST_SET_CHANNEL_INFO = 1,
+ OTHERS = 2, /*reserved for future params*/
+};
+
+/* CSV Voice call routing structure */
+struct snd_sst_control_routing {
+ u8 control; /* 0=start, 1=Stop */
+ u8 reserved[3]; /* Reserved- for 32 bit alignment */
+};
+
+struct ipc_post {
+ struct list_head node;
+ union ipc_header header; /* driver specific */
+ bool is_large;
+ bool is_process_reply;
+ union ipc_header_mrfld mrfld_header;
+ char *mailbox_data;
+};
+
+struct snd_sst_ctxt_params {
+ u32 address; /* Physical Address in DDR where the context is stored */
+ u32 size; /* size of the context */
+};
+
+struct snd_sst_lpe_log_params {
+ u8 dbg_type;
+ u8 module_id;
+ u8 log_level;
+ u8 reserved;
+} __packed;
+
+enum snd_sst_bytes_type {
+ SND_SST_BYTES_SET = 0x1,
+ SND_SST_BYTES_GET = 0x2,
+};
+
+struct snd_sst_bytes_v2 {
+ u8 type;
+ u8 ipc_msg;
+ u8 block;
+ u8 task_id;
+ u8 pipe_id;
+ u8 rsvd;
+ u16 len;
+ char bytes[0];
+};
+
+#define MAX_VTSV_FILES 2
+struct snd_sst_vtsv_info {
+ struct sst_address_info vfiles[MAX_VTSV_FILES];
+} __packed;
+
+#endif /* __SST_MFLD_DSP_H__ */
diff --git a/sound/soc/intel/atom/sst-mfld-platform-compress.c b/sound/soc/intel/atom/sst-mfld-platform-compress.c
new file mode 100644
index 000000000..89c9c5ad6
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform-compress.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_mfld_platform.c - Intel MID Platform driver
+ *
+ * Copyright (C) 2010-2014 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include "sst-mfld-platform.h"
+
+/* compress stream operations */
+static void sst_compr_fragment_elapsed(void *arg)
+{
+ struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg;
+
+ pr_debug("fragment elapsed by driver\n");
+ if (cstream)
+ snd_compr_fragment_elapsed(cstream);
+}
+
+static void sst_drain_notify(void *arg)
+{
+ struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg;
+
+ pr_debug("drain notify by driver\n");
+ if (cstream)
+ snd_compr_drain_notify(cstream);
+}
+
+static int sst_platform_compr_open(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream)
+{
+ int ret_val;
+ struct snd_compr_runtime *runtime = cstream->runtime;
+ struct sst_runtime_stream *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ spin_lock_init(&stream->status_lock);
+
+ /* get the sst ops */
+ if (!sst || !try_module_get(sst->dev->driver->owner)) {
+ pr_err("no device available to run\n");
+ ret_val = -ENODEV;
+ goto out_ops;
+ }
+ stream->compr_ops = sst->compr_ops;
+ stream->id = 0;
+
+ /* Turn on LPE */
+ sst->compr_ops->power(sst->dev, true);
+
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ runtime->private_data = stream;
+ return 0;
+out_ops:
+ kfree(stream);
+ return ret_val;
+}
+
+static int sst_platform_compr_free(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ stream = cstream->runtime->private_data;
+ /* Turn off LPE */
+ sst->compr_ops->power(sst->dev, false);
+
+ /*need to check*/
+ str_id = stream->id;
+ if (str_id)
+ ret_val = stream->compr_ops->close(sst->dev, str_id);
+ module_put(sst->dev->driver->owner);
+ kfree(stream);
+ pr_debug("%s: %d\n", __func__, ret_val);
+ return 0;
+}
+
+static int sst_platform_compr_set_params(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_params *params)
+{
+ struct sst_runtime_stream *stream;
+ int retval;
+ struct snd_sst_params str_params;
+ struct sst_compress_cb cb;
+ struct sst_data *ctx = snd_soc_component_get_drvdata(component);
+
+ stream = cstream->runtime->private_data;
+ /* construct fw structure for this*/
+ memset(&str_params, 0, sizeof(str_params));
+
+ /* fill the device type and stream id to pass to SST driver */
+ retval = sst_fill_stream_params(cstream, ctx, &str_params, true);
+ pr_debug("compr_set_params: fill stream params ret_val = 0x%x\n", retval);
+ if (retval < 0)
+ return retval;
+
+ switch (params->codec.id) {
+ case SND_AUDIOCODEC_MP3: {
+ str_params.codec = SST_CODEC_TYPE_MP3;
+ str_params.sparams.uc.mp3_params.num_chan = params->codec.ch_in;
+ str_params.sparams.uc.mp3_params.pcm_wd_sz = 16;
+ break;
+ }
+
+ case SND_AUDIOCODEC_AAC: {
+ str_params.codec = SST_CODEC_TYPE_AAC;
+ str_params.sparams.uc.aac_params.num_chan = params->codec.ch_in;
+ str_params.sparams.uc.aac_params.pcm_wd_sz = 16;
+ if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS)
+ str_params.sparams.uc.aac_params.bs_format =
+ AAC_BIT_STREAM_ADTS;
+ else if (params->codec.format == SND_AUDIOSTREAMFORMAT_RAW)
+ str_params.sparams.uc.aac_params.bs_format =
+ AAC_BIT_STREAM_RAW;
+ else {
+ pr_err("Undefined format%d\n", params->codec.format);
+ return -EINVAL;
+ }
+ str_params.sparams.uc.aac_params.externalsr =
+ params->codec.sample_rate;
+ break;
+ }
+
+ default:
+ pr_err("codec not supported, id =%d\n", params->codec.id);
+ return -EINVAL;
+ }
+
+ str_params.aparams.ring_buf_info[0].addr =
+ virt_to_phys(cstream->runtime->buffer);
+ str_params.aparams.ring_buf_info[0].size =
+ cstream->runtime->buffer_size;
+ str_params.aparams.sg_count = 1;
+ str_params.aparams.frag_size = cstream->runtime->fragment_size;
+
+ cb.param = cstream;
+ cb.compr_cb = sst_compr_fragment_elapsed;
+ cb.drain_cb_param = cstream;
+ cb.drain_notify = sst_drain_notify;
+
+ retval = stream->compr_ops->open(sst->dev, &str_params, &cb);
+ if (retval < 0) {
+ pr_err("stream allocation failed %d\n", retval);
+ return retval;
+ }
+
+ stream->id = retval;
+ return 0;
+}
+
+static int sst_platform_compr_trigger(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream, int cmd)
+{
+ struct sst_runtime_stream *stream = cstream->runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (stream->compr_ops->stream_start)
+ return stream->compr_ops->stream_start(sst->dev, stream->id);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (stream->compr_ops->stream_drop)
+ return stream->compr_ops->stream_drop(sst->dev, stream->id);
+ break;
+ case SND_COMPR_TRIGGER_DRAIN:
+ if (stream->compr_ops->stream_drain)
+ return stream->compr_ops->stream_drain(sst->dev, stream->id);
+ break;
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ if (stream->compr_ops->stream_partial_drain)
+ return stream->compr_ops->stream_partial_drain(sst->dev, stream->id);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (stream->compr_ops->stream_pause)
+ return stream->compr_ops->stream_pause(sst->dev, stream->id);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (stream->compr_ops->stream_pause_release)
+ return stream->compr_ops->stream_pause_release(sst->dev, stream->id);
+ break;
+ }
+ return -EINVAL;
+}
+
+static int sst_platform_compr_pointer(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct sst_runtime_stream *stream;
+
+ stream = cstream->runtime->private_data;
+ stream->compr_ops->tstamp(sst->dev, stream->id, tstamp);
+ tstamp->byte_offset = tstamp->copied_total %
+ (u32)cstream->runtime->buffer_size;
+ pr_debug("calc bytes offset/copied bytes as %d\n", tstamp->byte_offset);
+ return 0;
+}
+
+static int sst_platform_compr_ack(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ size_t bytes)
+{
+ struct sst_runtime_stream *stream;
+
+ stream = cstream->runtime->private_data;
+ stream->compr_ops->ack(sst->dev, stream->id, (unsigned long)bytes);
+ stream->bytes_written += bytes;
+
+ return 0;
+}
+
+static int sst_platform_compr_get_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_caps *caps)
+{
+ struct sst_runtime_stream *stream =
+ cstream->runtime->private_data;
+
+ return stream->compr_ops->get_caps(caps);
+}
+
+static int sst_platform_compr_get_codec_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_codec_caps *codec)
+{
+ struct sst_runtime_stream *stream =
+ cstream->runtime->private_data;
+
+ return stream->compr_ops->get_codec_caps(codec);
+}
+
+static int sst_platform_compr_set_metadata(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_metadata *metadata)
+{
+ struct sst_runtime_stream *stream =
+ cstream->runtime->private_data;
+
+ return stream->compr_ops->set_metadata(sst->dev, stream->id, metadata);
+}
+
+const struct snd_compress_ops sst_platform_compress_ops = {
+
+ .open = sst_platform_compr_open,
+ .free = sst_platform_compr_free,
+ .set_params = sst_platform_compr_set_params,
+ .set_metadata = sst_platform_compr_set_metadata,
+ .trigger = sst_platform_compr_trigger,
+ .pointer = sst_platform_compr_pointer,
+ .ack = sst_platform_compr_ack,
+ .get_caps = sst_platform_compr_get_caps,
+ .get_codec_caps = sst_platform_compr_get_codec_caps,
+};
diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c
new file mode 100644
index 000000000..255b4d528
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_mfld_platform.c - Intel MID Platform driver
+ *
+ * Copyright (C) 2010-2014 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/platform_sst_audio.h>
+#include "sst-mfld-platform.h"
+#include "sst-atom-controls.h"
+
+struct sst_device *sst;
+static DEFINE_MUTEX(sst_lock);
+
+int sst_register_dsp(struct sst_device *dev)
+{
+ if (WARN_ON(!dev))
+ return -EINVAL;
+ if (!try_module_get(dev->dev->driver->owner))
+ return -ENODEV;
+ mutex_lock(&sst_lock);
+ if (sst) {
+ dev_err(dev->dev, "we already have a device %s\n", sst->name);
+ module_put(dev->dev->driver->owner);
+ mutex_unlock(&sst_lock);
+ return -EEXIST;
+ }
+ dev_dbg(dev->dev, "registering device %s\n", dev->name);
+ sst = dev;
+ mutex_unlock(&sst_lock);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_register_dsp);
+
+int sst_unregister_dsp(struct sst_device *dev)
+{
+ if (WARN_ON(!dev))
+ return -EINVAL;
+ if (dev != sst)
+ return -EINVAL;
+
+ mutex_lock(&sst_lock);
+
+ if (!sst) {
+ mutex_unlock(&sst_lock);
+ return -EIO;
+ }
+
+ module_put(sst->dev->driver->owner);
+ dev_dbg(dev->dev, "unreg %s\n", sst->name);
+ sst = NULL;
+ mutex_unlock(&sst_lock);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_unregister_dsp);
+
+static const struct snd_pcm_hardware sst_platform_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP|
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_SYNC_START),
+ .buffer_bytes_max = SST_MAX_BUFFER,
+ .period_bytes_min = SST_MIN_PERIOD_BYTES,
+ .period_bytes_max = SST_MAX_PERIOD_BYTES,
+ .periods_min = SST_MIN_PERIODS,
+ .periods_max = SST_MAX_PERIODS,
+ .fifo_size = SST_FIFO_SIZE,
+};
+
+static struct sst_dev_stream_map dpcm_strm_map[] = {
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, /* Reserved, not in use */
+ {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0},
+ {MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0},
+ {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0},
+ {MERR_DPCM_DEEP_BUFFER, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA3_IN, SST_TASK_ID_MEDIA, 0},
+};
+
+static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+
+ return sst_send_pipe_gains(dai, stream, mute);
+}
+
+/* helper functions */
+void sst_set_stream_status(struct sst_runtime_stream *stream,
+ int state)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&stream->status_lock, flags);
+ stream->stream_status = state;
+ spin_unlock_irqrestore(&stream->status_lock, flags);
+}
+
+static inline int sst_get_stream_status(struct sst_runtime_stream *stream)
+{
+ int state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stream->status_lock, flags);
+ state = stream->stream_status;
+ spin_unlock_irqrestore(&stream->status_lock, flags);
+ return state;
+}
+
+static void sst_fill_alloc_params(struct snd_pcm_substream *substream,
+ struct snd_sst_alloc_params_ext *alloc_param)
+{
+ unsigned int channels;
+ snd_pcm_uframes_t period_size;
+ ssize_t periodbytes;
+ ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+ u32 buffer_addr = virt_to_phys(substream->runtime->dma_area);
+
+ channels = substream->runtime->channels;
+ period_size = substream->runtime->period_size;
+ periodbytes = samples_to_bytes(substream->runtime, period_size);
+ alloc_param->ring_buf_info[0].addr = buffer_addr;
+ alloc_param->ring_buf_info[0].size = buffer_bytes;
+ alloc_param->sg_count = 1;
+ alloc_param->reserved = 0;
+ alloc_param->frag_size = periodbytes * channels;
+
+}
+static void sst_fill_pcm_params(struct snd_pcm_substream *substream,
+ struct snd_sst_stream_params *param)
+{
+ param->uc.pcm_params.num_chan = (u8) substream->runtime->channels;
+ param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits;
+ param->uc.pcm_params.sfreq = substream->runtime->rate;
+
+ /* PCM stream via ALSA interface */
+ param->uc.pcm_params.use_offload_path = 0;
+ param->uc.pcm_params.reserved2 = 0;
+ memset(param->uc.pcm_params.channel_map, 0, sizeof(u8));
+
+}
+
+static int sst_get_stream_mapping(int dev, int sdev, int dir,
+ struct sst_dev_stream_map *map, int size)
+{
+ int i;
+
+ if (map == NULL)
+ return -EINVAL;
+
+
+ /* index 0 is not used in stream map */
+ for (i = 1; i < size; i++) {
+ if ((map[i].dev_num == dev) && (map[i].direction == dir))
+ return i;
+ }
+ return 0;
+}
+
+int sst_fill_stream_params(void *substream,
+ const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress)
+{
+ int map_size;
+ int index;
+ struct sst_dev_stream_map *map;
+ struct snd_pcm_substream *pstream = NULL;
+ struct snd_compr_stream *cstream = NULL;
+
+ map = ctx->pdata->pdev_strm_map;
+ map_size = ctx->pdata->strm_map_size;
+
+ if (is_compress)
+ cstream = (struct snd_compr_stream *)substream;
+ else
+ pstream = (struct snd_pcm_substream *)substream;
+
+ str_params->stream_type = SST_STREAM_TYPE_MUSIC;
+
+ /* For pcm streams */
+ if (pstream) {
+ index = sst_get_stream_mapping(pstream->pcm->device,
+ pstream->number, pstream->stream,
+ map, map_size);
+ if (index <= 0)
+ return -EINVAL;
+
+ str_params->stream_id = index;
+ str_params->device_type = map[index].device_id;
+ str_params->task = map[index].task_id;
+
+ str_params->ops = (u8)pstream->stream;
+ }
+
+ if (cstream) {
+ index = sst_get_stream_mapping(cstream->device->device,
+ 0, cstream->direction,
+ map, map_size);
+ if (index <= 0)
+ return -EINVAL;
+ str_params->stream_id = index;
+ str_params->device_type = map[index].device_id;
+ str_params->task = map[index].task_id;
+
+ str_params->ops = (u8)cstream->direction;
+ }
+ return 0;
+}
+
+static int sst_platform_alloc_stream(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ struct snd_sst_stream_params param = {{{0,},},};
+ struct snd_sst_params str_params = {0};
+ struct snd_sst_alloc_params_ext alloc_params = {0};
+ int ret_val = 0;
+ struct sst_data *ctx = snd_soc_dai_get_drvdata(dai);
+
+ /* set codec params and inform SST driver the same */
+ sst_fill_pcm_params(substream, &param);
+ sst_fill_alloc_params(substream, &alloc_params);
+ str_params.sparams = param;
+ str_params.aparams = alloc_params;
+ str_params.codec = SST_CODEC_TYPE_PCM;
+
+ /* fill the device type and stream id to pass to SST driver */
+ ret_val = sst_fill_stream_params(substream, ctx, &str_params, false);
+ if (ret_val < 0)
+ return ret_val;
+
+ stream->stream_info.str_id = str_params.stream_id;
+
+ ret_val = stream->ops->open(sst->dev, &str_params);
+ if (ret_val <= 0)
+ return ret_val;
+
+
+ return ret_val;
+}
+
+static void sst_period_elapsed(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct sst_runtime_stream *stream;
+ int status;
+
+ if (!substream || !substream->runtime)
+ return;
+ stream = substream->runtime->private_data;
+ if (!stream)
+ return;
+ status = sst_get_stream_status(stream);
+ if (status != SST_PLATFORM_RUNNING)
+ return;
+ snd_pcm_period_elapsed(substream);
+}
+
+static int sst_platform_init_stream(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ int ret_val;
+
+ dev_dbg(rtd->dev, "setting buffer ptr param\n");
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ stream->stream_info.period_elapsed = sst_period_elapsed;
+ stream->stream_info.arg = substream;
+ stream->stream_info.buffer_ptr = 0;
+ stream->stream_info.sfreq = substream->runtime->rate;
+ ret_val = stream->ops->stream_init(sst->dev, &stream->stream_info);
+ if (ret_val)
+ dev_err(rtd->dev, "control_set ret error %d\n", ret_val);
+ return ret_val;
+
+}
+
+static int power_up_sst(struct sst_runtime_stream *stream)
+{
+ return stream->ops->power(sst->dev, true);
+}
+
+static void power_down_sst(struct sst_runtime_stream *stream)
+{
+ stream->ops->power(sst->dev, false);
+}
+
+static int sst_media_open(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret_val = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sst_runtime_stream *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+ spin_lock_init(&stream->status_lock);
+
+ /* get the sst ops */
+ mutex_lock(&sst_lock);
+ if (!sst ||
+ !try_module_get(sst->dev->driver->owner)) {
+ dev_err(dai->dev, "no device available to run\n");
+ ret_val = -ENODEV;
+ goto out_ops;
+ }
+ stream->ops = sst->ops;
+ mutex_unlock(&sst_lock);
+
+ stream->stream_info.str_id = 0;
+
+ stream->stream_info.arg = substream;
+ /* allocate memory for SST API set */
+ runtime->private_data = stream;
+
+ ret_val = power_up_sst(stream);
+ if (ret_val < 0)
+ goto out_power_up;
+
+ /*
+ * Make sure the period to be multiple of 1ms to align the
+ * design of firmware. Apply same rule to buffer size to make
+ * sure alsa could always find a value for period size
+ * regardless the buffer size given by user space.
+ */
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 48);
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 48);
+
+ /* Make sure, that the period size is always even */
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIODS, 2);
+
+ return snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+out_ops:
+ mutex_unlock(&sst_lock);
+out_power_up:
+ kfree(stream);
+ return ret_val;
+}
+
+static void sst_media_close(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sst_runtime_stream *stream;
+ int str_id;
+
+ stream = substream->runtime->private_data;
+ power_down_sst(stream);
+
+ str_id = stream->stream_info.str_id;
+ if (str_id)
+ stream->ops->close(sst->dev, str_id);
+ module_put(sst->dev->driver->owner);
+ kfree(stream);
+}
+
+static int sst_media_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val, str_id;
+
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ if (stream->stream_info.str_id) {
+ ret_val = stream->ops->stream_drop(sst->dev, str_id);
+ return ret_val;
+ }
+
+ ret_val = sst_platform_alloc_stream(substream, dai);
+ if (ret_val <= 0)
+ return ret_val;
+ snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+ "%d", stream->stream_info.str_id);
+
+ ret_val = sst_platform_init_stream(substream);
+ if (ret_val)
+ return ret_val;
+ substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER;
+ return 0;
+}
+
+static int sst_enable_ssp(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ if (!snd_soc_dai_active(dai)) {
+ ret = sst_handle_vb_timer(dai, true);
+ sst_fill_ssp_defaults(dai);
+ }
+ return ret;
+}
+
+static int sst_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ if (snd_soc_dai_active(dai) == 1)
+ ret = send_ssp_cmd(dai, dai->name, 1);
+ return ret;
+}
+
+static int sst_set_format(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ int ret = 0;
+
+ if (!snd_soc_dai_active(dai))
+ return 0;
+
+ ret = sst_fill_ssp_config(dai, fmt);
+ if (ret < 0)
+ dev_err(dai->dev, "sst_set_format failed..\n");
+
+ return ret;
+}
+
+static int sst_platform_set_ssp_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width) {
+ int ret = 0;
+
+ if (!snd_soc_dai_active(dai))
+ return ret;
+
+ ret = sst_fill_ssp_slot(dai, tx_mask, rx_mask, slots, slot_width);
+ if (ret < 0)
+ dev_err(dai->dev, "sst_fill_ssp_slot failed..%d\n", ret);
+
+ return ret;
+}
+
+static void sst_disable_ssp(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (!snd_soc_dai_active(dai)) {
+ send_ssp_cmd(dai, dai->name, 0);
+ sst_handle_vb_timer(dai, false);
+ }
+}
+
+static const struct snd_soc_dai_ops sst_media_dai_ops = {
+ .startup = sst_media_open,
+ .shutdown = sst_media_close,
+ .prepare = sst_media_prepare,
+ .mute_stream = sst_media_digital_mute,
+};
+
+static const struct snd_soc_dai_ops sst_compr_dai_ops = {
+ .mute_stream = sst_media_digital_mute,
+};
+
+static const struct snd_soc_dai_ops sst_be_dai_ops = {
+ .startup = sst_enable_ssp,
+ .hw_params = sst_be_hw_params,
+ .set_fmt = sst_set_format,
+ .set_tdm_slot = sst_platform_set_ssp_slot,
+ .shutdown = sst_disable_ssp,
+};
+
+static struct snd_soc_dai_driver sst_platform_dai[] = {
+{
+ .name = "media-cpu-dai",
+ .ops = &sst_media_dai_ops,
+ .playback = {
+ .stream_name = "Headset Playback",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Headset Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "deepbuffer-cpu-dai",
+ .ops = &sst_media_dai_ops,
+ .playback = {
+ .stream_name = "Deepbuffer Playback",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "compress-cpu-dai",
+ .compress_new = snd_soc_new_compress,
+ .ops = &sst_compr_dai_ops,
+ .playback = {
+ .stream_name = "Compress Playback",
+ .channels_min = 1,
+ },
+},
+/* BE CPU Dais */
+{
+ .name = "ssp0-port",
+ .ops = &sst_be_dai_ops,
+ .playback = {
+ .stream_name = "ssp0 Tx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp0 Rx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "ssp1-port",
+ .ops = &sst_be_dai_ops,
+ .playback = {
+ .stream_name = "ssp1 Tx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp1 Rx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "ssp2-port",
+ .ops = &sst_be_dai_ops,
+ .playback = {
+ .stream_name = "ssp2 Tx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp2 Rx",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+};
+
+static int sst_soc_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime;
+
+ if (substream->pcm->internal)
+ return 0;
+
+ runtime = substream->runtime;
+ runtime->hw = sst_platform_pcm_hw;
+ return 0;
+}
+
+static int sst_soc_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int ret_val = 0, str_id;
+ struct sst_runtime_stream *stream;
+ int status;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+
+ dev_dbg(rtd->dev, "%s called\n", __func__);
+ if (substream->pcm->internal)
+ return 0;
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ dev_dbg(rtd->dev, "sst: Trigger Start\n");
+ status = SST_PLATFORM_RUNNING;
+ stream->stream_info.arg = substream;
+ ret_val = stream->ops->stream_start(sst->dev, str_id);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ dev_dbg(rtd->dev, "sst: in stop\n");
+ status = SST_PLATFORM_DROPPED;
+ ret_val = stream->ops->stream_drop(sst->dev, str_id);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ dev_dbg(rtd->dev, "sst: in pause\n");
+ status = SST_PLATFORM_PAUSED;
+ ret_val = stream->ops->stream_pause(sst->dev, str_id);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ dev_dbg(rtd->dev, "sst: in pause release\n");
+ status = SST_PLATFORM_RUNNING;
+ ret_val = stream->ops->stream_pause_release(sst->dev, str_id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!ret_val)
+ sst_set_stream_status(stream, status);
+
+ return ret_val;
+}
+
+
+static snd_pcm_uframes_t sst_soc_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val, status;
+ struct pcm_stream_info *str_info;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+
+ stream = substream->runtime->private_data;
+ status = sst_get_stream_status(stream);
+ if (status == SST_PLATFORM_INIT)
+ return 0;
+ str_info = &stream->stream_info;
+ ret_val = stream->ops->stream_read_tstamp(sst->dev, str_info);
+ if (ret_val) {
+ dev_err(rtd->dev, "sst: error code = %d\n", ret_val);
+ return ret_val;
+ }
+ substream->runtime->delay = str_info->pcm_delay;
+ return str_info->buffer_ptr;
+}
+
+static int sst_soc_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_pcm *pcm = rtd->pcm;
+
+ if (dai->driver->playback.channels_min ||
+ dai->driver->capture.channels_min) {
+ snd_pcm_set_managed_buffer_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_DMA),
+ SST_MIN_BUFFER, SST_MAX_BUFFER);
+ }
+ return 0;
+}
+
+static int sst_soc_probe(struct snd_soc_component *component)
+{
+ struct sst_data *drv = dev_get_drvdata(component->dev);
+
+ drv->soc_card = component->card;
+ return sst_dsp_init_v2_dpcm(component);
+}
+
+static void sst_soc_remove(struct snd_soc_component *component)
+{
+ struct sst_data *drv = dev_get_drvdata(component->dev);
+
+ drv->soc_card = NULL;
+}
+
+static const struct snd_soc_component_driver sst_soc_platform_drv = {
+ .name = DRV_NAME,
+ .probe = sst_soc_probe,
+ .remove = sst_soc_remove,
+ .open = sst_soc_open,
+ .trigger = sst_soc_trigger,
+ .pointer = sst_soc_pointer,
+ .compress_ops = &sst_platform_compress_ops,
+ .pcm_construct = sst_soc_pcm_new,
+};
+
+static int sst_platform_probe(struct platform_device *pdev)
+{
+ struct sst_data *drv;
+ int ret;
+ struct sst_platform_data *pdata;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (drv == NULL) {
+ return -ENOMEM;
+ }
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (pdata == NULL) {
+ return -ENOMEM;
+ }
+
+ pdata->pdev_strm_map = dpcm_strm_map;
+ pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map);
+ drv->pdata = pdata;
+ drv->pdev = pdev;
+ mutex_init(&drv->lock);
+ dev_set_drvdata(&pdev->dev, drv);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &sst_soc_platform_drv,
+ sst_platform_dai, ARRAY_SIZE(sst_platform_dai));
+ if (ret)
+ dev_err(&pdev->dev, "registering cpu dais failed\n");
+
+ return ret;
+}
+
+static int sst_platform_remove(struct platform_device *pdev)
+{
+ dev_dbg(&pdev->dev, "sst_platform_remove success\n");
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int sst_soc_prepare(struct device *dev)
+{
+ struct sst_data *drv = dev_get_drvdata(dev);
+ struct snd_soc_pcm_runtime *rtd;
+
+ if (!drv->soc_card)
+ return 0;
+
+ /* suspend all pcms first */
+ snd_soc_suspend(drv->soc_card->dev);
+ snd_soc_poweroff(drv->soc_card->dev);
+
+ /* set the SSPs to idle */
+ for_each_card_rtds(drv->soc_card, rtd) {
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
+
+ if (snd_soc_dai_active(dai)) {
+ send_ssp_cmd(dai, dai->name, 0);
+ sst_handle_vb_timer(dai, false);
+ }
+ }
+
+ return 0;
+}
+
+static void sst_soc_complete(struct device *dev)
+{
+ struct sst_data *drv = dev_get_drvdata(dev);
+ struct snd_soc_pcm_runtime *rtd;
+
+ if (!drv->soc_card)
+ return;
+
+ /* restart SSPs */
+ for_each_card_rtds(drv->soc_card, rtd) {
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
+
+ if (snd_soc_dai_active(dai)) {
+ sst_handle_vb_timer(dai, true);
+ send_ssp_cmd(dai, dai->name, 1);
+ }
+ }
+ snd_soc_resume(drv->soc_card->dev);
+}
+
+#else
+
+#define sst_soc_prepare NULL
+#define sst_soc_complete NULL
+
+#endif
+
+
+static const struct dev_pm_ops sst_platform_pm = {
+ .prepare = sst_soc_prepare,
+ .complete = sst_soc_complete,
+};
+
+static struct platform_driver sst_platform_driver = {
+ .driver = {
+ .name = "sst-mfld-platform",
+ .pm = &sst_platform_pm,
+ },
+ .probe = sst_platform_probe,
+ .remove = sst_platform_remove,
+};
+
+module_platform_driver(sst_platform_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sst-atom-hifi2-platform");
+MODULE_ALIAS("platform:sst-mfld-platform");
diff --git a/sound/soc/intel/atom/sst-mfld-platform.h b/sound/soc/intel/atom/sst-mfld-platform.h
new file mode 100644
index 000000000..8b5777d32
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * sst_mfld_platform.h - Intel MID Platform driver header file
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SST_PLATFORMDRV_H__
+#define __SST_PLATFORMDRV_H__
+
+#include "sst-mfld-dsp.h"
+#include "sst-atom-controls.h"
+
+extern struct sst_device *sst;
+extern const struct snd_compress_ops sst_platform_compress_ops;
+
+#define DRV_NAME "sst"
+
+#define SST_MONO 1
+#define SST_STEREO 2
+#define SST_MAX_CAP 5
+
+#define SST_MAX_BUFFER (800*1024)
+#define SST_MIN_BUFFER (800*1024)
+#define SST_MIN_PERIOD_BYTES 32
+#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER
+#define SST_MIN_PERIODS 2
+#define SST_MAX_PERIODS (1024*2)
+#define SST_FIFO_SIZE 0
+
+struct pcm_stream_info {
+ int str_id;
+ void *arg;
+ void (*period_elapsed) (void *arg);
+ unsigned long long buffer_ptr;
+ unsigned long long pcm_delay;
+ int sfreq;
+};
+
+enum sst_drv_status {
+ SST_PLATFORM_INIT = 1,
+ SST_PLATFORM_STARTED,
+ SST_PLATFORM_RUNNING,
+ SST_PLATFORM_PAUSED,
+ SST_PLATFORM_DROPPED,
+};
+
+enum sst_stream_ops {
+ STREAM_OPS_PLAYBACK = 0,
+ STREAM_OPS_CAPTURE,
+};
+
+enum sst_audio_device_type {
+ SND_SST_DEVICE_HEADSET = 1,
+ SND_SST_DEVICE_IHF,
+ SND_SST_DEVICE_VIBRA,
+ SND_SST_DEVICE_HAPTIC,
+ SND_SST_DEVICE_CAPTURE,
+ SND_SST_DEVICE_COMPRESS,
+};
+
+/* PCM Parameters */
+struct sst_pcm_params {
+ u16 codec; /* codec type */
+ u8 num_chan; /* 1=Mono, 2=Stereo */
+ u8 pcm_wd_sz; /* 16/24 - bit*/
+ u32 reserved; /* Bitrate in bits per second */
+ u32 sfreq; /* Sampling rate in Hz */
+ u32 ring_buffer_size;
+ u32 period_count; /* period elapsed in samples*/
+ u32 ring_buffer_addr;
+};
+
+struct sst_stream_params {
+ u32 result;
+ u32 stream_id;
+ u8 codec;
+ u8 ops;
+ u8 stream_type;
+ u8 device_type;
+ struct sst_pcm_params sparams;
+};
+
+struct sst_compress_cb {
+ void *param;
+ void (*compr_cb)(void *param);
+ void *drain_cb_param;
+ void (*drain_notify)(void *param);
+};
+
+struct compress_sst_ops {
+ const char *name;
+ int (*open)(struct device *dev,
+ struct snd_sst_params *str_params, struct sst_compress_cb *cb);
+ int (*stream_start)(struct device *dev, unsigned int str_id);
+ int (*stream_drop)(struct device *dev, unsigned int str_id);
+ int (*stream_drain)(struct device *dev, unsigned int str_id);
+ int (*stream_partial_drain)(struct device *dev, unsigned int str_id);
+ int (*stream_pause)(struct device *dev, unsigned int str_id);
+ int (*stream_pause_release)(struct device *dev, unsigned int str_id);
+
+ int (*tstamp)(struct device *dev, unsigned int str_id,
+ struct snd_compr_tstamp *tstamp);
+ int (*ack)(struct device *dev, unsigned int str_id,
+ unsigned long bytes);
+ int (*close)(struct device *dev, unsigned int str_id);
+ int (*get_caps)(struct snd_compr_caps *caps);
+ int (*get_codec_caps)(struct snd_compr_codec_caps *codec);
+ int (*set_metadata)(struct device *dev, unsigned int str_id,
+ struct snd_compr_metadata *mdata);
+ int (*power)(struct device *dev, bool state);
+};
+
+struct sst_ops {
+ int (*open)(struct device *dev, struct snd_sst_params *str_param);
+ int (*stream_init)(struct device *dev, struct pcm_stream_info *str_info);
+ int (*stream_start)(struct device *dev, int str_id);
+ int (*stream_drop)(struct device *dev, int str_id);
+ int (*stream_pause)(struct device *dev, int str_id);
+ int (*stream_pause_release)(struct device *dev, int str_id);
+ int (*stream_read_tstamp)(struct device *dev, struct pcm_stream_info *str_info);
+ int (*send_byte_stream)(struct device *dev, struct snd_sst_bytes_v2 *bytes);
+ int (*close)(struct device *dev, unsigned int str_id);
+ int (*power)(struct device *dev, bool state);
+};
+
+struct sst_runtime_stream {
+ int stream_status;
+ unsigned int id;
+ size_t bytes_written;
+ struct pcm_stream_info stream_info;
+ struct sst_ops *ops;
+ struct compress_sst_ops *compr_ops;
+ spinlock_t status_lock;
+};
+
+struct sst_device {
+ char *name;
+ struct device *dev;
+ struct sst_ops *ops;
+ struct platform_device *pdev;
+ struct compress_sst_ops *compr_ops;
+};
+
+struct sst_data;
+
+int sst_dsp_init_v2_dpcm(struct snd_soc_component *component);
+int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute);
+int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable);
+int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable);
+
+void sst_set_stream_status(struct sst_runtime_stream *stream, int state);
+int sst_fill_stream_params(void *substream, const struct sst_data *ctx,
+ struct snd_sst_params *str_params, bool is_compress);
+
+struct sst_algo_int_control_v2 {
+ struct soc_mixer_control mc;
+ u16 module_id; /* module identifieer */
+ u16 pipe_id; /* location info: pipe_id + instance_id */
+ u16 instance_id;
+ unsigned int value; /* Value received is stored here */
+};
+struct sst_data {
+ struct platform_device *pdev;
+ struct sst_platform_data *pdata;
+ struct snd_sst_bytes_v2 *byte_stream;
+ struct mutex lock;
+ struct snd_soc_card *soc_card;
+ struct sst_cmd_sba_hw_set_ssp ssp_cmd;
+};
+int sst_register_dsp(struct sst_device *dev);
+int sst_unregister_dsp(struct sst_device *dev);
+#endif
diff --git a/sound/soc/intel/atom/sst/Makefile b/sound/soc/intel/atom/sst/Makefile
new file mode 100644
index 000000000..5761d30a5
--- /dev/null
+++ b/sound/soc/intel/atom/sst/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o
+snd-intel-sst-pci-objs += sst_pci.o
+snd-intel-sst-acpi-objs += sst_acpi.o
+
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-intel-sst-core.o
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_PCI) += snd-intel-sst-pci.o
+obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI) += snd-intel-sst-acpi.o
diff --git a/sound/soc/intel/atom/sst/sst.c b/sound/soc/intel/atom/sst/sst.c
new file mode 100644
index 000000000..e90590559
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/async.h>
+#include <linux/acpi.h>
+#include <linux/sysfs.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver");
+MODULE_LICENSE("GPL v2");
+
+static inline bool sst_is_process_reply(u32 msg_id)
+{
+ return ((msg_id & PROCESS_MSG) ? true : false);
+}
+
+static inline bool sst_validate_mailbox_size(unsigned int size)
+{
+ return ((size <= SST_MAILBOX_SIZE) ? true : false);
+}
+
+static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context)
+{
+ union interrupt_reg_mrfld isr;
+ union ipc_header_mrfld header;
+ union sst_imr_reg_mrfld imr;
+ struct ipc_post *msg = NULL;
+ unsigned int size;
+ struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
+ irqreturn_t retval = IRQ_HANDLED;
+
+ /* Interrupt arrived, check src */
+ isr.full = sst_shim_read64(drv->shim, SST_ISRX);
+
+ if (isr.part.done_interrupt) {
+ /* Clear done bit */
+ spin_lock(&drv->ipc_spin_lock);
+ header.full = sst_shim_read64(drv->shim,
+ drv->ipc_reg.ipcx);
+ header.p.header_high.part.done = 0;
+ sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full);
+
+ /* write 1 to clear status register */;
+ isr.part.done_interrupt = 1;
+ sst_shim_write64(drv->shim, SST_ISRX, isr.full);
+ spin_unlock(&drv->ipc_spin_lock);
+
+ /* we can send more messages to DSP so trigger work */
+ queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq);
+ retval = IRQ_HANDLED;
+ }
+
+ if (isr.part.busy_interrupt) {
+ /* message from dsp so copy that */
+ spin_lock(&drv->ipc_spin_lock);
+ imr.full = sst_shim_read64(drv->shim, SST_IMRX);
+ imr.part.busy_interrupt = 1;
+ sst_shim_write64(drv->shim, SST_IMRX, imr.full);
+ spin_unlock(&drv->ipc_spin_lock);
+ header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd);
+
+ if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) {
+ drv->ops->clear_interrupt(drv);
+ return IRQ_HANDLED;
+ }
+
+ if (header.p.header_high.part.large) {
+ size = header.p.header_low_payload;
+ if (sst_validate_mailbox_size(size)) {
+ memcpy_fromio(msg->mailbox_data,
+ drv->mailbox + drv->mailbox_recv_offset, size);
+ } else {
+ dev_err(drv->dev,
+ "Mailbox not copied, payload size is: %u\n", size);
+ header.p.header_low_payload = 0;
+ }
+ }
+
+ msg->mrfld_header = header;
+ msg->is_process_reply =
+ sst_is_process_reply(header.p.header_high.part.msg_id);
+ spin_lock(&drv->rx_msg_lock);
+ list_add_tail(&msg->node, &drv->rx_list);
+ spin_unlock(&drv->rx_msg_lock);
+ drv->ops->clear_interrupt(drv);
+ retval = IRQ_WAKE_THREAD;
+ }
+ return retval;
+}
+
+static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context)
+{
+ struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
+ struct ipc_post *__msg, *msg = NULL;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
+ if (list_empty(&drv->rx_list)) {
+ spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
+ return IRQ_HANDLED;
+ }
+
+ list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
+ list_del(&msg->node);
+ spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
+ if (msg->is_process_reply)
+ drv->ops->process_message(msg);
+ else
+ drv->ops->process_reply(drv, msg);
+
+ if (msg->is_large)
+ kfree(msg->mailbox_data);
+ kfree(msg);
+ spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
+ }
+ spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags);
+ return IRQ_HANDLED;
+}
+
+static int sst_save_dsp_context_v2(struct intel_sst_drv *sst)
+{
+ int ret = 0;
+
+ ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD,
+ IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL,
+ true, true, false, true);
+
+ if (ret < 0) {
+ dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+
+static struct intel_sst_ops mrfld_ops = {
+ .interrupt = intel_sst_interrupt_mrfld,
+ .irq_thread = intel_sst_irq_thread_mrfld,
+ .clear_interrupt = intel_sst_clear_intr_mrfld,
+ .start = sst_start_mrfld,
+ .reset = intel_sst_reset_dsp_mrfld,
+ .post_message = sst_post_message_mrfld,
+ .process_reply = sst_process_reply_mrfld,
+ .save_dsp_context = sst_save_dsp_context_v2,
+ .alloc_stream = sst_alloc_stream_mrfld,
+ .post_download = sst_post_download_mrfld,
+};
+
+int sst_driver_ops(struct intel_sst_drv *sst)
+{
+
+ switch (sst->dev_id) {
+ case SST_MRFLD_PCI_ID:
+ case SST_BYT_ACPI_ID:
+ case SST_CHV_ACPI_ID:
+ sst->tstamp = SST_TIME_STAMP_MRFLD;
+ sst->ops = &mrfld_ops;
+ return 0;
+
+ default:
+ dev_err(sst->dev,
+ "SST Driver capabilities missing for dev_id: %x",
+ sst->dev_id);
+ return -EINVAL;
+ };
+}
+
+void sst_process_pending_msg(struct work_struct *work)
+{
+ struct intel_sst_drv *ctx = container_of(work,
+ struct intel_sst_drv, ipc_post_msg_wq);
+
+ ctx->ops->post_message(ctx, NULL, false);
+}
+
+static int sst_workqueue_init(struct intel_sst_drv *ctx)
+{
+ INIT_LIST_HEAD(&ctx->memcpy_list);
+ INIT_LIST_HEAD(&ctx->rx_list);
+ INIT_LIST_HEAD(&ctx->ipc_dispatch_list);
+ INIT_LIST_HEAD(&ctx->block_list);
+ INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg);
+ init_waitqueue_head(&ctx->wait_queue);
+
+ ctx->post_msg_wq =
+ create_singlethread_workqueue("sst_post_msg_wq");
+ if (!ctx->post_msg_wq)
+ return -EBUSY;
+ return 0;
+}
+
+static void sst_init_locks(struct intel_sst_drv *ctx)
+{
+ mutex_init(&ctx->sst_lock);
+ spin_lock_init(&ctx->rx_msg_lock);
+ spin_lock_init(&ctx->ipc_spin_lock);
+ spin_lock_init(&ctx->block_lock);
+}
+
+int sst_alloc_drv_context(struct intel_sst_drv **ctx,
+ struct device *dev, unsigned int dev_id)
+{
+ *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL);
+ if (!(*ctx))
+ return -ENOMEM;
+
+ (*ctx)->dev = dev;
+ (*ctx)->dev_id = dev_id;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_alloc_drv_context);
+
+static ssize_t firmware_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->fw_version.type == 0 && ctx->fw_version.major == 0 &&
+ ctx->fw_version.minor == 0 && ctx->fw_version.build == 0)
+ return sprintf(buf, "FW not yet loaded\n");
+ else
+ return sprintf(buf, "v%02x.%02x.%02x.%02x\n",
+ ctx->fw_version.type, ctx->fw_version.major,
+ ctx->fw_version.minor, ctx->fw_version.build);
+
+}
+
+static DEVICE_ATTR_RO(firmware_version);
+
+static const struct attribute *sst_fw_version_attrs[] = {
+ &dev_attr_firmware_version.attr,
+ NULL,
+};
+
+static const struct attribute_group sst_fw_version_attr_group = {
+ .attrs = (struct attribute **)sst_fw_version_attrs,
+};
+
+int sst_context_init(struct intel_sst_drv *ctx)
+{
+ int ret = 0, i;
+
+ if (!ctx->pdata)
+ return -EINVAL;
+
+ if (!ctx->pdata->probe_data)
+ return -EINVAL;
+
+ memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info));
+
+ ret = sst_driver_ops(ctx);
+ if (ret != 0)
+ return -EINVAL;
+
+ sst_init_locks(ctx);
+ sst_set_fw_state_locked(ctx, SST_RESET);
+
+ /* pvt_id 0 reserved for async messages */
+ ctx->pvt_id = 1;
+ ctx->stream_cnt = 0;
+ ctx->fw_in_mem = NULL;
+ /* we use memcpy, so set to 0 */
+ ctx->use_dma = 0;
+ ctx->use_lli = 0;
+
+ if (sst_workqueue_init(ctx))
+ return -EINVAL;
+
+ ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off;
+ ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset;
+ ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset;
+
+ dev_info(ctx->dev, "Got drv data max stream %d\n",
+ ctx->info.max_streams);
+
+ for (i = 1; i <= ctx->info.max_streams; i++) {
+ struct stream_info *stream = &ctx->streams[i];
+
+ memset(stream, 0, sizeof(*stream));
+ stream->pipe_id = PIPE_RSVD;
+ mutex_init(&stream->lock);
+ }
+
+ /* Register the ISR */
+ ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt,
+ ctx->ops->irq_thread, 0, SST_DRV_NAME,
+ ctx);
+ if (ret)
+ goto do_free_mem;
+
+ dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num);
+
+ /* default intr are unmasked so set this as masked */
+ sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038);
+
+ ctx->qos = devm_kzalloc(ctx->dev,
+ sizeof(struct pm_qos_request), GFP_KERNEL);
+ if (!ctx->qos) {
+ ret = -ENOMEM;
+ goto do_free_mem;
+ }
+ cpu_latency_qos_add_request(ctx->qos, PM_QOS_DEFAULT_VALUE);
+
+ dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name);
+ ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name,
+ ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb);
+ if (ret) {
+ dev_err(ctx->dev, "Firmware download failed:%d\n", ret);
+ goto do_free_mem;
+ }
+
+ ret = sysfs_create_group(&ctx->dev->kobj,
+ &sst_fw_version_attr_group);
+ if (ret) {
+ dev_err(ctx->dev,
+ "Unable to create sysfs\n");
+ goto err_sysfs;
+ }
+
+ sst_register(ctx->dev);
+ return 0;
+err_sysfs:
+ sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group);
+
+do_free_mem:
+ destroy_workqueue(ctx->post_msg_wq);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_context_init);
+
+void sst_context_cleanup(struct intel_sst_drv *ctx)
+{
+ pm_runtime_get_noresume(ctx->dev);
+ pm_runtime_disable(ctx->dev);
+ sst_unregister(ctx->dev);
+ sst_set_fw_state_locked(ctx, SST_SHUTDOWN);
+ sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group);
+ flush_scheduled_work();
+ destroy_workqueue(ctx->post_msg_wq);
+ cpu_latency_qos_remove_request(ctx->qos);
+ kfree(ctx->fw_sg_list.src);
+ kfree(ctx->fw_sg_list.dst);
+ ctx->fw_sg_list.list_len = 0;
+ kfree(ctx->fw_in_mem);
+ ctx->fw_in_mem = NULL;
+ sst_memcpy_free_resources(ctx);
+}
+EXPORT_SYMBOL_GPL(sst_context_cleanup);
+
+void sst_configure_runtime_pm(struct intel_sst_drv *ctx)
+{
+ pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY);
+ pm_runtime_use_autosuspend(ctx->dev);
+ /*
+ * For acpi devices, the actual physical device state is
+ * initially active. So change the state to active before
+ * enabling the pm
+ */
+
+ if (!acpi_disabled)
+ pm_runtime_set_active(ctx->dev);
+
+ pm_runtime_enable(ctx->dev);
+
+ if (acpi_disabled)
+ pm_runtime_set_active(ctx->dev);
+ else
+ pm_runtime_put_noidle(ctx->dev);
+}
+EXPORT_SYMBOL_GPL(sst_configure_runtime_pm);
+
+static int intel_sst_runtime_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->sst_state == SST_RESET) {
+ dev_dbg(dev, "LPE is already in RESET state, No action\n");
+ return 0;
+ }
+ /* save fw context */
+ if (ctx->ops->save_dsp_context(ctx))
+ return -EBUSY;
+
+ /* Move the SST state to Reset */
+ sst_set_fw_state_locked(ctx, SST_RESET);
+
+ synchronize_irq(ctx->irq_num);
+ flush_workqueue(ctx->post_msg_wq);
+
+ ctx->ops->reset(ctx);
+
+ return ret;
+}
+
+static int intel_sst_suspend(struct device *dev)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+ struct sst_fw_save *fw_save;
+ int i, ret;
+
+ /* check first if we are already in SW reset */
+ if (ctx->sst_state == SST_RESET)
+ return 0;
+
+ /*
+ * check if any stream is active and running
+ * they should already by suspend by soc_suspend
+ */
+ for (i = 1; i <= ctx->info.max_streams; i++) {
+ struct stream_info *stream = &ctx->streams[i];
+
+ if (stream->status == STREAM_RUNNING) {
+ dev_err(dev, "stream %d is running, can't suspend, abort\n", i);
+ return -EBUSY;
+ }
+
+ if (ctx->pdata->streams_lost_on_suspend) {
+ stream->resume_status = stream->status;
+ stream->resume_prev = stream->prev;
+ if (stream->status != STREAM_UN_INIT)
+ sst_free_stream(ctx, i);
+ }
+ }
+ synchronize_irq(ctx->irq_num);
+ flush_workqueue(ctx->post_msg_wq);
+
+ /* Move the SST state to Reset */
+ sst_set_fw_state_locked(ctx, SST_RESET);
+
+ /* tell DSP we are suspending */
+ if (ctx->ops->save_dsp_context(ctx))
+ return -EBUSY;
+
+ /* save the memories */
+ fw_save = kzalloc(sizeof(*fw_save), GFP_KERNEL);
+ if (!fw_save)
+ return -ENOMEM;
+ fw_save->iram = kvzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL);
+ if (!fw_save->iram) {
+ ret = -ENOMEM;
+ goto iram;
+ }
+ fw_save->dram = kvzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL);
+ if (!fw_save->dram) {
+ ret = -ENOMEM;
+ goto dram;
+ }
+ fw_save->sram = kvzalloc(SST_MAILBOX_SIZE, GFP_KERNEL);
+ if (!fw_save->sram) {
+ ret = -ENOMEM;
+ goto sram;
+ }
+
+ fw_save->ddr = kvzalloc(ctx->ddr_end - ctx->ddr_base, GFP_KERNEL);
+ if (!fw_save->ddr) {
+ ret = -ENOMEM;
+ goto ddr;
+ }
+
+ memcpy32_fromio(fw_save->iram, ctx->iram, ctx->iram_end - ctx->iram_base);
+ memcpy32_fromio(fw_save->dram, ctx->dram, ctx->dram_end - ctx->dram_base);
+ memcpy32_fromio(fw_save->sram, ctx->mailbox, SST_MAILBOX_SIZE);
+ memcpy32_fromio(fw_save->ddr, ctx->ddr, ctx->ddr_end - ctx->ddr_base);
+
+ ctx->fw_save = fw_save;
+ ctx->ops->reset(ctx);
+ return 0;
+ddr:
+ kvfree(fw_save->sram);
+sram:
+ kvfree(fw_save->dram);
+dram:
+ kvfree(fw_save->iram);
+iram:
+ kfree(fw_save);
+ return ret;
+}
+
+static int intel_sst_resume(struct device *dev)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+ struct sst_fw_save *fw_save = ctx->fw_save;
+ struct sst_block *block;
+ int i, ret = 0;
+
+ if (!fw_save)
+ return 0;
+
+ sst_set_fw_state_locked(ctx, SST_FW_LOADING);
+
+ /* we have to restore the memory saved */
+ ctx->ops->reset(ctx);
+
+ ctx->fw_save = NULL;
+
+ memcpy32_toio(ctx->iram, fw_save->iram, ctx->iram_end - ctx->iram_base);
+ memcpy32_toio(ctx->dram, fw_save->dram, ctx->dram_end - ctx->dram_base);
+ memcpy32_toio(ctx->mailbox, fw_save->sram, SST_MAILBOX_SIZE);
+ memcpy32_toio(ctx->ddr, fw_save->ddr, ctx->ddr_end - ctx->ddr_base);
+
+ kvfree(fw_save->sram);
+ kvfree(fw_save->dram);
+ kvfree(fw_save->iram);
+ kvfree(fw_save->ddr);
+ kfree(fw_save);
+
+ block = sst_create_block(ctx, 0, FW_DWNL_ID);
+ if (block == NULL)
+ return -ENOMEM;
+
+
+ /* start and wait for ack */
+ ctx->ops->start(ctx);
+ ret = sst_wait_timeout(ctx, block);
+ if (ret) {
+ dev_err(ctx->dev, "fw download failed %d\n", ret);
+ /* FW download failed due to timeout */
+ ret = -EBUSY;
+
+ } else {
+ sst_set_fw_state_locked(ctx, SST_FW_RUNNING);
+ }
+
+ if (ctx->pdata->streams_lost_on_suspend) {
+ for (i = 1; i <= ctx->info.max_streams; i++) {
+ struct stream_info *stream = &ctx->streams[i];
+
+ if (stream->resume_status != STREAM_UN_INIT) {
+ dev_dbg(ctx->dev, "Re-allocing stream %d status %d prev %d\n",
+ i, stream->resume_status,
+ stream->resume_prev);
+ sst_realloc_stream(ctx, i);
+ stream->status = stream->resume_status;
+ stream->prev = stream->resume_prev;
+ }
+ }
+ }
+
+ sst_free_block(ctx, block);
+ return ret;
+}
+
+const struct dev_pm_ops intel_sst_pm = {
+ .suspend = intel_sst_suspend,
+ .resume = intel_sst_resume,
+ .runtime_suspend = intel_sst_runtime_suspend,
+};
+EXPORT_SYMBOL_GPL(intel_sst_pm);
diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h
new file mode 100644
index 000000000..4d37d39fd
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst.h
@@ -0,0 +1,533 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * sst.h - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Common private declarations for SST
+ */
+#ifndef __SST_H__
+#define __SST_H__
+
+#include <linux/firmware.h>
+
+/* driver names */
+#define SST_DRV_NAME "intel_sst_driver"
+#define SST_MRFLD_PCI_ID 0x119A
+#define SST_BYT_ACPI_ID 0x80860F28
+#define SST_CHV_ACPI_ID 0x808622A8
+
+#define SST_SUSPEND_DELAY 2000
+#define FW_CONTEXT_MEM (64*1024)
+#define SST_ICCM_BOUNDARY 4
+#define SST_CONFIG_SSP_SIGN 0x7ffe8001
+
+#define MRFLD_FW_VIRTUAL_BASE 0xC0000000
+#define MRFLD_FW_DDR_BASE_OFFSET 0x0
+#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4
+#define MRFLD_FW_BSS_RESET_BIT 0
+
+/* SST Shim register map */
+#define SST_CSR 0x00
+#define SST_ISRX 0x18
+#define SST_IMRX 0x28
+#define SST_IPCX 0x38 /* IPC IA -> SST */
+#define SST_IPCD 0x40 /* IPC SST -> IA */
+
+extern const struct dev_pm_ops intel_sst_pm;
+enum sst_states {
+ SST_FW_LOADING = 1,
+ SST_FW_RUNNING,
+ SST_RESET,
+ SST_SHUTDOWN,
+};
+
+enum sst_algo_ops {
+ SST_SET_ALGO = 0,
+ SST_GET_ALGO = 1,
+};
+
+#define SST_BLOCK_TIMEOUT 1000
+
+#define FW_SIGNATURE_SIZE 4
+#define FW_NAME_SIZE 32
+
+/* stream states */
+enum sst_stream_states {
+ STREAM_UN_INIT = 0, /* Freed/Not used stream */
+ STREAM_RUNNING = 1, /* Running */
+ STREAM_PAUSED = 2, /* Paused stream */
+ STREAM_INIT = 3, /* stream init, waiting for data */
+};
+
+enum sst_ram_type {
+ SST_IRAM = 1,
+ SST_DRAM = 2,
+ SST_DDR = 5,
+ SST_CUSTOM_INFO = 7, /* consists of FW binary information */
+};
+
+/* SST shim registers to structure mapping */
+union interrupt_reg {
+ struct {
+ u64 done_interrupt:1;
+ u64 busy_interrupt:1;
+ u64 rsvd:62;
+ } part;
+ u64 full;
+};
+
+union sst_pisr_reg {
+ struct {
+ u32 pssp0:1;
+ u32 pssp1:1;
+ u32 rsvd0:3;
+ u32 dmac:1;
+ u32 rsvd1:26;
+ } part;
+ u32 full;
+};
+
+union sst_pimr_reg {
+ struct {
+ u32 ssp0:1;
+ u32 ssp1:1;
+ u32 rsvd0:3;
+ u32 dmac:1;
+ u32 rsvd1:10;
+ u32 ssp0_sc:1;
+ u32 ssp1_sc:1;
+ u32 rsvd2:3;
+ u32 dmac_sc:1;
+ u32 rsvd3:10;
+ } part;
+ u32 full;
+};
+
+union config_status_reg_mrfld {
+ struct {
+ u64 lpe_reset:1;
+ u64 lpe_reset_vector:1;
+ u64 runstall:1;
+ u64 pwaitmode:1;
+ u64 clk_sel:3;
+ u64 rsvd2:1;
+ u64 sst_clk:3;
+ u64 xt_snoop:1;
+ u64 rsvd3:4;
+ u64 clk_sel1:6;
+ u64 clk_enable:3;
+ u64 rsvd4:6;
+ u64 slim0baseclk:1;
+ u64 rsvd:32;
+ } part;
+ u64 full;
+};
+
+union interrupt_reg_mrfld {
+ struct {
+ u64 done_interrupt:1;
+ u64 busy_interrupt:1;
+ u64 rsvd:62;
+ } part;
+ u64 full;
+};
+
+union sst_imr_reg_mrfld {
+ struct {
+ u64 done_interrupt:1;
+ u64 busy_interrupt:1;
+ u64 rsvd:62;
+ } part;
+ u64 full;
+};
+
+/**
+ * struct sst_block - This structure is used to block a user/fw data call to another
+ * fw/user call
+ *
+ * @condition: condition for blocking check
+ * @ret_code: ret code when block is released
+ * @data: data ptr
+ * @size: size of data
+ * @on: block condition
+ * @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL
+ * @drv_id: str_id in mfld/ctp, = drv_id in mrfld
+ * @node: list head node
+ */
+struct sst_block {
+ bool condition;
+ int ret_code;
+ void *data;
+ u32 size;
+ bool on;
+ u32 msg_id;
+ u32 drv_id;
+ struct list_head node;
+};
+
+/**
+ * struct stream_info - structure that holds the stream information
+ *
+ * @status : stream current state
+ * @prev : stream prev state
+ * @resume_status : stream current state to restore on resume
+ * @resume_prev : stream prev state to restore on resume
+ * @lock : stream mutex for protecting state
+ * @alloc_param : parameters used for stream (re-)allocation
+ * @pcm_substream : PCM substream
+ * @period_elapsed : PCM period elapsed callback
+ * @sfreq : stream sampling freq
+ * @cumm_bytes : cummulative bytes decoded
+ */
+struct stream_info {
+ unsigned int status;
+ unsigned int prev;
+ unsigned int resume_status;
+ unsigned int resume_prev;
+ struct mutex lock;
+ struct snd_sst_alloc_mrfld alloc_param;
+
+ void *pcm_substream;
+ void (*period_elapsed)(void *pcm_substream);
+
+ unsigned int sfreq;
+ u32 cumm_bytes;
+
+ void *compr_cb_param;
+ void (*compr_cb)(void *compr_cb_param);
+
+ void *drain_cb_param;
+ void (*drain_notify)(void *drain_cb_param);
+
+ unsigned int num_ch;
+ unsigned int pipe_id;
+ unsigned int task_id;
+};
+
+#define SST_FW_SIGN "$SST"
+#define SST_FW_LIB_SIGN "$LIB"
+
+/**
+ * struct sst_fw_header - FW file headers
+ *
+ * @signature : FW signature
+ * @file_size: size of fw image
+ * @modules : # of modules
+ * @file_format : version of header format
+ * @reserved : reserved fields
+ */
+struct sst_fw_header {
+ unsigned char signature[FW_SIGNATURE_SIZE];
+ u32 file_size;
+ u32 modules;
+ u32 file_format;
+ u32 reserved[4];
+};
+
+/**
+ * struct fw_module_header - module header in FW
+ *
+ * @signature: module signature
+ * @mod_size: size of module
+ * @blocks: block count
+ * @type: block type
+ * @entry_point: module netry point
+ */
+struct fw_module_header {
+ unsigned char signature[FW_SIGNATURE_SIZE];
+ u32 mod_size;
+ u32 blocks;
+ u32 type;
+ u32 entry_point;
+};
+
+/**
+ * struct fw_block_info - block header for FW
+ *
+ * @type: block ram type I/D
+ * @size: size of block
+ * @ram_offset: offset in ram
+ */
+struct fw_block_info {
+ enum sst_ram_type type;
+ u32 size;
+ u32 ram_offset;
+ u32 rsvd;
+};
+
+struct sst_runtime_param {
+ struct snd_sst_runtime_params param;
+};
+
+struct sst_sg_list {
+ struct scatterlist *src;
+ struct scatterlist *dst;
+ int list_len;
+ unsigned int sg_idx;
+};
+
+struct sst_memcpy_list {
+ struct list_head memcpylist;
+ void *dstn;
+ const void *src;
+ u32 size;
+ bool is_io;
+};
+
+/*Firmware Module Information*/
+enum sst_lib_dwnld_status {
+ SST_LIB_NOT_FOUND = 0,
+ SST_LIB_FOUND,
+ SST_LIB_DOWNLOADED,
+};
+
+struct sst_module_info {
+ const char *name; /*Library name*/
+ u32 id; /*Module ID*/
+ u32 entry_pt; /*Module entry point*/
+ u8 status; /*module status*/
+ u8 rsvd1;
+ u16 rsvd2;
+};
+
+/*
+ * Structure for managing the Library Region(1.5MB)
+ * in DDR in Merrifield
+ */
+struct sst_mem_mgr {
+ phys_addr_t current_base;
+ int avail;
+ unsigned int count;
+};
+
+struct sst_ipc_reg {
+ int ipcx;
+ int ipcd;
+};
+
+struct sst_fw_save {
+ void *iram; /* allocated via kvmalloc() */
+ void *dram; /* allocated via kvmalloc() */
+ void *sram; /* allocated via kvmalloc() */
+ void *ddr; /* allocated via kvmalloc() */
+};
+
+/**
+ * struct intel_sst_drv - driver ops
+ *
+ * @sst_state : current sst device state
+ * @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi
+ * devices
+ * @shim : SST shim pointer
+ * @mailbox : SST mailbox pointer
+ * @iram : SST IRAM pointer
+ * @dram : SST DRAM pointer
+ * @pdata : SST info passed as a part of pci platform data
+ * @shim_phy_add : SST shim phy addr
+ * @ipc_dispatch_list : ipc messages dispatched
+ * @rx_list : to copy the process_reply/process_msg from DSP
+ * @ipc_post_msg_wq : wq to post IPC messages context
+ * @mad_ops : MAD driver operations registered
+ * @mad_wq : MAD driver wq
+ * @post_msg_wq : wq to post IPC messages
+ * @streams : sst stream contexts
+ * @list_lock : sst driver list lock (deprecated)
+ * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue
+ * @block_lock : spin lock to add block to block_list and assign pvt_id
+ * @rx_msg_lock : spin lock to handle the rx messages from the DSP
+ * @scard_ops : sst card ops
+ * @pci : sst pci device struture
+ * @dev : pointer to current device struct
+ * @sst_lock : sst device lock
+ * @pvt_id : sst private id
+ * @stream_cnt : total sst active stream count
+ * @pb_streams : total active pb streams
+ * @cp_streams : total active cp streams
+ * @audio_start : audio status
+ * @qos : PM Qos struct
+ * firmware_name : Firmware / Library name
+ */
+struct intel_sst_drv {
+ int sst_state;
+ int irq_num;
+ unsigned int dev_id;
+ void __iomem *ddr;
+ void __iomem *shim;
+ void __iomem *mailbox;
+ void __iomem *iram;
+ void __iomem *dram;
+ unsigned int mailbox_add;
+ unsigned int iram_base;
+ unsigned int dram_base;
+ unsigned int shim_phy_add;
+ unsigned int iram_end;
+ unsigned int dram_end;
+ unsigned int ddr_end;
+ unsigned int ddr_base;
+ unsigned int mailbox_recv_offset;
+ struct list_head block_list;
+ struct list_head ipc_dispatch_list;
+ struct sst_platform_info *pdata;
+ struct list_head rx_list;
+ struct work_struct ipc_post_msg_wq;
+ wait_queue_head_t wait_queue;
+ struct workqueue_struct *post_msg_wq;
+ unsigned int tstamp;
+ /* str_id 0 is not used */
+ struct stream_info streams[MAX_NUM_STREAMS+1];
+ spinlock_t ipc_spin_lock;
+ spinlock_t block_lock;
+ spinlock_t rx_msg_lock;
+ struct pci_dev *pci;
+ struct device *dev;
+ volatile long unsigned pvt_id;
+ struct mutex sst_lock;
+ unsigned int stream_cnt;
+ unsigned int csr_value;
+ void *fw_in_mem;
+ struct sst_sg_list fw_sg_list, library_list;
+ struct intel_sst_ops *ops;
+ struct sst_info info;
+ struct pm_qos_request *qos;
+ unsigned int use_dma;
+ unsigned int use_lli;
+ atomic_t fw_clear_context;
+ bool lib_dwnld_reqd;
+ struct list_head memcpy_list;
+ struct sst_ipc_reg ipc_reg;
+ struct sst_mem_mgr lib_mem_mgr;
+ /*
+ * Holder for firmware name. Due to async call it needs to be
+ * persistent till worker thread gets called
+ */
+ char firmware_name[FW_NAME_SIZE];
+
+ struct snd_sst_fw_version fw_version;
+ struct sst_fw_save *fw_save;
+};
+
+/* misc definitions */
+#define FW_DWNL_ID 0x01
+
+struct intel_sst_ops {
+ irqreturn_t (*interrupt)(int, void *);
+ irqreturn_t (*irq_thread)(int, void *);
+ void (*clear_interrupt)(struct intel_sst_drv *ctx);
+ int (*start)(struct intel_sst_drv *ctx);
+ int (*reset)(struct intel_sst_drv *ctx);
+ void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg);
+ int (*post_message)(struct intel_sst_drv *ctx,
+ struct ipc_post *msg, bool sync);
+ void (*process_message)(struct ipc_post *msg);
+ void (*set_bypass)(bool set);
+ int (*save_dsp_context)(struct intel_sst_drv *sst);
+ void (*restore_dsp_context)(void);
+ int (*alloc_stream)(struct intel_sst_drv *ctx, void *params);
+ void (*post_download)(struct intel_sst_drv *sst);
+};
+
+int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id);
+int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ struct snd_sst_bytes_v2 *bytes);
+int sst_set_stream_param(int str_id, struct snd_sst_params *str_param);
+int sst_set_metadata(int str_id, char *params);
+int sst_get_stream(struct intel_sst_drv *ctx,
+ struct snd_sst_params *str_param);
+int sst_get_stream_allocated(struct intel_sst_drv *ctx,
+ struct snd_sst_params *str_param,
+ struct snd_sst_lib_download **lib_dnld);
+int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
+ int str_id, bool partial_drain);
+int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ struct ipc_post *ipc_msg, bool sync);
+void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *msg);
+int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx);
+int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx);
+void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx);
+
+int sst_load_fw(struct intel_sst_drv *sst_drv_ctx);
+int sst_load_library(struct snd_sst_lib_download *lib, u8 ops);
+void sst_post_download_mrfld(struct intel_sst_drv *ctx);
+int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx);
+void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx);
+
+int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block);
+int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block);
+int sst_create_ipc_msg(struct ipc_post **arg, bool large);
+int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id);
+void sst_clean_stream(struct stream_info *stream);
+int intel_sst_register_compress(struct intel_sst_drv *sst);
+int intel_sst_remove_compress(struct intel_sst_drv *sst);
+void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id);
+int sst_send_sync_msg(int ipc, int str_id);
+int sst_get_num_channel(struct snd_sst_params *str_param);
+int sst_get_sfreq(struct snd_sst_params *str_param);
+int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params);
+void sst_restore_fw_context(void);
+struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
+ u32 msg_id, u32 drv_id);
+int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
+ struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
+ u32 msg_id, u32 drv_id);
+int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed);
+int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
+ u32 drv_id, u32 ipc, void *data, u32 size);
+int sst_request_firmware_async(struct intel_sst_drv *ctx);
+int sst_driver_ops(struct intel_sst_drv *sst);
+struct sst_platform_info *sst_get_acpi_driver_data(const char *hid);
+void sst_firmware_load_cb(const struct firmware *fw, void *context);
+int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
+ int task_id, int ipc_msg, int cmd_id, int pipe_id,
+ size_t mbox_data_len, const void *mbox_data, void **data,
+ bool large, bool fill_dsp, bool sync, bool response);
+
+void sst_process_pending_msg(struct work_struct *work);
+int sst_assign_pvt_id(struct intel_sst_drv *drv);
+int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id);
+struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx,
+ int str_id);
+int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ u32 pipe_id);
+u32 relocate_imr_addr_mrfld(u32 base_addr);
+void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
+ struct ipc_post *msg);
+int sst_pm_runtime_put(struct intel_sst_drv *sst_drv);
+int sst_shim_write(void __iomem *addr, int offset, int value);
+u32 sst_shim_read(void __iomem *addr, int offset);
+u64 sst_reg_read64(void __iomem *addr, int offset);
+int sst_shim_write64(void __iomem *addr, int offset, u64 value);
+u64 sst_shim_read64(void __iomem *addr, int offset);
+void sst_set_fw_state_locked(
+ struct intel_sst_drv *sst_drv_ctx, int sst_state);
+void sst_fill_header_mrfld(union ipc_header_mrfld *header,
+ int msg, int task_id, int large, int drv_id);
+void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
+ int pipe_id, int len);
+
+int sst_register(struct device *);
+int sst_unregister(struct device *);
+
+int sst_alloc_drv_context(struct intel_sst_drv **ctx,
+ struct device *dev, unsigned int dev_id);
+int sst_context_init(struct intel_sst_drv *ctx);
+void sst_context_cleanup(struct intel_sst_drv *ctx);
+void sst_configure_runtime_pm(struct intel_sst_drv *ctx);
+void memcpy32_toio(void __iomem *dst, const void *src, int count);
+void memcpy32_fromio(void *dst, const void __iomem *src, int count);
+
+#endif
diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c
new file mode 100644
index 000000000..f943a0884
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_acpi.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration.
+ *
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * Authors: Ramesh Babu K V <Ramesh.Babu@intel.com>
+ * Authors: Omair Mohammed Abdullah <omair.m.abdullah@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <asm/platform_sst_audio.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <acpi/acbuffer.h>
+#include <acpi/platform/acenv.h>
+#include <acpi/platform/aclinux.h>
+#include <acpi/actypes.h>
+#include <acpi/acpi_bus.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../sst-mfld-platform.h"
+#include "../../common/soc-intel-quirks.h"
+#include "sst.h"
+
+/* LPE viewpoint addresses */
+#define SST_BYT_IRAM_PHY_START 0xff2c0000
+#define SST_BYT_IRAM_PHY_END 0xff2d4000
+#define SST_BYT_DRAM_PHY_START 0xff300000
+#define SST_BYT_DRAM_PHY_END 0xff320000
+#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */
+#define SST_BYT_IMR_VIRT_END 0xc01fffff
+#define SST_BYT_SHIM_PHY_ADDR 0xff340000
+#define SST_BYT_MBOX_PHY_ADDR 0xff344000
+#define SST_BYT_DMA0_PHY_ADDR 0xff298000
+#define SST_BYT_DMA1_PHY_ADDR 0xff29c000
+#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000
+#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000
+
+#define BYT_FW_MOD_TABLE_OFFSET 0x80000
+#define BYT_FW_MOD_TABLE_SIZE 0x100
+#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE)
+
+static const struct sst_info byt_fwparse_info = {
+ .use_elf = false,
+ .max_streams = 25,
+ .iram_start = SST_BYT_IRAM_PHY_START,
+ .iram_end = SST_BYT_IRAM_PHY_END,
+ .iram_use = true,
+ .dram_start = SST_BYT_DRAM_PHY_START,
+ .dram_end = SST_BYT_DRAM_PHY_END,
+ .dram_use = true,
+ .imr_start = SST_BYT_IMR_VIRT_START,
+ .imr_end = SST_BYT_IMR_VIRT_END,
+ .imr_use = true,
+ .mailbox_start = SST_BYT_MBOX_PHY_ADDR,
+ .num_probes = 0,
+ .lpe_viewpt_rqd = true,
+};
+
+static const struct sst_ipc_info byt_ipc_info = {
+ .ipc_offset = 0,
+ .mbox_recv_off = 0x400,
+};
+
+static const struct sst_lib_dnld_info byt_lib_dnld_info = {
+ .mod_base = SST_BYT_IMR_VIRT_START,
+ .mod_end = SST_BYT_IMR_VIRT_END,
+ .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET,
+ .mod_table_size = BYT_FW_MOD_TABLE_SIZE,
+ .mod_ddr_dnld = false,
+};
+
+static const struct sst_res_info byt_rvp_res_info = {
+ .shim_offset = 0x140000,
+ .shim_size = 0x000100,
+ .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
+ .ssp0_offset = 0xa0000,
+ .ssp0_size = 0x1000,
+ .dma0_offset = 0x98000,
+ .dma0_size = 0x4000,
+ .dma1_offset = 0x9c000,
+ .dma1_size = 0x4000,
+ .iram_offset = 0x0c0000,
+ .iram_size = 0x14000,
+ .dram_offset = 0x100000,
+ .dram_size = 0x28000,
+ .mbox_offset = 0x144000,
+ .mbox_size = 0x1000,
+ .acpi_lpe_res_index = 0,
+ .acpi_ddr_index = 2,
+ .acpi_ipc_irq_index = 5,
+};
+
+/* BYTCR has different BIOS from BYT */
+static const struct sst_res_info bytcr_res_info = {
+ .shim_offset = 0x140000,
+ .shim_size = 0x000100,
+ .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
+ .ssp0_offset = 0xa0000,
+ .ssp0_size = 0x1000,
+ .dma0_offset = 0x98000,
+ .dma0_size = 0x4000,
+ .dma1_offset = 0x9c000,
+ .dma1_size = 0x4000,
+ .iram_offset = 0x0c0000,
+ .iram_size = 0x14000,
+ .dram_offset = 0x100000,
+ .dram_size = 0x28000,
+ .mbox_offset = 0x144000,
+ .mbox_size = 0x1000,
+ .acpi_lpe_res_index = 0,
+ .acpi_ddr_index = 2,
+ .acpi_ipc_irq_index = 0
+};
+
+static struct sst_platform_info byt_rvp_platform_data = {
+ .probe_data = &byt_fwparse_info,
+ .ipc_info = &byt_ipc_info,
+ .lib_info = &byt_lib_dnld_info,
+ .res_info = &byt_rvp_res_info,
+ .platform = "sst-mfld-platform",
+ .streams_lost_on_suspend = true,
+};
+
+/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail,
+ * so pdata is same as Baytrail, minus the streams_lost_on_suspend quirk.
+ */
+static struct sst_platform_info chv_platform_data = {
+ .probe_data = &byt_fwparse_info,
+ .ipc_info = &byt_ipc_info,
+ .lib_info = &byt_lib_dnld_info,
+ .res_info = &byt_rvp_res_info,
+ .platform = "sst-mfld-platform",
+};
+
+static int sst_platform_get_resources(struct intel_sst_drv *ctx)
+{
+ struct resource *rsrc;
+ struct platform_device *pdev = to_platform_device(ctx->dev);
+
+ /* All ACPI resource request here */
+ /* Get Shim addr */
+ rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
+ ctx->pdata->res_info->acpi_lpe_res_index);
+ if (!rsrc) {
+ dev_err(ctx->dev, "Invalid SHIM base from IFWI\n");
+ return -EIO;
+ }
+ dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start,
+ (unsigned int)resource_size(rsrc));
+
+ ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset;
+ ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1;
+ dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base);
+ ctx->iram = devm_ioremap(ctx->dev, ctx->iram_base,
+ ctx->pdata->res_info->iram_size);
+ if (!ctx->iram) {
+ dev_err(ctx->dev, "unable to map IRAM\n");
+ return -EIO;
+ }
+
+ ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset;
+ ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1;
+ dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base);
+ ctx->dram = devm_ioremap(ctx->dev, ctx->dram_base,
+ ctx->pdata->res_info->dram_size);
+ if (!ctx->dram) {
+ dev_err(ctx->dev, "unable to map DRAM\n");
+ return -EIO;
+ }
+
+ ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset;
+ dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add);
+ ctx->shim = devm_ioremap(ctx->dev, ctx->shim_phy_add,
+ ctx->pdata->res_info->shim_size);
+ if (!ctx->shim) {
+ dev_err(ctx->dev, "unable to map SHIM\n");
+ return -EIO;
+ }
+
+ /* reassign physical address to LPE viewpoint address */
+ ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr;
+
+ /* Get mailbox addr */
+ ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset;
+ dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add);
+ ctx->mailbox = devm_ioremap(ctx->dev, ctx->mailbox_add,
+ ctx->pdata->res_info->mbox_size);
+ if (!ctx->mailbox) {
+ dev_err(ctx->dev, "unable to map mailbox\n");
+ return -EIO;
+ }
+
+ /* reassign physical address to LPE viewpoint address */
+ ctx->mailbox_add = ctx->info.mailbox_start;
+
+ rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
+ ctx->pdata->res_info->acpi_ddr_index);
+ if (!rsrc) {
+ dev_err(ctx->dev, "Invalid DDR base from IFWI\n");
+ return -EIO;
+ }
+ ctx->ddr_base = rsrc->start;
+ ctx->ddr_end = rsrc->end;
+ dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base);
+ ctx->ddr = devm_ioremap(ctx->dev, ctx->ddr_base,
+ resource_size(rsrc));
+ if (!ctx->ddr) {
+ dev_err(ctx->dev, "unable to map DDR\n");
+ return -EIO;
+ }
+
+ /* Find the IRQ */
+ ctx->irq_num = platform_get_irq(pdev,
+ ctx->pdata->res_info->acpi_ipc_irq_index);
+ if (ctx->irq_num <= 0)
+ return ctx->irq_num < 0 ? ctx->irq_num : -EIO;
+
+ return 0;
+}
+
+static int sst_acpi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret = 0;
+ struct intel_sst_drv *ctx;
+ const struct acpi_device_id *id;
+ struct snd_soc_acpi_mach *mach;
+ struct platform_device *mdev;
+ struct platform_device *plat_dev;
+ struct sst_platform_info *pdata;
+ unsigned int dev_id;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+ dev_dbg(dev, "for %s\n", id->id);
+
+ mach = (struct snd_soc_acpi_mach *)id->driver_data;
+ mach = snd_soc_acpi_find_machine(mach);
+ if (mach == NULL) {
+ dev_err(dev, "No matching machine driver found\n");
+ return -ENODEV;
+ }
+
+ if (soc_intel_is_byt())
+ mach->pdata = &byt_rvp_platform_data;
+ else
+ mach->pdata = &chv_platform_data;
+ pdata = mach->pdata;
+
+ ret = kstrtouint(id->id, 16, &dev_id);
+ if (ret < 0) {
+ dev_err(dev, "Unique device id conversion error: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "ACPI device id: %x\n", dev_id);
+
+ ret = sst_alloc_drv_context(&ctx, dev, dev_id);
+ if (ret < 0)
+ return ret;
+
+ if (soc_intel_is_byt_cr(pdev)) {
+ /* override resource info */
+ byt_rvp_platform_data.res_info = &bytcr_res_info;
+ }
+
+ /* update machine parameters */
+ mach->mach_params.acpi_ipc_irq_index =
+ pdata->res_info->acpi_ipc_irq_index;
+
+ plat_dev = platform_device_register_data(dev, pdata->platform, -1,
+ NULL, 0);
+ if (IS_ERR(plat_dev)) {
+ dev_err(dev, "Failed to create machine device: %s\n",
+ pdata->platform);
+ return PTR_ERR(plat_dev);
+ }
+
+ /*
+ * Create platform device for sst machine driver,
+ * pass machine info as pdata
+ */
+ mdev = platform_device_register_data(dev, mach->drv_name, -1,
+ (const void *)mach, sizeof(*mach));
+ if (IS_ERR(mdev)) {
+ dev_err(dev, "Failed to create machine device: %s\n",
+ mach->drv_name);
+ return PTR_ERR(mdev);
+ }
+
+ /* Fill sst platform data */
+ ctx->pdata = pdata;
+ strcpy(ctx->firmware_name, mach->fw_filename);
+
+ ret = sst_platform_get_resources(ctx);
+ if (ret)
+ return ret;
+
+ ret = sst_context_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ sst_configure_runtime_pm(ctx);
+ platform_set_drvdata(pdev, ctx);
+ return ret;
+}
+
+/**
+* intel_sst_remove - remove function
+*
+* @pdev: platform device structure
+*
+* This function is called by OS when a device is unloaded
+* This frees the interrupt etc
+*/
+static int sst_acpi_remove(struct platform_device *pdev)
+{
+ struct intel_sst_drv *ctx;
+
+ ctx = platform_get_drvdata(pdev);
+ sst_context_cleanup(ctx);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static const struct acpi_device_id sst_acpi_ids[] = {
+ { "80860F28", (unsigned long)&snd_soc_acpi_intel_baytrail_machines},
+ { "808622A8", (unsigned long)&snd_soc_acpi_intel_cherrytrail_machines},
+ { },
+};
+
+MODULE_DEVICE_TABLE(acpi, sst_acpi_ids);
+
+static struct platform_driver sst_acpi_driver = {
+ .driver = {
+ .name = "intel_sst_acpi",
+ .acpi_match_table = ACPI_PTR(sst_acpi_ids),
+ .pm = &intel_sst_pm,
+ },
+ .probe = sst_acpi_probe,
+ .remove = sst_acpi_remove,
+};
+
+module_platform_driver(sst_acpi_driver);
+
+MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver");
+MODULE_AUTHOR("Ramesh Babu K V");
+MODULE_AUTHOR("Omair Mohammed Abdullah");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("sst");
diff --git a/sound/soc/intel/atom/sst/sst_drv_interface.c b/sound/soc/intel/atom/sst/sst_drv_interface.c
new file mode 100644
index 000000000..0af618dd8
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_drv_interface.c
@@ -0,0 +1,717 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_drv_interface.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com)
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/math64.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+#define NUM_CODEC 2
+#define MIN_FRAGMENT 2
+#define MAX_FRAGMENT 4
+#define MIN_FRAGMENT_SIZE (50 * 1024)
+#define MAX_FRAGMENT_SIZE (1024 * 1024)
+#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1)
+#ifdef CONFIG_PM
+#define GET_USAGE_COUNT(dev) (atomic_read(&dev->power.usage_count))
+#else
+#define GET_USAGE_COUNT(dev) 1
+#endif
+
+int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id)
+{
+ struct stream_info *stream;
+ int ret = 0;
+
+ stream = get_stream_info(ctx, str_id);
+ if (stream) {
+ /* str_id is valid, so stream is alloacted */
+ ret = sst_free_stream(ctx, str_id);
+ if (ret)
+ sst_clean_stream(&ctx->streams[str_id]);
+ return ret;
+ } else {
+ dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id);
+ }
+ return ret;
+}
+
+int sst_get_stream_allocated(struct intel_sst_drv *ctx,
+ struct snd_sst_params *str_param,
+ struct snd_sst_lib_download **lib_dnld)
+{
+ int retval;
+
+ retval = ctx->ops->alloc_stream(ctx, str_param);
+ if (retval > 0)
+ dev_dbg(ctx->dev, "Stream allocated %d\n", retval);
+ return retval;
+
+}
+
+/*
+ * sst_get_sfreq - this function returns the frequency of the stream
+ *
+ * @str_param : stream params
+ */
+int sst_get_sfreq(struct snd_sst_params *str_param)
+{
+ switch (str_param->codec) {
+ case SST_CODEC_TYPE_PCM:
+ return str_param->sparams.uc.pcm_params.sfreq;
+ case SST_CODEC_TYPE_AAC:
+ return str_param->sparams.uc.aac_params.externalsr;
+ case SST_CODEC_TYPE_MP3:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * sst_get_num_channel - get number of channels for the stream
+ *
+ * @str_param : stream params
+ */
+int sst_get_num_channel(struct snd_sst_params *str_param)
+{
+ switch (str_param->codec) {
+ case SST_CODEC_TYPE_PCM:
+ return str_param->sparams.uc.pcm_params.num_chan;
+ case SST_CODEC_TYPE_MP3:
+ return str_param->sparams.uc.mp3_params.num_chan;
+ case SST_CODEC_TYPE_AAC:
+ return str_param->sparams.uc.aac_params.num_chan;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * sst_get_stream - this function prepares for stream allocation
+ *
+ * @str_param : stream param
+ */
+int sst_get_stream(struct intel_sst_drv *ctx,
+ struct snd_sst_params *str_param)
+{
+ int retval;
+ struct stream_info *str_info;
+
+ /* stream is not allocated, we are allocating */
+ retval = ctx->ops->alloc_stream(ctx, str_param);
+ if (retval <= 0) {
+ return -EIO;
+ }
+ /* store sampling freq */
+ str_info = &ctx->streams[retval];
+ str_info->sfreq = sst_get_sfreq(str_param);
+
+ return retval;
+}
+
+static int sst_power_control(struct device *dev, bool state)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+ int ret = 0;
+ int usage_count = 0;
+
+ if (state) {
+ ret = pm_runtime_get_sync(dev);
+ usage_count = GET_USAGE_COUNT(dev);
+ dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count);
+ if (ret < 0) {
+ pm_runtime_put_sync(dev);
+ dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret);
+ return ret;
+ }
+ if ((ctx->sst_state == SST_RESET) && (usage_count == 1)) {
+ ret = sst_load_fw(ctx);
+ if (ret) {
+ dev_err(dev, "FW download fail %d\n", ret);
+ sst_set_fw_state_locked(ctx, SST_RESET);
+ ret = sst_pm_runtime_put(ctx);
+ }
+ }
+ } else {
+ usage_count = GET_USAGE_COUNT(dev);
+ dev_dbg(ctx->dev, "Disable: pm usage count: %d\n", usage_count);
+ return sst_pm_runtime_put(ctx);
+ }
+ return ret;
+}
+
+/*
+ * sst_open_pcm_stream - Open PCM interface
+ *
+ * @str_param: parameters of pcm stream
+ *
+ * This function is called by MID sound card driver to open
+ * a new pcm interface
+ */
+static int sst_open_pcm_stream(struct device *dev,
+ struct snd_sst_params *str_param)
+{
+ int retval;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (!str_param)
+ return -EINVAL;
+
+ retval = sst_get_stream(ctx, str_param);
+ if (retval > 0)
+ ctx->stream_cnt++;
+ else
+ dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval);
+
+ return retval;
+}
+
+static int sst_cdev_open(struct device *dev,
+ struct snd_sst_params *str_params, struct sst_compress_cb *cb)
+{
+ int str_id, retval;
+ struct stream_info *stream;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ retval = pm_runtime_get_sync(ctx->dev);
+ if (retval < 0) {
+ pm_runtime_put_sync(ctx->dev);
+ return retval;
+ }
+
+ str_id = sst_get_stream(ctx, str_params);
+ if (str_id > 0) {
+ dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id);
+ stream = &ctx->streams[str_id];
+ stream->compr_cb = cb->compr_cb;
+ stream->compr_cb_param = cb->param;
+ stream->drain_notify = cb->drain_notify;
+ stream->drain_cb_param = cb->drain_cb_param;
+ } else {
+ dev_err(dev, "stream encountered error during alloc %d\n", str_id);
+ str_id = -EINVAL;
+ sst_pm_runtime_put(ctx);
+ }
+ return str_id;
+}
+
+static int sst_cdev_close(struct device *dev, unsigned int str_id)
+{
+ int retval;
+ struct stream_info *stream;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ stream = get_stream_info(ctx, str_id);
+ if (!stream) {
+ dev_err(dev, "stream info is NULL for str %d!!!\n", str_id);
+ return -EINVAL;
+ }
+
+ retval = sst_free_stream(ctx, str_id);
+ stream->compr_cb_param = NULL;
+ stream->compr_cb = NULL;
+
+ if (retval)
+ dev_err(dev, "free stream returned err %d\n", retval);
+
+ dev_dbg(dev, "End\n");
+ return retval;
+}
+
+static int sst_cdev_ack(struct device *dev, unsigned int str_id,
+ unsigned long bytes)
+{
+ struct stream_info *stream;
+ struct snd_sst_tstamp fw_tstamp = {0,};
+ int offset;
+ void __iomem *addr;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ stream = get_stream_info(ctx, str_id);
+ if (!stream)
+ return -EINVAL;
+
+ /* update bytes sent */
+ stream->cumm_bytes += bytes;
+ dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes);
+
+ addr = ((void __iomem *)(ctx->mailbox + ctx->tstamp)) +
+ (str_id * sizeof(fw_tstamp));
+
+ memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
+
+ fw_tstamp.bytes_copied = stream->cumm_bytes;
+ dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n",
+ fw_tstamp.bytes_copied, bytes);
+
+ offset = offsetof(struct snd_sst_tstamp, bytes_copied);
+ sst_shim_write(addr, offset, fw_tstamp.bytes_copied);
+ return 0;
+}
+
+static int sst_cdev_set_metadata(struct device *dev,
+ unsigned int str_id, struct snd_compr_metadata *metadata)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "set metadata for stream %d\n", str_id);
+
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id);
+ retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD,
+ IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id,
+ sizeof(*metadata), metadata, NULL,
+ true, true, true, false);
+
+ return retval;
+}
+
+static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ return sst_pause_stream(ctx, str_id);
+}
+
+static int sst_cdev_stream_pause_release(struct device *dev,
+ unsigned int str_id)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ return sst_resume_stream(ctx, str_id);
+}
+
+static int sst_cdev_stream_start(struct device *dev, unsigned int str_id)
+{
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_RUNNING;
+ return sst_start_stream(ctx, str_id);
+}
+
+static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ return sst_drop_stream(ctx, str_id);
+}
+
+static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ return sst_drain_stream(ctx, str_id, false);
+}
+
+static int sst_cdev_stream_partial_drain(struct device *dev,
+ unsigned int str_id)
+{
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ return sst_drain_stream(ctx, str_id, true);
+}
+
+static int sst_cdev_tstamp(struct device *dev, unsigned int str_id,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct snd_sst_tstamp fw_tstamp = {0,};
+ struct stream_info *stream;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+ void __iomem *addr;
+
+ addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) +
+ (str_id * sizeof(fw_tstamp));
+
+ memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
+
+ stream = get_stream_info(ctx, str_id);
+ if (!stream)
+ return -EINVAL;
+ dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter);
+
+ tstamp->copied_total = fw_tstamp.ring_buffer_counter;
+ tstamp->pcm_frames = fw_tstamp.frames_decoded;
+ tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter,
+ (u64)stream->num_ch * SST_GET_BYTES_PER_SAMPLE(24));
+ tstamp->sampling_rate = fw_tstamp.sampling_frequency;
+
+ dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames);
+ dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n",
+ str_id, tstamp->copied_total, tstamp->pcm_frames);
+ dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames);
+
+ return 0;
+}
+
+static int sst_cdev_caps(struct snd_compr_caps *caps)
+{
+ caps->num_codecs = NUM_CODEC;
+ caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */
+ caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */
+ caps->min_fragments = MIN_FRAGMENT;
+ caps->max_fragments = MAX_FRAGMENT;
+ caps->codecs[0] = SND_AUDIOCODEC_MP3;
+ caps->codecs[1] = SND_AUDIOCODEC_AAC;
+ return 0;
+}
+
+static const struct snd_compr_codec_caps caps_mp3 = {
+ .num_descriptors = 1,
+ .descriptor[0].max_ch = 2,
+ .descriptor[0].sample_rates[0] = 48000,
+ .descriptor[0].sample_rates[1] = 44100,
+ .descriptor[0].sample_rates[2] = 32000,
+ .descriptor[0].sample_rates[3] = 16000,
+ .descriptor[0].sample_rates[4] = 8000,
+ .descriptor[0].num_sample_rates = 5,
+ .descriptor[0].bit_rate[0] = 320,
+ .descriptor[0].bit_rate[1] = 192,
+ .descriptor[0].num_bitrates = 2,
+ .descriptor[0].profiles = 0,
+ .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
+ .descriptor[0].formats = 0,
+};
+
+static const struct snd_compr_codec_caps caps_aac = {
+ .num_descriptors = 2,
+ .descriptor[1].max_ch = 2,
+ .descriptor[0].sample_rates[0] = 48000,
+ .descriptor[0].sample_rates[1] = 44100,
+ .descriptor[0].sample_rates[2] = 32000,
+ .descriptor[0].sample_rates[3] = 16000,
+ .descriptor[0].sample_rates[4] = 8000,
+ .descriptor[0].num_sample_rates = 5,
+ .descriptor[1].bit_rate[0] = 320,
+ .descriptor[1].bit_rate[1] = 192,
+ .descriptor[1].num_bitrates = 2,
+ .descriptor[1].profiles = 0,
+ .descriptor[1].modes = 0,
+ .descriptor[1].formats =
+ (SND_AUDIOSTREAMFORMAT_MP4ADTS |
+ SND_AUDIOSTREAMFORMAT_RAW),
+};
+
+static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec)
+{
+ if (codec->codec == SND_AUDIOCODEC_MP3)
+ *codec = caps_mp3;
+ else if (codec->codec == SND_AUDIOCODEC_AAC)
+ *codec = caps_aac;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id)
+{
+ struct stream_info *stream;
+
+ dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n",
+ str_id);
+ stream = &ctx->streams[str_id];
+ if (stream->compr_cb)
+ stream->compr_cb(stream->compr_cb_param);
+}
+
+/*
+ * sst_close_pcm_stream - Close PCM interface
+ *
+ * @str_id: stream id to be closed
+ *
+ * This function is called by MID sound card driver to close
+ * an existing pcm interface
+ */
+static int sst_close_pcm_stream(struct device *dev, unsigned int str_id)
+{
+ struct stream_info *stream;
+ int retval = 0;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ stream = get_stream_info(ctx, str_id);
+ if (!stream) {
+ dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id);
+ return -EINVAL;
+ }
+
+ retval = free_stream_context(ctx, str_id);
+ stream->pcm_substream = NULL;
+ stream->status = STREAM_UN_INIT;
+ stream->period_elapsed = NULL;
+ ctx->stream_cnt--;
+
+ if (retval)
+ dev_err(ctx->dev, "free stream returned err %d\n", retval);
+
+ dev_dbg(ctx->dev, "Exit\n");
+ return 0;
+}
+
+static inline int sst_calc_tstamp(struct intel_sst_drv *ctx,
+ struct pcm_stream_info *info,
+ struct snd_pcm_substream *substream,
+ struct snd_sst_tstamp *fw_tstamp)
+{
+ size_t delay_bytes, delay_frames;
+ size_t buffer_sz;
+ u32 pointer_bytes, pointer_samples;
+
+ dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n",
+ fw_tstamp->ring_buffer_counter);
+ dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n",
+ fw_tstamp->hardware_counter);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter -
+ fw_tstamp->hardware_counter);
+ else
+ delay_bytes = (size_t) (fw_tstamp->hardware_counter -
+ fw_tstamp->ring_buffer_counter);
+ delay_frames = bytes_to_frames(substream->runtime, delay_bytes);
+ buffer_sz = snd_pcm_lib_buffer_bytes(substream);
+ div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes);
+ pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes);
+
+ dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes);
+
+ info->buffer_ptr = pointer_samples / substream->runtime->channels;
+
+ info->pcm_delay = delay_frames;
+ dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n",
+ info->buffer_ptr, info->pcm_delay);
+ return 0;
+}
+
+static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info)
+{
+ struct stream_info *stream;
+ struct snd_pcm_substream *substream;
+ struct snd_sst_tstamp fw_tstamp;
+ unsigned int str_id;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+ void __iomem *addr;
+
+ str_id = info->str_id;
+ stream = get_stream_info(ctx, str_id);
+ if (!stream)
+ return -EINVAL;
+
+ if (!stream->pcm_substream)
+ return -EINVAL;
+ substream = stream->pcm_substream;
+
+ addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) +
+ (str_id * sizeof(fw_tstamp));
+
+ memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp));
+
+ return sst_calc_tstamp(ctx, info, substream, &fw_tstamp);
+}
+
+static int sst_stream_start(struct device *dev, int str_id)
+{
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->sst_state != SST_FW_RUNNING)
+ return 0;
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_RUNNING;
+ sst_start_stream(ctx, str_id);
+
+ return 0;
+}
+
+static int sst_stream_drop(struct device *dev, int str_id)
+{
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->sst_state != SST_FW_RUNNING)
+ return 0;
+
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ str_info->prev = STREAM_UN_INIT;
+ str_info->status = STREAM_INIT;
+ return sst_drop_stream(ctx, str_id);
+}
+
+static int sst_stream_pause(struct device *dev, int str_id)
+{
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->sst_state != SST_FW_RUNNING)
+ return 0;
+
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ return sst_pause_stream(ctx, str_id);
+}
+
+static int sst_stream_resume(struct device *dev, int str_id)
+{
+ struct stream_info *str_info;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (ctx->sst_state != SST_FW_RUNNING)
+ return 0;
+
+ str_info = get_stream_info(ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ return sst_resume_stream(ctx, str_id);
+}
+
+static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info)
+{
+ int str_id = 0;
+ struct stream_info *stream;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ str_id = str_info->str_id;
+
+ if (ctx->sst_state != SST_FW_RUNNING)
+ return 0;
+
+ stream = get_stream_info(ctx, str_id);
+ if (!stream)
+ return -EINVAL;
+
+ dev_dbg(ctx->dev, "setting the period ptrs\n");
+ stream->pcm_substream = str_info->arg;
+ stream->period_elapsed = str_info->period_elapsed;
+ stream->sfreq = str_info->sfreq;
+ stream->prev = stream->status;
+ stream->status = STREAM_INIT;
+ dev_dbg(ctx->dev,
+ "pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n",
+ stream->pcm_substream, stream->period_elapsed,
+ stream->sfreq, stream->status);
+
+ return 0;
+}
+
+/*
+ * sst_set_byte_stream - Set generic params
+ *
+ * @cmd: control cmd to be set
+ * @arg: command argument
+ *
+ * This function is called by MID sound card driver to configure
+ * SST runtime params.
+ */
+static int sst_send_byte_stream(struct device *dev,
+ struct snd_sst_bytes_v2 *bytes)
+{
+ int ret_val = 0;
+ struct intel_sst_drv *ctx = dev_get_drvdata(dev);
+
+ if (NULL == bytes)
+ return -EINVAL;
+ ret_val = pm_runtime_get_sync(ctx->dev);
+ if (ret_val < 0) {
+ pm_runtime_put_sync(ctx->dev);
+ return ret_val;
+ }
+
+ ret_val = sst_send_byte_stream_mrfld(ctx, bytes);
+ sst_pm_runtime_put(ctx);
+
+ return ret_val;
+}
+
+static struct sst_ops pcm_ops = {
+ .open = sst_open_pcm_stream,
+ .stream_init = sst_stream_init,
+ .stream_start = sst_stream_start,
+ .stream_drop = sst_stream_drop,
+ .stream_pause = sst_stream_pause,
+ .stream_pause_release = sst_stream_resume,
+ .stream_read_tstamp = sst_read_timestamp,
+ .send_byte_stream = sst_send_byte_stream,
+ .close = sst_close_pcm_stream,
+ .power = sst_power_control,
+};
+
+static struct compress_sst_ops compr_ops = {
+ .open = sst_cdev_open,
+ .close = sst_cdev_close,
+ .stream_pause = sst_cdev_stream_pause,
+ .stream_pause_release = sst_cdev_stream_pause_release,
+ .stream_start = sst_cdev_stream_start,
+ .stream_drop = sst_cdev_stream_drop,
+ .stream_drain = sst_cdev_stream_drain,
+ .stream_partial_drain = sst_cdev_stream_partial_drain,
+ .tstamp = sst_cdev_tstamp,
+ .ack = sst_cdev_ack,
+ .get_caps = sst_cdev_caps,
+ .get_codec_caps = sst_cdev_codec_caps,
+ .set_metadata = sst_cdev_set_metadata,
+ .power = sst_power_control,
+};
+
+static struct sst_device sst_dsp_device = {
+ .name = "Intel(R) SST LPE",
+ .dev = NULL,
+ .ops = &pcm_ops,
+ .compr_ops = &compr_ops,
+};
+
+/*
+ * sst_register - function to register DSP
+ *
+ * This functions registers DSP with the platform driver
+ */
+int sst_register(struct device *dev)
+{
+ int ret_val;
+
+ sst_dsp_device.dev = dev;
+ ret_val = sst_register_dsp(&sst_dsp_device);
+ if (ret_val)
+ dev_err(dev, "Unable to register DSP with platform driver\n");
+
+ return ret_val;
+}
+
+int sst_unregister(struct device *dev)
+{
+ return sst_unregister_dsp(&sst_dsp_device);
+}
diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c
new file mode 100644
index 000000000..a8a9aa005
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_ipc.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_ipc.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/intel-mid.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+struct sst_block *sst_create_block(struct intel_sst_drv *ctx,
+ u32 msg_id, u32 drv_id)
+{
+ struct sst_block *msg = NULL;
+
+ dev_dbg(ctx->dev, "Enter\n");
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg)
+ return NULL;
+ msg->condition = false;
+ msg->on = true;
+ msg->msg_id = msg_id;
+ msg->drv_id = drv_id;
+ spin_lock_bh(&ctx->block_lock);
+ list_add_tail(&msg->node, &ctx->block_list);
+ spin_unlock_bh(&ctx->block_lock);
+
+ return msg;
+}
+
+/*
+ * while handling the interrupts, we need to check for message status and
+ * then if we are blocking for a message
+ *
+ * here we are unblocking the blocked ones, this is based on id we have
+ * passed and search that for block threads.
+ * We will not find block in two cases
+ * a) when its small message and block in not there, so silently ignore
+ * them
+ * b) when we are actually not able to find the block (bug perhaps)
+ *
+ * Since we have bit of small messages we can spam kernel log with err
+ * print on above so need to keep as debug prints which should be enabled
+ * via dynamic debug while debugging IPC issues
+ */
+int sst_wake_up_block(struct intel_sst_drv *ctx, int result,
+ u32 drv_id, u32 ipc, void *data, u32 size)
+{
+ struct sst_block *block = NULL;
+
+ dev_dbg(ctx->dev, "Enter\n");
+
+ spin_lock_bh(&ctx->block_lock);
+ list_for_each_entry(block, &ctx->block_list, node) {
+ dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id,
+ block->drv_id);
+ if (block->msg_id == ipc && block->drv_id == drv_id) {
+ dev_dbg(ctx->dev, "free up the block\n");
+ block->ret_code = result;
+ block->data = data;
+ block->size = size;
+ block->condition = true;
+ spin_unlock_bh(&ctx->block_lock);
+ wake_up(&ctx->wait_queue);
+ return 0;
+ }
+ }
+ spin_unlock_bh(&ctx->block_lock);
+ dev_dbg(ctx->dev,
+ "Block not found or a response received for a short msg for ipc %d, drv_id %d\n",
+ ipc, drv_id);
+ return -EINVAL;
+}
+
+int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed)
+{
+ struct sst_block *block = NULL, *__block;
+
+ dev_dbg(ctx->dev, "Enter\n");
+ spin_lock_bh(&ctx->block_lock);
+ list_for_each_entry_safe(block, __block, &ctx->block_list, node) {
+ if (block == freed) {
+ pr_debug("pvt_id freed --> %d\n", freed->drv_id);
+ /* toggle the index position of pvt_id */
+ list_del(&freed->node);
+ spin_unlock_bh(&ctx->block_lock);
+ kfree(freed->data);
+ freed->data = NULL;
+ kfree(freed);
+ return 0;
+ }
+ }
+ spin_unlock_bh(&ctx->block_lock);
+ dev_err(ctx->dev, "block is already freed!!!\n");
+ return -EINVAL;
+}
+
+int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ struct ipc_post *ipc_msg, bool sync)
+{
+ struct ipc_post *msg = ipc_msg;
+ union ipc_header_mrfld header;
+ unsigned int loop_count = 0;
+ int retval = 0;
+ unsigned long irq_flags;
+
+ dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync);
+ spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+ header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
+ if (sync) {
+ while (header.p.header_high.part.busy) {
+ if (loop_count > 25) {
+ dev_err(sst_drv_ctx->dev,
+ "sst: Busy wait failed, cant send this msg\n");
+ retval = -EBUSY;
+ goto out;
+ }
+ cpu_relax();
+ loop_count++;
+ header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
+ }
+ } else {
+ if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) {
+ /* queue is empty, nothing to send */
+ spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+ dev_dbg(sst_drv_ctx->dev,
+ "Empty msg queue... NO Action\n");
+ return 0;
+ }
+
+ if (header.p.header_high.part.busy) {
+ spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+ dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n");
+ return 0;
+ }
+
+ /* copy msg from list */
+ msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next,
+ struct ipc_post, node);
+ list_del(&msg->node);
+ }
+ dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n",
+ msg->mrfld_header.p.header_high.full);
+ dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n",
+ msg->mrfld_header.p.header_low_payload);
+
+ if (msg->mrfld_header.p.header_high.part.large)
+ memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND,
+ msg->mailbox_data,
+ msg->mrfld_header.p.header_low_payload);
+
+ sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full);
+
+out:
+ spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+ kfree(msg->mailbox_data);
+ kfree(msg);
+ return retval;
+}
+
+void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx)
+{
+ union interrupt_reg_mrfld isr;
+ union interrupt_reg_mrfld imr;
+ union ipc_header_mrfld clear_ipc;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+ imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX);
+ isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX);
+
+ /* write 1 to clear*/
+ isr.part.busy_interrupt = 1;
+ sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full);
+
+ /* Set IA done bit */
+ clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD);
+
+ clear_ipc.p.header_high.part.busy = 0;
+ clear_ipc.p.header_high.part.done = 1;
+ clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS;
+ sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full);
+ /* un mask busy interrupt */
+ imr.part.busy_interrupt = 0;
+ sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full);
+ spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags);
+}
+
+
+/*
+ * process_fw_init - process the FW init msg
+ *
+ * @msg: IPC message mailbox data from FW
+ *
+ * This function processes the FW init msg from FW
+ * marks FW state and prints debug info of loaded FW
+ */
+static void process_fw_init(struct intel_sst_drv *sst_drv_ctx,
+ void *msg)
+{
+ struct ipc_header_fw_init *init =
+ (struct ipc_header_fw_init *)msg;
+ int retval = 0;
+
+ dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n");
+ if (init->result) {
+ sst_set_fw_state_locked(sst_drv_ctx, SST_RESET);
+ dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n",
+ init->result);
+ retval = init->result;
+ goto ret;
+ }
+ if (memcmp(&sst_drv_ctx->fw_version, &init->fw_version,
+ sizeof(init->fw_version)))
+ dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n",
+ init->fw_version.type, init->fw_version.major,
+ init->fw_version.minor, init->fw_version.build);
+ dev_dbg(sst_drv_ctx->dev, "Build date %s Time %s\n",
+ init->build_info.date, init->build_info.time);
+
+ /* Save FW version */
+ sst_drv_ctx->fw_version.type = init->fw_version.type;
+ sst_drv_ctx->fw_version.major = init->fw_version.major;
+ sst_drv_ctx->fw_version.minor = init->fw_version.minor;
+ sst_drv_ctx->fw_version.build = init->fw_version.build;
+
+ret:
+ sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0);
+}
+
+static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx,
+ struct ipc_post *msg)
+{
+ u32 msg_id;
+ int str_id;
+ u32 data_size, i;
+ void *data_offset;
+ struct stream_info *stream;
+ u32 msg_low, pipe_id;
+
+ msg_low = msg->mrfld_header.p.header_low_payload;
+ msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id;
+ data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr));
+ data_size = msg_low - (sizeof(struct ipc_dsp_hdr));
+
+ switch (msg_id) {
+ case IPC_SST_PERIOD_ELAPSED_MRFLD:
+ pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
+ str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
+ if (str_id > 0) {
+ dev_dbg(sst_drv_ctx->dev,
+ "Period elapsed rcvd for pipe id 0x%x\n",
+ pipe_id);
+ stream = &sst_drv_ctx->streams[str_id];
+ /* If stream is dropped, skip processing this message*/
+ if (stream->status == STREAM_INIT)
+ break;
+ if (stream->period_elapsed)
+ stream->period_elapsed(stream->pcm_substream);
+ if (stream->compr_cb)
+ stream->compr_cb(stream->compr_cb_param);
+ }
+ break;
+
+ case IPC_IA_DRAIN_STREAM_MRFLD:
+ pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
+ str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
+ if (str_id > 0) {
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->drain_notify)
+ stream->drain_notify(stream->drain_cb_param);
+ }
+ break;
+
+ case IPC_IA_FW_ASYNC_ERR_MRFLD:
+ dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n");
+ for (i = 0; i < (data_size/4); i++)
+ print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE,
+ 16, 4, data_offset, data_size, false);
+ break;
+
+ case IPC_IA_FW_INIT_CMPLT_MRFLD:
+ process_fw_init(sst_drv_ctx, data_offset);
+ break;
+
+ case IPC_IA_BUF_UNDER_RUN_MRFLD:
+ pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id;
+ str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id);
+ if (str_id > 0)
+ dev_err(sst_drv_ctx->dev,
+ "Buffer under-run for pipe:%#x str_id:%d\n",
+ pipe_id, str_id);
+ break;
+
+ default:
+ dev_err(sst_drv_ctx->dev,
+ "Unrecognized async msg from FW msg_id %#x\n", msg_id);
+ }
+}
+
+void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ struct ipc_post *msg)
+{
+ unsigned int drv_id;
+ void *data;
+ union ipc_header_high msg_high;
+ u32 msg_low;
+ struct ipc_dsp_hdr *dsp_hdr;
+
+ msg_high = msg->mrfld_header.p.header_high;
+ msg_low = msg->mrfld_header.p.header_low_payload;
+
+ dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n",
+ msg->mrfld_header.p.header_high.full,
+ msg->mrfld_header.p.header_low_payload);
+
+ drv_id = msg_high.part.drv_id;
+
+ /* Check for async messages first */
+ if (drv_id == SST_ASYNC_DRV_ID) {
+ /*FW sent async large message*/
+ process_fw_async_msg(sst_drv_ctx, msg);
+ return;
+ }
+
+ /* FW sent short error response for an IPC */
+ if (msg_high.part.result && drv_id && !msg_high.part.large) {
+ /* 32-bit FW error code in msg_low */
+ dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low);
+ sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
+ msg_high.part.drv_id,
+ msg_high.part.msg_id, NULL, 0);
+ return;
+ }
+
+ /*
+ * Process all valid responses
+ * if it is a large message, the payload contains the size to
+ * copy from mailbox
+ **/
+ if (msg_high.part.large) {
+ data = kmemdup((void *)msg->mailbox_data, msg_low, GFP_KERNEL);
+ if (!data)
+ return;
+ /* Copy command id so that we can use to put sst to reset */
+ dsp_hdr = (struct ipc_dsp_hdr *)data;
+ dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id);
+ if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
+ msg_high.part.drv_id,
+ msg_high.part.msg_id, data, msg_low))
+ kfree(data);
+ } else {
+ sst_wake_up_block(sst_drv_ctx, msg_high.part.result,
+ msg_high.part.drv_id,
+ msg_high.part.msg_id, NULL, 0);
+ }
+
+}
diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c
new file mode 100644
index 000000000..1c9b0c9ec
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_loader.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_dsp.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains all dsp controlling functions like firmware download,
+ * setting/resetting dsp cores, etc
+ */
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/firmware.h>
+#include <linux/dmaengine.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+void memcpy32_toio(void __iomem *dst, const void *src, int count)
+{
+ /* __iowrite32_copy uses 32-bit count values so divide by 4 for
+ * right count in words
+ */
+ __iowrite32_copy(dst, src, count / 4);
+}
+
+void memcpy32_fromio(void *dst, const void __iomem *src, int count)
+{
+ /* __ioread32_copy uses 32-bit count values so divide by 4 for
+ * right count in words
+ */
+ __ioread32_copy(dst, src, count / 4);
+}
+
+/**
+ * intel_sst_reset_dsp_mrfld - Resetting SST DSP
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ *
+ * This resets DSP in case of MRFLD platfroms
+ */
+int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx)
+{
+ union config_status_reg_mrfld csr;
+
+ dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n");
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+
+ dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
+
+ csr.full |= 0x7;
+ sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+
+ dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
+
+ csr.full &= ~(0x1);
+ sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+ dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
+ return 0;
+}
+
+/**
+ * sst_start_merrifield - Start the SST DSP processor
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ *
+ * This starts the DSP in MERRIFIELD platfroms
+ */
+int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx)
+{
+ union config_status_reg_mrfld csr;
+
+ dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n");
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+ dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
+
+ csr.full |= 0x7;
+ sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+ dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
+
+ csr.part.xt_snoop = 1;
+ csr.full &= ~(0x5);
+ sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
+ dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n",
+ csr.full);
+ return 0;
+}
+
+static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size,
+ struct fw_module_header **module, u32 *num_modules)
+{
+ struct sst_fw_header *header;
+ const void *sst_fw_in_mem = ctx->fw_in_mem;
+
+ dev_dbg(ctx->dev, "Enter\n");
+
+ /* Read the header information from the data pointer */
+ header = (struct sst_fw_header *)sst_fw_in_mem;
+ dev_dbg(ctx->dev,
+ "header sign=%s size=%x modules=%x fmt=%x size=%zx\n",
+ header->signature, header->file_size, header->modules,
+ header->file_format, sizeof(*header));
+
+ /* verify FW */
+ if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) ||
+ (size != header->file_size + sizeof(*header))) {
+ /* Invalid FW signature */
+ dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n");
+ return -EINVAL;
+ }
+ *num_modules = header->modules;
+ *module = (void *)sst_fw_in_mem + sizeof(*header);
+
+ return 0;
+}
+
+/*
+ * sst_fill_memcpy_list - Fill the memcpy list
+ *
+ * @memcpy_list: List to be filled
+ * @destn: Destination addr to be filled in the list
+ * @src: Source addr to be filled in the list
+ * @size: Size to be filled in the list
+ *
+ * Adds the node to the list after required fields
+ * are populated in the node
+ */
+static int sst_fill_memcpy_list(struct list_head *memcpy_list,
+ void *destn, const void *src, u32 size, bool is_io)
+{
+ struct sst_memcpy_list *listnode;
+
+ listnode = kzalloc(sizeof(*listnode), GFP_KERNEL);
+ if (listnode == NULL)
+ return -ENOMEM;
+ listnode->dstn = destn;
+ listnode->src = src;
+ listnode->size = size;
+ listnode->is_io = is_io;
+ list_add_tail(&listnode->memcpylist, memcpy_list);
+
+ return 0;
+}
+
+/**
+ * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list
+ *
+ * @sst_drv_ctx : driver context
+ * @module : FW module header
+ * @memcpy_list : Pointer to the list to be populated
+ * Create the memcpy list as the number of block to be copied
+ * returns error or 0 if module sizes are proper
+ */
+static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx,
+ struct fw_module_header *module, struct list_head *memcpy_list)
+{
+ struct fw_block_info *block;
+ u32 count;
+ int ret_val = 0;
+ void __iomem *ram_iomem;
+
+ dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n",
+ module->signature, module->mod_size,
+ module->blocks, module->type);
+ dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point);
+
+ block = (void *)module + sizeof(*module);
+
+ for (count = 0; count < module->blocks; count++) {
+ if (block->size <= 0) {
+ dev_err(sst_drv_ctx->dev, "block size invalid\n");
+ return -EINVAL;
+ }
+ switch (block->type) {
+ case SST_IRAM:
+ ram_iomem = sst_drv_ctx->iram;
+ break;
+ case SST_DRAM:
+ ram_iomem = sst_drv_ctx->dram;
+ break;
+ case SST_DDR:
+ ram_iomem = sst_drv_ctx->ddr;
+ break;
+ case SST_CUSTOM_INFO:
+ block = (void *)block + sizeof(*block) + block->size;
+ continue;
+ default:
+ dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n",
+ block->type, count);
+ return -EINVAL;
+ }
+
+ ret_val = sst_fill_memcpy_list(memcpy_list,
+ ram_iomem + block->ram_offset,
+ (void *)block + sizeof(*block), block->size, 1);
+ if (ret_val)
+ return ret_val;
+
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ return 0;
+}
+
+/**
+ * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy
+ *
+ * @ctx : pointer to drv context
+ * @size : size of the firmware
+ * @fw_list : pointer to list_head to be populated
+ * This function parses the FW image and saves the parsed image in the list
+ * for memcpy
+ */
+static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size,
+ struct list_head *fw_list)
+{
+ struct fw_module_header *module;
+ u32 count, num_modules;
+ int ret_val;
+
+ ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules);
+ if (ret_val)
+ return ret_val;
+
+ for (count = 0; count < num_modules; count++) {
+ ret_val = sst_parse_module_memcpy(ctx, module, fw_list);
+ if (ret_val)
+ return ret_val;
+ module = (void *)module + sizeof(*module) + module->mod_size;
+ }
+
+ return 0;
+}
+
+/**
+ * sst_do_memcpy - function initiates the memcpy
+ *
+ * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated
+ *
+ * Triggers the memcpy
+ */
+static void sst_do_memcpy(struct list_head *memcpy_list)
+{
+ struct sst_memcpy_list *listnode;
+
+ list_for_each_entry(listnode, memcpy_list, memcpylist) {
+ if (listnode->is_io)
+ memcpy32_toio((void __iomem *)listnode->dstn,
+ listnode->src, listnode->size);
+ else
+ memcpy(listnode->dstn, listnode->src, listnode->size);
+ }
+}
+
+void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx)
+{
+ struct sst_memcpy_list *listnode, *tmplistnode;
+
+ /* Free the list */
+ list_for_each_entry_safe(listnode, tmplistnode,
+ &sst_drv_ctx->memcpy_list, memcpylist) {
+ list_del(&listnode->memcpylist);
+ kfree(listnode);
+ }
+}
+
+static int sst_cache_and_parse_fw(struct intel_sst_drv *sst,
+ const struct firmware *fw)
+{
+ int retval = 0;
+
+ sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
+ if (!sst->fw_in_mem) {
+ retval = -ENOMEM;
+ goto end_release;
+ }
+ dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem);
+ dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem));
+ memcpy(sst->fw_in_mem, fw->data, fw->size);
+ retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list);
+ if (retval) {
+ dev_err(sst->dev, "Failed to parse fw\n");
+ kfree(sst->fw_in_mem);
+ sst->fw_in_mem = NULL;
+ }
+
+end_release:
+ release_firmware(fw);
+ return retval;
+
+}
+
+void sst_firmware_load_cb(const struct firmware *fw, void *context)
+{
+ struct intel_sst_drv *ctx = context;
+
+ dev_dbg(ctx->dev, "Enter\n");
+
+ if (fw == NULL) {
+ dev_err(ctx->dev, "request fw failed\n");
+ return;
+ }
+
+ mutex_lock(&ctx->sst_lock);
+
+ if (ctx->sst_state != SST_RESET ||
+ ctx->fw_in_mem != NULL) {
+ release_firmware(fw);
+ mutex_unlock(&ctx->sst_lock);
+ return;
+ }
+
+ dev_dbg(ctx->dev, "Request Fw completed\n");
+ sst_cache_and_parse_fw(ctx, fw);
+ mutex_unlock(&ctx->sst_lock);
+}
+
+/*
+ * sst_request_fw - requests audio fw from kernel and saves a copy
+ *
+ * This function requests the SST FW from the kernel, parses it and
+ * saves a copy in the driver context
+ */
+static int sst_request_fw(struct intel_sst_drv *sst)
+{
+ int retval = 0;
+ const struct firmware *fw;
+
+ retval = request_firmware(&fw, sst->firmware_name, sst->dev);
+ if (retval) {
+ dev_err(sst->dev, "request fw failed %d\n", retval);
+ return retval;
+ }
+ if (fw == NULL) {
+ dev_err(sst->dev, "fw is returning as null\n");
+ return -EINVAL;
+ }
+ mutex_lock(&sst->sst_lock);
+ retval = sst_cache_and_parse_fw(sst, fw);
+ mutex_unlock(&sst->sst_lock);
+
+ return retval;
+}
+
+/*
+ * Writing the DDR physical base to DCCM offset
+ * so that FW can use it to setup TLB
+ */
+static void sst_dccm_config_write(void __iomem *dram_base,
+ unsigned int ddr_base)
+{
+ void __iomem *addr;
+ u32 bss_reset = 0;
+
+ addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET);
+ memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32));
+ bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT);
+ addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET);
+ memcpy32_toio(addr, &bss_reset, sizeof(u32));
+
+}
+
+void sst_post_download_mrfld(struct intel_sst_drv *ctx)
+{
+ sst_dccm_config_write(ctx->dram, ctx->ddr_base);
+ dev_dbg(ctx->dev, "config written to DCCM\n");
+}
+
+/**
+ * sst_load_fw - function to load FW into DSP
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ *
+ * Transfers the FW to DSP using dma/memcpy
+ */
+int sst_load_fw(struct intel_sst_drv *sst_drv_ctx)
+{
+ int ret_val = 0;
+ struct sst_block *block;
+
+ dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n");
+
+ if (sst_drv_ctx->sst_state != SST_RESET)
+ return -EAGAIN;
+
+ if (!sst_drv_ctx->fw_in_mem) {
+ dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n");
+ ret_val = sst_request_fw(sst_drv_ctx);
+ if (ret_val)
+ return ret_val;
+ }
+
+ block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID);
+ if (block == NULL)
+ return -ENOMEM;
+
+ /* Prevent C-states beyond C6 */
+ cpu_latency_qos_update_request(sst_drv_ctx->qos, 0);
+
+ sst_drv_ctx->sst_state = SST_FW_LOADING;
+
+ ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx);
+ if (ret_val)
+ goto restore;
+
+ sst_do_memcpy(&sst_drv_ctx->memcpy_list);
+
+ /* Write the DRAM/DCCM config before enabling FW */
+ if (sst_drv_ctx->ops->post_download)
+ sst_drv_ctx->ops->post_download(sst_drv_ctx);
+
+ /* bring sst out of reset */
+ ret_val = sst_drv_ctx->ops->start(sst_drv_ctx);
+ if (ret_val)
+ goto restore;
+
+ ret_val = sst_wait_timeout(sst_drv_ctx, block);
+ if (ret_val) {
+ dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val);
+ /* FW download failed due to timeout */
+ ret_val = -EBUSY;
+
+ }
+
+
+restore:
+ /* Re-enable Deeper C-states beyond C6 */
+ cpu_latency_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE);
+ sst_free_block(sst_drv_ctx, block);
+ dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n");
+
+ if (sst_drv_ctx->ops->restore_dsp_context)
+ sst_drv_ctx->ops->restore_dsp_context();
+ sst_drv_ctx->sst_state = SST_FW_RUNNING;
+ return ret_val;
+}
+
diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c
new file mode 100644
index 000000000..5862fe968
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_pci.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_pci.c - SST (LPE) driver init file for pci enumeration.
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+static int sst_platform_get_resources(struct intel_sst_drv *ctx)
+{
+ int ddr_base, ret = 0;
+ struct pci_dev *pci = ctx->pci;
+
+ ret = pci_request_regions(pci, SST_DRV_NAME);
+ if (ret)
+ return ret;
+
+ /* map registers */
+ /* DDR base */
+ if (ctx->dev_id == SST_MRFLD_PCI_ID) {
+ ctx->ddr_base = pci_resource_start(pci, 0);
+ /* check that the relocated IMR base matches with FW Binary */
+ ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base);
+ if (!ctx->pdata->lib_info) {
+ dev_err(ctx->dev, "lib_info pointer NULL\n");
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ if (ddr_base != ctx->pdata->lib_info->mod_base) {
+ dev_err(ctx->dev,
+ "FW LSP DDR BASE does not match with IFWI\n");
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ ctx->ddr_end = pci_resource_end(pci, 0);
+
+ ctx->ddr = pcim_iomap(pci, 0,
+ pci_resource_len(pci, 0));
+ if (!ctx->ddr) {
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr);
+ } else {
+ ctx->ddr = NULL;
+ }
+ /* SHIM */
+ ctx->shim_phy_add = pci_resource_start(pci, 1);
+ ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1));
+ if (!ctx->shim) {
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim);
+
+ /* Shared SRAM */
+ ctx->mailbox_add = pci_resource_start(pci, 2);
+ ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2));
+ if (!ctx->mailbox) {
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox);
+
+ /* IRAM */
+ ctx->iram_end = pci_resource_end(pci, 3);
+ ctx->iram_base = pci_resource_start(pci, 3);
+ ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3));
+ if (!ctx->iram) {
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram);
+
+ /* DRAM */
+ ctx->dram_end = pci_resource_end(pci, 4);
+ ctx->dram_base = pci_resource_start(pci, 4);
+ ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4));
+ if (!ctx->dram) {
+ ret = -EINVAL;
+ goto do_release_regions;
+ }
+ dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram);
+do_release_regions:
+ pci_release_regions(pci);
+ return ret;
+}
+
+/*
+ * intel_sst_probe - PCI probe function
+ *
+ * @pci: PCI device structure
+ * @pci_id: PCI device ID structure
+ *
+ */
+static int intel_sst_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ int ret = 0;
+ struct intel_sst_drv *sst_drv_ctx;
+ struct sst_platform_info *sst_pdata = pci->dev.platform_data;
+
+ dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device);
+ ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device);
+ if (ret < 0)
+ return ret;
+
+ sst_drv_ctx->pdata = sst_pdata;
+ sst_drv_ctx->irq_num = pci->irq;
+ snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name),
+ "%s%04x%s", "fw_sst_",
+ sst_drv_ctx->dev_id, ".bin");
+
+ ret = sst_context_init(sst_drv_ctx);
+ if (ret < 0)
+ return ret;
+
+ /* Init the device */
+ ret = pcim_enable_device(pci);
+ if (ret) {
+ dev_err(sst_drv_ctx->dev,
+ "device can't be enabled. Returned err: %d\n", ret);
+ goto do_free_drv_ctx;
+ }
+ sst_drv_ctx->pci = pci_dev_get(pci);
+ ret = sst_platform_get_resources(sst_drv_ctx);
+ if (ret < 0)
+ goto do_free_drv_ctx;
+
+ pci_set_drvdata(pci, sst_drv_ctx);
+ sst_configure_runtime_pm(sst_drv_ctx);
+
+ return ret;
+
+do_free_drv_ctx:
+ sst_context_cleanup(sst_drv_ctx);
+ dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret);
+ return ret;
+}
+
+/**
+ * intel_sst_remove - PCI remove function
+ *
+ * @pci: PCI device structure
+ *
+ * This function is called by OS when a device is unloaded
+ * This frees the interrupt etc
+ */
+static void intel_sst_remove(struct pci_dev *pci)
+{
+ struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci);
+
+ sst_context_cleanup(sst_drv_ctx);
+ pci_dev_put(sst_drv_ctx->pci);
+ pci_release_regions(pci);
+ pci_set_drvdata(pci, NULL);
+}
+
+/* PCI Routines */
+static const struct pci_device_id intel_sst_ids[] = {
+ { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0},
+ { 0, }
+};
+
+static struct pci_driver sst_driver = {
+ .name = SST_DRV_NAME,
+ .id_table = intel_sst_ids,
+ .probe = intel_sst_probe,
+ .remove = intel_sst_remove,
+#ifdef CONFIG_PM
+ .driver = {
+ .pm = &intel_sst_pm,
+ },
+#endif
+};
+
+module_pci_driver(sst_driver);
+
+MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_AUTHOR("Dharageswari R <dharageswari.r@intel.com>");
+MODULE_AUTHOR("KP Jeeja <jeeja.kp@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("sst");
diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c
new file mode 100644
index 000000000..e6a5c18a7
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_pvt.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_pvt.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kobject.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <sound/asound.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+int sst_shim_write(void __iomem *addr, int offset, int value)
+{
+ writel(value, addr + offset);
+ return 0;
+}
+
+u32 sst_shim_read(void __iomem *addr, int offset)
+{
+ return readl(addr + offset);
+}
+
+u64 sst_reg_read64(void __iomem *addr, int offset)
+{
+ u64 val = 0;
+
+ memcpy_fromio(&val, addr + offset, sizeof(val));
+
+ return val;
+}
+
+int sst_shim_write64(void __iomem *addr, int offset, u64 value)
+{
+ memcpy_toio(addr + offset, &value, sizeof(value));
+ return 0;
+}
+
+u64 sst_shim_read64(void __iomem *addr, int offset)
+{
+ u64 val = 0;
+
+ memcpy_fromio(&val, addr + offset, sizeof(val));
+ return val;
+}
+
+void sst_set_fw_state_locked(
+ struct intel_sst_drv *sst_drv_ctx, int sst_state)
+{
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = sst_state;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+}
+
+/*
+ * sst_wait_interruptible - wait on event
+ *
+ * @sst_drv_ctx: Driver context
+ * @block: Driver block to wait on
+ *
+ * This function waits without a timeout (and is interruptable) for a
+ * given block event
+ */
+int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block)
+{
+ int retval = 0;
+
+ if (!wait_event_interruptible(sst_drv_ctx->wait_queue,
+ block->condition)) {
+ /* event wake */
+ if (block->ret_code < 0) {
+ dev_err(sst_drv_ctx->dev,
+ "stream failed %d\n", block->ret_code);
+ retval = -EBUSY;
+ } else {
+ dev_dbg(sst_drv_ctx->dev, "event up\n");
+ retval = 0;
+ }
+ } else {
+ dev_err(sst_drv_ctx->dev, "signal interrupted\n");
+ retval = -EINTR;
+ }
+ return retval;
+
+}
+
+/*
+ * sst_wait_timeout - wait on event for timeout
+ *
+ * @sst_drv_ctx: Driver context
+ * @block: Driver block to wait on
+ *
+ * This function waits with a timeout value (and is not interruptible) on a
+ * given block event
+ */
+int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block)
+{
+ int retval = 0;
+
+ /*
+ * NOTE:
+ * Observed that FW processes the alloc msg and replies even
+ * before the alloc thread has finished execution
+ */
+ dev_dbg(sst_drv_ctx->dev,
+ "waiting for condition %x ipc %d drv_id %d\n",
+ block->condition, block->msg_id, block->drv_id);
+ if (wait_event_timeout(sst_drv_ctx->wait_queue,
+ block->condition,
+ msecs_to_jiffies(SST_BLOCK_TIMEOUT))) {
+ /* event wake */
+ dev_dbg(sst_drv_ctx->dev, "Event wake %x\n",
+ block->condition);
+ dev_dbg(sst_drv_ctx->dev, "message ret: %d\n",
+ block->ret_code);
+ retval = -block->ret_code;
+ } else {
+ block->on = false;
+ dev_err(sst_drv_ctx->dev,
+ "Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n",
+ block->condition, block->msg_id, sst_drv_ctx->sst_state);
+ sst_drv_ctx->sst_state = SST_RESET;
+
+ retval = -EBUSY;
+ }
+ return retval;
+}
+
+/*
+ * sst_create_ipc_msg - create a IPC message
+ *
+ * @arg: ipc message
+ * @large: large or short message
+ *
+ * this function allocates structures to send a large or short
+ * message to the firmware
+ */
+int sst_create_ipc_msg(struct ipc_post **arg, bool large)
+{
+ struct ipc_post *msg;
+
+ msg = kzalloc(sizeof(*msg), GFP_ATOMIC);
+ if (!msg)
+ return -ENOMEM;
+ if (large) {
+ msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC);
+ if (!msg->mailbox_data) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+ } else {
+ msg->mailbox_data = NULL;
+ }
+ msg->is_large = large;
+ *arg = msg;
+ return 0;
+}
+
+/*
+ * sst_create_block_and_ipc_msg - Creates IPC message and sst block
+ * @arg: passed to sst_create_ipc_message API
+ * @large: large or short message
+ * @sst_drv_ctx: sst driver context
+ * @block: return block allocated
+ * @msg_id: IPC
+ * @drv_id: stream id or private id
+ */
+int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large,
+ struct intel_sst_drv *sst_drv_ctx, struct sst_block **block,
+ u32 msg_id, u32 drv_id)
+{
+ int retval;
+
+ retval = sst_create_ipc_msg(arg, large);
+ if (retval)
+ return retval;
+ *block = sst_create_block(sst_drv_ctx, msg_id, drv_id);
+ if (*block == NULL) {
+ kfree(*arg);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ * sst_clean_stream - clean the stream context
+ *
+ * @stream: stream structure
+ *
+ * this function resets the stream contexts
+ * should be called in free
+ */
+void sst_clean_stream(struct stream_info *stream)
+{
+ stream->status = STREAM_UN_INIT;
+ stream->prev = STREAM_UN_INIT;
+ mutex_lock(&stream->lock);
+ stream->cumm_bytes = 0;
+ mutex_unlock(&stream->lock);
+}
+
+int sst_prepare_and_post_msg(struct intel_sst_drv *sst,
+ int task_id, int ipc_msg, int cmd_id, int pipe_id,
+ size_t mbox_data_len, const void *mbox_data, void **data,
+ bool large, bool fill_dsp, bool sync, bool response)
+{
+ struct sst_block *block = NULL;
+ struct ipc_post *msg = NULL;
+ struct ipc_dsp_hdr dsp_hdr;
+ int ret = 0, pvt_id;
+
+ pvt_id = sst_assign_pvt_id(sst);
+ if (pvt_id < 0)
+ return pvt_id;
+
+ if (response)
+ ret = sst_create_block_and_ipc_msg(
+ &msg, large, sst, &block, ipc_msg, pvt_id);
+ else
+ ret = sst_create_ipc_msg(&msg, large);
+
+ if (ret < 0) {
+ test_and_clear_bit(pvt_id, &sst->pvt_id);
+ return -ENOMEM;
+ }
+
+ dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n",
+ pvt_id, pipe_id, task_id, ipc_msg);
+ sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg,
+ task_id, large, pvt_id);
+ msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len;
+ msg->mrfld_header.p.header_high.part.res_rqd = !sync;
+ dev_dbg(sst->dev, "header:%x\n",
+ msg->mrfld_header.p.header_high.full);
+ dev_dbg(sst->dev, "response rqd: %x",
+ msg->mrfld_header.p.header_high.part.res_rqd);
+ dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d",
+ msg->mrfld_header.p.header_low_payload);
+ if (fill_dsp) {
+ sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len);
+ memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr));
+ if (mbox_data_len) {
+ memcpy(msg->mailbox_data + sizeof(dsp_hdr),
+ mbox_data, mbox_data_len);
+ }
+ }
+
+ if (sync)
+ sst->ops->post_message(sst, msg, true);
+ else
+ sst_add_to_dispatch_list_and_post(sst, msg);
+
+ if (response) {
+ ret = sst_wait_timeout(sst, block);
+ if (ret < 0)
+ goto out;
+
+ if (data && block->data) {
+ *data = kmemdup(block->data, block->size, GFP_KERNEL);
+ if (!*data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+ }
+out:
+ if (response)
+ sst_free_block(sst, block);
+ test_and_clear_bit(pvt_id, &sst->pvt_id);
+ return ret;
+}
+
+int sst_pm_runtime_put(struct intel_sst_drv *sst_drv)
+{
+ int ret;
+
+ pm_runtime_mark_last_busy(sst_drv->dev);
+ ret = pm_runtime_put_autosuspend(sst_drv->dev);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+void sst_fill_header_mrfld(union ipc_header_mrfld *header,
+ int msg, int task_id, int large, int drv_id)
+{
+ header->full = 0;
+ header->p.header_high.part.msg_id = msg;
+ header->p.header_high.part.task_id = task_id;
+ header->p.header_high.part.large = large;
+ header->p.header_high.part.drv_id = drv_id;
+ header->p.header_high.part.done = 0;
+ header->p.header_high.part.busy = 1;
+ header->p.header_high.part.res_rqd = 1;
+}
+
+void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg,
+ int pipe_id, int len)
+{
+ dsp->cmd_id = msg;
+ dsp->mod_index_id = 0xff;
+ dsp->pipe_id = pipe_id;
+ dsp->length = len;
+ dsp->mod_id = 0;
+}
+
+#define SST_MAX_BLOCKS 15
+/*
+ * sst_assign_pvt_id - assign a pvt id for stream
+ *
+ * @sst_drv_ctx : driver context
+ *
+ * this function assigns a private id for calls that dont have stream
+ * context yet, should be called with lock held
+ * uses bits for the id, and finds first free bits and assigns that
+ */
+int sst_assign_pvt_id(struct intel_sst_drv *drv)
+{
+ int local;
+
+ spin_lock(&drv->block_lock);
+ /* find first zero index from lsb */
+ local = ffz(drv->pvt_id);
+ dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local);
+ if (local >= SST_MAX_BLOCKS){
+ spin_unlock(&drv->block_lock);
+ dev_err(drv->dev, "PVT _ID error: no free id blocks ");
+ return -EINVAL;
+ }
+ /* toggle the index */
+ change_bit(local, &drv->pvt_id);
+ spin_unlock(&drv->block_lock);
+ return local;
+}
+
+int sst_validate_strid(
+ struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) {
+ dev_err(sst_drv_ctx->dev,
+ "SST ERR: invalid stream id : %d, max %d\n",
+ str_id, sst_drv_ctx->info.max_streams);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct stream_info *get_stream_info(
+ struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ if (sst_validate_strid(sst_drv_ctx, str_id))
+ return NULL;
+ return &sst_drv_ctx->streams[str_id];
+}
+
+int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ u32 pipe_id)
+{
+ int i;
+
+ for (i = 1; i <= sst_drv_ctx->info.max_streams; i++)
+ if (pipe_id == sst_drv_ctx->streams[i].pipe_id)
+ return i;
+
+ dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id);
+ return -1;
+}
+
+u32 relocate_imr_addr_mrfld(u32 base_addr)
+{
+ /* Get the difference from 512MB aligned base addr */
+ /* relocate the base */
+ base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024));
+ return base_addr;
+}
+EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld);
+
+void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst,
+ struct ipc_post *msg)
+{
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags);
+ list_add_tail(&msg->node, &sst->ipc_dispatch_list);
+ spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags);
+ sst->ops->post_message(sst, NULL, false);
+}
diff --git a/sound/soc/intel/atom/sst/sst_stream.c b/sound/soc/intel/atom/sst/sst_stream.c
new file mode 100644
index 000000000..ea1ef8a61
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_stream.c
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sst_stream.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-14 Intel Corp
+ * Authors: Vinod Koul <vinod.koul@intel.com>
+ * Harsha Priya <priya.harsha@intel.com>
+ * Dharageswari R <dharageswari.r@intel.com>
+ * KP Jeeja <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+
+int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params)
+{
+ struct snd_pcm_params *pcm_params;
+ struct snd_sst_params *str_params;
+ struct snd_sst_tstamp fw_tstamp;
+ struct stream_info *str_info;
+ int i, num_ch, str_id;
+
+ dev_dbg(sst_drv_ctx->dev, "Enter\n");
+
+ str_params = (struct snd_sst_params *)params;
+ str_id = str_params->stream_id;
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ memset(&str_info->alloc_param, 0, sizeof(str_info->alloc_param));
+ str_info->alloc_param.operation = str_params->ops;
+ str_info->alloc_param.codec_type = str_params->codec;
+ str_info->alloc_param.sg_count = str_params->aparams.sg_count;
+ str_info->alloc_param.ring_buf_info[0].addr =
+ str_params->aparams.ring_buf_info[0].addr;
+ str_info->alloc_param.ring_buf_info[0].size =
+ str_params->aparams.ring_buf_info[0].size;
+ str_info->alloc_param.frag_size = str_params->aparams.frag_size;
+
+ memcpy(&str_info->alloc_param.codec_params, &str_params->sparams,
+ sizeof(struct snd_sst_stream_params));
+
+ /*
+ * fill channel map params for multichannel support.
+ * Ideally channel map should be received from upper layers
+ * for multichannel support.
+ * Currently hardcoding as per FW reqm.
+ */
+ num_ch = sst_get_num_channel(str_params);
+ pcm_params = &str_info->alloc_param.codec_params.uc.pcm_params;
+ for (i = 0; i < 8; i++) {
+ if (i < num_ch)
+ pcm_params->channel_map[i] = i;
+ else
+ pcm_params->channel_map[i] = 0xff;
+ }
+
+ sst_drv_ctx->streams[str_id].status = STREAM_INIT;
+ sst_drv_ctx->streams[str_id].prev = STREAM_UN_INIT;
+ sst_drv_ctx->streams[str_id].pipe_id = str_params->device_type;
+ sst_drv_ctx->streams[str_id].task_id = str_params->task;
+ sst_drv_ctx->streams[str_id].num_ch = num_ch;
+
+ if (sst_drv_ctx->info.lpe_viewpt_rqd)
+ str_info->alloc_param.ts = sst_drv_ctx->info.mailbox_start +
+ sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
+ else
+ str_info->alloc_param.ts = sst_drv_ctx->mailbox_add +
+ sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
+
+ dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n",
+ str_info->alloc_param.ts);
+ dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n",
+ str_info->pipe_id, str_info->task_id);
+
+ return sst_realloc_stream(sst_drv_ctx, str_id);
+}
+
+/**
+ * sst_realloc_stream - Send msg for (re-)allocating a stream using the
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * Send a msg for (re-)allocating a stream using the parameters previously
+ * passed to sst_alloc_stream_mrfld() for the same stream ID.
+ * Return: 0 or negative errno value.
+ */
+int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ struct snd_sst_alloc_response *response;
+ struct stream_info *str_info;
+ void *data = NULL;
+ int ret;
+
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ dev_dbg(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n",
+ str_id, str_info->pipe_id);
+
+ ret = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
+ IPC_IA_ALLOC_STREAM_MRFLD, str_info->pipe_id,
+ sizeof(str_info->alloc_param), &str_info->alloc_param,
+ &data, true, true, false, true);
+
+ if (ret < 0) {
+ dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
+ /* alloc failed, so reset the state to uninit */
+ str_info->status = STREAM_UN_INIT;
+ str_id = ret;
+ } else if (data) {
+ response = (struct snd_sst_alloc_response *)data;
+ ret = response->str_type.result;
+ if (!ret)
+ goto out;
+ dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret);
+ if (ret == SST_ERR_STREAM_IN_USE) {
+ dev_err(sst_drv_ctx->dev,
+ "FW not in clean state, send free for:%d\n", str_id);
+ sst_free_stream(sst_drv_ctx, str_id);
+ }
+ str_id = -ret;
+ }
+out:
+ kfree(data);
+ return str_id;
+}
+
+/**
+ * sst_start_stream - Send msg for a starting stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to start
+ * a stream.
+ */
+int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+ u16 data = 0;
+
+ dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ if (str_info->status != STREAM_RUNNING)
+ return -EBADRQC;
+
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
+ IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id,
+ sizeof(u16), &data, NULL, true, true, true, false);
+
+ return retval;
+}
+
+int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx,
+ struct snd_sst_bytes_v2 *bytes)
+{ struct ipc_post *msg = NULL;
+ u32 length;
+ int pvt_id, ret = 0;
+ struct sst_block *block = NULL;
+
+ dev_dbg(sst_drv_ctx->dev,
+ "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n",
+ bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id,
+ bytes->pipe_id, bytes->len);
+
+ if (sst_create_ipc_msg(&msg, true))
+ return -ENOMEM;
+
+ pvt_id = sst_assign_pvt_id(sst_drv_ctx);
+ sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg,
+ bytes->task_id, 1, pvt_id);
+ msg->mrfld_header.p.header_high.part.res_rqd = bytes->block;
+ length = bytes->len;
+ msg->mrfld_header.p.header_low_payload = length;
+ dev_dbg(sst_drv_ctx->dev, "length is %d\n", length);
+ memcpy(msg->mailbox_data, &bytes->bytes, bytes->len);
+ if (bytes->block) {
+ block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id);
+ if (block == NULL) {
+ kfree(msg);
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+
+ sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg);
+ dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d",
+ msg->mrfld_header.p.header_low_payload);
+
+ if (bytes->block) {
+ ret = sst_wait_timeout(sst_drv_ctx, block);
+ if (ret) {
+ dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret);
+ sst_free_block(sst_drv_ctx, block);
+ goto out;
+ }
+ }
+ if (bytes->type == SND_SST_BYTES_GET) {
+ /*
+ * copy the reply and send back
+ * we need to update only sz and payload
+ */
+ if (bytes->block) {
+ unsigned char *r = block->data;
+
+ dev_dbg(sst_drv_ctx->dev, "read back %d bytes",
+ bytes->len);
+ memcpy(bytes->bytes, r, bytes->len);
+ }
+ }
+ if (bytes->block)
+ sst_free_block(sst_drv_ctx, block);
+out:
+ test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id);
+ return ret;
+}
+
+/**
+ * sst_pause_stream - Send msg for a pausing stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to pause
+ * an already running stream.
+ */
+int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ if (str_info->status == STREAM_PAUSED)
+ return 0;
+ if (str_info->status == STREAM_RUNNING ||
+ str_info->status == STREAM_INIT) {
+ if (str_info->prev == STREAM_UN_INIT)
+ return -EBADRQC;
+
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
+ IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id,
+ 0, NULL, NULL, true, true, false, true);
+
+ if (retval == 0) {
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_PAUSED;
+ } else if (retval == -SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ }
+ } else {
+ retval = -EBADRQC;
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n");
+ }
+
+ return retval;
+}
+
+/**
+ * sst_resume_stream - Send msg for resuming stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to resume
+ * an already paused stream.
+ */
+int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ if (str_info->status == STREAM_RUNNING)
+ return 0;
+
+ if (str_info->resume_status == STREAM_PAUSED &&
+ str_info->resume_prev == STREAM_RUNNING) {
+ /*
+ * Stream was running before suspend and re-created on resume,
+ * start it to get back to running state.
+ */
+ dev_dbg(sst_drv_ctx->dev, "restart recreated stream after resume\n");
+ str_info->status = STREAM_RUNNING;
+ str_info->prev = STREAM_PAUSED;
+ retval = sst_start_stream(sst_drv_ctx, str_id);
+ str_info->resume_status = STREAM_UN_INIT;
+ } else if (str_info->resume_status == STREAM_PAUSED &&
+ str_info->resume_prev == STREAM_INIT) {
+ /*
+ * Stream was idle before suspend and re-created on resume,
+ * keep it as is.
+ */
+ dev_dbg(sst_drv_ctx->dev, "leaving recreated stream idle after resume\n");
+ str_info->status = STREAM_INIT;
+ str_info->prev = STREAM_PAUSED;
+ str_info->resume_status = STREAM_UN_INIT;
+ } else if (str_info->status == STREAM_PAUSED) {
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
+ IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD,
+ str_info->pipe_id, 0, NULL, NULL,
+ true, true, false, true);
+
+ if (!retval) {
+ if (str_info->prev == STREAM_RUNNING)
+ str_info->status = STREAM_RUNNING;
+ else
+ str_info->status = STREAM_INIT;
+ str_info->prev = STREAM_PAUSED;
+ } else if (retval == -SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ }
+ } else {
+ retval = -EBADRQC;
+ dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n");
+ }
+
+ return retval;
+}
+
+
+/**
+ * sst_drop_stream - Send msg for stopping stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to stop
+ * a stream.
+ */
+int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ if (str_info->status != STREAM_UN_INIT) {
+ str_info->prev = STREAM_UN_INIT;
+ str_info->status = STREAM_INIT;
+ str_info->cumm_bytes = 0;
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id,
+ IPC_CMD, IPC_IA_DROP_STREAM_MRFLD,
+ str_info->pipe_id, 0, NULL, NULL,
+ true, true, true, false);
+ } else {
+ retval = -EBADRQC;
+ dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n",
+ str_info->status);
+ }
+ return retval;
+}
+
+/**
+ * sst_drain_stream - Send msg for draining stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ * @partial_drain: boolean indicating if a gapless transition is taking place
+ *
+ * This function is called by any function which wants to drain
+ * a stream.
+ */
+int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx,
+ int str_id, bool partial_drain)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+ if (str_info->status != STREAM_RUNNING &&
+ str_info->status != STREAM_INIT &&
+ str_info->status != STREAM_PAUSED) {
+ dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n",
+ str_info->status);
+ return -EBADRQC;
+ }
+
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
+ IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id,
+ sizeof(u8), &partial_drain, NULL, true, true, false, false);
+ /*
+ * with new non blocked drain implementation in core we dont need to
+ * wait for respsonse, and need to only invoke callback for drain
+ * complete
+ */
+
+ return retval;
+}
+
+/**
+ * sst_free_stream - Frees a stream
+ * @sst_drv_ctx: intel_sst_drv context pointer
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to free
+ * a stream.
+ */
+int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id);
+
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ if (sst_drv_ctx->sst_state == SST_RESET) {
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ return -ENODEV;
+ }
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (!str_info)
+ return -EINVAL;
+
+ mutex_lock(&str_info->lock);
+ if (str_info->status != STREAM_UN_INIT) {
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_UN_INIT;
+ mutex_unlock(&str_info->lock);
+
+ dev_dbg(sst_drv_ctx->dev, "Free for str %d pipe %#x\n",
+ str_id, str_info->pipe_id);
+ retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD,
+ IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0,
+ NULL, NULL, true, true, false, true);
+
+ dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n",
+ retval);
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n");
+ } else {
+ mutex_unlock(&str_info->lock);
+ retval = -EBADRQC;
+ dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n");
+ }
+
+ return retval;
+}
diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig
new file mode 100644
index 000000000..c10c37803
--- /dev/null
+++ b/sound/soc/intel/boards/Kconfig
@@ -0,0 +1,563 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig SND_SOC_INTEL_MACH
+ bool "Intel Machine drivers"
+ depends on SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL
+ help
+ Intel ASoC Machine Drivers. If you have a Intel machine that
+ has an audio controller with a DSP and I2S or DMIC port, then
+ enable this option by saying Y
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Intel ASoC machine drivers.
+
+if SND_SOC_INTEL_MACH
+
+config SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES
+ bool "Use more user friendly long card names"
+ help
+ Some drivers report the I/O configuration to userspace through the
+ soundcard's long card name in the control user space AP. An unfortunate
+ side effect is that this long name may also be used by the GUI,
+ confusing users with information they don't need.
+ This option prevents the long name from being modified, and the I/O
+ configuration will be provided through a different component interface.
+ Select Y if userspace like UCM (Use Case Manager) uses the component
+ interface.
+ If unsure select N.
+
+if SND_SOC_INTEL_CATPT
+
+config SND_SOC_INTEL_HASWELL_MACH
+ tristate "Haswell Lynxpoint"
+ depends on I2C
+ depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT5640
+ help
+ This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell
+ Ultrabook platforms. This is a recommended option.
+ Say Y or m if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_INTEL_CATPT
+
+if SND_SOC_INTEL_CATPT || SND_SOC_SOF_BROADWELL
+
+config SND_SOC_INTEL_BDW_RT5650_MACH
+ tristate "Broadwell with RT5650 codec"
+ depends on I2C
+ depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT5645
+ help
+ This adds the ASoC machine driver for Intel Broadwell platforms with
+ the RT5650 codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BDW_RT5677_MACH
+ tristate "Broadwell with RT5677 codec"
+ depends on I2C
+ depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST
+ depends on GPIOLIB || COMPILE_TEST
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ depends on SPI_MASTER
+ select SPI_PXA2XX
+ select SND_SOC_RT5677_SPI
+ select SND_SOC_RT5677
+ help
+ This adds support for Intel Broadwell platform based boards with
+ the RT5677 audio codec. This is a recommended option.
+ Say Y or m if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BROADWELL_MACH
+ tristate "Broadwell Wildcatpoint"
+ depends on I2C
+ depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT286
+ help
+ This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell
+ Ultrabook platforms.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+endif ## SND_SOC_INTEL_CATPT || SND_SOC_SOF_BROADWELL
+
+if SND_SST_ATOM_HIFI2_PLATFORM || SND_SOC_SOF_BAYTRAIL
+
+config SND_SOC_INTEL_BYTCR_RT5640_MACH
+ tristate "Baytrail and Baytrail-CR with RT5640 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_RT5640
+ help
+ This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR
+ platforms with RT5640 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYTCR_RT5651_MACH
+ tristate "Baytrail and Baytrail-CR with RT5651 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_RT5651
+ help
+ This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR
+ platforms with RT5651 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_RT5672_MACH
+ tristate "Cherrytrail & Braswell with RT5672 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_RT5670
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with RT5672 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_RT5645_MACH
+ tristate "Cherrytrail & Braswell with RT5645/5650 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_RT5645
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with RT5645/5650 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH
+ tristate "Cherrytrail & Braswell with MAX98090 & TI codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_MAX98090
+ select SND_SOC_TS3A227E
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with MAX98090 audio codec it also can support TI jack chip as aux device.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_NAU8824_MACH
+ tristate "Cherrytrail & Braswell with NAU88L24 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_NAU8824
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with NAU88L24 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYT_CHT_CX2072X_MACH
+ tristate "Baytrail & Cherrytrail with CX2072X codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_CX2072X
+ help
+ This adds support for ASoC machine driver for Intel(R) Baytrail &
+ Cherrytrail platforms with Conexant CX2072X audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYT_CHT_DA7213_MACH
+ tristate "Baytrail & Cherrytrail with DA7212/7213 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_DA7213
+ help
+ This adds support for ASoC machine driver for Intel(R) Baytrail & CherryTrail
+ platforms with DA7212/7213 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYT_CHT_ES8316_MACH
+ tristate "Baytrail & Cherrytrail with ES8316 codec"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_ACPI
+ select SND_SOC_ES8316
+ help
+ This adds support for ASoC machine driver for Intel(R) Baytrail &
+ Cherrytrail platforms with ES8316 audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+endif ## SND_SST_ATOM_HIFI2_PLATFORM || SND_SOC_SOF_BAYTRAIL
+
+if SND_SST_ATOM_HIFI2_PLATFORM
+
+config SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH
+ tristate "Baytrail & Cherrytrail platform with no codec (MinnowBoard MAX, Up)"
+ depends on I2C && ACPI
+ depends on X86_INTEL_LPSS || COMPILE_TEST
+ help
+ This adds support for ASoC machine driver for the MinnowBoard Max or
+ Up boards and provides access to I2S signals on the Low-Speed
+ connector. This is not a recommended option outside of these cases.
+ It is not intended to be enabled by distros by default.
+ Say Y or m if you have such a device.
+
+ If unsure select "N".
+
+endif ## SND_SST_ATOM_HIFI2_PLATFORM
+
+if SND_SOC_INTEL_SKL
+
+config SND_SOC_INTEL_SKL_RT286_MACH
+ tristate "SKL with RT286 I2S mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT286
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC machine driver for Skylake platforms
+ with RT286 I2S audio codec.
+ Say Y or m if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH
+ tristate "SKL with NAU88L25 and SSM4567 in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_NAU8825
+ select SND_SOC_SSM4567
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for NAU88L25 + SSM4567.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH
+ tristate "SKL with NAU88L25 and MAX98357A in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_NAU8825
+ select SND_SOC_MAX98357A
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for NAU88L25 + MAX98357A.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+endif ## SND_SOC_INTEL_SKL
+
+config SND_SOC_INTEL_DA7219_MAX98357A_GENERIC
+ tristate
+ select SND_SOC_DA7219
+ select SND_SOC_MAX98357A
+ select SND_SOC_MAX98390
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+
+config SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON
+ tristate
+ select SND_SOC_INTEL_DA7219_MAX98357A_GENERIC
+
+if SND_SOC_INTEL_APL
+
+config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH
+ tristate "Broxton with DA7219 and MAX98357A/MAX98390 in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI
+ select SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON
+ help
+ This adds support for ASoC machine driver for Broxton-P platforms
+ with DA7219 + MAX98357A/MAX98390 I2S audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_BXT_RT298_MACH
+ tristate "Broxton with RT298 I2S mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT298
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC machine driver for Broxton platforms
+ with RT286 I2S audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+endif ## SND_SOC_INTEL_APL
+
+if SND_SOC_SOF_APOLLOLAKE
+
+config SND_SOC_INTEL_SOF_WM8804_MACH
+ tristate "SOF with Wolfson/Cirrus WM8804 codec"
+ depends on I2C && ACPI
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_WM8804_I2C
+ help
+ This adds support for ASoC machine driver for Intel platforms
+ with the Wolfson/Cirrus WM8804 I2S audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_APOLLOLAKE
+
+if SND_SOC_INTEL_KBL
+
+config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH
+ tristate "KBL with RT5663 and MAX98927 in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT5663
+ select SND_SOC_MAX98927
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ select SND_SOC_INTEL_SKYLAKE_SSP_CLK
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for RT5663 + MAX98927.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH
+ tristate "KBL with RT5663, RT5514 and MAX98927 in I2S Mode"
+ depends on I2C && ACPI
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SPI
+ select SND_SOC_RT5663
+ select SND_SOC_RT5514
+ select SND_SOC_RT5514_SPI
+ select SND_SOC_MAX98927
+ select SND_SOC_HDAC_HDMI
+ select SND_SOC_INTEL_SKYLAKE_SSP_CLK
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for RT5663 + RT5514 + MAX98927.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH
+ tristate "KBL with DA7219 and MAX98357A in I2S Mode"
+ depends on I2C && ACPI
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_INTEL_DA7219_MAX98357A_GENERIC
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for DA7219 + MAX98357A I2S audio codec.
+ Say Y if you have such a device.
+
+config SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH
+ tristate "KBL with DA7219 and MAX98927 in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_DA7219
+ select SND_SOC_MAX98927
+ select SND_SOC_MAX98373_I2C
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for DA7219 + MAX98927 I2S audio codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_INTEL_KBL_RT5660_MACH
+ tristate "KBL with RT5660 in I2S Mode"
+ depends on I2C && ACPI
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_RT5660
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC Onboard Codec I2S machine driver. This will
+ create an alsa sound card for RT5660 I2S audio codec.
+ Say Y if you have such a device.
+
+endif ## SND_SOC_INTEL_KBL
+
+if SND_SOC_SOF_GEMINILAKE
+
+config SND_SOC_INTEL_GLK_DA7219_MAX98357A_MACH
+ tristate "GLK with DA7219 and MAX98357A in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON
+ help
+ This adds support for ASoC machine driver for Geminilake platforms
+ with DA7219 + MAX98357A I2S audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH
+ tristate "GLK with RT5682 and MAX98357A in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_MAX98357A
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC machine driver for Geminilake platforms
+ with RT5682 + MAX98357A I2S audio codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_GEMINILAKE
+
+if SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC || SND_SOC_SOF_HDA_AUDIO_CODEC
+
+config SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH
+ tristate "SKL/KBL/BXT/APL with HDA Codecs"
+ depends on SND_HDA_CODEC_HDMI
+ depends on GPIOLIB
+ select SND_SOC_HDAC_HDMI
+ select SND_SOC_DMIC
+ # SND_SOC_HDAC_HDA is already selected
+ help
+ This adds support for ASoC machine driver for Intel platforms
+ SKL/KBL/BXT/APL with iDisp, HDA audio codecs.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+endif ## SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC || SND_SOC_SOF_HDA_AUDIO_CODEC
+
+if SND_SOC_SOF_HDA_LINK || SND_SOC_SOF_BAYTRAIL
+config SND_SOC_INTEL_SOF_RT5682_MACH
+ tristate "SOF with rt5682 codec in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on ((SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC) &&\
+ (MFD_INTEL_LPSS || COMPILE_TEST)) ||\
+ (SND_SOC_SOF_BAYTRAIL && (X86_INTEL_LPSS || COMPILE_TEST))
+ select SND_SOC_MAX98373_I2C
+ select SND_SOC_RT1015
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC machine driver for SOF platforms
+ with rt5682 codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_INTEL_SOF_PCM512x_MACH
+ tristate "SOF with TI PCM512x codec"
+ depends on I2C && ACPI
+ depends on (SND_SOC_SOF_HDA_AUDIO_CODEC && (MFD_INTEL_LPSS || COMPILE_TEST)) ||\
+ (SND_SOC_SOF_BAYTRAIL && (X86_INTEL_LPSS || COMPILE_TEST))
+ depends on SND_HDA_CODEC_HDMI
+ select SND_SOC_PCM512x_I2C
+ help
+ This adds support for ASoC machine driver for SOF platforms
+ with TI PCM512x I2S audio codec.
+ Say Y or m if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_HDA_LINK || SND_SOC_SOF_BAYTRAIL
+
+if (SND_SOC_SOF_COMETLAKE && SND_SOC_SOF_HDA_LINK)
+
+config SND_SOC_INTEL_CML_LP_DA7219_MAX98357A_MACH
+ tristate "CML_LP with DA7219 and MAX98357A in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ select SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON
+ help
+ This adds support for ASoC machine driver for Cometlake platforms
+ with DA7219 + MAX98357A I2S audio codec.
+ Say Y or m if you have such a device. This is a recommended option.
+ If unsure select "N".
+
+config SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH
+ tristate "CML with RT1011 and RT5682 in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_RT1011
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_DMIC
+ select SND_SOC_HDAC_HDMI
+ help
+ This adds support for ASoC machine driver for SOF platform with
+ RT1011 + RT5682 I2S codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_COMETLAKE && SND_SOC_SOF_HDA_LINK
+
+if SND_SOC_SOF_JASPERLAKE
+
+config SND_SOC_INTEL_SOF_DA7219_MAX98373_MACH
+ tristate "SOF with DA7219 and MAX98373/MAX98360A in I2S Mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_DA7219
+ select SND_SOC_MAX98373_I2C
+ select SND_SOC_DMIC
+ help
+ This adds support for ASoC machine driver for SOF platforms
+ with DA7219 + MAX98373/MAX98360A I2S audio codec.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_JASPERLAKE
+
+if SND_SOC_SOF_ELKHARTLAKE
+
+config SND_SOC_INTEL_EHL_RT5660_MACH
+ tristate "EHL with RT5660 in I2S mode"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_RT5660
+ select SND_SOC_DMIC
+ help
+ This adds support for ASoC machine driver for Elkhart Lake
+ platform with RT5660 I2S audio codec.
+
+endif ## SND_SOC_SOF_ELKHARTLAKE
+
+if SND_SOC_SOF_INTEL_SOUNDWIRE
+
+config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH
+ tristate "SoundWire generic machine driver"
+ depends on I2C && ACPI && GPIOLIB
+ depends on MFD_INTEL_LPSS || COMPILE_TEST
+ depends on SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES || COMPILE_TEST
+ depends on SOUNDWIRE
+ depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC
+ select SND_SOC_MAX98373_I2C
+ select SND_SOC_MAX98373_SDW
+ select SND_SOC_RT700_SDW
+ select SND_SOC_RT711_SDW
+ select SND_SOC_RT711_SDCA_SDW
+ select SND_SOC_RT1308_SDW
+ select SND_SOC_RT1308
+ select SND_SOC_RT1316_SDW
+ select SND_SOC_RT715_SDW
+ select SND_SOC_RT715_SDCA_SDW
+ select SND_SOC_RT5682_SDW
+ select SND_SOC_DMIC
+ help
+ Add support for Intel SoundWire-based platforms connected to
+ MAX98373, RT700, RT711, RT1308 and RT715
+ If unsure select "N".
+
+endif
+
+
+endif ## SND_SOC_INTEL_MACH
diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile
new file mode 100644
index 000000000..a58e4d22e
--- /dev/null
+++ b/sound/soc/intel/boards/Makefile
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-soc-sst-haswell-objs := haswell.o
+snd-soc-sst-bdw-rt5650-mach-objs := bdw-rt5650.o
+snd-soc-sst-bdw-rt5677-mach-objs := bdw-rt5677.o
+snd-soc-sst-broadwell-objs := broadwell.o
+snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o hda_dsp_common.o
+snd-soc-sst-bxt-rt298-objs := bxt_rt298.o hda_dsp_common.o
+snd-soc-sst-sof-pcm512x-objs := sof_pcm512x.o hda_dsp_common.o
+snd-soc-sst-sof-wm8804-objs := sof_wm8804.o
+snd-soc-sst-glk-rt5682_max98357a-objs := glk_rt5682_max98357a.o hda_dsp_common.o
+snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o
+snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o
+snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o
+snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o
+snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o
+snd-soc-sst-cht-bsw-nau8824-objs := cht_bsw_nau8824.o
+snd-soc-sst-byt-cht-cx2072x-objs := bytcht_cx2072x.o
+snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o
+snd-soc-sst-byt-cht-es8316-objs := bytcht_es8316.o
+snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o
+snd-soc-sof_rt5682-objs := sof_rt5682.o hda_dsp_common.o sof_maxim_common.o
+snd-soc-cml_rt1011_rt5682-objs := cml_rt1011_rt5682.o hda_dsp_common.o
+snd-soc-kbl_da7219_max98357a-objs := kbl_da7219_max98357a.o
+snd-soc-kbl_da7219_max98927-objs := kbl_da7219_max98927.o
+snd-soc-kbl_rt5663_max98927-objs := kbl_rt5663_max98927.o
+snd-soc-kbl_rt5663_rt5514_max98927-objs := kbl_rt5663_rt5514_max98927.o
+snd-soc-kbl_rt5660-objs := kbl_rt5660.o
+snd-soc-skl_rt286-objs := skl_rt286.o
+snd-soc-skl_hda_dsp-objs := skl_hda_dsp_generic.o skl_hda_dsp_common.o hda_dsp_common.o
+snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o
+snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o
+snd-soc-sof_da7219_max98373-objs := sof_da7219_max98373.o hda_dsp_common.o
+snd-soc-ehl-rt5660-objs := ehl_rt5660.o hda_dsp_common.o
+snd-soc-sof-sdw-objs += sof_sdw.o \
+ sof_sdw_max98373.o \
+ sof_sdw_rt1308.o sof_sdw_rt1316.o \
+ sof_sdw_rt5682.o sof_sdw_rt700.o \
+ sof_sdw_rt711.o sof_sdw_rt711_sdca.o \
+ sof_sdw_rt715.o sof_sdw_rt715_sdca.o \
+ sof_maxim_common.o \
+ sof_sdw_dmic.o sof_sdw_hdmi.o hda_dsp_common.o
+obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o
+obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o
+obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON) += snd-soc-sst-bxt-da7219_max98357a.o
+obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o
+obj-$(CONFIG_SND_SOC_INTEL_SOF_PCM512x_MACH) += snd-soc-sst-sof-pcm512x.o
+obj-$(CONFIG_SND_SOC_INTEL_SOF_WM8804_MACH) += snd-soc-sst-sof-wm8804.o
+obj-$(CONFIG_SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH) += snd-soc-sst-glk-rt5682_max98357a.o
+obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o
+obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5650_MACH) += snd-soc-sst-bdw-rt5650-mach.o
+obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH) += snd-soc-sst-bdw-rt5677-mach.o
+obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o
+obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o
+obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o
+obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o
+obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o
+obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH) += snd-soc-sst-cht-bsw-nau8824.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_CX2072X_MACH) += snd-soc-sst-byt-cht-cx2072x.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH) += snd-soc-sst-byt-cht-da7213.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_ES8316_MACH) += snd-soc-sst-byt-cht-es8316.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) += snd-soc-sst-byt-cht-nocodec.o
+obj-$(CONFIG_SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH) += snd-soc-cml_rt1011_rt5682.o
+obj-$(CONFIG_SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH) += snd-soc-kbl_da7219_max98357a.o
+obj-$(CONFIG_SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH) += snd-soc-kbl_da7219_max98927.o
+obj-$(CONFIG_SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH) += snd-soc-kbl_rt5663_max98927.o
+obj-$(CONFIG_SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH) += snd-soc-kbl_rt5663_rt5514_max98927.o
+obj-$(CONFIG_SND_SOC_INTEL_KBL_RT5660_MACH) += snd-soc-kbl_rt5660.o
+obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o
+obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o
+obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o
+obj-$(CONFIG_SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH) += snd-soc-skl_hda_dsp.o
+obj-$(CONFIG_SND_SOC_INTEL_SOF_DA7219_MAX98373_MACH) += snd-soc-sof_da7219_max98373.o
+obj-$(CONFIG_SND_SOC_INTEL_EHL_RT5660_MACH) += snd-soc-ehl-rt5660.o
+obj-$(CONFIG_SND_SOC_INTEL_SOUNDWIRE_SOF_MACH) += snd-soc-sof-sdw.o
diff --git a/sound/soc/intel/boards/bdw-rt5650.c b/sound/soc/intel/boards/bdw-rt5650.c
new file mode 100644
index 000000000..aa420b201
--- /dev/null
+++ b/sound/soc/intel/boards/bdw-rt5650.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ASoC machine driver for Intel Broadwell platforms with RT5650 codec
+ *
+ * Copyright 2019, The Chromium OS Authors. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+
+#include "../../codecs/rt5645.h"
+
+struct bdw_rt5650_priv {
+ struct gpio_desc *gpio_hp_en;
+ struct snd_soc_component *component;
+};
+
+static const struct snd_soc_dapm_widget bdw_rt5650_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("DMIC Pair1", NULL),
+ SND_SOC_DAPM_MIC("DMIC Pair2", NULL),
+};
+
+static const struct snd_soc_dapm_route bdw_rt5650_map[] = {
+ /* Speakers */
+ {"Speaker", NULL, "SPOL"},
+ {"Speaker", NULL, "SPOR"},
+
+ /* Headset jack connectors */
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN1N", NULL, "Headset Mic"},
+
+ /* Digital MICs
+ * DMIC Pair1 are the two DMICs connected on the DMICN1 connector.
+ * DMIC Pair2 are the two DMICs connected on the DMICN2 connector.
+ * Facing the camera, DMIC Pair1 are on the left side, DMIC Pair2
+ * are on the right side.
+ */
+ {"DMIC L1", NULL, "DMIC Pair1"},
+ {"DMIC R1", NULL, "DMIC Pair1"},
+ {"DMIC L2", NULL, "DMIC Pair2"},
+ {"DMIC R2", NULL, "DMIC Pair2"},
+
+ /* CODEC BE connections */
+ {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
+ {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
+};
+
+static const struct snd_kcontrol_new bdw_rt5650_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("DMIC Pair1"),
+ SOC_DAPM_PIN_SWITCH("DMIC Pair2"),
+};
+
+
+static struct snd_soc_jack headphone_jack;
+static struct snd_soc_jack mic_jack;
+
+static struct snd_soc_jack_pin headphone_jack_pin = {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+};
+
+static struct snd_soc_jack_pin mic_jack_pin = {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+};
+
+static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The ADSP will covert the FE rate to 48k, max 4-channels */
+ rate->min = rate->max = 48000;
+ chan->min = 2;
+ chan->max = 4;
+
+ /* set SSP0 to 24 bit */
+ snd_mask_set_format(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
+ SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int bdw_rt5650_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* Workaround: set codec PLL to 19.2MHz that PLL source is
+ * from MCLK(24MHz) to conform 2.4MHz DMIC clock.
+ */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK,
+ 24000000, 19200000);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ /* The actual MCLK freq is 24MHz. The codec is told that MCLK is
+ * 24.576MHz to satisfy the requirement of rl6231_get_clk_info.
+ * ASRC is enabled on AD and DA filters to ensure good audio quality.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, 24576000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops bdw_rt5650_ops = {
+ .hw_params = bdw_rt5650_hw_params,
+};
+
+static const unsigned int channels[] = {
+ 2, 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int bdw_rt5650_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /* Board supports stereo and quad configurations for capture */
+ if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
+ return 0;
+
+ runtime->hw.channels_max = 4;
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+}
+
+static const struct snd_soc_ops bdw_rt5650_fe_ops = {
+ .startup = bdw_rt5650_fe_startup,
+};
+
+static int bdw_rt5650_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct bdw_rt5650_priv *bdw_rt5650 =
+ snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ int ret;
+
+ /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1.
+ * The ASRC clock source is clk_i2s1_asrc.
+ */
+ rt5645_sel_asrc_clk_src(component,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER |
+ RT5645_AD_STEREO_FILTER |
+ RT5645_AD_MONO_L_FILTER |
+ RT5645_AD_MONO_R_FILTER,
+ RT5645_CLK_SEL_I2S1_ASRC);
+
+ /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24);
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret);
+ return ret;
+ }
+
+ /* Create and initialize headphone jack */
+ if (snd_soc_card_jack_new(rtd->card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &headphone_jack,
+ &headphone_jack_pin, 1)) {
+ dev_err(component->dev, "Can't create headphone jack\n");
+ }
+
+ /* Create and initialize mic jack */
+ if (snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE,
+ &mic_jack, &mic_jack_pin, 1)) {
+ dev_err(component->dev, "Can't create mic jack\n");
+ }
+
+ rt5645_set_jack_detect(component, &headphone_jack, &mic_jack, NULL);
+
+ bdw_rt5650->component = component;
+
+ return 0;
+}
+
+/* broadwell digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(fe,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
+
+SND_SOC_DAILINK_DEF(be,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5650:00", "rt5645-aif1")));
+
+SND_SOC_DAILINK_DEF(ssp0_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
+
+static struct snd_soc_dai_link bdw_rt5650_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "System PCM",
+ .stream_name = "System Playback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &bdw_rt5650_fe_ops,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST
+ },
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(fe, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broadwell_ssp0_fixup,
+ .ops = &bdw_rt5650_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .init = bdw_rt5650_init,
+ SND_SOC_DAILINK_REG(ssp0_port, be, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bdw rt5650" /* card name will be 'sof-bdw rt5650' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bdw-rt5650"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* ASoC machine driver for Broadwell DSP + RT5650 */
+static struct snd_soc_card bdw_rt5650_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = bdw_rt5650_dais,
+ .num_links = ARRAY_SIZE(bdw_rt5650_dais),
+ .dapm_widgets = bdw_rt5650_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(bdw_rt5650_widgets),
+ .dapm_routes = bdw_rt5650_map,
+ .num_dapm_routes = ARRAY_SIZE(bdw_rt5650_map),
+ .controls = bdw_rt5650_controls,
+ .num_controls = ARRAY_SIZE(bdw_rt5650_controls),
+ .fully_routed = true,
+};
+
+static int bdw_rt5650_probe(struct platform_device *pdev)
+{
+ struct bdw_rt5650_priv *bdw_rt5650;
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ bdw_rt5650_card.dev = &pdev->dev;
+
+ /* Allocate driver private struct */
+ bdw_rt5650 = devm_kzalloc(&pdev->dev, sizeof(struct bdw_rt5650_priv),
+ GFP_KERNEL);
+ if (!bdw_rt5650)
+ return -ENOMEM;
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt5650_card,
+ mach->mach_params.platform);
+
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(&bdw_rt5650_card, bdw_rt5650);
+
+ return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5650_card);
+}
+
+static struct platform_driver bdw_rt5650_audio = {
+ .probe = bdw_rt5650_probe,
+ .driver = {
+ .name = "bdw-rt5650",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+
+module_platform_driver(bdw_rt5650_audio)
+
+/* Module information */
+MODULE_AUTHOR("Ben Zhang <benzh@chromium.org>");
+MODULE_DESCRIPTION("Intel Broadwell RT5650 machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bdw-rt5650");
diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c
new file mode 100644
index 000000000..7a3e773d0
--- /dev/null
+++ b/sound/soc/intel/boards/bdw-rt5677.c
@@ -0,0 +1,458 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ASoC machine driver for Intel Broadwell platforms with RT5677 codec
+ *
+ * Copyright (c) 2014, The Chromium OS Authors. All rights reserved.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+
+#include "../../codecs/rt5677.h"
+
+struct bdw_rt5677_priv {
+ struct gpio_desc *gpio_hp_en;
+ struct snd_soc_component *component;
+};
+
+static int bdw_rt5677_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ msleep(70);
+
+ gpiod_set_value_cansleep(bdw_rt5677->gpio_hp_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget bdw_rt5677_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", bdw_rt5677_event_hp),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Local DMICs", NULL),
+ SND_SOC_DAPM_MIC("Remote DMICs", NULL),
+};
+
+static const struct snd_soc_dapm_route bdw_rt5677_map[] = {
+ /* Speakers */
+ {"Speaker", NULL, "PDM1L"},
+ {"Speaker", NULL, "PDM1R"},
+
+ /* Headset jack connectors */
+ {"Headphone", NULL, "LOUT1"},
+ {"Headphone", NULL, "LOUT2"},
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN1N", NULL, "Headset Mic"},
+
+ /* Digital MICs
+ * Local DMICs: the two DMICs on the mainboard
+ * Remote DMICs: the two DMICs on the camera module
+ */
+ {"DMIC L1", NULL, "Remote DMICs"},
+ {"DMIC R1", NULL, "Remote DMICs"},
+ {"DMIC L2", NULL, "Local DMICs"},
+ {"DMIC R2", NULL, "Local DMICs"},
+
+ /* CODEC BE connections */
+ {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
+ {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
+ {"DSP Capture", NULL, "DSP Buffer"},
+
+ /* DSP Clock Connections */
+ { "DSP Buffer", NULL, "SSP0 CODEC IN" },
+ { "SSP0 CODEC IN", NULL, "DSPTX" },
+};
+
+static const struct snd_kcontrol_new bdw_rt5677_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Local DMICs"),
+ SOC_DAPM_PIN_SWITCH("Remote DMICs"),
+};
+
+
+static struct snd_soc_jack headphone_jack;
+static struct snd_soc_jack mic_jack;
+
+static struct snd_soc_jack_pin headphone_jack_pin = {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+};
+
+static struct snd_soc_jack_pin mic_jack_pin = {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+};
+
+static struct snd_soc_jack_gpio headphone_jack_gpio = {
+ .name = "plug-det",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+};
+
+static struct snd_soc_jack_gpio mic_jack_gpio = {
+ .name = "mic-present",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 200,
+ .invert = 1,
+};
+
+/* GPIO indexes defined by ACPI */
+enum {
+ RT5677_GPIO_PLUG_DET = 0,
+ RT5677_GPIO_MIC_PRESENT_L = 1,
+ RT5677_GPIO_HOTWORD_DET_L = 2,
+ RT5677_GPIO_DSP_INT = 3,
+ RT5677_GPIO_HP_AMP_SHDN_L = 4,
+};
+
+static const struct acpi_gpio_params plug_det_gpio = { RT5677_GPIO_PLUG_DET, 0, false };
+static const struct acpi_gpio_params mic_present_gpio = { RT5677_GPIO_MIC_PRESENT_L, 0, false };
+static const struct acpi_gpio_params headphone_enable_gpio = { RT5677_GPIO_HP_AMP_SHDN_L, 0, false };
+
+static const struct acpi_gpio_mapping bdw_rt5677_gpios[] = {
+ { "plug-det-gpios", &plug_det_gpio, 1 },
+ { "mic-present-gpios", &mic_present_gpio, 1 },
+ { "headphone-enable-gpios", &headphone_enable_gpio, 1 },
+ { NULL },
+};
+
+static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP0 to 16 bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ return 0;
+}
+
+static int bdw_rt5677_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_MCLK, 24576000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int bdw_rt5677_dsp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, 24576000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK,
+ 24000000, 24576000);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll configuration\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops bdw_rt5677_ops = {
+ .hw_params = bdw_rt5677_hw_params,
+};
+
+static const struct snd_soc_ops bdw_rt5677_dsp_ops = {
+ .hw_params = bdw_rt5677_dsp_hw_params,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int bdw_rt5677_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /* Board supports stereo configuration only */
+ runtime->hw.channels_max = 2;
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+}
+
+static const struct snd_soc_ops bdw_rt5677_fe_ops = {
+ .startup = bdw_rt5677_fe_startup,
+};
+
+static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct bdw_rt5677_priv *bdw_rt5677 =
+ snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+ int ret;
+
+ ret = devm_acpi_dev_add_driver_gpios(component->dev, bdw_rt5677_gpios);
+ if (ret)
+ dev_warn(component->dev, "Failed to add driver gpios\n");
+
+ /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1.
+ * The ASRC clock source is clk_i2s1_asrc.
+ */
+ rt5677_sel_asrc_clk_src(component, RT5677_DA_STEREO_FILTER |
+ RT5677_AD_STEREO1_FILTER | RT5677_I2S1_SOURCE,
+ RT5677_CLK_SEL_I2S1_ASRC);
+ /* Enable codec ASRC function for Mono ADC L.
+ * The ASRC clock source is clk_sys2_asrc.
+ */
+ rt5677_sel_asrc_clk_src(component, RT5677_AD_MONO_L_FILTER,
+ RT5677_CLK_SEL_SYS2);
+
+ /* Request rt5677 GPIO for headphone amp control */
+ bdw_rt5677->gpio_hp_en = gpiod_get(component->dev, "headphone-enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(bdw_rt5677->gpio_hp_en)) {
+ dev_err(component->dev, "Can't find HP_AMP_SHDN_L gpio\n");
+ return PTR_ERR(bdw_rt5677->gpio_hp_en);
+ }
+
+ /* Create and initialize headphone jack */
+ if (!snd_soc_card_jack_new(rtd->card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &headphone_jack,
+ &headphone_jack_pin, 1)) {
+ headphone_jack_gpio.gpiod_dev = component->dev;
+ if (snd_soc_jack_add_gpios(&headphone_jack, 1,
+ &headphone_jack_gpio))
+ dev_err(component->dev, "Can't add headphone jack gpio\n");
+ } else {
+ dev_err(component->dev, "Can't create headphone jack\n");
+ }
+
+ /* Create and initialize mic jack */
+ if (!snd_soc_card_jack_new(rtd->card, "Mic Jack",
+ SND_JACK_MICROPHONE, &mic_jack,
+ &mic_jack_pin, 1)) {
+ mic_jack_gpio.gpiod_dev = component->dev;
+ if (snd_soc_jack_add_gpios(&mic_jack, 1, &mic_jack_gpio))
+ dev_err(component->dev, "Can't add mic jack gpio\n");
+ } else {
+ dev_err(component->dev, "Can't create mic jack\n");
+ }
+ bdw_rt5677->component = component;
+
+ snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1");
+ return 0;
+}
+
+static void bdw_rt5677_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct bdw_rt5677_priv *bdw_rt5677 =
+ snd_soc_card_get_drvdata(rtd->card);
+
+ /*
+ * The .exit() can be reached without going through the .init()
+ * so explicitly test if the gpiod is valid
+ */
+ if (!IS_ERR_OR_NULL(bdw_rt5677->gpio_hp_en))
+ gpiod_put(bdw_rt5677->gpio_hp_en);
+}
+
+/* broadwell digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(fe,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
+
+SND_SOC_DAILINK_DEF(be,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RT5677CE:00", "rt5677-aif1")));
+
+SND_SOC_DAILINK_DEF(ssp0_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
+
+/* Wake on voice interface */
+SND_SOC_DAILINK_DEFS(dsp,
+ DAILINK_COMP_ARRAY(COMP_CPU("spi-RT5677AA:00")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RT5677CE:00", "rt5677-dspbuffer")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("spi-RT5677AA:00")));
+
+static struct snd_soc_dai_link bdw_rt5677_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "System PCM",
+ .stream_name = "System Playback/Capture",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST
+ },
+ .dpcm_capture = 1,
+ .dpcm_playback = 1,
+ .ops = &bdw_rt5677_fe_ops,
+ SND_SOC_DAILINK_REG(fe, dummy, platform),
+ },
+
+ /* Non-DPCM links */
+ {
+ .name = "Codec DSP",
+ .stream_name = "Wake on Voice",
+ .capture_only = 1,
+ .ops = &bdw_rt5677_dsp_ops,
+ SND_SOC_DAILINK_REG(dsp),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broadwell_ssp0_fixup,
+ .ops = &bdw_rt5677_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .init = bdw_rt5677_init,
+ .exit = bdw_rt5677_exit,
+ SND_SOC_DAILINK_REG(ssp0_port, be, platform),
+ },
+};
+
+static int bdw_rt5677_suspend_pre(struct snd_soc_card *card)
+{
+ struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dapm_context *dapm;
+
+ if (bdw_rt5677->component) {
+ dapm = snd_soc_component_get_dapm(bdw_rt5677->component);
+ snd_soc_dapm_disable_pin(dapm, "MICBIAS1");
+ }
+ return 0;
+}
+
+static int bdw_rt5677_resume_post(struct snd_soc_card *card)
+{
+ struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dapm_context *dapm;
+
+ if (bdw_rt5677->component) {
+ dapm = snd_soc_component_get_dapm(bdw_rt5677->component);
+ snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1");
+ }
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bdw rt5677" /* card name will be 'sof-bdw rt5677' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bdw-rt5677"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* ASoC machine driver for Broadwell DSP + RT5677 */
+static struct snd_soc_card bdw_rt5677_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = bdw_rt5677_dais,
+ .num_links = ARRAY_SIZE(bdw_rt5677_dais),
+ .dapm_widgets = bdw_rt5677_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(bdw_rt5677_widgets),
+ .dapm_routes = bdw_rt5677_map,
+ .num_dapm_routes = ARRAY_SIZE(bdw_rt5677_map),
+ .controls = bdw_rt5677_controls,
+ .num_controls = ARRAY_SIZE(bdw_rt5677_controls),
+ .fully_routed = true,
+ .suspend_pre = bdw_rt5677_suspend_pre,
+ .resume_post = bdw_rt5677_resume_post,
+};
+
+static int bdw_rt5677_probe(struct platform_device *pdev)
+{
+ struct bdw_rt5677_priv *bdw_rt5677;
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ bdw_rt5677_card.dev = &pdev->dev;
+
+ /* Allocate driver private struct */
+ bdw_rt5677 = devm_kzalloc(&pdev->dev, sizeof(struct bdw_rt5677_priv),
+ GFP_KERNEL);
+ if (!bdw_rt5677) {
+ dev_err(&pdev->dev, "Can't allocate bdw_rt5677\n");
+ return -ENOMEM;
+ }
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt5677_card,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(&bdw_rt5677_card, bdw_rt5677);
+
+ return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5677_card);
+}
+
+static struct platform_driver bdw_rt5677_audio = {
+ .probe = bdw_rt5677_probe,
+ .driver = {
+ .name = "bdw-rt5677",
+ },
+};
+
+module_platform_driver(bdw_rt5677_audio)
+
+/* Module information */
+MODULE_AUTHOR("Ben Zhang");
+MODULE_DESCRIPTION("Intel Broadwell RT5677 machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bdw-rt5677");
diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c
new file mode 100644
index 000000000..77c85f17a
--- /dev/null
+++ b/sound/soc/intel/boards/broadwell.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Broadwell Wildcatpoint SST Audio
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-acpi.h>
+
+#include "../../codecs/rt286.h"
+
+static struct snd_soc_jack broadwell_headset;
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin broadwell_headset_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static const struct snd_kcontrol_new broadwell_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+};
+
+static const struct snd_soc_dapm_widget broadwell_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("DMIC1", NULL),
+ SND_SOC_DAPM_MIC("DMIC2", NULL),
+ SND_SOC_DAPM_LINE("Line Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route broadwell_rt286_map[] = {
+
+ /* speaker */
+ {"Speaker", NULL, "SPOR"},
+ {"Speaker", NULL, "SPOL"},
+
+ /* HP jack connectors - unknown if we have jack deteck */
+ {"Headphone Jack", NULL, "HPO Pin"},
+
+ /* other jacks */
+ {"MIC1", NULL, "Mic Jack"},
+ {"LINE1", NULL, "Line Jack"},
+
+ /* digital mics */
+ {"DMIC1 Pin", NULL, "DMIC1"},
+ {"DMIC2 Pin", NULL, "DMIC2"},
+
+ /* CODEC BE connections */
+ {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
+ {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
+};
+
+static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret = 0;
+ ret = snd_soc_card_jack_new(rtd->card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset,
+ broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins));
+ if (ret)
+ return ret;
+
+ rt286_mic_detect(component, &broadwell_headset);
+ return 0;
+}
+
+
+static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP0 to 16 bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ return 0;
+}
+
+static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_ops broadwell_rt286_ops = {
+ .hw_params = broadwell_rt286_hw_params,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int broadwell_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /* Board supports stereo configuration only */
+ runtime->hw.channels_max = 2;
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+}
+
+static const struct snd_soc_ops broadwell_fe_ops = {
+ .startup = broadwell_fe_startup,
+};
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(offload0,
+ DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
+
+SND_SOC_DAILINK_DEF(offload1,
+ DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
+
+SND_SOC_DAILINK_DEF(loopback,
+ DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
+
+SND_SOC_DAILINK_DEF(codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1")));
+
+SND_SOC_DAILINK_DEF(ssp0_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
+
+/* broadwell digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link broadwell_rt286_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "System PCM",
+ .stream_name = "System Playback/Capture",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .ops = &broadwell_fe_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ {
+ .name = "Offload0",
+ .stream_name = "Offload0 Playback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(offload0, dummy, platform),
+ },
+ {
+ .name = "Offload1",
+ .stream_name = "Offload1 Playback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(offload1, dummy, platform),
+ },
+ {
+ .name = "Loopback PCM",
+ .stream_name = "Loopback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(loopback, dummy, platform),
+ },
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = broadwell_rt286_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broadwell_ssp0_fixup,
+ .ops = &broadwell_rt286_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
+ },
+};
+
+static int broadwell_disable_jack(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, "i2c-INT343A:00")) {
+
+ dev_dbg(component->dev, "disabling jack detect before going to suspend.\n");
+ rt286_mic_detect(component, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int broadwell_suspend(struct snd_soc_card *card)
+{
+ return broadwell_disable_jack(card);
+}
+
+static int broadwell_resume(struct snd_soc_card *card){
+ struct snd_soc_component *component;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, "i2c-INT343A:00")) {
+
+ dev_dbg(component->dev, "enabling jack detect for resume.\n");
+ rt286_mic_detect(component, &broadwell_headset);
+ break;
+ }
+ }
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bdw rt286" /* card name will be 'sof-bdw rt286' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "broadwell-rt286"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* broadwell audio machine driver for WPT + RT286S */
+static struct snd_soc_card broadwell_rt286 = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = broadwell_rt286_dais,
+ .num_links = ARRAY_SIZE(broadwell_rt286_dais),
+ .controls = broadwell_controls,
+ .num_controls = ARRAY_SIZE(broadwell_controls),
+ .dapm_widgets = broadwell_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets),
+ .dapm_routes = broadwell_rt286_map,
+ .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map),
+ .fully_routed = true,
+ .suspend_pre = broadwell_suspend,
+ .resume_post = broadwell_resume,
+};
+
+static int broadwell_audio_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ broadwell_rt286.dev = &pdev->dev;
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(&broadwell_rt286,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286);
+}
+
+static int broadwell_audio_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ return broadwell_disable_jack(card);
+}
+
+static struct platform_driver broadwell_audio = {
+ .probe = broadwell_audio_probe,
+ .remove = broadwell_audio_remove,
+ .driver = {
+ .name = "broadwell-audio",
+ },
+};
+
+module_platform_driver(broadwell_audio)
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
+MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:broadwell-audio");
diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c
new file mode 100644
index 000000000..0c0a71782
--- /dev/null
+++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Broxton-P I2S Machine Driver
+ *
+ * Copyright (C) 2016, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Skylake I2S Machine driver
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/hdac_hdmi.h"
+#include "../../codecs/da7219.h"
+#include "../../codecs/da7219-aad.h"
+#include "../common/soc-intel-quirks.h"
+#include "hda_dsp_common.h"
+
+#define BXT_DIALOG_CODEC_DAI "da7219-hifi"
+#define BXT_MAXIM_CODEC_DAI "HiFi"
+#define MAX98390_DEV0_NAME "i2c-MX98390:00"
+#define MAX98390_DEV1_NAME "i2c-MX98390:01"
+#define DUAL_CHANNEL 2
+#define QUAD_CHANNEL 4
+
+#define SPKAMP_MAX98357A 1
+#define SPKAMP_MAX98390 2
+
+static struct snd_soc_jack broxton_headset;
+static struct snd_soc_jack broxton_hdmi[3];
+
+struct bxt_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct bxt_card_private {
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+ int spkamp;
+};
+
+enum {
+ BXT_DPCM_AUDIO_PB = 0,
+ BXT_DPCM_AUDIO_CP,
+ BXT_DPCM_AUDIO_HS_PB,
+ BXT_DPCM_AUDIO_REF_CP,
+ BXT_DPCM_AUDIO_DMIC_CP,
+ BXT_DPCM_AUDIO_HDMI1_PB,
+ BXT_DPCM_AUDIO_HDMI2_PB,
+ BXT_DPCM_AUDIO_HDMI3_PB,
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int ret = 0;
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, BXT_DIALOG_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7219_SYSCLK_MCLK, 0, 0);
+ if (ret)
+ dev_err(card->dev, "failed to stop PLL: %d\n", ret);
+ } else if(SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304);
+ if (ret)
+ dev_err(card->dev, "failed to start PLL: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new broxton_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_kcontrol_new max98357a_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Spk"),
+};
+
+static const struct snd_kcontrol_new max98390_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+};
+
+static const struct snd_soc_dapm_widget broxton_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SPK("HDMI3", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU),
+};
+
+static const struct snd_soc_dapm_widget max98357a_widgets[] = {
+ SND_SOC_DAPM_SPK("Spk", NULL),
+};
+
+static const struct snd_soc_dapm_widget max98390_widgets[] = {
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ {"Headphone Jack", NULL, "HPL"},
+ {"Headphone Jack", NULL, "HPR"},
+
+ /* other jacks */
+ {"MIC", NULL, "Headset Mic"},
+
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+
+ /* CODEC BE connections */
+ {"HDMI1", NULL, "hif5-0 Output"},
+ {"HDMI2", NULL, "hif6-0 Output"},
+ {"HDMI2", NULL, "hif7-0 Output"},
+
+ {"hifi3", NULL, "iDisp3 Tx"},
+ {"iDisp3 Tx", NULL, "iDisp3_out"},
+ {"hifi2", NULL, "iDisp2 Tx"},
+ {"iDisp2 Tx", NULL, "iDisp2_out"},
+ {"hifi1", NULL, "iDisp1 Tx"},
+ {"iDisp1 Tx", NULL, "iDisp1_out"},
+
+ /* DMIC */
+ {"dmic01_hifi", NULL, "DMIC01 Rx"},
+ {"DMIC01 Rx", NULL, "DMIC AIF"},
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+};
+
+static const struct snd_soc_dapm_route max98357a_routes[] = {
+ /* speaker */
+ {"Spk", NULL, "Speaker"},
+};
+
+static const struct snd_soc_dapm_route max98390_routes[] = {
+ /* Speaker */
+ {"Left Spk", NULL, "Left BE_OUT"},
+ {"Right Spk", NULL, "Right BE_OUT"},
+};
+
+static const struct snd_soc_dapm_route broxton_map[] = {
+ {"HiFi Playback", NULL, "ssp5 Tx"},
+ {"ssp5 Tx", NULL, "codec0_out"},
+
+ {"Playback", NULL, "ssp1 Tx"},
+ {"ssp1 Tx", NULL, "codec1_out"},
+
+ {"codec0_in", NULL, "ssp1 Rx"},
+ {"ssp1 Rx", NULL, "Capture"},
+};
+
+static const struct snd_soc_dapm_route gemini_map[] = {
+ {"HiFi Playback", NULL, "ssp1 Tx"},
+ {"ssp1 Tx", NULL, "codec0_out"},
+
+ {"Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec1_out"},
+
+ {"codec0_in", NULL, "ssp2 Rx"},
+ {"ssp2 Rx", NULL, "Capture"},
+};
+
+static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will convert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = DUAL_CHANNEL;
+
+ /* set SSP to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int clk_freq;
+
+ /* Configure sysclk for codec */
+ if (soc_intel_is_cml())
+ clk_freq = 24000000;
+ else
+ clk_freq = 19200000;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, clk_freq,
+ SND_SOC_CLOCK_IN);
+
+ if (ret) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT,
+ &broxton_headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ snd_jack_set_key(broxton_headset.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(broxton_headset.jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(broxton_headset.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+ snd_jack_set_key(broxton_headset.jack, SND_JACK_BTN_3,
+ KEY_VOICECOMMAND);
+
+ da7219_aad_jack_det(component, &broxton_headset);
+
+ snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+
+ return ret;
+}
+
+static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct bxt_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct bxt_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = BXT_DPCM_AUDIO_HDMI1_PB + dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int broxton_da7219_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static const unsigned int channels_quad[] = {
+ QUAD_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels_quad = {
+ .count = ARRAY_SIZE(channels_quad),
+ .list = channels_quad,
+ .mask = 0,
+};
+
+static int bxt_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = DUAL_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops broxton_da7219_fe_ops = {
+ .startup = bxt_fe_startup,
+};
+
+static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ if (params_channels(params) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int broxton_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels_quad);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops broxton_dmic_ops = {
+ .startup = broxton_dmic_startup,
+};
+
+static const unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static const unsigned int ch_mono[] = {
+ 1,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_refcap = {
+ .count = ARRAY_SIZE(ch_mono),
+ .list = ch_mono,
+};
+
+static int broxton_refcap_startup(struct snd_pcm_substream *substream)
+{
+ substream->runtime->hw.channels_max = 1;
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_refcap);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+};
+
+static const struct snd_soc_ops broxton_refcap_ops = {
+ .startup = broxton_refcap_startup,
+};
+
+/* broxton digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(system2,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin2")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+ /* Back End DAI */
+SND_SOC_DAILINK_DEF(ssp5_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));
+SND_SOC_DAILINK_DEF(ssp5_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00",
+ BXT_MAXIM_CODEC_DAI)));
+SND_SOC_DAILINK_DEF(max98390_codec,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC(MAX98390_DEV0_NAME, "max98390-aif1"),
+ /* Right */ COMP_CODEC(MAX98390_DEV1_NAME, "max98390-aif1")));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00",
+ BXT_DIALOG_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+
+SND_SOC_DAILINK_DEF(dmic16k_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
+
+static struct snd_soc_dai_link broxton_dais[] = {
+ /* Front End DAI links */
+ [BXT_DPCM_AUDIO_PB] =
+ {
+ .name = "Bxt Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = broxton_da7219_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &broxton_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_CP] =
+ {
+ .name = "Bxt Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &broxton_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HS_PB] = {
+ .name = "Bxt Audio Headset Playback",
+ .stream_name = "Headset Playback",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &broxton_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system2, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_REF_CP] =
+ {
+ .name = "Bxt Audio Reference cap",
+ .stream_name = "Refcap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &broxton_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_DMIC_CP] =
+ {
+ .name = "Bxt Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &broxton_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI1_PB] =
+ {
+ .name = "Bxt HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI2_PB] =
+ {
+ .name = "Bxt HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI3_PB] =
+ {
+ .name = "Bxt HDMI Port3",
+ .stream_name = "Hdmi3",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+ /* Back End DAI links */
+ {
+ /* SSP5 - Codec */
+ .name = "SSP5-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broxton_ssp_fixup,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = broxton_da7219_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broxton_ssp_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .ignore_suspend = 1,
+ .be_hw_params_fixup = broxton_dmic_fixup,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 6,
+ .be_hw_params_fixup = broxton_dmic_fixup,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k_pin, dmic_codec, platform),
+ },
+};
+
+static struct snd_soc_codec_conf max98390_codec_confs[] = {
+ {
+ .dlc = COMP_CODEC_CONF(MAX98390_DEV0_NAME),
+ .name_prefix = "Left",
+ },
+ {
+ .dlc = COMP_CODEC_CONF(MAX98390_DEV1_NAME),
+ .name_prefix = "Right",
+ },
+};
+
+#define NAME_SIZE 32
+static int bxt_card_late_probe(struct snd_soc_card *card)
+{
+ struct bxt_card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct bxt_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ const struct snd_kcontrol_new *controls;
+ const struct snd_soc_dapm_widget *widgets;
+ const struct snd_soc_dapm_route *routes;
+ int num_controls, num_widgets, num_routes, err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ switch (ctx->spkamp) {
+ case SPKAMP_MAX98357A:
+ controls = max98357a_controls;
+ num_controls = ARRAY_SIZE(max98357a_controls);
+ widgets = max98357a_widgets;
+ num_widgets = ARRAY_SIZE(max98357a_widgets);
+ routes = max98357a_routes;
+ num_routes = ARRAY_SIZE(max98357a_routes);
+ break;
+ case SPKAMP_MAX98390:
+ controls = max98390_controls;
+ num_controls = ARRAY_SIZE(max98390_controls);
+ widgets = max98390_widgets;
+ num_widgets = ARRAY_SIZE(max98390_widgets);
+ routes = max98390_routes;
+ num_routes = ARRAY_SIZE(max98390_routes);
+ break;
+ default:
+ dev_err(card->dev, "Invalid speaker amplifier %d\n", ctx->spkamp);
+ return -EINVAL;
+ }
+
+ err = snd_soc_dapm_new_controls(&card->dapm, widgets, num_widgets);
+ if (err) {
+ dev_err(card->dev, "Fail to new widgets\n");
+ return err;
+ }
+
+ err = snd_soc_add_card_controls(card, controls, num_controls);
+ if (err) {
+ dev_err(card->dev, "Fail to add controls\n");
+ return err;
+ }
+
+ err = snd_soc_dapm_add_routes(&card->dapm, routes, num_routes);
+ if (err) {
+ dev_err(card->dev, "Fail to add routes\n");
+ return err;
+ }
+
+ if (soc_intel_is_glk())
+ snd_soc_dapm_add_routes(&card->dapm, gemini_map,
+ ARRAY_SIZE(gemini_map));
+ else
+ snd_soc_dapm_add_routes(&card->dapm, broxton_map,
+ ARRAY_SIZE(broxton_map));
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (ctx->common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct bxt_hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ return hda_dsp_hdmi_build_controls(card, component);
+ }
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &broxton_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &broxton_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* broxton audio machine driver for SPT + da7219 */
+static struct snd_soc_card broxton_audio_card = {
+ .name = "bxtda7219max",
+ .owner = THIS_MODULE,
+ .dai_link = broxton_dais,
+ .num_links = ARRAY_SIZE(broxton_dais),
+ .controls = broxton_controls,
+ .num_controls = ARRAY_SIZE(broxton_controls),
+ .dapm_widgets = broxton_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(broxton_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .fully_routed = true,
+ .late_probe = bxt_card_late_probe,
+};
+
+static int broxton_audio_probe(struct platform_device *pdev)
+{
+ struct bxt_card_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ if (acpi_dev_present("MX98390", NULL, -1))
+ ctx->spkamp = SPKAMP_MAX98390;
+ else
+ ctx->spkamp = SPKAMP_MAX98357A;
+
+ broxton_audio_card.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&broxton_audio_card, ctx);
+ if (soc_intel_is_glk()) {
+ unsigned int i;
+
+ broxton_audio_card.name = "glkda7219max";
+ /* Fixup the SSP entries for geminilake */
+ for (i = 0; i < ARRAY_SIZE(broxton_dais); i++) {
+ /* MAXIM_CODEC is connected to SSP1. */
+ if (!strcmp(broxton_dais[i].codecs->dai_name,
+ BXT_MAXIM_CODEC_DAI)) {
+ broxton_dais[i].name = "SSP1-Codec";
+ broxton_dais[i].cpus->dai_name = "SSP1 Pin";
+ }
+ /* DIALOG_CODE is connected to SSP2 */
+ else if (!strcmp(broxton_dais[i].codecs->dai_name,
+ BXT_DIALOG_CODEC_DAI)) {
+ broxton_dais[i].name = "SSP2-Codec";
+ broxton_dais[i].cpus->dai_name = "SSP2 Pin";
+ }
+ }
+ } else if (soc_intel_is_cml()) {
+ unsigned int i;
+
+ if (ctx->spkamp == SPKAMP_MAX98390) {
+ broxton_audio_card.name = "cml_max98390_da7219";
+
+ broxton_audio_card.codec_conf = max98390_codec_confs;
+ broxton_audio_card.num_configs = ARRAY_SIZE(max98390_codec_confs);
+ } else
+ broxton_audio_card.name = "cmlda7219max";
+
+ for (i = 0; i < ARRAY_SIZE(broxton_dais); i++) {
+ /* MAXIM_CODEC is connected to SSP1. */
+ if (!strcmp(broxton_dais[i].codecs->dai_name,
+ BXT_MAXIM_CODEC_DAI)) {
+ broxton_dais[i].name = "SSP1-Codec";
+ broxton_dais[i].cpus->dai_name = "SSP1 Pin";
+
+ if (ctx->spkamp == SPKAMP_MAX98390) {
+ broxton_dais[i].codecs = max98390_codec;
+ broxton_dais[i].num_codecs = ARRAY_SIZE(max98390_codec);
+ }
+ }
+ /* DIALOG_CODEC is connected to SSP0 */
+ else if (!strcmp(broxton_dais[i].codecs->dai_name,
+ BXT_DIALOG_CODEC_DAI)) {
+ broxton_dais[i].name = "SSP0-Codec";
+ broxton_dais[i].cpus->dai_name = "SSP0 Pin";
+ }
+ }
+ }
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ platform_name = mach->mach_params.platform;
+
+ ret = snd_soc_fixup_dai_links_platform_name(&broxton_audio_card,
+ platform_name);
+ if (ret)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ return devm_snd_soc_register_card(&pdev->dev, &broxton_audio_card);
+}
+
+static const struct platform_device_id bxt_board_ids[] = {
+ { .name = "bxt_da7219_max98357a" },
+ { .name = "glk_da7219_max98357a" },
+ { .name = "cml_da7219_max98357a" },
+ { }
+};
+
+static struct platform_driver broxton_audio = {
+ .probe = broxton_audio_probe,
+ .driver = {
+ .name = "bxt_da7219_max98357a",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = bxt_board_ids,
+};
+module_platform_driver(broxton_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode");
+MODULE_AUTHOR("Sathyanarayana Nujella <sathyanarayana.nujella@intel.com>");
+MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com>");
+MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
+MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>");
+MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>");
+MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>");
+MODULE_AUTHOR("Brent Lu <brent.lu@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bxt_da7219_max98357a");
+MODULE_ALIAS("platform:glk_da7219_max98357a");
+MODULE_ALIAS("platform:cml_da7219_max98357a");
diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c
new file mode 100644
index 000000000..0f3157dfa
--- /dev/null
+++ b/sound/soc/intel/boards/bxt_rt298.c
@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Broxton-P I2S Machine Driver
+ *
+ * Copyright (C) 2014-2016, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Skylake I2S Machine driver
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include "../../codecs/hdac_hdmi.h"
+#include "../../codecs/rt298.h"
+#include "hda_dsp_common.h"
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack broxton_headset;
+static struct snd_soc_jack broxton_hdmi[3];
+
+struct bxt_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct bxt_rt286_private {
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+};
+
+enum {
+ BXT_DPCM_AUDIO_PB = 0,
+ BXT_DPCM_AUDIO_CP,
+ BXT_DPCM_AUDIO_REF_CP,
+ BXT_DPCM_AUDIO_DMIC_CP,
+ BXT_DPCM_AUDIO_HDMI1_PB,
+ BXT_DPCM_AUDIO_HDMI2_PB,
+ BXT_DPCM_AUDIO_HDMI3_PB,
+};
+
+static struct snd_soc_jack_pin broxton_headset_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static const struct snd_kcontrol_new broxton_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Mic Jack"),
+};
+
+static const struct snd_soc_dapm_widget broxton_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("DMIC2", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SPK("HDMI3", NULL),
+};
+
+static const struct snd_soc_dapm_route broxton_rt298_map[] = {
+ /* speaker */
+ {"Speaker", NULL, "SPOR"},
+ {"Speaker", NULL, "SPOL"},
+
+ /* HP jack connectors - unknown if we have jack detect */
+ {"Headphone Jack", NULL, "HPO Pin"},
+
+ /* other jacks */
+ {"MIC1", NULL, "Mic Jack"},
+
+ /* digital mics */
+ {"DMIC1 Pin", NULL, "DMIC2"},
+ {"DMic", NULL, "SoC DMIC"},
+
+ {"HDMI1", NULL, "hif5-0 Output"},
+ {"HDMI2", NULL, "hif6-0 Output"},
+ {"HDMI2", NULL, "hif7-0 Output"},
+
+ /* CODEC BE connections */
+ { "AIF1 Playback", NULL, "ssp5 Tx"},
+ { "ssp5 Tx", NULL, "codec0_out"},
+ { "ssp5 Tx", NULL, "codec1_out"},
+
+ { "codec0_in", NULL, "ssp5 Rx" },
+ { "ssp5 Rx", NULL, "AIF1 Capture" },
+
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "Capture" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+};
+
+static const struct snd_soc_dapm_route geminilake_rt298_map[] = {
+ /* speaker */
+ {"Speaker", NULL, "SPOR"},
+ {"Speaker", NULL, "SPOL"},
+
+ /* HP jack connectors - unknown if we have jack detect */
+ {"Headphone Jack", NULL, "HPO Pin"},
+
+ /* other jacks */
+ {"MIC1", NULL, "Mic Jack"},
+
+ /* digital mics */
+ {"DMIC1 Pin", NULL, "DMIC2"},
+ {"DMic", NULL, "SoC DMIC"},
+
+ {"HDMI1", NULL, "hif5-0 Output"},
+ {"HDMI2", NULL, "hif6-0 Output"},
+ {"HDMI2", NULL, "hif7-0 Output"},
+
+ /* CODEC BE connections */
+ { "AIF1 Playback", NULL, "ssp2 Tx"},
+ { "ssp2 Tx", NULL, "codec0_out"},
+ { "ssp2 Tx", NULL, "codec1_out"},
+
+ { "codec0_in", NULL, "ssp2 Rx" },
+ { "ssp2 Rx", NULL, "AIF1 Capture" },
+
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "Capture" },
+
+ { "dmic_voice", NULL, "DMIC16k Rx" },
+ { "DMIC16k Rx", NULL, "Capture" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+};
+
+static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret = 0;
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &broxton_headset,
+ broxton_headset_pins, ARRAY_SIZE(broxton_headset_pins));
+
+ if (ret)
+ return ret;
+
+ rt298_mic_detect(component, &broxton_headset);
+
+ snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+
+ return 0;
+}
+
+static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct bxt_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct bxt_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = BXT_DPCM_AUDIO_HDMI1_PB + dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int broxton_ssp5_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP5 to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int broxton_rt298_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL,
+ 19200000, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_ops broxton_rt298_ops = {
+ .hw_params = broxton_rt298_hw_params,
+};
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static const unsigned int channels_dmic[] = {
+ 1, 2, 3, 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static int broxton_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = 4;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_dmic_channels);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops broxton_dmic_ops = {
+ .startup = broxton_dmic_startup,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int bxt_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * on this platform for PCM device we support:
+ * 48Khz
+ * stereo
+ * 16-bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops broxton_rt286_fe_ops = {
+ .startup = bxt_fe_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp5_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));
+SND_SOC_DAILINK_DEF(ssp5_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00",
+ "rt298-aif1")));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec",
+ "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(dmic16k,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
+
+/* broxton digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link broxton_rt298_dais[] = {
+ /* Front End DAI links */
+ [BXT_DPCM_AUDIO_PB] =
+ {
+ .name = "Bxt Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .init = broxton_rt298_fe_init,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &broxton_rt286_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_CP] =
+ {
+ .name = "Bxt Audio Capture Port",
+ .stream_name = "Audio Record",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &broxton_rt286_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_REF_CP] =
+ {
+ .name = "Bxt Audio Reference cap",
+ .stream_name = "refcap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_DMIC_CP] =
+ {
+ .name = "Bxt Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &broxton_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI1_PB] =
+ {
+ .name = "Bxt HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI2_PB] =
+ {
+ .name = "Bxt HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [BXT_DPCM_AUDIO_HDMI3_PB] =
+ {
+ .name = "Bxt HDMI Port3",
+ .stream_name = "Hdmi3",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+ /* Back End DAI links */
+ {
+ /* SSP5 - Codec */
+ .name = "SSP5-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = broxton_rt298_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broxton_ssp5_fixup,
+ .ops = &broxton_rt298_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 1,
+ .be_hw_params_fixup = broxton_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 2,
+ .be_hw_params_fixup = broxton_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = broxton_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int bxt_card_late_probe(struct snd_soc_card *card)
+{
+ struct bxt_rt286_private *ctx = snd_soc_card_get_drvdata(card);
+ struct bxt_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (ctx->common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct bxt_hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ return hda_dsp_hdmi_build_controls(card, component);
+ }
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &broxton_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &broxton_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+
+/* broxton audio machine driver for SPT + RT298S */
+static struct snd_soc_card broxton_rt298 = {
+ .name = "broxton-rt298",
+ .owner = THIS_MODULE,
+ .dai_link = broxton_rt298_dais,
+ .num_links = ARRAY_SIZE(broxton_rt298_dais),
+ .controls = broxton_controls,
+ .num_controls = ARRAY_SIZE(broxton_controls),
+ .dapm_widgets = broxton_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(broxton_widgets),
+ .dapm_routes = broxton_rt298_map,
+ .num_dapm_routes = ARRAY_SIZE(broxton_rt298_map),
+ .fully_routed = true,
+ .late_probe = bxt_card_late_probe,
+
+};
+
+static struct snd_soc_card geminilake_rt298 = {
+ .name = "geminilake-rt298",
+ .owner = THIS_MODULE,
+ .dai_link = broxton_rt298_dais,
+ .num_links = ARRAY_SIZE(broxton_rt298_dais),
+ .controls = broxton_controls,
+ .num_controls = ARRAY_SIZE(broxton_controls),
+ .dapm_widgets = broxton_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(broxton_widgets),
+ .dapm_routes = geminilake_rt298_map,
+ .num_dapm_routes = ARRAY_SIZE(geminilake_rt298_map),
+ .fully_routed = true,
+ .late_probe = bxt_card_late_probe,
+};
+
+static int broxton_audio_probe(struct platform_device *pdev)
+{
+ struct bxt_rt286_private *ctx;
+ struct snd_soc_card *card =
+ (struct snd_soc_card *)pdev->id_entry->driver_data;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(broxton_rt298_dais); i++) {
+ if (!strncmp(card->dai_link[i].codecs->name, "i2c-INT343A:00",
+ I2C_NAME_SIZE)) {
+ if (!strncmp(card->name, "broxton-rt298",
+ PLATFORM_NAME_SIZE)) {
+ card->dai_link[i].name = "SSP5-Codec";
+ card->dai_link[i].cpus->dai_name = "SSP5 Pin";
+ } else if (!strncmp(card->name, "geminilake-rt298",
+ PLATFORM_NAME_SIZE)) {
+ card->dai_link[i].name = "SSP2-Codec";
+ card->dai_link[i].cpus->dai_name = "SSP2 Pin";
+ }
+ }
+ }
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(card, ctx);
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ platform_name = mach->mach_params.platform;
+
+ ret = snd_soc_fixup_dai_links_platform_name(card,
+ platform_name);
+ if (ret)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct platform_device_id bxt_board_ids[] = {
+ { .name = "bxt_alc298s_i2s", .driver_data =
+ (unsigned long)&broxton_rt298 },
+ { .name = "glk_alc298s_i2s", .driver_data =
+ (unsigned long)&geminilake_rt298 },
+ {}
+};
+
+static struct platform_driver broxton_audio = {
+ .probe = broxton_audio_probe,
+ .driver = {
+ .name = "bxt_alc298s_i2s",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = bxt_board_ids,
+};
+module_platform_driver(broxton_audio)
+
+/* Module information */
+MODULE_AUTHOR("Ramesh Babu <Ramesh.Babu@intel.com>");
+MODULE_AUTHOR("Senthilnathan Veppur <senthilnathanx.veppur@intel.com>");
+MODULE_DESCRIPTION("Intel SST Audio for Broxton");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bxt_alc298s_i2s");
+MODULE_ALIAS("platform:glk_alc298s_i2s");
diff --git a/sound/soc/intel/boards/bytcht_cx2072x.c b/sound/soc/intel/boards/bytcht_cx2072x.c
new file mode 100644
index 000000000..0b50b3646
--- /dev/null
+++ b/sound/soc/intel/boards/bytcht_cx2072x.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// ASoC DPCM Machine driver for Baytrail / Cherrytrail platforms with
+// CX2072X codec
+//
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/cx2072x.h"
+#include "../atom/sst-atom-controls.h"
+
+static const struct snd_soc_dapm_widget byt_cht_cx2072x_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route byt_cht_cx2072x_audio_map[] = {
+ /* External Speakers: HFL, HFR */
+ {"Headphone", NULL, "PORTA"},
+ {"Ext Spk", NULL, "PORTG"},
+ {"PORTC", NULL, "Int Mic"},
+ {"PORTD", NULL, "Headset Mic"},
+
+ {"Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+ {"ssp2 Rx", NULL, "Capture"},
+};
+
+static const struct snd_kcontrol_new byt_cht_cx2072x_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static struct snd_soc_jack byt_cht_cx2072x_headset;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin byt_cht_cx2072x_headset_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static const struct acpi_gpio_params byt_cht_cx2072x_headset_gpios;
+static const struct acpi_gpio_mapping byt_cht_cx2072x_acpi_gpios[] = {
+ { "headset-gpios", &byt_cht_cx2072x_headset_gpios, 1 },
+ {},
+};
+
+static int byt_cht_cx2072x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret;
+
+ if (devm_acpi_dev_add_driver_gpios(codec->dev,
+ byt_cht_cx2072x_acpi_gpios))
+ dev_warn(rtd->dev, "Unable to add GPIO mapping table\n");
+
+ card->dapm.idle_bias_off = true;
+
+ /* set the default PLL rate, the clock is handled by the codec driver */
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), CX2072X_MCLK_EXTERNAL_PLL,
+ 19200000, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(rtd->dev, "Could not set sysclk\n");
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &byt_cht_cx2072x_headset,
+ byt_cht_cx2072x_headset_pins,
+ ARRAY_SIZE(byt_cht_cx2072x_headset_pins));
+ if (ret)
+ return ret;
+
+ snd_soc_component_set_jack(codec, &byt_cht_cx2072x_headset, NULL);
+
+ snd_soc_dai_set_bclk_ratio(asoc_rtd_to_codec(rtd, 0), 50);
+
+ return 0;
+}
+
+static int byt_cht_cx2072x_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret;
+
+ /* The DSP will covert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch 24-bit. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int byt_cht_cx2072x_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static struct snd_soc_ops byt_cht_cx2072x_aif1_ops = {
+ .startup = byt_cht_cx2072x_aif1_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+
+SND_SOC_DAILINK_DEF(cx2072x,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-14F10720:00", "cx2072x-hifi")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link byt_cht_cx2072x_dais[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_cht_cx2072x_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &byt_cht_cx2072x_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .init = byt_cht_cx2072x_init,
+ .be_hw_params_fixup = byt_cht_cx2072x_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp2, cx2072x, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht cx2072x" /* card name will be 'sof-bytcht cx2072x' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bytcht-cx2072x"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card byt_cht_cx2072x_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = byt_cht_cx2072x_dais,
+ .num_links = ARRAY_SIZE(byt_cht_cx2072x_dais),
+ .dapm_widgets = byt_cht_cx2072x_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_cht_cx2072x_widgets),
+ .dapm_routes = byt_cht_cx2072x_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_cht_cx2072x_audio_map),
+ .controls = byt_cht_cx2072x_controls,
+ .num_controls = ARRAY_SIZE(byt_cht_cx2072x_controls),
+};
+
+static char codec_name[SND_ACPI_I2C_ID_LEN];
+
+static int snd_byt_cht_cx2072x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ struct acpi_device *adev;
+ int dai_index = 0;
+ int i, ret;
+
+ byt_cht_cx2072x_card.dev = &pdev->dev;
+ mach = dev_get_platdata(&pdev->dev);
+
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(byt_cht_cx2072x_dais); i++) {
+ if (!strcmp(byt_cht_cx2072x_dais[i].codecs->name,
+ "i2c-14F10720:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(codec_name, sizeof(codec_name), "i2c-%s",
+ acpi_dev_name(adev));
+ put_device(&adev->dev);
+ byt_cht_cx2072x_dais[dai_index].codecs->name = codec_name;
+ }
+
+ /* override plaform name, if required */
+ ret = snd_soc_fixup_dai_links_platform_name(&byt_cht_cx2072x_card,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ return devm_snd_soc_register_card(&pdev->dev, &byt_cht_cx2072x_card);
+}
+
+static struct platform_driver snd_byt_cht_cx2072x_driver = {
+ .driver = {
+ .name = "bytcht_cx2072x",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_byt_cht_cx2072x_probe,
+};
+module_platform_driver(snd_byt_cht_cx2072x_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcht_cx2072x");
diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c
new file mode 100644
index 000000000..e1e46b4bb
--- /dev/null
+++ b/sound/soc/intel/boards/bytcht_da7213.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and
+ * Cherrytrail-based platforms, with Dialog DA7213 codec
+ *
+ * Copyright (C) 2017 Intel Corporation
+ * Author: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/da7213.h"
+#include "../atom/sst-atom-controls.h"
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Mic"),
+ SOC_DAPM_PIN_SWITCH("Aux In"),
+};
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+ SND_SOC_DAPM_LINE("Aux In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "HPL"},
+ {"Headphone Jack", NULL, "HPR"},
+
+ {"AUXL", NULL, "Aux In"},
+ {"AUXR", NULL, "Aux In"},
+
+ /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */
+ {"MIC1", NULL, "Headset Mic"},
+ {"MIC2", NULL, "Mic"},
+
+ /* SOC-codec link */
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Rx", NULL, "Capture"},
+};
+
+static int codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ int ret;
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The DSP will convert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch 24-bit. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static int aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK,
+ 19200000, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(codec_dai->dev, "can't set codec sysclk configuration\n");
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret);
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int aif1_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7213_SYSCLK_MCLK, 0, 0);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret);
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_ops aif1_ops = {
+ .startup = aif1_startup,
+};
+
+static const struct snd_soc_ops ssp2_ops = {
+ .hw_params = aif1_hw_params,
+ .hw_free = aif1_hw_free,
+
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7213:00",
+ "da7213-hifi")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link dailink[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* CODEC<->CODEC link */
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = codec_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht da7213" /* card name will be 'sof-bytcht da7213' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bytcht-da7213"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card bytcht_da7213_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = dailink,
+ .num_links = ARRAY_SIZE(dailink),
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+};
+
+static char codec_name[SND_ACPI_I2C_ID_LEN];
+
+static int bytcht_da7213_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct acpi_device *adev;
+ int dai_index = 0;
+ int ret_val = 0;
+ int i;
+
+ mach = pdev->dev.platform_data;
+ card = &bytcht_da7213_card;
+ card->dev = &pdev->dev;
+
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(dailink); i++) {
+ if (!strcmp(dailink[i].codecs->name, "i2c-DLGS7213:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(codec_name, sizeof(codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ dailink[dai_index].codecs->name = codec_name;
+ }
+
+ /* override plaform name, if required */
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(card, platform_name);
+ if (ret_val)
+ return ret_val;
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, card);
+ return ret_val;
+}
+
+static struct platform_driver bytcht_da7213_driver = {
+ .driver = {
+ .name = "bytcht_da7213",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = bytcht_da7213_probe,
+};
+module_platform_driver(bytcht_da7213_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver");
+MODULE_AUTHOR("Pierre-Louis Bossart");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcht_da7213");
diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c
new file mode 100644
index 000000000..81269ed5a
--- /dev/null
+++ b/sound/soc/intel/boards/bytcht_es8316.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bytcht_es8316.c - ASoc Machine driver for Intel Baytrail/Cherrytrail
+ * platforms with Everest ES8316 SoC
+ *
+ * Copyright (C) 2017 Endless Mobile, Inc.
+ * Authors: David Yang <yangxiaohua@everest-semi.com>,
+ * Daniel Drake <drake@endlessm.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../atom/sst-atom-controls.h"
+#include "../common/soc-intel-quirks.h"
+
+/* jd-inv + terminating entry */
+#define MAX_NO_PROPS 2
+
+struct byt_cht_es8316_private {
+ struct clk *mclk;
+ struct snd_soc_jack jack;
+ struct gpio_desc *speaker_en_gpio;
+ bool speaker_en;
+};
+
+enum {
+ BYT_CHT_ES8316_INTMIC_IN1_MAP,
+ BYT_CHT_ES8316_INTMIC_IN2_MAP,
+};
+
+#define BYT_CHT_ES8316_MAP(quirk) ((quirk) & GENMASK(3, 0))
+#define BYT_CHT_ES8316_SSP0 BIT(16)
+#define BYT_CHT_ES8316_MONO_SPEAKER BIT(17)
+#define BYT_CHT_ES8316_JD_INVERTED BIT(18)
+
+static unsigned long quirk;
+
+static int quirk_override = -1;
+module_param_named(quirk, quirk_override, int, 0444);
+MODULE_PARM_DESC(quirk, "Board-specific quirk override");
+
+static void log_quirks(struct device *dev)
+{
+ if (BYT_CHT_ES8316_MAP(quirk) == BYT_CHT_ES8316_INTMIC_IN1_MAP)
+ dev_info(dev, "quirk IN1_MAP enabled");
+ if (BYT_CHT_ES8316_MAP(quirk) == BYT_CHT_ES8316_INTMIC_IN2_MAP)
+ dev_info(dev, "quirk IN2_MAP enabled");
+ if (quirk & BYT_CHT_ES8316_SSP0)
+ dev_info(dev, "quirk SSP0 enabled");
+ if (quirk & BYT_CHT_ES8316_MONO_SPEAKER)
+ dev_info(dev, "quirk MONO_SPEAKER enabled\n");
+ if (quirk & BYT_CHT_ES8316_JD_INVERTED)
+ dev_info(dev, "quirk JD_INVERTED enabled\n");
+}
+
+static int byt_cht_es8316_speaker_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ priv->speaker_en = true;
+ else
+ priv->speaker_en = false;
+
+ gpiod_set_value_cansleep(priv->speaker_en_gpio, priv->speaker_en);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget byt_cht_es8316_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+
+ SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0,
+ byt_cht_es8316_speaker_power_event,
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+};
+
+static const struct snd_soc_dapm_route byt_cht_es8316_audio_map[] = {
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+
+ /*
+ * There is no separate speaker output instead the speakers are muxed to
+ * the HP outputs. The mux is controlled by the "Speaker Power" supply.
+ */
+ {"Speaker", NULL, "HPOL"},
+ {"Speaker", NULL, "HPOR"},
+ {"Speaker", NULL, "Speaker Power"},
+};
+
+static const struct snd_soc_dapm_route byt_cht_es8316_intmic_in1_map[] = {
+ {"MIC1", NULL, "Internal Mic"},
+ {"MIC2", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_cht_es8316_intmic_in2_map[] = {
+ {"MIC2", NULL, "Internal Mic"},
+ {"MIC1", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_cht_es8316_ssp0_map[] = {
+ {"Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx"},
+ {"ssp0 Rx", NULL, "Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_cht_es8316_ssp2_map[] = {
+ {"Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx" },
+ {"codec_in1", NULL, "ssp2 Rx" },
+ {"ssp2 Rx", NULL, "Capture"},
+};
+
+static const struct snd_kcontrol_new byt_cht_es8316_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static struct snd_soc_jack_pin byt_cht_es8316_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int byt_cht_es8316_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component;
+ struct snd_soc_card *card = runtime->card;
+ struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card);
+ const struct snd_soc_dapm_route *custom_map;
+ int num_routes;
+ int ret;
+
+ card->dapm.idle_bias_off = true;
+
+ switch (BYT_CHT_ES8316_MAP(quirk)) {
+ case BYT_CHT_ES8316_INTMIC_IN1_MAP:
+ default:
+ custom_map = byt_cht_es8316_intmic_in1_map;
+ num_routes = ARRAY_SIZE(byt_cht_es8316_intmic_in1_map);
+ break;
+ case BYT_CHT_ES8316_INTMIC_IN2_MAP:
+ custom_map = byt_cht_es8316_intmic_in2_map;
+ num_routes = ARRAY_SIZE(byt_cht_es8316_intmic_in2_map);
+ break;
+ }
+ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
+ if (ret)
+ return ret;
+
+ if (quirk & BYT_CHT_ES8316_SSP0) {
+ custom_map = byt_cht_es8316_ssp0_map;
+ num_routes = ARRAY_SIZE(byt_cht_es8316_ssp0_map);
+ } else {
+ custom_map = byt_cht_es8316_ssp2_map;
+ num_routes = ARRAY_SIZE(byt_cht_es8316_ssp2_map);
+ }
+ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
+ if (ret)
+ return ret;
+
+ /*
+ * The firmware might enable the clock at boot (this information
+ * may or may not be reflected in the enable clock register).
+ * To change the rate we must disable the clock first to cover these
+ * cases. Due to common clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled, we need to enable
+ * the clock first.
+ */
+ ret = clk_prepare_enable(priv->mclk);
+ if (!ret)
+ clk_disable_unprepare(priv->mclk);
+
+ ret = clk_set_rate(priv->mclk, 19200000);
+ if (ret)
+ dev_err(card->dev, "unable to set MCLK rate\n");
+
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret)
+ dev_err(card->dev, "unable to enable MCLK\n");
+
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(runtime, 0), 0, 19200000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec clock %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &priv->jack, byt_cht_es8316_jack_pins,
+ ARRAY_SIZE(byt_cht_es8316_jack_pins));
+ if (ret) {
+ dev_err(card->dev, "jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_soc_component_set_jack(codec, &priv->jack, NULL);
+
+ return 0;
+}
+
+static int byt_cht_es8316_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret, bits;
+
+ /* The DSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ if (quirk & BYT_CHT_ES8316_SSP0) {
+ /* set SSP0 to 16-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ bits = 16;
+ } else {
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+ bits = 24;
+ }
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch 24-bit. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS
+ );
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int byt_cht_es8316_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static const struct snd_soc_ops byt_cht_es8316_aif1_ops = {
+ .startup = byt_cht_es8316_aif1_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8316:00", "ES8316 HiFi")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link byt_cht_es8316_dais[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_cht_es8316_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &byt_cht_es8316_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+
+ /* back ends */
+ {
+ /* Only SSP2 has been tested here, so BYT-CR platforms that
+ * require SSP0 will not work.
+ */
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = byt_cht_es8316_codec_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .init = byt_cht_es8316_init,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+
+/* SoC card */
+static char codec_name[SND_ACPI_I2C_ID_LEN];
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+static char long_name[50]; /* = "bytcht-es8316-*-spk-*-mic" */
+#endif
+static char components_string[32]; /* = "cfg-spk:* cfg-mic:* */
+
+static int byt_cht_es8316_suspend(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, codec_name)) {
+ dev_dbg(component->dev, "disabling jack detect before suspend\n");
+ snd_soc_component_set_jack(component, NULL, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int byt_cht_es8316_resume(struct snd_soc_card *card)
+{
+ struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, codec_name)) {
+ dev_dbg(component->dev, "re-enabling jack detect after resume\n");
+ snd_soc_component_set_jack(component, &priv->jack, NULL);
+ break;
+ }
+ }
+
+ /*
+ * Some Cherry Trail boards with an ES8316 codec have a bug in their
+ * ACPI tables where the MSSL1680 touchscreen's _PS0 and _PS3 methods
+ * wrongly also set the speaker-enable GPIO to 1/0. Testing has shown
+ * that this really is a bug and the GPIO has no influence on the
+ * touchscreen at all.
+ *
+ * The silead.c touchscreen driver does not support runtime suspend, so
+ * the GPIO can only be changed underneath us during a system suspend.
+ * This resume() function runs from a pm complete() callback, and thus
+ * is guaranteed to run after the touchscreen driver/ACPI-subsys has
+ * brought the touchscreen back up again (and thus changed the GPIO).
+ *
+ * So to work around this we pass GPIOD_FLAGS_BIT_NONEXCLUSIVE when
+ * requesting the GPIO and we set its value here to undo any changes
+ * done by the touchscreen's broken _PS0 ACPI method.
+ */
+ gpiod_set_value_cansleep(priv->speaker_en_gpio, priv->speaker_en);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht es8316" /* card name will be 'sof-bytcht es8316' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bytcht-es8316"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+static struct snd_soc_card byt_cht_es8316_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = byt_cht_es8316_dais,
+ .num_links = ARRAY_SIZE(byt_cht_es8316_dais),
+ .dapm_widgets = byt_cht_es8316_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_cht_es8316_widgets),
+ .dapm_routes = byt_cht_es8316_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_cht_es8316_audio_map),
+ .controls = byt_cht_es8316_controls,
+ .num_controls = ARRAY_SIZE(byt_cht_es8316_controls),
+ .fully_routed = true,
+ .suspend_pre = byt_cht_es8316_suspend,
+ .resume_post = byt_cht_es8316_resume,
+};
+
+static const struct acpi_gpio_params first_gpio = { 0, 0, false };
+
+static const struct acpi_gpio_mapping byt_cht_es8316_gpios[] = {
+ { "speaker-enable-gpios", &first_gpio, 1 },
+ { },
+};
+
+/* Please keep this list alphabetically sorted */
+static const struct dmi_system_id byt_cht_es8316_quirk_table[] = {
+ { /* Irbis NB41 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "IRBIS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "NB41"),
+ },
+ .driver_data = (void *)(BYT_CHT_ES8316_SSP0
+ | BYT_CHT_ES8316_INTMIC_IN2_MAP
+ | BYT_CHT_ES8316_JD_INVERTED),
+ },
+ { /* Nanote UMPC-01 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "RWC CO.,LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "UMPC-01"),
+ },
+ .driver_data = (void *)BYT_CHT_ES8316_INTMIC_IN1_MAP,
+ },
+ { /* Teclast X98 Plus II */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"),
+ },
+ .driver_data = (void *)(BYT_CHT_ES8316_INTMIC_IN1_MAP
+ | BYT_CHT_ES8316_JD_INVERTED),
+ },
+ {}
+};
+
+static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev)
+{
+ static const char * const mic_name[] = { "in1", "in2" };
+ struct property_entry props[MAX_NO_PROPS] = {};
+ struct byt_cht_es8316_private *priv;
+ const struct dmi_system_id *dmi_id;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct acpi_device *adev;
+ struct device *codec_dev;
+ unsigned int cnt = 0;
+ int dai_index = 0;
+ int i;
+ int ret = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mach = dev->platform_data;
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(byt_cht_es8316_dais); i++) {
+ if (!strcmp(byt_cht_es8316_dais[i].codecs->name,
+ "i2c-ESSX8316:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(codec_name, sizeof(codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ byt_cht_es8316_dais[dai_index].codecs->name = codec_name;
+ }
+
+ /* override plaform name, if required */
+ byt_cht_es8316_card.dev = dev;
+ platform_name = mach->mach_params.platform;
+
+ ret = snd_soc_fixup_dai_links_platform_name(&byt_cht_es8316_card,
+ platform_name);
+ if (ret)
+ return ret;
+
+ /* Check for BYTCR or other platform and setup quirks */
+ dmi_id = dmi_first_match(byt_cht_es8316_quirk_table);
+ if (dmi_id) {
+ quirk = (unsigned long)dmi_id->driver_data;
+ } else if (soc_intel_is_byt() &&
+ mach->mach_params.acpi_ipc_irq_index == 0) {
+ /* On BYTCR default to SSP0, internal-mic-in2-map, mono-spk */
+ quirk = BYT_CHT_ES8316_SSP0 | BYT_CHT_ES8316_INTMIC_IN2_MAP |
+ BYT_CHT_ES8316_MONO_SPEAKER;
+ } else {
+ /* Others default to internal-mic-in1-map, mono-speaker */
+ quirk = BYT_CHT_ES8316_INTMIC_IN1_MAP |
+ BYT_CHT_ES8316_MONO_SPEAKER;
+ }
+ if (quirk_override != -1) {
+ dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n",
+ quirk, quirk_override);
+ quirk = quirk_override;
+ }
+ log_quirks(dev);
+
+ if (quirk & BYT_CHT_ES8316_SSP0)
+ byt_cht_es8316_dais[dai_index].cpus->dai_name = "ssp0-port";
+
+ /* get the clock */
+ priv->mclk = devm_clk_get(dev, "pmc_plt_clk_3");
+ if (IS_ERR(priv->mclk)) {
+ ret = PTR_ERR(priv->mclk);
+ dev_err(dev, "clk_get pmc_plt_clk_3 failed: %d\n", ret);
+ return ret;
+ }
+
+ /* get speaker enable GPIO */
+ codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, codec_name);
+ if (!codec_dev)
+ return -EPROBE_DEFER;
+
+ if (quirk & BYT_CHT_ES8316_JD_INVERTED)
+ props[cnt++] = PROPERTY_ENTRY_BOOL("everest,jack-detect-inverted");
+
+ if (cnt) {
+ ret = device_add_properties(codec_dev, props);
+ if (ret) {
+ put_device(codec_dev);
+ return ret;
+ }
+ }
+
+ devm_acpi_dev_add_driver_gpios(codec_dev, byt_cht_es8316_gpios);
+ priv->speaker_en_gpio =
+ gpiod_get_index(codec_dev, "speaker-enable", 0,
+ /* see comment in byt_cht_es8316_resume */
+ GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_NONEXCLUSIVE);
+ put_device(codec_dev);
+
+ if (IS_ERR(priv->speaker_en_gpio)) {
+ ret = PTR_ERR(priv->speaker_en_gpio);
+ switch (ret) {
+ case -ENOENT:
+ priv->speaker_en_gpio = NULL;
+ break;
+ default:
+ dev_err(dev, "get speaker GPIO failed: %d\n", ret);
+ fallthrough;
+ case -EPROBE_DEFER:
+ return ret;
+ }
+ }
+
+ snprintf(components_string, sizeof(components_string),
+ "cfg-spk:%s cfg-mic:%s",
+ (quirk & BYT_CHT_ES8316_MONO_SPEAKER) ? "1" : "2",
+ mic_name[BYT_CHT_ES8316_MAP(quirk)]);
+ byt_cht_es8316_card.components = components_string;
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+ snprintf(long_name, sizeof(long_name), "bytcht-es8316-%s-spk-%s-mic",
+ (quirk & BYT_CHT_ES8316_MONO_SPEAKER) ? "mono" : "stereo",
+ mic_name[BYT_CHT_ES8316_MAP(quirk)]);
+ byt_cht_es8316_card.long_name = long_name;
+#endif
+
+ /* register the soc card */
+ snd_soc_card_set_drvdata(&byt_cht_es8316_card, priv);
+
+ ret = devm_snd_soc_register_card(dev, &byt_cht_es8316_card);
+ if (ret) {
+ gpiod_put(priv->speaker_en_gpio);
+ dev_err(dev, "snd_soc_register_card failed: %d\n", ret);
+ return ret;
+ }
+ platform_set_drvdata(pdev, &byt_cht_es8316_card);
+ return 0;
+}
+
+static int snd_byt_cht_es8316_mc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card);
+
+ gpiod_put(priv->speaker_en_gpio);
+ return 0;
+}
+
+static struct platform_driver snd_byt_cht_es8316_mc_driver = {
+ .driver = {
+ .name = "bytcht_es8316",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_byt_cht_es8316_mc_probe,
+ .remove = snd_byt_cht_es8316_mc_remove,
+};
+
+module_platform_driver(snd_byt_cht_es8316_mc_driver);
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Machine driver");
+MODULE_AUTHOR("David Yang <yangxiaohua@everest-semi.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcht_es8316");
diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c
new file mode 100644
index 000000000..8c0dab1f4
--- /dev/null
+++ b/sound/soc/intel/boards/bytcht_nocodec.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bytcht_nocodec.c - ASoc Machine driver for MinnowBoard Max and Up
+ * to make I2S signals observable on the Low-Speed connector. Audio codec
+ * is not managed by ASoC/DAPM
+ *
+ * Copyright (C) 2015-2017 Intel Corp
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../atom/sst-atom-controls.h"
+
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_MIC("Mic", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Mic"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"ssp2 Rx", NULL, "Mic"},
+ {"Speaker", NULL, "ssp2 Tx"},
+};
+
+static int codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret;
+
+ /* The DSP will convert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch 24-bit. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 24);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const unsigned int rates_48000[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_48000 = {
+ .count = ARRAY_SIZE(rates_48000),
+ .list = rates_48000,
+};
+
+static int aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_48000);
+}
+
+static struct snd_soc_ops aif1_ops = {
+ .startup = aif1_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link dais[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .ignore_suspend = 1,
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .ignore_suspend = 1,
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* CODEC<->CODEC link */
+ /* back ends */
+ {
+ .name = "SSP2-LowSpeed Connector",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = codec_fixup,
+ .ignore_suspend = 1,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp2_port, dummy, platform),
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card bytcht_nocodec_card = {
+ .name = "bytcht-nocodec",
+ .owner = THIS_MODULE,
+ .dai_link = dais,
+ .num_links = ARRAY_SIZE(dais),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .fully_routed = true,
+};
+
+static int snd_bytcht_nocodec_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0;
+
+ /* register the soc card */
+ bytcht_nocodec_card.dev = &pdev->dev;
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &bytcht_nocodec_card);
+
+ if (ret_val) {
+ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n",
+ ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &bytcht_nocodec_card);
+ return ret_val;
+}
+
+static struct platform_driver snd_bytcht_nocodec_mc_driver = {
+ .driver = {
+ .name = "bytcht_nocodec",
+ },
+ .probe = snd_bytcht_nocodec_mc_probe,
+};
+module_platform_driver(snd_bytcht_nocodec_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Nocodec Machine driver");
+MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcht_nocodec");
diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c
new file mode 100644
index 000000000..9a5ab96f9
--- /dev/null
+++ b/sound/soc/intel/boards/bytcr_rt5640.c
@@ -0,0 +1,1524 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform
+ *
+ * Copyright (C) 2014 Intel Corp
+ * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include <dt-bindings/sound/rt5640.h>
+#include "../../codecs/rt5640.h"
+#include "../atom/sst-atom-controls.h"
+#include "../common/soc-intel-quirks.h"
+
+enum {
+ BYT_RT5640_DMIC1_MAP,
+ BYT_RT5640_DMIC2_MAP,
+ BYT_RT5640_IN1_MAP,
+ BYT_RT5640_IN3_MAP,
+};
+
+enum {
+ BYT_RT5640_JD_SRC_GPIO1 = (RT5640_JD_SRC_GPIO1 << 4),
+ BYT_RT5640_JD_SRC_JD1_IN4P = (RT5640_JD_SRC_JD1_IN4P << 4),
+ BYT_RT5640_JD_SRC_JD2_IN4N = (RT5640_JD_SRC_JD2_IN4N << 4),
+ BYT_RT5640_JD_SRC_GPIO2 = (RT5640_JD_SRC_GPIO2 << 4),
+ BYT_RT5640_JD_SRC_GPIO3 = (RT5640_JD_SRC_GPIO3 << 4),
+ BYT_RT5640_JD_SRC_GPIO4 = (RT5640_JD_SRC_GPIO4 << 4),
+};
+
+enum {
+ BYT_RT5640_OVCD_TH_600UA = (6 << 8),
+ BYT_RT5640_OVCD_TH_1500UA = (15 << 8),
+ BYT_RT5640_OVCD_TH_2000UA = (20 << 8),
+};
+
+enum {
+ BYT_RT5640_OVCD_SF_0P5 = (RT5640_OVCD_SF_0P5 << 13),
+ BYT_RT5640_OVCD_SF_0P75 = (RT5640_OVCD_SF_0P75 << 13),
+ BYT_RT5640_OVCD_SF_1P0 = (RT5640_OVCD_SF_1P0 << 13),
+ BYT_RT5640_OVCD_SF_1P5 = (RT5640_OVCD_SF_1P5 << 13),
+};
+
+#define BYT_RT5640_MAP(quirk) ((quirk) & GENMASK(3, 0))
+#define BYT_RT5640_JDSRC(quirk) (((quirk) & GENMASK(7, 4)) >> 4)
+#define BYT_RT5640_OVCD_TH(quirk) (((quirk) & GENMASK(12, 8)) >> 8)
+#define BYT_RT5640_OVCD_SF(quirk) (((quirk) & GENMASK(14, 13)) >> 13)
+#define BYT_RT5640_JD_NOT_INV BIT(16)
+#define BYT_RT5640_MONO_SPEAKER BIT(17)
+#define BYT_RT5640_DIFF_MIC BIT(18) /* default is single-ended */
+#define BYT_RT5640_SSP2_AIF2 BIT(19) /* default is using AIF1 */
+#define BYT_RT5640_SSP0_AIF1 BIT(20)
+#define BYT_RT5640_SSP0_AIF2 BIT(21)
+#define BYT_RT5640_MCLK_EN BIT(22)
+#define BYT_RT5640_MCLK_25MHZ BIT(23)
+#define BYT_RT5640_NO_SPEAKERS BIT(24)
+
+#define BYTCR_INPUT_DEFAULTS \
+ (BYT_RT5640_IN3_MAP | \
+ BYT_RT5640_JD_SRC_JD1_IN4P | \
+ BYT_RT5640_OVCD_TH_2000UA | \
+ BYT_RT5640_OVCD_SF_0P75 | \
+ BYT_RT5640_DIFF_MIC)
+
+/* in-diff or dmic-pin + jdsrc + ovcd-th + -sf + jd-inv + terminating entry */
+#define MAX_NO_PROPS 6
+
+struct byt_rt5640_private {
+ struct snd_soc_jack jack;
+ struct clk *mclk;
+};
+static bool is_bytcr;
+
+static unsigned long byt_rt5640_quirk = BYT_RT5640_MCLK_EN;
+static int quirk_override = -1;
+module_param_named(quirk, quirk_override, int, 0444);
+MODULE_PARM_DESC(quirk, "Board-specific quirk override");
+
+static void log_quirks(struct device *dev)
+{
+ int map;
+ bool has_mclk = false;
+ bool has_ssp0 = false;
+ bool has_ssp0_aif1 = false;
+ bool has_ssp0_aif2 = false;
+ bool has_ssp2_aif2 = false;
+
+ map = BYT_RT5640_MAP(byt_rt5640_quirk);
+ switch (map) {
+ case BYT_RT5640_DMIC1_MAP:
+ dev_info(dev, "quirk DMIC1_MAP enabled\n");
+ break;
+ case BYT_RT5640_DMIC2_MAP:
+ dev_info(dev, "quirk DMIC2_MAP enabled\n");
+ break;
+ case BYT_RT5640_IN1_MAP:
+ dev_info(dev, "quirk IN1_MAP enabled\n");
+ break;
+ case BYT_RT5640_IN3_MAP:
+ dev_info(dev, "quirk IN3_MAP enabled\n");
+ break;
+ default:
+ dev_err(dev, "quirk map 0x%x is not supported, microphone input will not work\n", map);
+ break;
+ }
+ if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) {
+ dev_info(dev, "quirk realtek,jack-detect-source %ld\n",
+ BYT_RT5640_JDSRC(byt_rt5640_quirk));
+ dev_info(dev, "quirk realtek,over-current-threshold-microamp %ld\n",
+ BYT_RT5640_OVCD_TH(byt_rt5640_quirk) * 100);
+ dev_info(dev, "quirk realtek,over-current-scale-factor %ld\n",
+ BYT_RT5640_OVCD_SF(byt_rt5640_quirk));
+ }
+ if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV)
+ dev_info(dev, "quirk JD_NOT_INV enabled\n");
+ if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER)
+ dev_info(dev, "quirk MONO_SPEAKER enabled\n");
+ if (byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS)
+ dev_info(dev, "quirk NO_SPEAKERS enabled\n");
+ if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC)
+ dev_info(dev, "quirk DIFF_MIC enabled\n");
+ if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) {
+ dev_info(dev, "quirk SSP0_AIF1 enabled\n");
+ has_ssp0 = true;
+ has_ssp0_aif1 = true;
+ }
+ if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) {
+ dev_info(dev, "quirk SSP0_AIF2 enabled\n");
+ has_ssp0 = true;
+ has_ssp0_aif2 = true;
+ }
+ if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) {
+ dev_info(dev, "quirk SSP2_AIF2 enabled\n");
+ has_ssp2_aif2 = true;
+ }
+ if (is_bytcr && !has_ssp0)
+ dev_err(dev, "Invalid routing, bytcr detected but no SSP0-based quirk, audio cannot work with SSP2 on bytcr\n");
+ if (has_ssp0_aif1 && has_ssp0_aif2)
+ dev_err(dev, "Invalid routing, SSP0 cannot be connected to both AIF1 and AIF2\n");
+ if (has_ssp0 && has_ssp2_aif2)
+ dev_err(dev, "Invalid routing, cannot have both SSP0 and SSP2 connected to codec\n");
+
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) {
+ dev_info(dev, "quirk MCLK_EN enabled\n");
+ has_mclk = true;
+ }
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) {
+ if (has_mclk)
+ dev_info(dev, "quirk MCLK_25MHZ enabled\n");
+ else
+ dev_err(dev, "quirk MCLK_25MHZ enabled but quirk MCLK not selected, will be ignored\n");
+ }
+}
+
+static int byt_rt5640_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai,
+ int rate)
+{
+ int ret;
+
+ /* Configure the PLL before selecting it */
+ if (!(byt_rt5640_quirk & BYT_RT5640_MCLK_EN)) {
+ /* use bitclock as PLL input */
+ if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) ||
+ (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) {
+ /* 2x16 bit slots on SSP0 */
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5640_PLL1_S_BCLK1,
+ rate * 32, rate * 512);
+ } else {
+ /* 2x15 bit slots on SSP2 */
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5640_PLL1_S_BCLK1,
+ rate * 50, rate * 512);
+ }
+ } else {
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5640_PLL1_S_MCLK,
+ 25000000, rate * 512);
+ } else {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5640_PLL1_S_MCLK,
+ 19200000, rate * 512);
+ }
+ }
+
+ if (ret < 0) {
+ dev_err(codec_dai->component->dev, "can't set pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1,
+ rate * 512, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->component->dev, "can't set clock %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+#define BYT_CODEC_DAI1 "rt5640-aif1"
+#define BYT_CODEC_DAI2 "rt5640-aif2"
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1);
+ if (!codec_dai)
+ codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2);
+
+ if (!codec_dai) {
+ dev_err(card->dev,
+ "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) {
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "could not configure MCLK state\n");
+ return ret;
+ }
+ }
+ ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000);
+ } else {
+ /*
+ * Set codec clock source to internal clock before
+ * turning off the platform clock. Codec needs clock
+ * for Jack detection and button press
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_RCCLK,
+ 48000 * 512,
+ SND_SOC_CLOCK_IN);
+ if (!ret) {
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN)
+ clk_disable_unprepare(priv->mclk);
+ }
+ }
+
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = {
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "MICBIAS1"},
+ {"IN2P", NULL, "Headset Mic"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = {
+ {"Internal Mic", NULL, "Platform Clock"},
+ {"DMIC1", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = {
+ {"Internal Mic", NULL, "Platform Clock"},
+ {"DMIC2", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = {
+ {"Internal Mic", NULL, "Platform Clock"},
+ {"Internal Mic", NULL, "MICBIAS1"},
+ {"IN1P", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_in3_map[] = {
+ {"Internal Mic", NULL, "Platform Clock"},
+ {"Internal Mic", NULL, "MICBIAS1"},
+ {"IN3P", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_ssp2_aif1_map[] = {
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"AIF1 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_ssp2_aif2_map[] = {
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"AIF2 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_ssp0_aif1_map[] = {
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx"},
+
+ {"AIF1 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_ssp0_aif2_map[] = {
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx"},
+
+ {"AIF2 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_stereo_spk_map[] = {
+ {"Speaker", NULL, "Platform Clock"},
+ {"Speaker", NULL, "SPOLP"},
+ {"Speaker", NULL, "SPOLN"},
+ {"Speaker", NULL, "SPORP"},
+ {"Speaker", NULL, "SPORN"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_mono_spk_map[] = {
+ {"Speaker", NULL, "Platform Clock"},
+ {"Speaker", NULL, "SPOLP"},
+ {"Speaker", NULL, "SPOLN"},
+};
+
+static const struct snd_kcontrol_new byt_rt5640_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static struct snd_soc_jack_pin rt5640_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int byt_rt5640_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+
+ return byt_rt5640_prepare_and_enable_pll1(dai, params_rate(params));
+}
+
+/* Please keep this list alphabetically sorted */
+static const struct dmi_system_id byt_rt5640_quirk_table[] = {
+ { /* Acer Iconia One 7 B1-750 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "VESPA2"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD1_IN4P |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Acer Iconia Tab 8 W1-810 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Iconia W1-810"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD1_IN4P |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Acer One 10 S1002 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "One S1002"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF2 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* Advantech MICA-071 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Advantech"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MICA-071"),
+ },
+ /* OVCD Th = 1500uA to reliable detect head-phones vs -set */
+ .driver_data = (void *)(BYT_RT5640_IN3_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ARCHOS"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ARCHOS 80 Cesium"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ARCHOS"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ARCHOS 140 CESIUM"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ME176C"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TA"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TAF"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF2 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Chuwi Vi8 (CWI506) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "i86"),
+ /* The above are too generic, also match BIOS info */
+ DMI_MATCH(DMI_BIOS_VERSION, "CHUWI.D86JLBNR"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* Chuwi Vi10 (CWI505) */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
+ DMI_MATCH(DMI_BOARD_NAME, "BYT-PF02"),
+ DMI_MATCH(DMI_SYS_VENDOR, "ilife"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "S165"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* Chuwi Hi8 (CWI509) */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
+ DMI_MATCH(DMI_BOARD_NAME, "BYT-PA03C"),
+ DMI_MATCH(DMI_SYS_VENDOR, "ilife"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "S806"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP),
+ },
+ { /* Connect Tablet 9 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Connect"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Tablet 9"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Estar Beauty HD MID 7316R */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Estar"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Glavey TM800A550L */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* Above strings are too generic, also match on BIOS version */
+ DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP ElitePad 1000 G2"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* HP Pavilion x2 10-k0XX, 10-n0XX */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* HP Pavilion x2 10-p0XX */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD1_IN4P |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* HP Pro Tablet 408 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pro Tablet 408"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* HP Stream 7 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Stream 7 Tablet"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_JD_NOT_INV |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* I.T.Works TW891 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"),
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Lamina I8270 / T701BR.SE */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Lamina"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "T701BR.SE"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_JD_NOT_INV |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Lenovo Miix 2 8 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "20326"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Hiking"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Lenovo Miix 3-830 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 3-830"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Linx Linx7 tablet */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LINX"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LINX7"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_JD_NOT_INV |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* MPMAN Converter 9, similar hw as the I.T.Works TW891 2-in-1 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MPMAN"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Converter9"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* MPMAN MPWIN895CL */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MPMAN"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MPWIN8900CL"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* MSI S100 tablet */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "S100"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Nuvison/TMax TM800W560 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TMAX"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TM800W560L"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_JD_NOT_INV |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Onda v975w */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* The above are too generic, also match BIOS info */
+ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "5.6.5"),
+ DMI_EXACT_MATCH(DMI_BIOS_DATE, "07/25/2014"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Pipo W4 */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* The above are too generic, also match BIOS info */
+ DMI_MATCH(DMI_BIOS_VERSION, "V8L_WIN32_CHIPHD"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Point of View Mobii TAB-P800W (V2.0) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* The above are too generic, also match BIOS info */
+ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "3BAIR1014"),
+ DMI_EXACT_MATCH(DMI_BIOS_DATE, "10/24/2014"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF2 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Point of View Mobii TAB-P800W (V2.1) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* The above are too generic, also match BIOS info */
+ DMI_EXACT_MATCH(DMI_BIOS_VERSION, "3BAIR1013"),
+ DMI_EXACT_MATCH(DMI_BIOS_DATE, "08/22/2014"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_MONO_SPEAKER |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF2 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Point of View Mobii TAB-P1005W-232 (V2.0) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "POV"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "I102A"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* Prowise PT301 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Prowise"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PT301"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ {
+ /* Teclast X89 */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"),
+ DMI_MATCH(DMI_BOARD_NAME, "tPAD"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN3_MAP |
+ BYT_RT5640_JD_SRC_JD1_IN4P |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_1P0 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Toshiba Satellite Click Mini L9W-B */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SATELLITE Click Mini L9W-B"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_1500UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF1 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Toshiba Encore WT8-A */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT8-A"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_JD_NOT_INV |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Toshiba Encore WT10-A */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT10-A-103"),
+ },
+ .driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD1_IN4P |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_SSP0_AIF2 |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Voyo Winpad A15 */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
+ /* Above strings are too generic, also match on BIOS date */
+ DMI_MATCH(DMI_BIOS_DATE, "11/20/2014"),
+ },
+ .driver_data = (void *)(BYT_RT5640_IN1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75 |
+ BYT_RT5640_DIFF_MIC |
+ BYT_RT5640_MCLK_EN),
+ },
+ { /* Catch-all for generic Insyde tablets, must be last */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ },
+ .driver_data = (void *)(BYTCR_INPUT_DEFAULTS |
+ BYT_RT5640_MCLK_EN |
+ BYT_RT5640_SSP0_AIF1),
+
+ },
+ {}
+};
+
+/*
+ * Note this MUST be called before snd_soc_register_card(), so that the props
+ * are in place before the codec component driver's probe function parses them.
+ */
+static int byt_rt5640_add_codec_device_props(const char *i2c_dev_name)
+{
+ struct property_entry props[MAX_NO_PROPS] = {};
+ struct device *i2c_dev;
+ int ret, cnt = 0;
+
+ i2c_dev = bus_find_device_by_name(&i2c_bus_type, NULL, i2c_dev_name);
+ if (!i2c_dev)
+ return -EPROBE_DEFER;
+
+ switch (BYT_RT5640_MAP(byt_rt5640_quirk)) {
+ case BYT_RT5640_DMIC1_MAP:
+ props[cnt++] = PROPERTY_ENTRY_U32("realtek,dmic1-data-pin",
+ RT5640_DMIC1_DATA_PIN_IN1P);
+ break;
+ case BYT_RT5640_DMIC2_MAP:
+ props[cnt++] = PROPERTY_ENTRY_U32("realtek,dmic2-data-pin",
+ RT5640_DMIC2_DATA_PIN_IN1N);
+ break;
+ case BYT_RT5640_IN1_MAP:
+ if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC)
+ props[cnt++] =
+ PROPERTY_ENTRY_BOOL("realtek,in1-differential");
+ break;
+ case BYT_RT5640_IN3_MAP:
+ if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC)
+ props[cnt++] =
+ PROPERTY_ENTRY_BOOL("realtek,in3-differential");
+ break;
+ }
+
+ if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) {
+ props[cnt++] = PROPERTY_ENTRY_U32(
+ "realtek,jack-detect-source",
+ BYT_RT5640_JDSRC(byt_rt5640_quirk));
+
+ props[cnt++] = PROPERTY_ENTRY_U32(
+ "realtek,over-current-threshold-microamp",
+ BYT_RT5640_OVCD_TH(byt_rt5640_quirk) * 100);
+
+ props[cnt++] = PROPERTY_ENTRY_U32(
+ "realtek,over-current-scale-factor",
+ BYT_RT5640_OVCD_SF(byt_rt5640_quirk));
+ }
+
+ if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV)
+ props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted");
+
+ ret = device_add_properties(i2c_dev, props);
+ put_device(i2c_dev);
+
+ return ret;
+}
+
+static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_card *card = runtime->card;
+ struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component;
+ const struct snd_soc_dapm_route *custom_map;
+ int num_routes;
+ int ret;
+
+ card->dapm.idle_bias_off = true;
+
+ /* Start with RC clk for jack-detect (we disable MCLK below) */
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN)
+ snd_soc_component_update_bits(component, RT5640_GLB_CLK,
+ RT5640_SCLK_SRC_MASK, RT5640_SCLK_SRC_RCCLK);
+
+ rt5640_sel_asrc_clk_src(component,
+ RT5640_DA_STEREO_FILTER |
+ RT5640_DA_MONO_L_FILTER |
+ RT5640_DA_MONO_R_FILTER |
+ RT5640_AD_STEREO_FILTER |
+ RT5640_AD_MONO_L_FILTER |
+ RT5640_AD_MONO_R_FILTER,
+ RT5640_CLK_SEL_ASRC);
+
+ ret = snd_soc_add_card_controls(card, byt_rt5640_controls,
+ ARRAY_SIZE(byt_rt5640_controls));
+ if (ret) {
+ dev_err(card->dev, "unable to add card controls\n");
+ return ret;
+ }
+
+ switch (BYT_RT5640_MAP(byt_rt5640_quirk)) {
+ case BYT_RT5640_IN1_MAP:
+ custom_map = byt_rt5640_intmic_in1_map;
+ num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map);
+ break;
+ case BYT_RT5640_IN3_MAP:
+ custom_map = byt_rt5640_intmic_in3_map;
+ num_routes = ARRAY_SIZE(byt_rt5640_intmic_in3_map);
+ break;
+ case BYT_RT5640_DMIC2_MAP:
+ custom_map = byt_rt5640_intmic_dmic2_map;
+ num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map);
+ break;
+ default:
+ custom_map = byt_rt5640_intmic_dmic1_map;
+ num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map);
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
+ if (ret)
+ return ret;
+
+ if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_ssp2_aif2_map,
+ ARRAY_SIZE(byt_rt5640_ssp2_aif2_map));
+ } else if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_ssp0_aif1_map,
+ ARRAY_SIZE(byt_rt5640_ssp0_aif1_map));
+ } else if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_ssp0_aif2_map,
+ ARRAY_SIZE(byt_rt5640_ssp0_aif2_map));
+ } else {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_ssp2_aif1_map,
+ ARRAY_SIZE(byt_rt5640_ssp2_aif1_map));
+ }
+ if (ret)
+ return ret;
+
+ if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_mono_spk_map,
+ ARRAY_SIZE(byt_rt5640_mono_spk_map));
+ } else if (!(byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS)) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5640_stereo_spk_map,
+ ARRAY_SIZE(byt_rt5640_stereo_spk_map));
+ }
+ if (ret)
+ return ret;
+
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) {
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(priv->mclk);
+ if (!ret)
+ clk_disable_unprepare(priv->mclk);
+
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ)
+ ret = clk_set_rate(priv->mclk, 25000000);
+ else
+ ret = clk_set_rate(priv->mclk, 19200000);
+
+ if (ret) {
+ dev_err(card->dev, "unable to set MCLK rate\n");
+ return ret;
+ }
+ }
+
+ if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) {
+ ret = snd_soc_card_jack_new(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &priv->jack, rt5640_pins,
+ ARRAY_SIZE(rt5640_pins));
+ if (ret) {
+ dev_err(card->dev, "Jack creation failed %d\n", ret);
+ return ret;
+ }
+ snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0,
+ KEY_PLAYPAUSE);
+ snd_soc_component_set_jack(component, &priv->jack, NULL);
+ }
+
+ return 0;
+}
+
+static int byt_rt5640_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret, bits;
+
+ /* The DSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) ||
+ (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) {
+ /* set SSP0 to 16-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ bits = 16;
+ } else {
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+ bits = 24;
+ }
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int byt_rt5640_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static const struct snd_soc_ops byt_rt5640_aif1_ops = {
+ .startup = byt_rt5640_aif1_startup,
+};
+
+static const struct snd_soc_ops byt_rt5640_be_ssp2_ops = {
+ .hw_params = byt_rt5640_aif1_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ /* overwritten for ssp0 routing */
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC(
+ /* overwritten with HID */ "i2c-10EC5640:00",
+ /* changed w/ quirk */ "rt5640-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link byt_rt5640_dais[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Baytrail Audio Port",
+ .stream_name = "Baytrail Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_rt5640_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &byt_rt5640_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = byt_rt5640_codec_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .init = byt_rt5640_init,
+ .ops = &byt_rt5640_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+/* SoC card */
+static char byt_rt5640_codec_name[SND_ACPI_I2C_ID_LEN];
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+static char byt_rt5640_long_name[40]; /* = "bytcr-rt5640-*-spk-*-mic" */
+#endif
+static char byt_rt5640_components[32]; /* = "cfg-spk:* cfg-mic:*" */
+
+static int byt_rt5640_suspend(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+
+ if (!BYT_RT5640_JDSRC(byt_rt5640_quirk))
+ return 0;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, byt_rt5640_codec_name)) {
+ dev_dbg(component->dev, "disabling jack detect before suspend\n");
+ snd_soc_component_set_jack(component, NULL, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int byt_rt5640_resume(struct snd_soc_card *card)
+{
+ struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+
+ if (!BYT_RT5640_JDSRC(byt_rt5640_quirk))
+ return 0;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, byt_rt5640_codec_name)) {
+ dev_dbg(component->dev, "re-enabling jack detect after resume\n");
+ snd_soc_component_set_jack(component, &priv->jack, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht rt5640" /* card name will be 'sof-bytcht rt5640' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bytcr-rt5640"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+static struct snd_soc_card byt_rt5640_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = byt_rt5640_dais,
+ .num_links = ARRAY_SIZE(byt_rt5640_dais),
+ .dapm_widgets = byt_rt5640_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets),
+ .dapm_routes = byt_rt5640_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map),
+ .fully_routed = true,
+ .suspend_pre = byt_rt5640_suspend,
+ .resume_post = byt_rt5640_resume,
+};
+
+struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */
+ u64 aif_value; /* 1: AIF1, 2: AIF2 */
+ u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */
+};
+
+static int snd_byt_rt5640_mc_probe(struct platform_device *pdev)
+{
+ static const char * const map_name[] = { "dmic1", "dmic2", "in1", "in3" };
+ __maybe_unused const char *spk_type;
+ const struct dmi_system_id *dmi_id;
+ struct byt_rt5640_private *priv;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct acpi_device *adev;
+ int ret_val = 0;
+ int dai_index = 0;
+ int i, cfg_spk;
+
+ is_bytcr = false;
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* register the soc card */
+ byt_rt5640_card.dev = &pdev->dev;
+ mach = byt_rt5640_card.dev->platform_data;
+ snd_soc_card_set_drvdata(&byt_rt5640_card, priv);
+
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(byt_rt5640_dais); i++) {
+ if (!strcmp(byt_rt5640_dais[i].codecs->name,
+ "i2c-10EC5640:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(byt_rt5640_codec_name, sizeof(byt_rt5640_codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ byt_rt5640_dais[dai_index].codecs->name = byt_rt5640_codec_name;
+ }
+
+ /*
+ * swap SSP0 if bytcr is detected
+ * (will be overridden if DMI quirk is detected)
+ */
+ if (soc_intel_is_byt()) {
+ if (mach->mach_params.acpi_ipc_irq_index == 0)
+ is_bytcr = true;
+ }
+
+ if (is_bytcr) {
+ /*
+ * Baytrail CR platforms may have CHAN package in BIOS, try
+ * to find relevant routing quirk based as done on Windows
+ * platforms. We have to read the information directly from the
+ * BIOS, at this stage the card is not created and the links
+ * with the codec driver/pdata are non-existent
+ */
+
+ struct acpi_chan_package chan_package;
+
+ /* format specified: 2 64-bit integers */
+ struct acpi_buffer format = {sizeof("NN"), "NN"};
+ struct acpi_buffer state = {0, NULL};
+ struct snd_soc_acpi_package_context pkg_ctx;
+ bool pkg_found = false;
+
+ state.length = sizeof(chan_package);
+ state.pointer = &chan_package;
+
+ pkg_ctx.name = "CHAN";
+ pkg_ctx.length = 2;
+ pkg_ctx.format = &format;
+ pkg_ctx.state = &state;
+ pkg_ctx.data_valid = false;
+
+ pkg_found = snd_soc_acpi_find_package_from_hid(mach->id,
+ &pkg_ctx);
+ if (pkg_found) {
+ if (chan_package.aif_value == 1) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n");
+ byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF1;
+ } else if (chan_package.aif_value == 2) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n");
+ byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2;
+ } else {
+ dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n");
+ pkg_found = false;
+ }
+ }
+
+ if (!pkg_found) {
+ /* no BIOS indications, assume SSP0-AIF2 connection */
+ byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2;
+ }
+
+ /* change defaults for Baytrail-CR capture */
+ byt_rt5640_quirk |= BYTCR_INPUT_DEFAULTS;
+ } else {
+ byt_rt5640_quirk |= BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_JD_SRC_JD2_IN4N |
+ BYT_RT5640_OVCD_TH_2000UA |
+ BYT_RT5640_OVCD_SF_0P75;
+ }
+
+ /* check quirks before creating card */
+ dmi_id = dmi_first_match(byt_rt5640_quirk_table);
+ if (dmi_id)
+ byt_rt5640_quirk = (unsigned long)dmi_id->driver_data;
+ if (quirk_override != -1) {
+ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n",
+ byt_rt5640_quirk, quirk_override);
+ byt_rt5640_quirk = quirk_override;
+ }
+
+ /* Must be called before register_card, also see declaration comment. */
+ ret_val = byt_rt5640_add_codec_device_props(byt_rt5640_codec_name);
+ if (ret_val)
+ return ret_val;
+
+ log_quirks(&pdev->dev);
+
+ if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) ||
+ (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2))
+ byt_rt5640_dais[dai_index].codecs->dai_name = "rt5640-aif2";
+
+ if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) ||
+ (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2))
+ byt_rt5640_dais[dai_index].cpus->dai_name = "ssp0-port";
+
+ if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) {
+ priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3");
+ if (IS_ERR(priv->mclk)) {
+ ret_val = PTR_ERR(priv->mclk);
+
+ dev_err(&pdev->dev,
+ "Failed to get MCLK from pmc_plt_clk_3: %d\n",
+ ret_val);
+
+ /*
+ * Fall back to bit clock usage for -ENOENT (clock not
+ * available likely due to missing dependencies), bail
+ * for all other errors, including -EPROBE_DEFER
+ */
+ if (ret_val != -ENOENT)
+ return ret_val;
+ byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN;
+ }
+ }
+
+ if (byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS) {
+ cfg_spk = 0;
+ spk_type = "none";
+ } else if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) {
+ cfg_spk = 1;
+ spk_type = "mono";
+ } else {
+ cfg_spk = 2;
+ spk_type = "stereo";
+ }
+
+ snprintf(byt_rt5640_components, sizeof(byt_rt5640_components),
+ "cfg-spk:%d cfg-mic:%s", cfg_spk,
+ map_name[BYT_RT5640_MAP(byt_rt5640_quirk)]);
+ byt_rt5640_card.components = byt_rt5640_components;
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+ snprintf(byt_rt5640_long_name, sizeof(byt_rt5640_long_name),
+ "bytcr-rt5640-%s-spk-%s-mic", spk_type,
+ map_name[BYT_RT5640_MAP(byt_rt5640_quirk)]);
+ byt_rt5640_card.long_name = byt_rt5640_long_name;
+#endif
+
+ /* override plaform name, if required */
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(&byt_rt5640_card,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5640_card);
+
+ if (ret_val) {
+ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n",
+ ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &byt_rt5640_card);
+ return ret_val;
+}
+
+static struct platform_driver snd_byt_rt5640_mc_driver = {
+ .driver = {
+ .name = "bytcr_rt5640",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_byt_rt5640_mc_probe,
+};
+
+module_platform_driver(snd_byt_rt5640_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver");
+MODULE_AUTHOR("Subhransu S. Prusty <subhransu.s.prusty@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcr_rt5640");
diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c
new file mode 100644
index 000000000..bf8b87d45
--- /dev/null
+++ b/sound/soc/intel/boards/bytcr_rt5651.c
@@ -0,0 +1,1135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bytcr_rt5651.c - ASoc Machine driver for Intel Byt CR platform
+ * (derived from bytcr_rt5640.c)
+ *
+ * Copyright (C) 2015 Intel Corp
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5651.h"
+#include "../atom/sst-atom-controls.h"
+#include "../common/soc-intel-quirks.h"
+
+enum {
+ BYT_RT5651_DMIC_MAP,
+ BYT_RT5651_IN1_MAP,
+ BYT_RT5651_IN2_MAP,
+ BYT_RT5651_IN1_IN2_MAP,
+};
+
+enum {
+ BYT_RT5651_JD_NULL = (RT5651_JD_NULL << 4),
+ BYT_RT5651_JD1_1 = (RT5651_JD1_1 << 4),
+ BYT_RT5651_JD1_2 = (RT5651_JD1_2 << 4),
+ BYT_RT5651_JD2 = (RT5651_JD2 << 4),
+};
+
+enum {
+ BYT_RT5651_OVCD_TH_600UA = (6 << 8),
+ BYT_RT5651_OVCD_TH_1500UA = (15 << 8),
+ BYT_RT5651_OVCD_TH_2000UA = (20 << 8),
+};
+
+enum {
+ BYT_RT5651_OVCD_SF_0P5 = (RT5651_OVCD_SF_0P5 << 13),
+ BYT_RT5651_OVCD_SF_0P75 = (RT5651_OVCD_SF_0P75 << 13),
+ BYT_RT5651_OVCD_SF_1P0 = (RT5651_OVCD_SF_1P0 << 13),
+ BYT_RT5651_OVCD_SF_1P5 = (RT5651_OVCD_SF_1P5 << 13),
+};
+
+#define BYT_RT5651_MAP(quirk) ((quirk) & GENMASK(3, 0))
+#define BYT_RT5651_JDSRC(quirk) (((quirk) & GENMASK(7, 4)) >> 4)
+#define BYT_RT5651_OVCD_TH(quirk) (((quirk) & GENMASK(12, 8)) >> 8)
+#define BYT_RT5651_OVCD_SF(quirk) (((quirk) & GENMASK(14, 13)) >> 13)
+#define BYT_RT5651_DMIC_EN BIT(16)
+#define BYT_RT5651_MCLK_EN BIT(17)
+#define BYT_RT5651_MCLK_25MHZ BIT(18)
+#define BYT_RT5651_SSP2_AIF2 BIT(19) /* default is using AIF1 */
+#define BYT_RT5651_SSP0_AIF1 BIT(20)
+#define BYT_RT5651_SSP0_AIF2 BIT(21)
+#define BYT_RT5651_HP_LR_SWAPPED BIT(22)
+#define BYT_RT5651_MONO_SPEAKER BIT(23)
+#define BYT_RT5651_JD_NOT_INV BIT(24)
+
+#define BYT_RT5651_DEFAULT_QUIRKS (BYT_RT5651_MCLK_EN | \
+ BYT_RT5651_JD1_1 | \
+ BYT_RT5651_OVCD_TH_2000UA | \
+ BYT_RT5651_OVCD_SF_0P75)
+
+/* jack-detect-source + inv + dmic-en + ovcd-th + -sf + terminating entry */
+#define MAX_NO_PROPS 6
+
+struct byt_rt5651_private {
+ struct clk *mclk;
+ struct gpio_desc *ext_amp_gpio;
+ struct gpio_desc *hp_detect;
+ struct snd_soc_jack jack;
+};
+
+static const struct acpi_gpio_mapping *byt_rt5651_gpios;
+
+/* Default: jack-detect on JD1_1, internal mic on in2, headsetmic on in3 */
+static unsigned long byt_rt5651_quirk = BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP;
+
+static int quirk_override = -1;
+module_param_named(quirk, quirk_override, int, 0444);
+MODULE_PARM_DESC(quirk, "Board-specific quirk override");
+
+static void log_quirks(struct device *dev)
+{
+ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_DMIC_MAP)
+ dev_info(dev, "quirk DMIC_MAP enabled");
+ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN1_MAP)
+ dev_info(dev, "quirk IN1_MAP enabled");
+ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN2_MAP)
+ dev_info(dev, "quirk IN2_MAP enabled");
+ if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN1_IN2_MAP)
+ dev_info(dev, "quirk IN1_IN2_MAP enabled");
+ if (BYT_RT5651_JDSRC(byt_rt5651_quirk)) {
+ dev_info(dev, "quirk realtek,jack-detect-source %ld\n",
+ BYT_RT5651_JDSRC(byt_rt5651_quirk));
+ dev_info(dev, "quirk realtek,over-current-threshold-microamp %ld\n",
+ BYT_RT5651_OVCD_TH(byt_rt5651_quirk) * 100);
+ dev_info(dev, "quirk realtek,over-current-scale-factor %ld\n",
+ BYT_RT5651_OVCD_SF(byt_rt5651_quirk));
+ }
+ if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN)
+ dev_info(dev, "quirk DMIC enabled");
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN)
+ dev_info(dev, "quirk MCLK_EN enabled");
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ)
+ dev_info(dev, "quirk MCLK_25MHZ enabled");
+ if (byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2)
+ dev_info(dev, "quirk SSP2_AIF2 enabled\n");
+ if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1)
+ dev_info(dev, "quirk SSP0_AIF1 enabled\n");
+ if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)
+ dev_info(dev, "quirk SSP0_AIF2 enabled\n");
+ if (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER)
+ dev_info(dev, "quirk MONO_SPEAKER enabled\n");
+ if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV)
+ dev_info(dev, "quirk JD_NOT_INV enabled\n");
+}
+
+#define BYT_CODEC_DAI1 "rt5651-aif1"
+#define BYT_CODEC_DAI2 "rt5651-aif2"
+
+static int byt_rt5651_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai,
+ int rate, int bclk_ratio)
+{
+ int clk_id, clk_freq, ret;
+
+ /* Configure the PLL before selecting it */
+ if (!(byt_rt5651_quirk & BYT_RT5651_MCLK_EN)) {
+ clk_id = RT5651_PLL1_S_BCLK1,
+ clk_freq = rate * bclk_ratio;
+ } else {
+ clk_id = RT5651_PLL1_S_MCLK;
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ)
+ clk_freq = 25000000;
+ else
+ clk_freq = 19200000;
+ }
+ ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, rate * 512);
+ if (ret < 0) {
+ dev_err(codec_dai->component->dev, "can't set pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_PLL1,
+ rate * 512, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->component->dev, "can't set clock %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1);
+ if (!codec_dai)
+ codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2);
+ if (!codec_dai) {
+ dev_err(card->dev,
+ "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) {
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ }
+ ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50);
+ } else {
+ /*
+ * Set codec clock source to internal clock before
+ * turning off the platform clock. Codec needs clock
+ * for Jack detection and button press
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_RCCLK,
+ 48000 * 512,
+ SND_SOC_CLOCK_IN);
+ if (!ret)
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN)
+ clk_disable_unprepare(priv->mclk);
+ }
+
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rt5651_ext_amp_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpiod_set_value_cansleep(priv->ext_amp_gpio, 1);
+ else
+ gpiod_set_value_cansleep(priv->ext_amp_gpio, 0);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget byt_rt5651_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("Ext Amp Power", SND_SOC_NOPM, 0, 0,
+ rt5651_ext_amp_power_event,
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_audio_map[] = {
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Internal Mic", NULL, "Platform Clock"},
+ {"Speaker", NULL, "Platform Clock"},
+ {"Speaker", NULL, "Ext Amp Power"},
+ {"Line In", NULL, "Platform Clock"},
+
+ {"Headset Mic", NULL, "micbias1"}, /* lowercase for rt5651 */
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Speaker", NULL, "LOUTL"},
+ {"Speaker", NULL, "LOUTR"},
+ {"IN2P", NULL, "Line In"},
+ {"IN2N", NULL, "Line In"},
+
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_intmic_dmic_map[] = {
+ {"DMIC L1", NULL, "Internal Mic"},
+ {"DMIC R1", NULL, "Internal Mic"},
+ {"IN2P", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_map[] = {
+ {"Internal Mic", NULL, "micbias1"},
+ {"IN1P", NULL, "Internal Mic"},
+ {"IN3P", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_intmic_in2_map[] = {
+ {"Internal Mic", NULL, "micbias1"},
+ {"IN2P", NULL, "Internal Mic"},
+ {"IN3P", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_in2_map[] = {
+ {"Internal Mic", NULL, "micbias1"},
+ {"IN1P", NULL, "Internal Mic"},
+ {"IN2P", NULL, "Internal Mic"},
+ {"IN3P", NULL, "Headset Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_ssp0_aif1_map[] = {
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx"},
+
+ {"AIF1 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_ssp0_aif2_map[] = {
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx"},
+
+ {"AIF2 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_ssp2_aif1_map[] = {
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"AIF1 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5651_ssp2_aif2_map[] = {
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+
+ {"AIF2 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_kcontrol_new byt_rt5651_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Line In"),
+};
+
+static struct snd_soc_jack_pin bytcr_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ snd_pcm_format_t format = params_format(params);
+ int rate = params_rate(params);
+ int bclk_ratio;
+
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ bclk_ratio = 32;
+ else
+ bclk_ratio = 50;
+
+ return byt_rt5651_prepare_and_enable_pll1(codec_dai, rate, bclk_ratio);
+}
+
+static const struct acpi_gpio_params pov_p1006w_hp_detect = { 1, 0, false };
+static const struct acpi_gpio_params pov_p1006w_ext_amp_en = { 2, 0, true };
+
+static const struct acpi_gpio_mapping byt_rt5651_pov_p1006w_gpios[] = {
+ { "hp-detect-gpios", &pov_p1006w_hp_detect, 1, },
+ { "ext-amp-enable-gpios", &pov_p1006w_ext_amp_en, 1, },
+ { },
+};
+
+static int byt_rt5651_pov_p1006w_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_rt5651_quirk = (unsigned long)id->driver_data;
+ byt_rt5651_gpios = byt_rt5651_pov_p1006w_gpios;
+ return 1;
+}
+
+static int byt_rt5651_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_rt5651_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id byt_rt5651_quirk_table[] = {
+ {
+ /* Chuwi Hi8 Pro (CWI513) */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X1D3_C806N"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_HP_LR_SWAPPED |
+ BYT_RT5651_MONO_SPEAKER),
+ },
+ {
+ /* Chuwi Vi8 Plus (CWI519) */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "D2D3_Vi8A1"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_HP_LR_SWAPPED |
+ BYT_RT5651_MONO_SPEAKER),
+ },
+ {
+ /* Complet Electro Serv MY8307 */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Complet Electro Serv"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MY8307"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_MONO_SPEAKER |
+ BYT_RT5651_JD_NOT_INV),
+ },
+ {
+ /* I.T.Works TW701, Ployer Momo7w and Trekstor ST70416-6
+ * (these all use the same mainboard) */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_BIOS_VENDOR, "INSYDE Corp."),
+ /* Partial match for all of itWORKS.G.WI71C.JGBMRBA,
+ * TREK.G.WI71C.JGBMRBA0x and MOMO.G.WI71C.MABMRBA02 */
+ DMI_MATCH(DMI_BIOS_VERSION, ".G.WI71C."),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_SSP0_AIF1 |
+ BYT_RT5651_MONO_SPEAKER),
+ },
+ {
+ /* Jumper EZpad 7 */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Jumper"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"),
+ /* Jumper12x.WJ2012.bsBKRCP05 with the version dropped */
+ DMI_MATCH(DMI_BIOS_VERSION, "Jumper12x.WJ2012.bsBKRCP"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_JD_NOT_INV),
+ },
+ {
+ /* KIANO SlimNote 14.2 */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "KIANO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "KIANO SlimNote 14.2"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN1_IN2_MAP),
+ },
+ {
+ /* Minnowboard Max B3 */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"),
+ },
+ .driver_data = (void *)(BYT_RT5651_IN1_MAP),
+ },
+ {
+ /* Minnowboard Turbot */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ADI"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Turbot"),
+ },
+ .driver_data = (void *)(BYT_RT5651_MCLK_EN |
+ BYT_RT5651_IN1_MAP),
+ },
+ {
+ /* Point of View mobii wintab p1006w (v1.0) */
+ .callback = byt_rt5651_pov_p1006w_quirk_cb,
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BayTrail"),
+ /* Note 105b is Foxcon's USB/PCI vendor id */
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "105B"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "0E57"),
+ },
+ .driver_data = (void *)(BYT_RT5651_DMIC_MAP |
+ BYT_RT5651_OVCD_TH_2000UA |
+ BYT_RT5651_OVCD_SF_0P75 |
+ BYT_RT5651_DMIC_EN |
+ BYT_RT5651_MCLK_EN |
+ BYT_RT5651_SSP0_AIF1),
+ },
+ {
+ /* VIOS LTH17 */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "VIOS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LTH17"),
+ },
+ .driver_data = (void *)(BYT_RT5651_IN1_IN2_MAP |
+ BYT_RT5651_JD1_1 |
+ BYT_RT5651_OVCD_TH_2000UA |
+ BYT_RT5651_OVCD_SF_1P0 |
+ BYT_RT5651_MCLK_EN),
+ },
+ {
+ /* Yours Y8W81 (and others using the same mainboard) */
+ .callback = byt_rt5651_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_BIOS_VENDOR, "INSYDE Corp."),
+ /* Partial match for all devs with a W86C mainboard */
+ DMI_MATCH(DMI_BIOS_VERSION, ".F.W86C."),
+ },
+ .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS |
+ BYT_RT5651_IN2_MAP |
+ BYT_RT5651_SSP0_AIF1 |
+ BYT_RT5651_MONO_SPEAKER),
+ },
+ {}
+};
+
+/*
+ * Note this MUST be called before snd_soc_register_card(), so that the props
+ * are in place before the codec component driver's probe function parses them.
+ */
+static int byt_rt5651_add_codec_device_props(struct device *i2c_dev)
+{
+ struct property_entry props[MAX_NO_PROPS] = {};
+ int cnt = 0;
+
+ props[cnt++] = PROPERTY_ENTRY_U32("realtek,jack-detect-source",
+ BYT_RT5651_JDSRC(byt_rt5651_quirk));
+
+ props[cnt++] = PROPERTY_ENTRY_U32("realtek,over-current-threshold-microamp",
+ BYT_RT5651_OVCD_TH(byt_rt5651_quirk) * 100);
+
+ props[cnt++] = PROPERTY_ENTRY_U32("realtek,over-current-scale-factor",
+ BYT_RT5651_OVCD_SF(byt_rt5651_quirk));
+
+ if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN)
+ props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,dmic-en");
+
+ if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV)
+ props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted");
+
+ return device_add_properties(i2c_dev, props);
+}
+
+static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_card *card = runtime->card;
+ struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component;
+ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card);
+ const struct snd_soc_dapm_route *custom_map;
+ int num_routes;
+ int report;
+ int ret;
+
+ card->dapm.idle_bias_off = true;
+
+ /* Start with RC clk for jack-detect (we disable MCLK below) */
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN)
+ snd_soc_component_update_bits(codec, RT5651_GLB_CLK,
+ RT5651_SCLK_SRC_MASK, RT5651_SCLK_SRC_RCCLK);
+
+ switch (BYT_RT5651_MAP(byt_rt5651_quirk)) {
+ case BYT_RT5651_IN1_MAP:
+ custom_map = byt_rt5651_intmic_in1_map;
+ num_routes = ARRAY_SIZE(byt_rt5651_intmic_in1_map);
+ break;
+ case BYT_RT5651_IN2_MAP:
+ custom_map = byt_rt5651_intmic_in2_map;
+ num_routes = ARRAY_SIZE(byt_rt5651_intmic_in2_map);
+ break;
+ case BYT_RT5651_IN1_IN2_MAP:
+ custom_map = byt_rt5651_intmic_in1_in2_map;
+ num_routes = ARRAY_SIZE(byt_rt5651_intmic_in1_in2_map);
+ break;
+ default:
+ custom_map = byt_rt5651_intmic_dmic_map;
+ num_routes = ARRAY_SIZE(byt_rt5651_intmic_dmic_map);
+ }
+ ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
+ if (ret)
+ return ret;
+
+ if (byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5651_ssp2_aif2_map,
+ ARRAY_SIZE(byt_rt5651_ssp2_aif2_map));
+ } else if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5651_ssp0_aif1_map,
+ ARRAY_SIZE(byt_rt5651_ssp0_aif1_map));
+ } else if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5651_ssp0_aif2_map,
+ ARRAY_SIZE(byt_rt5651_ssp0_aif2_map));
+ } else {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ byt_rt5651_ssp2_aif1_map,
+ ARRAY_SIZE(byt_rt5651_ssp2_aif1_map));
+ }
+ if (ret)
+ return ret;
+
+ ret = snd_soc_add_card_controls(card, byt_rt5651_controls,
+ ARRAY_SIZE(byt_rt5651_controls));
+ if (ret) {
+ dev_err(card->dev, "unable to add card controls\n");
+ return ret;
+ }
+
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) {
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(priv->mclk);
+ if (!ret)
+ clk_disable_unprepare(priv->mclk);
+
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ)
+ ret = clk_set_rate(priv->mclk, 25000000);
+ else
+ ret = clk_set_rate(priv->mclk, 19200000);
+
+ if (ret)
+ dev_err(card->dev, "unable to set MCLK rate\n");
+ }
+
+ report = 0;
+ if (BYT_RT5651_JDSRC(byt_rt5651_quirk))
+ report = SND_JACK_HEADSET | SND_JACK_BTN_0;
+ else if (priv->hp_detect)
+ report = SND_JACK_HEADSET;
+
+ if (report) {
+ ret = snd_soc_card_jack_new(runtime->card, "Headset",
+ report, &priv->jack, bytcr_jack_pins,
+ ARRAY_SIZE(bytcr_jack_pins));
+ if (ret) {
+ dev_err(runtime->dev, "jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ if (report & SND_JACK_BTN_0)
+ snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0,
+ KEY_PLAYPAUSE);
+
+ ret = snd_soc_component_set_jack(codec, &priv->jack,
+ priv->hp_detect);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int byt_rt5651_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret, bits;
+
+ /* The DSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ if ((byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) ||
+ (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) {
+ /* set SSP0 to 16-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ bits = 16;
+ } else {
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+ bits = 24;
+ }
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS
+ );
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, bits);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const unsigned int rates_48000[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_48000 = {
+ .count = ARRAY_SIZE(rates_48000),
+ .list = rates_48000,
+};
+
+static int byt_rt5651_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_48000);
+}
+
+static const struct snd_soc_ops byt_rt5651_aif1_ops = {
+ .startup = byt_rt5651_aif1_startup,
+};
+
+static const struct snd_soc_ops byt_rt5651_be_ssp2_ops = {
+ .hw_params = byt_rt5651_aif1_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5651:00", "rt5651-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link byt_rt5651_dais[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_rt5651_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &byt_rt5651_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* CODEC<->CODEC link */
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = byt_rt5651_codec_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .init = byt_rt5651_init,
+ .ops = &byt_rt5651_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+/* SoC card */
+static char byt_rt5651_codec_name[SND_ACPI_I2C_ID_LEN];
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+static char byt_rt5651_long_name[50]; /* = "bytcr-rt5651-*-spk-*-mic[-swapped-hp]" */
+#endif
+static char byt_rt5651_components[50]; /* = "cfg-spk:* cfg-mic:*" */
+
+static int byt_rt5651_suspend(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+
+ if (!BYT_RT5651_JDSRC(byt_rt5651_quirk))
+ return 0;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, byt_rt5651_codec_name)) {
+ dev_dbg(component->dev, "disabling jack detect before suspend\n");
+ snd_soc_component_set_jack(component, NULL, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int byt_rt5651_resume(struct snd_soc_card *card)
+{
+ struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+
+ if (!BYT_RT5651_JDSRC(byt_rt5651_quirk))
+ return 0;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, byt_rt5651_codec_name)) {
+ dev_dbg(component->dev, "re-enabling jack detect after resume\n");
+ snd_soc_component_set_jack(component, &priv->jack,
+ priv->hp_detect);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht rt5651" /* card name will be 'sof-bytcht rt5651' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "bytcr-rt5651"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+static struct snd_soc_card byt_rt5651_card = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = byt_rt5651_dais,
+ .num_links = ARRAY_SIZE(byt_rt5651_dais),
+ .dapm_widgets = byt_rt5651_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_rt5651_widgets),
+ .dapm_routes = byt_rt5651_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_rt5651_audio_map),
+ .fully_routed = true,
+ .suspend_pre = byt_rt5651_suspend,
+ .resume_post = byt_rt5651_resume,
+};
+
+static const struct acpi_gpio_params ext_amp_enable_gpios = { 0, 0, false };
+
+static const struct acpi_gpio_mapping cht_rt5651_gpios[] = {
+ /*
+ * Some boards have I2cSerialBusV2, GpioIo, GpioInt as ACPI resources,
+ * other boards may have I2cSerialBusV2, GpioInt, GpioIo instead.
+ * We want the GpioIo one for the ext-amp-enable-gpio.
+ */
+ { "ext-amp-enable-gpios", &ext_amp_enable_gpios, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO },
+ { },
+};
+
+struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */
+ u64 aif_value; /* 1: AIF1, 2: AIF2 */
+ u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */
+};
+
+static int snd_byt_rt5651_mc_probe(struct platform_device *pdev)
+{
+ static const char * const mic_name[] = { "dmic", "in1", "in2", "in12" };
+ struct byt_rt5651_private *priv;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct acpi_device *adev;
+ struct device *codec_dev;
+ bool is_bytcr = false;
+ int ret_val = 0;
+ int dai_index = 0;
+ int i;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* register the soc card */
+ byt_rt5651_card.dev = &pdev->dev;
+
+ mach = byt_rt5651_card.dev->platform_data;
+ snd_soc_card_set_drvdata(&byt_rt5651_card, priv);
+
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(byt_rt5651_dais); i++) {
+ if (!strcmp(byt_rt5651_dais[i].codecs->name,
+ "i2c-10EC5651:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(byt_rt5651_codec_name, sizeof(byt_rt5651_codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ byt_rt5651_dais[dai_index].codecs->name = byt_rt5651_codec_name;
+ } else {
+ dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id);
+ return -ENODEV;
+ }
+
+ codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL,
+ byt_rt5651_codec_name);
+ if (!codec_dev)
+ return -EPROBE_DEFER;
+
+ /*
+ * swap SSP0 if bytcr is detected
+ * (will be overridden if DMI quirk is detected)
+ */
+ if (soc_intel_is_byt()) {
+ if (mach->mach_params.acpi_ipc_irq_index == 0)
+ is_bytcr = true;
+ }
+
+ if (is_bytcr) {
+ /*
+ * Baytrail CR platforms may have CHAN package in BIOS, try
+ * to find relevant routing quirk based as done on Windows
+ * platforms. We have to read the information directly from the
+ * BIOS, at this stage the card is not created and the links
+ * with the codec driver/pdata are non-existent
+ */
+
+ struct acpi_chan_package chan_package;
+
+ /* format specified: 2 64-bit integers */
+ struct acpi_buffer format = {sizeof("NN"), "NN"};
+ struct acpi_buffer state = {0, NULL};
+ struct snd_soc_acpi_package_context pkg_ctx;
+ bool pkg_found = false;
+
+ state.length = sizeof(chan_package);
+ state.pointer = &chan_package;
+
+ pkg_ctx.name = "CHAN";
+ pkg_ctx.length = 2;
+ pkg_ctx.format = &format;
+ pkg_ctx.state = &state;
+ pkg_ctx.data_valid = false;
+
+ pkg_found = snd_soc_acpi_find_package_from_hid(mach->id,
+ &pkg_ctx);
+ if (pkg_found) {
+ if (chan_package.aif_value == 1) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n");
+ byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF1;
+ } else if (chan_package.aif_value == 2) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n");
+ byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2;
+ } else {
+ dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n");
+ pkg_found = false;
+ }
+ }
+
+ if (!pkg_found) {
+ /* no BIOS indications, assume SSP0-AIF2 connection */
+ byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2;
+ }
+ }
+
+ /* check quirks before creating card */
+ dmi_check_system(byt_rt5651_quirk_table);
+
+ if (quirk_override != -1) {
+ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n",
+ byt_rt5651_quirk, quirk_override);
+ byt_rt5651_quirk = quirk_override;
+ }
+
+ /* Must be called before register_card, also see declaration comment. */
+ ret_val = byt_rt5651_add_codec_device_props(codec_dev);
+ if (ret_val) {
+ put_device(codec_dev);
+ return ret_val;
+ }
+
+ /* Cherry Trail devices use an external amplifier enable gpio */
+ if (soc_intel_is_cht() && !byt_rt5651_gpios)
+ byt_rt5651_gpios = cht_rt5651_gpios;
+
+ if (byt_rt5651_gpios) {
+ devm_acpi_dev_add_driver_gpios(codec_dev, byt_rt5651_gpios);
+ priv->ext_amp_gpio = devm_fwnode_gpiod_get(&pdev->dev,
+ codec_dev->fwnode,
+ "ext-amp-enable",
+ GPIOD_OUT_LOW,
+ "speaker-amp");
+ if (IS_ERR(priv->ext_amp_gpio)) {
+ ret_val = PTR_ERR(priv->ext_amp_gpio);
+ switch (ret_val) {
+ case -ENOENT:
+ priv->ext_amp_gpio = NULL;
+ break;
+ default:
+ dev_err(&pdev->dev, "Failed to get ext-amp-enable GPIO: %d\n",
+ ret_val);
+ fallthrough;
+ case -EPROBE_DEFER:
+ put_device(codec_dev);
+ return ret_val;
+ }
+ }
+ priv->hp_detect = devm_fwnode_gpiod_get(&pdev->dev,
+ codec_dev->fwnode,
+ "hp-detect",
+ GPIOD_IN,
+ "hp-detect");
+ if (IS_ERR(priv->hp_detect)) {
+ ret_val = PTR_ERR(priv->hp_detect);
+ switch (ret_val) {
+ case -ENOENT:
+ priv->hp_detect = NULL;
+ break;
+ default:
+ dev_err(&pdev->dev, "Failed to get hp-detect GPIO: %d\n",
+ ret_val);
+ fallthrough;
+ case -EPROBE_DEFER:
+ put_device(codec_dev);
+ return ret_val;
+ }
+ }
+ }
+
+ put_device(codec_dev);
+
+ log_quirks(&pdev->dev);
+
+ if ((byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) ||
+ (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2))
+ byt_rt5651_dais[dai_index].codecs->dai_name = "rt5651-aif2";
+
+ if ((byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) ||
+ (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2))
+ byt_rt5651_dais[dai_index].cpus->dai_name = "ssp0-port";
+
+ if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) {
+ priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3");
+ if (IS_ERR(priv->mclk)) {
+ ret_val = PTR_ERR(priv->mclk);
+ dev_err(&pdev->dev,
+ "Failed to get MCLK from pmc_plt_clk_3: %d\n",
+ ret_val);
+ /*
+ * Fall back to bit clock usage for -ENOENT (clock not
+ * available likely due to missing dependencies), bail
+ * for all other errors, including -EPROBE_DEFER
+ */
+ if (ret_val != -ENOENT)
+ return ret_val;
+ byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN;
+ }
+ }
+
+ snprintf(byt_rt5651_components, sizeof(byt_rt5651_components),
+ "cfg-spk:%s cfg-mic:%s%s",
+ (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) ? "1" : "2",
+ mic_name[BYT_RT5651_MAP(byt_rt5651_quirk)],
+ (byt_rt5651_quirk & BYT_RT5651_HP_LR_SWAPPED) ?
+ " cfg-hp:lrswap" : "");
+ byt_rt5651_card.components = byt_rt5651_components;
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
+ snprintf(byt_rt5651_long_name, sizeof(byt_rt5651_long_name),
+ "bytcr-rt5651-%s-spk-%s-mic%s",
+ (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) ?
+ "mono" : "stereo",
+ mic_name[BYT_RT5651_MAP(byt_rt5651_quirk)],
+ (byt_rt5651_quirk & BYT_RT5651_HP_LR_SWAPPED) ?
+ "-hp-swapped" : "");
+ byt_rt5651_card.long_name = byt_rt5651_long_name;
+#endif
+
+ /* override plaform name, if required */
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(&byt_rt5651_card,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card);
+
+ if (ret_val) {
+ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n",
+ ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &byt_rt5651_card);
+ return ret_val;
+}
+
+static struct platform_driver snd_byt_rt5651_mc_driver = {
+ .driver = {
+ .name = "bytcr_rt5651",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_byt_rt5651_mc_probe,
+};
+
+module_platform_driver(snd_byt_rt5651_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver for RT5651");
+MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bytcr_rt5651");
diff --git a/sound/soc/intel/boards/cht_bsw_max98090_ti.c b/sound/soc/intel/boards/cht_bsw_max98090_ti.c
new file mode 100644
index 000000000..835e9bd6b
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_max98090_ti.c
@@ -0,0 +1,642 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cht-bsw-max98090.c - ASoc Machine driver for Intel Cherryview-based
+ * platforms Cherrytrail and Braswell, with max98090 & TI codec.
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author: Fang, Yang A <yang.a.fang@intel.com>
+ * This file is modified from cht_bsw_rt5645.c
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/jack.h>
+#include "../../codecs/max98090.h"
+#include "../atom/sst-atom-controls.h"
+#include "../../codecs/ts3a227e.h"
+
+#define CHT_PLAT_CLK_3_HZ 19200000
+#define CHT_CODEC_DAI "HiFi"
+
+#define QUIRK_PMC_PLT_CLK_0 0x01
+
+struct cht_mc_private {
+ struct clk *mclk;
+ struct snd_soc_jack jack;
+ bool ts3a227e_present;
+ int quirks;
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ /* See the comment in snd_cht_mc_probe() */
+ if (ctx->quirks & QUIRK_PMC_PLT_CLK_0)
+ return 0;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = clk_prepare_enable(ctx->mclk);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ } else {
+ clk_disable_unprepare(ctx->mclk);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cht_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route cht_audio_map[] = {
+ {"IN34", NULL, "Headset Mic"},
+ {"Headset Mic", NULL, "MICBIAS"},
+ {"DMICL", NULL, "Int Mic"},
+ {"Headphone", NULL, "HPL"},
+ {"Headphone", NULL, "HPR"},
+ {"Ext Spk", NULL, "SPKL"},
+ {"Ext Spk", NULL, "SPKR"},
+ {"HiFi Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx" },
+ {"codec_in1", NULL, "ssp2 Rx" },
+ {"ssp2 Rx", NULL, "HiFi Capture"},
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Int Mic", NULL, "Platform Clock"},
+ {"Ext Spk", NULL, "Platform Clock"},
+};
+
+static const struct snd_kcontrol_new cht_mc_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static int cht_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, M98090_REG_SYSTEM_CLOCK,
+ CHT_PLAT_CLK_3_HZ, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cht_ti_jack_event(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct snd_soc_jack *jack = (struct snd_soc_jack *)data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+
+ if (event & SND_JACK_MICROPHONE) {
+ snd_soc_dapm_force_enable_pin(dapm, "SHDN");
+ snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
+ snd_soc_dapm_sync(dapm);
+ } else {
+ snd_soc_dapm_disable_pin(dapm, "MICBIAS");
+ snd_soc_dapm_disable_pin(dapm, "SHDN");
+ snd_soc_dapm_sync(dapm);
+ }
+
+ return 0;
+}
+
+static struct notifier_block cht_jack_nb = {
+ .notifier_call = cht_ti_jack_event,
+};
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ {
+ .name = "hp",
+ .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT,
+ .debounce_time = 200,
+ },
+ {
+ .name = "mic",
+ .invert = 1,
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static const struct acpi_gpio_params hp_gpios = { 0, 0, false };
+static const struct acpi_gpio_params mic_gpios = { 1, 0, false };
+
+static const struct acpi_gpio_mapping acpi_max98090_gpios[] = {
+ { "hp-gpios", &hp_gpios, 1 },
+ { "mic-gpios", &mic_gpios, 1 },
+ {},
+};
+
+static int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ int jack_type;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card);
+ struct snd_soc_jack *jack = &ctx->jack;
+
+ if (ctx->ts3a227e_present) {
+ /*
+ * The jack has already been created in the
+ * cht_max98090_headset_init() function.
+ */
+ snd_soc_jack_notifier_register(jack, &cht_jack_nb);
+ return 0;
+ }
+
+ jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE;
+
+ ret = snd_soc_card_jack_new(runtime->card, "Headset Jack",
+ jack_type, jack,
+ hs_jack_pins, ARRAY_SIZE(hs_jack_pins));
+ if (ret) {
+ dev_err(runtime->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpiods(runtime->card->dev->parent, jack,
+ ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+ if (ret) {
+ /*
+ * flag error but don't bail if jack detect is broken
+ * due to platform issues or bad BIOS/configuration
+ */
+ dev_err(runtime->dev,
+ "jack detection gpios not added, error %d\n", ret);
+ }
+
+ /* See the comment in snd_cht_mc_probe() */
+ if (ctx->quirks & QUIRK_PMC_PLT_CLK_0)
+ return 0;
+
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(ctx->mclk);
+ if (!ret)
+ clk_disable_unprepare(ctx->mclk);
+
+ ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ);
+
+ if (ret)
+ dev_err(runtime->dev, "unable to set MCLK rate\n");
+
+ return ret;
+}
+
+static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret = 0;
+ unsigned int fmt = 0;
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set cpu_dai slot fmt: %d\n", ret);
+ return ret;
+ }
+
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set cpu_dai set fmt: %d\n", ret);
+ return ret;
+ }
+
+ /* The DSP will covert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 16-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ return 0;
+}
+
+static int cht_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static int cht_max98090_headset_init(struct snd_soc_component *component)
+{
+ struct snd_soc_card *card = component->card;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_jack *jack = &ctx->jack;
+ int jack_type;
+ int ret;
+
+ /*
+ * TI supports 4 butons headset detection
+ * KEY_MEDIA
+ * KEY_VOICECOMMAND
+ * KEY_VOLUMEUP
+ * KEY_VOLUMEDOWN
+ */
+ jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3;
+
+ ret = snd_soc_card_jack_new(card, "Headset Jack", jack_type,
+ jack, NULL, 0);
+ if (ret) {
+ dev_err(card->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ return ts3a227e_enable_jack_detect(component, jack);
+}
+
+static const struct snd_soc_ops cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static const struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+static struct snd_soc_aux_dev cht_max98090_headset_dev = {
+ .dlc = COMP_AUX("i2c-104C227E:00"),
+ .init = cht_max98090_headset_init,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-193C9890:00", "HiFi")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .init = cht_codec_init,
+ .be_hw_params_fixup = cht_codec_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht max98090" /* card name will be 'sof-bytcht max98090 */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "chtmax98090"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_cht = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = cht_dailink,
+ .num_links = ARRAY_SIZE(cht_dailink),
+ .aux_dev = &cht_max98090_headset_dev,
+ .num_aux_devs = 1,
+ .dapm_widgets = cht_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
+ .dapm_routes = cht_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cht_audio_map),
+ .controls = cht_mc_controls,
+ .num_controls = ARRAY_SIZE(cht_mc_controls),
+};
+
+static const struct dmi_system_id cht_max98090_quirk_table[] = {
+ {
+ /* Banjo model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Banjo"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Candy model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Candy"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Clapper model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Clapper"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Cyan model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Cyan"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Enguarde model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Enguarde"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Glimmer model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Glimmer"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Gnawty model Chromebook (Acer Chromebook CB3-111) */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Gnawty"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Heli model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Heli"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Kip model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Kip"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Ninja model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ninja"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Orco model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Orco"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Quawks model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Quawks"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Rambi model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Rambi"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Squawks model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Squawks"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Sumo model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Sumo"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Swanky model Chromebook (Toshiba Chromebook 2) */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Swanky"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {
+ /* Winky model Chromebook */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Winky"),
+ },
+ .driver_data = (void *)QUIRK_PMC_PLT_CLK_0,
+ },
+ {}
+};
+
+static int snd_cht_mc_probe(struct platform_device *pdev)
+{
+ const struct dmi_system_id *dmi_id;
+ struct device *dev = &pdev->dev;
+ int ret_val = 0;
+ struct cht_mc_private *drv;
+ const char *mclk_name;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ dmi_id = dmi_first_match(cht_max98090_quirk_table);
+ if (dmi_id)
+ drv->quirks = (unsigned long)dmi_id->driver_data;
+
+ drv->ts3a227e_present = acpi_dev_found("104C227E");
+ if (!drv->ts3a227e_present) {
+ /* no need probe TI jack detection chip */
+ snd_soc_card_cht.aux_dev = NULL;
+ snd_soc_card_cht.num_aux_devs = 0;
+
+ ret_val = devm_acpi_dev_add_driver_gpios(dev->parent,
+ acpi_max98090_gpios);
+ if (ret_val)
+ dev_dbg(dev, "Unable to add GPIO mapping table\n");
+ }
+
+ /* override plaform name, if required */
+ snd_soc_card_cht.dev = &pdev->dev;
+ mach = pdev->dev.platform_data;
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ /* register the soc card */
+ snd_soc_card_set_drvdata(&snd_soc_card_cht, drv);
+
+ if (drv->quirks & QUIRK_PMC_PLT_CLK_0)
+ mclk_name = "pmc_plt_clk_0";
+ else
+ mclk_name = "pmc_plt_clk_3";
+
+ drv->mclk = devm_clk_get(&pdev->dev, mclk_name);
+ if (IS_ERR(drv->mclk)) {
+ dev_err(&pdev->dev,
+ "Failed to get MCLK from %s: %ld\n",
+ mclk_name, PTR_ERR(drv->mclk));
+ return PTR_ERR(drv->mclk);
+ }
+
+ /*
+ * Boards which have the MAX98090's clk connected to clk_0 do not seem
+ * to like it if we muck with the clock. If we disable the clock when
+ * it is unused we get "max98090 i2c-193C9890:00: PLL unlocked" errors
+ * and the PLL never seems to lock again.
+ * So for these boards we enable it here once and leave it at that.
+ */
+ if (drv->quirks & QUIRK_PMC_PLT_CLK_0) {
+ ret_val = clk_prepare_enable(drv->mclk);
+ if (ret_val < 0) {
+ dev_err(&pdev->dev, "MCLK enable error: %d\n", ret_val);
+ return ret_val;
+ }
+ }
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &snd_soc_card_cht);
+ return ret_val;
+}
+
+static int snd_cht_mc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+ if (ctx->quirks & QUIRK_PMC_PLT_CLK_0)
+ clk_disable_unprepare(ctx->mclk);
+
+ return 0;
+}
+
+static struct platform_driver snd_cht_mc_driver = {
+ .driver = {
+ .name = "cht-bsw-max98090",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_cht_mc_probe,
+ .remove = snd_cht_mc_remove,
+};
+
+module_platform_driver(snd_cht_mc_driver)
+
+MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver");
+MODULE_AUTHOR("Fang, Yang A <yang.a.fang@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cht-bsw-max98090");
diff --git a/sound/soc/intel/boards/cht_bsw_nau8824.c b/sound/soc/intel/boards/cht_bsw_nau8824.c
new file mode 100644
index 000000000..3e12bff15
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_nau8824.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cht-bsw-nau8824.c - ASoc Machine driver for Intel Cherryview-based
+ * platforms Cherrytrail and Braswell, with nau8824 codec.
+ *
+ * Copyright (C) 2018 Intel Corp
+ * Copyright (C) 2018 Nuvoton Technology Corp
+ *
+ * Author: Wang, Joseph C <joequant@gmail.com>
+ * Co-author: John Hsu <KCHSU0@nuvoton.com>
+ * This file is based on cht_bsw_rt5672.c and cht-bsw-max98090.c
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/jack.h>
+#include <linux/input.h>
+#include "../atom/sst-atom-controls.h"
+#include "../../codecs/nau8824.h"
+
+struct cht_mc_private {
+ struct snd_soc_jack jack;
+};
+
+static struct snd_soc_jack_pin cht_bsw_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static const struct snd_soc_dapm_widget cht_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route cht_audio_map[] = {
+ {"Ext Spk", NULL, "SPKOUTL"},
+ {"Ext Spk", NULL, "SPKOUTR"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"MIC1", NULL, "Int Mic"},
+ {"MIC2", NULL, "Int Mic"},
+ {"HSMIC1", NULL, "Headset Mic"},
+ {"HSMIC2", NULL, "Headset Mic"},
+ {"Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx" },
+ {"codec_in1", NULL, "ssp2 Rx" },
+ {"ssp2 Rx", NULL, "Capture"},
+};
+
+static const struct snd_kcontrol_new cht_mc_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static int cht_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, NAU8824_CLK_FLL_FS, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set FS clock %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_pll(codec_dai, 0, 0, params_rate(params),
+ params_rate(params) * 256);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set FLL: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card);
+ struct snd_soc_jack *jack = &ctx->jack;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ int ret, jack_type;
+
+ /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xf, 0x1, 4, 24);
+ if (ret < 0) {
+ dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret);
+ return ret;
+ }
+
+ /* NAU88L24 supports 4 butons headset detection
+ * KEY_PLAYPAUSE
+ * KEY_VOICECOMMAND
+ * KEY_VOLUMEUP
+ * KEY_VOLUMEDOWN
+ */
+ jack_type = SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3;
+ ret = snd_soc_card_jack_new(runtime->card, "Headset", jack_type, jack,
+ cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins));
+ if (ret) {
+ dev_err(runtime->dev,
+ "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ nau8824_enable_jack_detect(component, jack);
+
+ return ret;
+}
+
+static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt =
+ hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The DSP will covert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 24-bit */
+ snd_mask_none(fmt);
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int cht_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static const struct snd_soc_ops cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static const struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(compress,
+ DAILINK_COMP_ARRAY(COMP_CPU("compress-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10508824:00",
+ NAU8824_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ /* Front End DAI links */
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ [MERR_DPCM_COMPR] = {
+ .name = "Compressed Port",
+ .stream_name = "Compress",
+ SND_SOC_DAILINK_REG(compress, dummy, platform),
+ },
+ /* Back End DAI links */
+ {
+ /* SSP2 - Codec */
+ .name = "SSP2-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .init = cht_codec_init,
+ .be_hw_params_fixup = cht_codec_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht nau8824" /* card name will be 'sof-bytcht nau8824 */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "chtnau8824"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_cht = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = cht_dailink,
+ .num_links = ARRAY_SIZE(cht_dailink),
+ .dapm_widgets = cht_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
+ .dapm_routes = cht_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cht_audio_map),
+ .controls = cht_mc_controls,
+ .num_controls = ARRAY_SIZE(cht_mc_controls),
+};
+
+static int snd_cht_mc_probe(struct platform_device *pdev)
+{
+ struct cht_mc_private *drv;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ int ret_val;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+ snd_soc_card_set_drvdata(&snd_soc_card_cht, drv);
+
+ /* override plaform name, if required */
+ snd_soc_card_cht.dev = &pdev->dev;
+ mach = pdev->dev.platform_data;
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ /* register the soc card */
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &snd_soc_card_cht);
+
+ return ret_val;
+}
+
+static struct platform_driver snd_cht_mc_driver = {
+ .driver = {
+ .name = "cht-bsw-nau8824",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_cht_mc_probe,
+};
+
+module_platform_driver(snd_cht_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver");
+MODULE_AUTHOR("Wang, Joseph C <joequant@gmail.com>");
+MODULE_AUTHOR("John Hsu <KCHSU0@nuvoton.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cht-bsw-nau8824");
diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c
new file mode 100644
index 000000000..b53c02481
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_rt5645.c
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cht-bsw-rt5645.c - ASoc Machine driver for Intel Cherryview-based platforms
+ * Cherrytrail and Braswell, with RT5645 codec.
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author: Fang, Yang A <yang.a.fang@intel.com>
+ * N,Harshapriya <harshapriya.n@intel.com>
+ * This file is modified from cht_bsw_rt5672.c
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/dmi.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5645.h"
+#include "../atom/sst-atom-controls.h"
+#include "../common/soc-intel-quirks.h"
+
+#define CHT_PLAT_CLK_3_HZ 19200000
+#define CHT_CODEC_DAI1 "rt5645-aif1"
+#define CHT_CODEC_DAI2 "rt5645-aif2"
+
+struct cht_acpi_card {
+ char *codec_id;
+ int codec_type;
+ struct snd_soc_card *soc_card;
+};
+
+struct cht_mc_private {
+ struct snd_soc_jack jack;
+ struct cht_acpi_card *acpi_card;
+ char codec_name[SND_ACPI_I2C_ID_LEN];
+ struct clk *mclk;
+};
+
+#define CHT_RT5645_MAP(quirk) ((quirk) & GENMASK(7, 0))
+#define CHT_RT5645_SSP2_AIF2 BIT(16) /* default is using AIF1 */
+#define CHT_RT5645_SSP0_AIF1 BIT(17)
+#define CHT_RT5645_SSP0_AIF2 BIT(18)
+#define CHT_RT5645_PMC_PLT_CLK_0 BIT(19)
+
+static unsigned long cht_rt5645_quirk = 0;
+
+static void log_quirks(struct device *dev)
+{
+ if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2)
+ dev_info(dev, "quirk SSP2_AIF2 enabled");
+ if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1)
+ dev_info(dev, "quirk SSP0_AIF1 enabled");
+ if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)
+ dev_info(dev, "quirk SSP0_AIF2 enabled");
+ if (cht_rt5645_quirk & CHT_RT5645_PMC_PLT_CLK_0)
+ dev_info(dev, "quirk PMC_PLT_CLK_0 enabled");
+}
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI1);
+ if (!codec_dai)
+ codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI2);
+
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = clk_prepare_enable(ctx->mclk);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ } else {
+ /* Set codec sysclk source to its internal clock because codec PLL will
+ * be off when idle and MCLK will also be off when codec is
+ * runtime suspended. Codec needs clock for jack detection and button
+ * press. MCLK is turned off with clock framework or ACPI.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK,
+ 48000 * 512, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ clk_disable_unprepare(ctx->mclk);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cht_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Analog Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route cht_rt5645_audio_map[] = {
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN1N", NULL, "Headset Mic"},
+ {"DMIC L1", NULL, "Int Mic"},
+ {"DMIC R1", NULL, "Int Mic"},
+ {"IN2P", NULL, "Int Analog Mic"},
+ {"IN2N", NULL, "Int Analog Mic"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Ext Spk", NULL, "SPOL"},
+ {"Ext Spk", NULL, "SPOR"},
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Int Mic", NULL, "Platform Clock"},
+ {"Int Analog Mic", NULL, "Platform Clock"},
+ {"Int Analog Mic", NULL, "micbias1"},
+ {"Int Analog Mic", NULL, "micbias2"},
+ {"Ext Spk", NULL, "Platform Clock"},
+};
+
+static const struct snd_soc_dapm_route cht_rt5650_audio_map[] = {
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN1N", NULL, "Headset Mic"},
+ {"DMIC L2", NULL, "Int Mic"},
+ {"DMIC R2", NULL, "Int Mic"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Ext Spk", NULL, "SPOL"},
+ {"Ext Spk", NULL, "SPOR"},
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Int Mic", NULL, "Platform Clock"},
+ {"Ext Spk", NULL, "Platform Clock"},
+};
+
+static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif1_map[] = {
+ {"AIF1 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx" },
+ {"codec_in1", NULL, "ssp2 Rx" },
+ {"ssp2 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif2_map[] = {
+ {"AIF2 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx" },
+ {"codec_in1", NULL, "ssp2 Rx" },
+ {"ssp2 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif1_map[] = {
+ {"AIF1 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx" },
+ {"ssp0 Rx", NULL, "AIF1 Capture"},
+};
+
+static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif2_map[] = {
+ {"AIF2 Playback", NULL, "ssp0 Tx"},
+ {"ssp0 Tx", NULL, "modem_out"},
+ {"modem_in", NULL, "ssp0 Rx" },
+ {"ssp0 Rx", NULL, "AIF2 Capture"},
+};
+
+static const struct snd_kcontrol_new cht_mc_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Analog Mic"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static struct snd_soc_jack_pin cht_bsw_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int cht_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* set codec PLL source to the 19.2MHz platform clock (MCLK) */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK,
+ CHT_PLAT_CLK_3_HZ, params_rate(params) * 512);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1,
+ params_rate(params) * 512, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cht_rt5645_quirk_cb(const struct dmi_system_id *id)
+{
+ cht_rt5645_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id cht_rt5645_quirk_table[] = {
+ {
+ /* Strago family Chromebooks */
+ .callback = cht_rt5645_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Intel_Strago"),
+ },
+ .driver_data = (void *)CHT_RT5645_PMC_PLT_CLK_0,
+ },
+ {
+ },
+};
+
+static int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_card *card = runtime->card;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component;
+ int jack_type;
+ int ret;
+
+ if ((cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) ||
+ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) {
+ /* Select clk_i2s2_asrc as ASRC clock source */
+ rt5645_sel_asrc_clk_src(component,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER |
+ RT5645_AD_STEREO_FILTER,
+ RT5645_CLK_SEL_I2S2_ASRC);
+ } else {
+ /* Select clk_i2s1_asrc as ASRC clock source */
+ rt5645_sel_asrc_clk_src(component,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER |
+ RT5645_AD_STEREO_FILTER,
+ RT5645_CLK_SEL_I2S1_ASRC);
+ }
+
+ if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ cht_rt5645_ssp2_aif2_map,
+ ARRAY_SIZE(cht_rt5645_ssp2_aif2_map));
+ } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ cht_rt5645_ssp0_aif1_map,
+ ARRAY_SIZE(cht_rt5645_ssp0_aif1_map));
+ } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2) {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ cht_rt5645_ssp0_aif2_map,
+ ARRAY_SIZE(cht_rt5645_ssp0_aif2_map));
+ } else {
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ cht_rt5645_ssp2_aif1_map,
+ ARRAY_SIZE(cht_rt5645_ssp2_aif1_map));
+ }
+ if (ret)
+ return ret;
+
+ if (ctx->acpi_card->codec_type == CODEC_TYPE_RT5650)
+ jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3;
+ else
+ jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE;
+
+ ret = snd_soc_card_jack_new(runtime->card, "Headset",
+ jack_type, &ctx->jack,
+ cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins));
+ if (ret) {
+ dev_err(runtime->dev, "Headset jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ rt5645_set_jack_detect(component, &ctx->jack, &ctx->jack, &ctx->jack);
+
+
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(ctx->mclk);
+ if (!ret)
+ clk_disable_unprepare(ctx->mclk);
+
+ ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ);
+
+ if (ret)
+ dev_err(runtime->dev, "unable to set MCLK rate\n");
+
+ return ret;
+}
+
+static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ int ret;
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The DSP will covert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) ||
+ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) {
+
+ /* set SSP0 to 16-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot, override config
+ * with explicit setting to I2S 2ch 16-bit. The word length is set with
+ * dai_set_tdm_slot() since there is no other API exposed
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS
+ );
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS
+ );
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set I2S config, err %d\n", ret);
+ return ret;
+ }
+
+ } else {
+
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ /*
+ * Default mode for SSP configuration is TDM 4 slot
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0),
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to TDM %d\n", ret);
+ return ret;
+ }
+
+ /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 0), 0xF, 0xF, 4, 24);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int cht_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static const struct snd_soc_ops cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static const struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5645:00", "rt5645-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ /* CODEC<->CODEC link */
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = cht_codec_init,
+ .be_hw_params_fixup = cht_codec_fixup,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_RT5645_NAME "bytcht rt5645" /* card name 'sof-bytcht rt5645' */
+#define CARD_RT5650_NAME "bytcht rt5650" /* card name 'sof-bytcht rt5650' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_RT5645_NAME "chtrt5645"
+#define CARD_RT5650_NAME "chtrt5650"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_chtrt5645 = {
+ .name = CARD_RT5645_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = cht_dailink,
+ .num_links = ARRAY_SIZE(cht_dailink),
+ .dapm_widgets = cht_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
+ .dapm_routes = cht_rt5645_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cht_rt5645_audio_map),
+ .controls = cht_mc_controls,
+ .num_controls = ARRAY_SIZE(cht_mc_controls),
+};
+
+static struct snd_soc_card snd_soc_card_chtrt5650 = {
+ .name = CARD_RT5650_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = cht_dailink,
+ .num_links = ARRAY_SIZE(cht_dailink),
+ .dapm_widgets = cht_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
+ .dapm_routes = cht_rt5650_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cht_rt5650_audio_map),
+ .controls = cht_mc_controls,
+ .num_controls = ARRAY_SIZE(cht_mc_controls),
+};
+
+static struct cht_acpi_card snd_soc_cards[] = {
+ {"10EC5640", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645},
+ {"10EC5645", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645},
+ {"10EC5648", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645},
+ {"10EC3270", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645},
+ {"10EC5650", CODEC_TYPE_RT5650, &snd_soc_card_chtrt5650},
+};
+
+static char cht_rt5645_codec_name[SND_ACPI_I2C_ID_LEN];
+
+struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */
+ u64 aif_value; /* 1: AIF1, 2: AIF2 */
+ u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */
+};
+
+static int snd_cht_mc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = snd_soc_cards[0].soc_card;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct cht_mc_private *drv;
+ struct acpi_device *adev;
+ bool found = false;
+ bool is_bytcr = false;
+ int dai_index = 0;
+ int ret_val = 0;
+ int i;
+ const char *mclk_name;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ mach = pdev->dev.platform_data;
+
+ for (i = 0; i < ARRAY_SIZE(snd_soc_cards); i++) {
+ if (acpi_dev_found(snd_soc_cards[i].codec_id) &&
+ (!strncmp(snd_soc_cards[i].codec_id, mach->id, 8))) {
+ dev_dbg(&pdev->dev,
+ "found codec %s\n", snd_soc_cards[i].codec_id);
+ card = snd_soc_cards[i].soc_card;
+ drv->acpi_card = &snd_soc_cards[i];
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(&pdev->dev, "No matching HID found in supported list\n");
+ return -ENODEV;
+ }
+
+ card->dev = &pdev->dev;
+ sprintf(drv->codec_name, "i2c-%s:00", drv->acpi_card->codec_id);
+
+ /* set correct codec name */
+ for (i = 0; i < ARRAY_SIZE(cht_dailink); i++)
+ if (!strcmp(card->dai_link[i].codecs->name,
+ "i2c-10EC5645:00")) {
+ card->dai_link[i].codecs->name = drv->codec_name;
+ dai_index = i;
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(cht_rt5645_codec_name, sizeof(cht_rt5645_codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ cht_dailink[dai_index].codecs->name = cht_rt5645_codec_name;
+ }
+
+ /*
+ * swap SSP0 if bytcr is detected
+ * (will be overridden if DMI quirk is detected)
+ */
+ if (soc_intel_is_byt()) {
+ if (mach->mach_params.acpi_ipc_irq_index == 0)
+ is_bytcr = true;
+ }
+
+ if (is_bytcr) {
+ /*
+ * Baytrail CR platforms may have CHAN package in BIOS, try
+ * to find relevant routing quirk based as done on Windows
+ * platforms. We have to read the information directly from the
+ * BIOS, at this stage the card is not created and the links
+ * with the codec driver/pdata are non-existent
+ */
+
+ struct acpi_chan_package chan_package;
+
+ /* format specified: 2 64-bit integers */
+ struct acpi_buffer format = {sizeof("NN"), "NN"};
+ struct acpi_buffer state = {0, NULL};
+ struct snd_soc_acpi_package_context pkg_ctx;
+ bool pkg_found = false;
+
+ state.length = sizeof(chan_package);
+ state.pointer = &chan_package;
+
+ pkg_ctx.name = "CHAN";
+ pkg_ctx.length = 2;
+ pkg_ctx.format = &format;
+ pkg_ctx.state = &state;
+ pkg_ctx.data_valid = false;
+
+ pkg_found = snd_soc_acpi_find_package_from_hid(mach->id,
+ &pkg_ctx);
+ if (pkg_found) {
+ if (chan_package.aif_value == 1) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n");
+ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF1;
+ } else if (chan_package.aif_value == 2) {
+ dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n");
+ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2;
+ } else {
+ dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n");
+ pkg_found = false;
+ }
+ }
+
+ if (!pkg_found) {
+ /* no BIOS indications, assume SSP0-AIF2 connection */
+ cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2;
+ }
+ }
+
+ /* check quirks before creating card */
+ dmi_check_system(cht_rt5645_quirk_table);
+ log_quirks(&pdev->dev);
+
+ if ((cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) ||
+ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2))
+ cht_dailink[dai_index].codecs->dai_name = "rt5645-aif2";
+
+ if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) ||
+ (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2))
+ cht_dailink[dai_index].cpus->dai_name = "ssp0-port";
+
+ /* override plaform name, if required */
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(card,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ if (cht_rt5645_quirk & CHT_RT5645_PMC_PLT_CLK_0)
+ mclk_name = "pmc_plt_clk_0";
+ else
+ mclk_name = "pmc_plt_clk_3";
+
+ drv->mclk = devm_clk_get(&pdev->dev, mclk_name);
+ if (IS_ERR(drv->mclk)) {
+ dev_err(&pdev->dev, "Failed to get MCLK from %s: %ld\n",
+ mclk_name, PTR_ERR(drv->mclk));
+ return PTR_ERR(drv->mclk);
+ }
+
+ snd_soc_card_set_drvdata(card, drv);
+ ret_val = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, card);
+ return ret_val;
+}
+
+static struct platform_driver snd_cht_mc_driver = {
+ .driver = {
+ .name = "cht-bsw-rt5645",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_cht_mc_probe,
+};
+
+module_platform_driver(snd_cht_mc_driver)
+
+MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver");
+MODULE_AUTHOR("Fang, Yang A,N,Harshapriya");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cht-bsw-rt5645");
diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c
new file mode 100644
index 000000000..8442be93e
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_rt5672.c
@@ -0,0 +1,487 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms
+ * Cherrytrail and Braswell, with RT5672 codec.
+ *
+ * Copyright (C) 2014 Intel Corp
+ * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
+ * Mengdong Lin <mengdong.lin@intel.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5670.h"
+#include "../atom/sst-atom-controls.h"
+
+
+/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */
+#define CHT_PLAT_CLK_3_HZ 19200000
+#define CHT_CODEC_DAI "rt5670-aif1"
+
+struct cht_mc_private {
+ struct snd_soc_jack headset;
+ char codec_name[SND_ACPI_I2C_ID_LEN];
+ struct clk *mclk;
+};
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin cht_bsw_headset_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ if (ctx->mclk) {
+ ret = clk_prepare_enable(ctx->mclk);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ }
+
+ /* set codec PLL source to the 19.2MHz platform clock (MCLK) */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK,
+ CHT_PLAT_CLK_3_HZ, 48000 * 512);
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ /* set codec sysclk source to PLL */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1,
+ 48000 * 512, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /* Set codec sysclk source to its internal clock because codec
+ * PLL will be off when idle and MCLK will also be off by ACPI
+ * when codec is runtime suspended. Codec needs clock for jack
+ * detection and button press.
+ */
+ snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK,
+ 48000 * 512, SND_SOC_CLOCK_IN);
+
+ if (ctx->mclk)
+ clk_disable_unprepare(ctx->mclk);
+ }
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cht_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route cht_audio_map[] = {
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN1N", NULL, "Headset Mic"},
+ {"DMIC L1", NULL, "Int Mic"},
+ {"DMIC R1", NULL, "Int Mic"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Ext Spk", NULL, "SPOLP"},
+ {"Ext Spk", NULL, "SPOLN"},
+ {"Ext Spk", NULL, "SPORP"},
+ {"Ext Spk", NULL, "SPORN"},
+ {"AIF1 Playback", NULL, "ssp2 Tx"},
+ {"ssp2 Tx", NULL, "codec_out0"},
+ {"ssp2 Tx", NULL, "codec_out1"},
+ {"codec_in0", NULL, "ssp2 Rx"},
+ {"codec_in1", NULL, "ssp2 Rx"},
+ {"ssp2 Rx", NULL, "AIF1 Capture"},
+ {"Headphone", NULL, "Platform Clock"},
+ {"Headset Mic", NULL, "Platform Clock"},
+ {"Int Mic", NULL, "Platform Clock"},
+ {"Ext Spk", NULL, "Platform Clock"},
+};
+
+static const struct snd_kcontrol_new cht_mc_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static int cht_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* set codec PLL source to the 19.2MHz platform clock (MCLK) */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK,
+ CHT_PLAT_CLK_3_HZ, params_rate(params) * 512);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ /* set codec sysclk source to PLL */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1,
+ params_rate(params) * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static const struct acpi_gpio_params headset_gpios = { 0, 0, false };
+
+static const struct acpi_gpio_mapping cht_rt5672_gpios[] = {
+ { "headset-gpios", &headset_gpios, 1 },
+ {},
+};
+
+static int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card);
+
+ if (devm_acpi_dev_add_driver_gpios(component->dev, cht_rt5672_gpios))
+ dev_warn(runtime->dev, "Unable to add GPIO mapping table\n");
+
+ /* Select codec ASRC clock source to track I2S1 clock, because codec
+ * is in slave mode and 100fs I2S format (BCLK = 100 * LRCLK) cannot
+ * be supported by RT5672. Otherwise, ASRC will be disabled and cause
+ * noise.
+ */
+ rt5670_sel_asrc_clk_src(component,
+ RT5670_DA_STEREO_FILTER
+ | RT5670_DA_MONO_L_FILTER
+ | RT5670_DA_MONO_R_FILTER
+ | RT5670_AD_STEREO_FILTER
+ | RT5670_AD_MONO_L_FILTER
+ | RT5670_AD_MONO_R_FILTER,
+ RT5670_CLK_SEL_I2S1_ASRC);
+
+ ret = snd_soc_card_jack_new(runtime->card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &ctx->headset,
+ cht_bsw_headset_pins,
+ ARRAY_SIZE(cht_bsw_headset_pins));
+ if (ret)
+ return ret;
+
+ snd_jack_set_key(ctx->headset.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(ctx->headset.jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(ctx->headset.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+
+ rt5670_set_jack_detect(component, &ctx->headset);
+ if (ctx->mclk) {
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(ctx->mclk);
+ if (!ret)
+ clk_disable_unprepare(ctx->mclk);
+
+ ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ);
+
+ if (ret) {
+ dev_err(runtime->dev, "unable to set MCLK rate\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ int ret;
+
+ /* The DSP will covert the FE rate to 48k, stereo, 24bits */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP2 to 24-bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S24_LE);
+
+ /*
+ * The default mode for the cpu-dai is TDM 4 slot. The default mode
+ * for the codec-dai is I2S. So we need to either set the cpu-dai to
+ * I2S mode to match the codec-dai, or set the codec-dai to TDM 4 slot
+ * (or program both to yet another mode).
+ * One board, the Lenovo Miix 2 10, uses not 1 but 2 codecs connected
+ * to SSP2. The second piggy-backed, output-only codec is inside the
+ * keyboard-dock (which has extra speakers). Unlike the main rt5672
+ * codec, we cannot configure this codec, it is hard coded to use
+ * 2 channel 24 bit I2S. For this to work we must use I2S mode on this
+ * board. Since we only support 2 channels anyways, there is no need
+ * for TDM on any cht-bsw-rt5672 designs. So we use I2S 2ch everywhere.
+ */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cht_aif1_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000);
+}
+
+static const struct snd_soc_ops cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static const struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(media,
+ DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai")));
+
+SND_SOC_DAILINK_DEF(ssp2_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5670:00",
+ "rt5670-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform")));
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ /* Front End DAI links */
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(media, dummy, platform),
+ },
+ [MERR_DPCM_DEEP_BUFFER] = {
+ .name = "Deep-Buffer Audio Port",
+ .stream_name = "Deep-Buffer Audio",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .ops = &cht_aif1_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP2 - Codec */
+ .name = "SSP2-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .nonatomic = true,
+ .init = cht_codec_init,
+ .be_hw_params_fixup = cht_codec_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_be_ssp2_ops,
+ SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform),
+ },
+};
+
+static int cht_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+ for_each_card_components(card, component) {
+ if (!strncmp(component->name,
+ ctx->codec_name, sizeof(ctx->codec_name))) {
+
+ dev_dbg(component->dev, "disabling jack detect before going to suspend.\n");
+ rt5670_jack_suspend(component);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int cht_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_component *component;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+ for_each_card_components(card, component) {
+ if (!strncmp(component->name,
+ ctx->codec_name, sizeof(ctx->codec_name))) {
+
+ dev_dbg(component->dev, "enabling jack detect for resume.\n");
+ rt5670_jack_resume(component);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+/* use space before codec name to simplify card ID, and simplify driver name */
+#define CARD_NAME "bytcht rt5672" /* card name will be 'sof-bytcht rt5672' */
+#define DRIVER_NAME "SOF"
+#else
+#define CARD_NAME "cht-bsw-rt5672"
+#define DRIVER_NAME NULL /* card name will be used for driver name */
+#endif
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_cht = {
+ .name = CARD_NAME,
+ .driver_name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .dai_link = cht_dailink,
+ .num_links = ARRAY_SIZE(cht_dailink),
+ .dapm_widgets = cht_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
+ .dapm_routes = cht_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cht_audio_map),
+ .controls = cht_mc_controls,
+ .num_controls = ARRAY_SIZE(cht_mc_controls),
+ .suspend_pre = cht_suspend_pre,
+ .resume_post = cht_resume_post,
+};
+
+#define RT5672_I2C_DEFAULT "i2c-10EC5670:00"
+
+static int snd_cht_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0;
+ struct cht_mc_private *drv;
+ struct snd_soc_acpi_mach *mach = pdev->dev.platform_data;
+ const char *platform_name;
+ struct acpi_device *adev;
+ int i;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ strcpy(drv->codec_name, RT5672_I2C_DEFAULT);
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(drv->codec_name, sizeof(drv->codec_name),
+ "i2c-%s", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) {
+ if (!strcmp(cht_dailink[i].codecs->name,
+ RT5672_I2C_DEFAULT)) {
+ cht_dailink[i].codecs->name = drv->codec_name;
+ break;
+ }
+ }
+ }
+
+ /* override plaform name, if required */
+ snd_soc_card_cht.dev = &pdev->dev;
+ platform_name = mach->mach_params.platform;
+
+ ret_val = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cht,
+ platform_name);
+ if (ret_val)
+ return ret_val;
+
+ drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3");
+ if (IS_ERR(drv->mclk)) {
+ dev_err(&pdev->dev,
+ "Failed to get MCLK from pmc_plt_clk_3: %ld\n",
+ PTR_ERR(drv->mclk));
+ return PTR_ERR(drv->mclk);
+ }
+ snd_soc_card_set_drvdata(&snd_soc_card_cht, drv);
+
+ /* register the soc card */
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &snd_soc_card_cht);
+ return ret_val;
+}
+
+static struct platform_driver snd_cht_mc_driver = {
+ .driver = {
+ .name = "cht-bsw-rt5672",
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+ .pm = &snd_soc_pm_ops,
+#endif
+ },
+ .probe = snd_cht_mc_probe,
+};
+
+module_platform_driver(snd_cht_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver");
+MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cht-bsw-rt5672");
diff --git a/sound/soc/intel/boards/cml_rt1011_rt5682.c b/sound/soc/intel/boards/cml_rt1011_rt5682.c
new file mode 100644
index 000000000..14813beb3
--- /dev/null
+++ b/sound/soc/intel/boards/cml_rt1011_rt5682.c
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2019 Intel Corporation.
+
+/*
+ * Intel Cometlake I2S Machine driver for RT1011 + RT5682 codec
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/dmi.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/rt5682.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt1011.h"
+#include "../../codecs/rt5682.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "hda_dsp_common.h"
+
+/* The platform clock outputs 24Mhz clock to codec as I2S MCLK */
+#define CML_PLAT_CLK 24000000
+#define CML_RT1011_CODEC_DAI "rt1011-aif"
+#define CML_RT5682_CODEC_DAI "rt5682-aif1"
+#define NAME_SIZE 32
+
+#define SOF_RT1011_SPEAKER_WL BIT(0)
+#define SOF_RT1011_SPEAKER_WR BIT(1)
+#define SOF_RT1011_SPEAKER_TL BIT(2)
+#define SOF_RT1011_SPEAKER_TR BIT(3)
+
+/* Default: Woofer speakers */
+static unsigned long sof_rt1011_quirk = SOF_RT1011_SPEAKER_WL |
+ SOF_RT1011_SPEAKER_WR;
+
+static int sof_rt1011_quirk_cb(const struct dmi_system_id *id)
+{
+ sof_rt1011_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_rt1011_quirk_table[] = {
+ {
+ .callback = sof_rt1011_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Helios"),
+ },
+ .driver_data = (void *)(SOF_RT1011_SPEAKER_WL | SOF_RT1011_SPEAKER_WR |
+ SOF_RT1011_SPEAKER_TL | SOF_RT1011_SPEAKER_TR),
+ },
+ {
+ }
+};
+
+static struct snd_soc_jack hdmi_jack[3];
+
+struct hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct card_private {
+ char codec_name[SND_ACPI_I2C_ID_LEN];
+ struct snd_soc_jack headset;
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+};
+
+static const struct snd_kcontrol_new cml_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("WL Ext Spk"),
+ SOC_DAPM_PIN_SWITCH("WR Ext Spk"),
+};
+
+static const struct snd_kcontrol_new cml_rt1011_tt_controls[] = {
+ SOC_DAPM_PIN_SWITCH("TL Ext Spk"),
+ SOC_DAPM_PIN_SWITCH("TR Ext Spk"),
+};
+
+static const struct snd_soc_dapm_widget cml_rt1011_rt5682_widgets[] = {
+ SND_SOC_DAPM_SPK("WL Ext Spk", NULL),
+ SND_SOC_DAPM_SPK("WR Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_widget cml_rt1011_tt_widgets[] = {
+ SND_SOC_DAPM_SPK("TL Ext Spk", NULL),
+ SND_SOC_DAPM_SPK("TR Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route cml_rt1011_rt5682_map[] = {
+ /*WL/WR speaker*/
+ {"WL Ext Spk", NULL, "WL SPO"},
+ {"WR Ext Spk", NULL, "WR SPO"},
+
+ /* HP jack connectors - unknown if we have jack detection */
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* other jacks */
+ { "IN1P", NULL, "Headset Mic" },
+
+ /* DMIC */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+static const struct snd_soc_dapm_route cml_rt1011_tt_map[] = {
+ /*TL/TR speaker*/
+ {"TL Ext Spk", NULL, "TL SPO" },
+ {"TR Ext Spk", NULL, "TR SPO" },
+};
+
+static int cml_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ /* need to enable ASRC function for 24MHz mclk rate */
+ rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER |
+ RT5682_AD_STEREO1_FILTER,
+ RT5682_CLK_SEL_I2S1_ASRC);
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+ if (ret)
+ dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
+
+ return ret;
+};
+
+static void cml_rt5682_codec_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static int cml_rt1011_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret = 0;
+ struct snd_soc_card *card = rtd->card;
+
+ if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL |
+ SOF_RT1011_SPEAKER_TR)) {
+
+ ret = snd_soc_add_card_controls(card, cml_rt1011_tt_controls,
+ ARRAY_SIZE(cml_rt1011_tt_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(&card->dapm,
+ cml_rt1011_tt_widgets,
+ ARRAY_SIZE(cml_rt1011_tt_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, cml_rt1011_tt_map,
+ ARRAY_SIZE(cml_rt1011_tt_map));
+
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int cml_rt5682_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int clk_id, clk_freq, pll_out, ret;
+
+ clk_id = RT5682_PLL1_S_MCLK;
+ clk_freq = CML_PLAT_CLK;
+
+ pll_out = params_rate(params) * 512;
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out);
+ if (ret < 0)
+ dev_warn(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret);
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_warn(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ /*
+ * slot_width should be equal or large than data length, set them
+ * be the same
+ */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2,
+ params_width(params));
+ if (ret < 0)
+ dev_warn(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+}
+
+static int cml_rt1011_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ int srate, i, ret = 0;
+
+ srate = params_rate(params);
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+
+ /* 100 Fs to drive 24 bit data */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT1011_PLL1_S_BCLK,
+ 100 * srate, 256 * srate);
+ if (ret < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT1011_FS_SYS_PRE_S_PLL1,
+ 256 * srate, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return ret;
+ }
+
+ /*
+ * Codec TDM is configured as 24 bit capture/ playback.
+ * 2 CH PB is done over 4 codecs - 2 Woofers and 2 Tweeters.
+ * The Left woofer and tweeter plays the Left playback data
+ * and similar by the Right.
+ * Hence 2 codecs (1 T and 1 W pair) share same Rx slot.
+ * The feedback is captured for each codec individually.
+ * Hence all 4 codecs use 1 Tx slot each for feedback.
+ */
+ if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_WL |
+ SOF_RT1011_SPEAKER_WR)) {
+ if (!strcmp(codec_dai->component->name, "i2c-10EC1011:00")) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0x4, 0x1, 4, 24);
+ if (ret < 0)
+ break;
+ }
+
+ if (!strcmp(codec_dai->component->name, "i2c-10EC1011:01")) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0x8, 0x2, 4, 24);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL |
+ SOF_RT1011_SPEAKER_TR)) {
+ if (!strcmp(codec_dai->component->name, "i2c-10EC1011:02")) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0x1, 0x1, 4, 24);
+ if (ret < 0)
+ break;
+ }
+
+ if (!strcmp(codec_dai->component->name, "i2c-10EC1011:03")) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0x2, 0x2, 4, 24);
+ if (ret < 0)
+ break;
+ }
+ }
+ }
+ if (ret < 0)
+ dev_err(rtd->dev,
+ "set codec TDM slot for %s failed with error %d\n",
+ codec_dai->component->name, ret);
+ return ret;
+}
+
+static struct snd_soc_ops cml_rt5682_ops = {
+ .hw_params = cml_rt5682_hw_params,
+};
+
+static const struct snd_soc_ops cml_rt1011_ops = {
+ .hw_params = cml_rt1011_hw_params,
+};
+
+static int sof_card_late_probe(struct snd_soc_card *card)
+{
+ struct card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = NULL;
+ char jack_name[NAME_SIZE];
+ struct hdmi_pcm *pcm;
+ int ret, i = 0;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (ctx->common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ return hda_dsp_hdmi_build_controls(card, component);
+ }
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ ret = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &hdmi_jack[i],
+ NULL, 0);
+ if (ret)
+ return ret;
+
+ ret = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &hdmi_jack[i]);
+ if (ret < 0)
+ return ret;
+
+ i++;
+ }
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+static int hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+/* Cometlake digital audio interface glue - connects codec <--> CPU */
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00",
+ CML_RT5682_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec_2spk,
+ DAILINK_COMP_ARRAY(
+ /* WL */ COMP_CODEC("i2c-10EC1011:00", CML_RT1011_CODEC_DAI),
+ /* WR */ COMP_CODEC("i2c-10EC1011:01", CML_RT1011_CODEC_DAI)));
+SND_SOC_DAILINK_DEF(ssp1_codec_4spk,
+ DAILINK_COMP_ARRAY(
+ /* WL */ COMP_CODEC("i2c-10EC1011:00", CML_RT1011_CODEC_DAI),
+ /* WR */ COMP_CODEC("i2c-10EC1011:01", CML_RT1011_CODEC_DAI),
+ /* TL */ COMP_CODEC("i2c-10EC1011:02", CML_RT1011_CODEC_DAI),
+ /* TR */ COMP_CODEC("i2c-10EC1011:03", CML_RT1011_CODEC_DAI)));
+
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+
+SND_SOC_DAILINK_DEF(dmic16k_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+static struct snd_soc_dai_link cml_rt1011_rt5682_dailink[] = {
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .init = cml_rt5682_codec_init,
+ .exit = cml_rt5682_codec_exit,
+ .ignore_pmdown_time = 1,
+ .ops = &cml_rt5682_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 1,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 2,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+ {
+ /*
+ * SSP1 - Codec : added to end of list ensuring
+ * reuse of common topologies for other end points
+ * and changing only SSP1's codec
+ */
+ .name = "SSP1-Codec",
+ .id = 6,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1, /* Capture stream provides Feedback */
+ .no_pcm = 1,
+ .init = cml_rt1011_spk_init,
+ .ops = &cml_rt1011_ops,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec_2spk, platform),
+ },
+};
+
+static struct snd_soc_codec_conf rt1011_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1011:00"),
+ .name_prefix = "WL",
+ },
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1011:01"),
+ .name_prefix = "WR",
+ },
+ /* single configuration structure for 2 and 4 channels */
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1011:02"),
+ .name_prefix = "TL",
+ },
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1011:03"),
+ .name_prefix = "TR",
+ },
+};
+
+/* Cometlake audio machine driver for RT1011 and RT5682 */
+static struct snd_soc_card snd_soc_card_cml = {
+ .name = "cml_rt1011_rt5682",
+ .owner = THIS_MODULE,
+ .dai_link = cml_rt1011_rt5682_dailink,
+ .num_links = ARRAY_SIZE(cml_rt1011_rt5682_dailink),
+ .codec_conf = rt1011_conf,
+ .num_configs = ARRAY_SIZE(rt1011_conf),
+ .dapm_widgets = cml_rt1011_rt5682_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cml_rt1011_rt5682_widgets),
+ .dapm_routes = cml_rt1011_rt5682_map,
+ .num_dapm_routes = ARRAY_SIZE(cml_rt1011_rt5682_map),
+ .controls = cml_controls,
+ .num_controls = ARRAY_SIZE(cml_controls),
+ .fully_routed = true,
+ .late_probe = sof_card_late_probe,
+};
+
+static int snd_cml_rt1011_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_link *dai_link;
+ struct card_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ int ret, i;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+ mach = pdev->dev.platform_data;
+ snd_soc_card_cml.dev = &pdev->dev;
+ platform_name = mach->mach_params.platform;
+
+ dmi_check_system(sof_rt1011_quirk_table);
+
+ dev_dbg(&pdev->dev, "sof_rt1011_quirk = %lx\n", sof_rt1011_quirk);
+
+ /* when 4 speaker is available, update codec config */
+ if (sof_rt1011_quirk & (SOF_RT1011_SPEAKER_TL |
+ SOF_RT1011_SPEAKER_TR)) {
+ for_each_card_prelinks(&snd_soc_card_cml, i, dai_link) {
+ if (!strcmp(dai_link->codecs[0].dai_name,
+ CML_RT1011_CODEC_DAI)) {
+ dai_link->codecs = ssp1_codec_4spk;
+ dai_link->num_codecs = ARRAY_SIZE(ssp1_codec_4spk);
+ }
+ }
+ }
+
+ /* set platform name for each dailink */
+ ret = snd_soc_fixup_dai_links_platform_name(&snd_soc_card_cml,
+ platform_name);
+ if (ret)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ snd_soc_card_set_drvdata(&snd_soc_card_cml, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cml);
+}
+
+static struct platform_driver snd_cml_rt1011_rt5682_driver = {
+ .probe = snd_cml_rt1011_probe,
+ .driver = {
+ .name = "cml_rt1011_rt5682",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(snd_cml_rt1011_rt5682_driver);
+
+/* Module information */
+MODULE_DESCRIPTION("Cometlake Audio Machine driver - RT1011 and RT5682 in I2S mode");
+MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>");
+MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>");
+MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
+MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cml_rt1011_rt5682");
diff --git a/sound/soc/intel/boards/ehl_rt5660.c b/sound/soc/intel/boards/ehl_rt5660.c
new file mode 100644
index 000000000..7c0d4e915
--- /dev/null
+++ b/sound/soc/intel/boards/ehl_rt5660.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * ehl_rt5660 - ASOC Machine driver for Elkhart Lake platforms
+ * with rt5660 codec
+ */
+
+#include <linux/acpi.h>
+#include <sound/core.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <sound/jack.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+
+#include "hda_dsp_common.h"
+#include "../../codecs/rt5660.h"
+
+#define DUAL_CHANNEL 2
+#define HDMI_LINK_START 3
+#define HDMI_LINE_END 6
+#define NAME_SIZE 32
+#define IDISP_CODEC_MASK 0x4
+
+struct sof_card_private {
+ struct list_head hdmi_pcm_list;
+ bool idisp_codec;
+};
+
+static const struct snd_kcontrol_new rt5660_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ /* There are two MICBIAS in rt5660, each for one MIC */
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic2"),
+ SOC_DAPM_PIN_SWITCH("Line Out"),
+};
+
+static const struct snd_soc_dapm_widget rt5660_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic2", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct snd_soc_dapm_route rt5660_map[] = {
+ {"Speaker", NULL, "SPO"},
+
+ {"Headset Mic", NULL, "MICBIAS1"},
+ {"Headset Mic2", NULL, "MICBIAS2"},
+
+ {"IN1P", NULL, "Headset Mic"},
+ {"IN2P", NULL, "Headset Mic2"},
+
+ {"Line Out", NULL, "LOUTL"},
+ {"Line Out", NULL, "LOUTR"},
+
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+struct sof_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+static int hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct sof_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ /* dai_link id is 1:1 mapped to the PCM device */
+ pcm->device = rtd->dai_link->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int card_late_probe(struct snd_soc_card *card)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct sof_hdmi_pcm *pcm;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -ENOENT;
+
+ if (!ctx->idisp_codec)
+ return 0;
+
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head);
+
+ return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component);
+}
+
+static int rt5660_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT5660_SCLK_S_PLL1,
+ params_rate(params) * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5660_PLL1_S_BCLK,
+ params_rate(params) * 50,
+ params_rate(params) * 512);
+ if (ret < 0)
+ dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_ops rt5660_ops = {
+ .hw_params = rt5660_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+
+SND_SOC_DAILINK_DEF(rt5660_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5660:00", "rt5660-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+SND_SOC_DAILINK_DEF(dmic16k,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(idisp4_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp4 Pin")));
+SND_SOC_DAILINK_DEF(idisp4_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi4")));
+
+static struct snd_soc_dai_link ehl_rt5660_dailink[] = {
+ /* back ends */
+ {
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &rt5660_ops,
+ .nonatomic = true,
+ SND_SOC_DAILINK_REG(ssp0_pin, rt5660_codec, platform),
+ },
+ {
+ .name = "dmic48k",
+ .id = 1,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 2,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 5,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 6,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 7,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+ {
+ .name = "iDisp4",
+ .id = 8,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp4_pin, idisp4_codec, platform),
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_ehl_rt5660 = {
+ .name = "ehl-rt5660",
+ .owner = THIS_MODULE,
+ .dai_link = ehl_rt5660_dailink,
+ .num_links = ARRAY_SIZE(ehl_rt5660_dailink),
+ .dapm_widgets = rt5660_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(rt5660_widgets),
+ .dapm_routes = rt5660_map,
+ .num_dapm_routes = ARRAY_SIZE(rt5660_map),
+ .controls = rt5660_controls,
+ .num_controls = ARRAY_SIZE(rt5660_controls),
+ .fully_routed = true,
+ .late_probe = card_late_probe,
+};
+
+/* If hdmi codec is not supported, switch to use dummy codec */
+static void hdmi_link_init(struct snd_soc_card *card,
+ struct sof_card_private *ctx,
+ struct snd_soc_acpi_mach *mach)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ if (mach->mach_params.common_hdmi_codec_drv &&
+ (mach->mach_params.codec_mask & IDISP_CODEC_MASK)) {
+ ctx->idisp_codec = true;
+ return;
+ }
+
+ /*
+ * if HDMI is not enabled in kernel config, or
+ * hdmi codec is not supported
+ */
+ for (i = HDMI_LINK_START; i <= HDMI_LINE_END; i++) {
+ link = &card->dai_link[i];
+ link->codecs[0].name = "snd-soc-dummy";
+ link->codecs[0].dai_name = "snd-soc-dummy-dai";
+ }
+}
+
+static int snd_ehl_rt5660_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ struct snd_soc_card *card = &snd_soc_card_ehl_rt5660;
+ struct sof_card_private *ctx;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+ snd_soc_card_set_drvdata(card, ctx);
+
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(card,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ hdmi_link_init(card, ctx, mach);
+
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct platform_device_id ehl_board_ids[] = {
+ { .name = "ehl_rt5660" },
+ { }
+};
+
+static struct platform_driver snd_ehl_rt5660_driver = {
+ .driver = {
+ .name = "ehl_rt5660",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = snd_ehl_rt5660_probe,
+ .id_table = ehl_board_ids,
+};
+
+module_platform_driver(snd_ehl_rt5660_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Elkhartlake + rt5660 Machine driver");
+MODULE_AUTHOR("libin.yang@intel.com");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ehl_rt5660");
diff --git a/sound/soc/intel/boards/glk_rt5682_max98357a.c b/sound/soc/intel/boards/glk_rt5682_max98357a.c
new file mode 100644
index 000000000..62cca5115
--- /dev/null
+++ b/sound/soc/intel/boards/glk_rt5682_max98357a.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2018 Intel Corporation.
+
+/*
+ * Intel Geminilake I2S Machine Driver with MAX98357A & RT5682 Codecs
+ *
+ * Modified from:
+ * Intel Apollolake I2S Machine driver
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5682.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "hda_dsp_common.h"
+
+/* The platform clock outputs 19.2Mhz clock to codec as I2S MCLK */
+#define GLK_PLAT_CLK_FREQ 19200000
+#define RT5682_PLL_FREQ (48000 * 512)
+#define GLK_REALTEK_CODEC_DAI "rt5682-aif1"
+#define GLK_MAXIM_CODEC_DAI "HiFi"
+#define MAXIM_DEV0_NAME "MX98357A:00"
+#define DUAL_CHANNEL 2
+#define QUAD_CHANNEL 4
+#define NAME_SIZE 32
+
+static struct snd_soc_jack geminilake_hdmi[3];
+
+struct glk_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct glk_card_private {
+ struct snd_soc_jack geminilake_headset;
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+};
+
+enum {
+ GLK_DPCM_AUDIO_PB = 0,
+ GLK_DPCM_AUDIO_CP,
+ GLK_DPCM_AUDIO_HS_PB,
+ GLK_DPCM_AUDIO_ECHO_REF_CP,
+ GLK_DPCM_AUDIO_REF_CP,
+ GLK_DPCM_AUDIO_DMIC_CP,
+ GLK_DPCM_AUDIO_HDMI1_PB,
+ GLK_DPCM_AUDIO_HDMI2_PB,
+ GLK_DPCM_AUDIO_HDMI3_PB,
+};
+
+static const struct snd_kcontrol_new geminilake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Spk"),
+};
+
+static const struct snd_soc_dapm_widget geminilake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Spk", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SPK("HDMI3", NULL),
+};
+
+static const struct snd_soc_dapm_route geminilake_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* speaker */
+ { "Spk", NULL, "Speaker" },
+
+ /* other jacks */
+ { "IN1P", NULL, "Headset Mic" },
+
+ /* digital mics */
+ { "DMic", NULL, "SoC DMIC" },
+
+ /* CODEC BE connections */
+ { "HiFi Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec0_out" },
+
+ { "AIF1 Playback", NULL, "ssp2 Tx" },
+ { "ssp2 Tx", NULL, "codec1_out" },
+
+ { "codec0_in", NULL, "ssp2 Rx" },
+ { "ssp2 Rx", NULL, "AIF1 Capture" },
+
+ { "HDMI1", NULL, "hif5-0 Output" },
+ { "HDMI2", NULL, "hif6-0 Output" },
+ { "HDMI2", NULL, "hif7-0 Output" },
+
+ { "hifi3", NULL, "iDisp3 Tx" },
+ { "iDisp3 Tx", NULL, "iDisp3_out" },
+ { "hifi2", NULL, "iDisp2 Tx" },
+ { "iDisp2 Tx", NULL, "iDisp2_out" },
+ { "hifi1", NULL, "iDisp1 Tx" },
+ { "iDisp1 Tx", NULL, "iDisp1_out" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+};
+
+static int geminilake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will convert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = DUAL_CHANNEL;
+
+ /* set SSP to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_jack *jack;
+ int ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5682_PLL1_S_MCLK,
+ GLK_PLAT_CLK_FREQ, RT5682_PLL_FREQ);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1,
+ RT5682_PLL_FREQ, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT,
+ &ctx->geminilake_headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->geminilake_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+};
+
+static int geminilake_rt5682_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* Set valid bitmask & configuration for I2S in 24 bit */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, 24);
+ if (ret < 0) {
+ dev_err(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops geminilake_rt5682_ops = {
+ .hw_params = geminilake_rt5682_hw_params,
+};
+
+static int geminilake_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct glk_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = GLK_DPCM_AUDIO_HDMI1_PB + dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int geminilake_rt5682_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+ struct snd_soc_dapm_context *dapm;
+ int ret;
+
+ dapm = snd_soc_component_get_dapm(component);
+ ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+ if (ret) {
+ dev_err(rtd->dev, "Ref Cap ignore suspend failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static unsigned int channels_quad[] = {
+ QUAD_CHANNEL,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_channels_quad = {
+ .count = ARRAY_SIZE(channels_quad),
+ .list = channels_quad,
+ .mask = 0,
+};
+
+static int geminilake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /*
+ * set BE channel constraint as user FE channels
+ */
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int geminilake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels_quad);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops geminilake_dmic_ops = {
+ .startup = geminilake_dmic_startup,
+};
+
+static const unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static int geminilake_refcap_startup(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+};
+
+static const struct snd_soc_ops geminilake_refcap_ops = {
+ .startup = geminilake_refcap_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(system2,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin2")));
+
+SND_SOC_DAILINK_DEF(echoref,
+ DAILINK_COMP_ARRAY(COMP_CPU("Echoref Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC(MAXIM_DEV0_NAME,
+ GLK_MAXIM_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP2 Pin")));
+SND_SOC_DAILINK_DEF(ssp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00",
+ GLK_REALTEK_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
+
+/* geminilake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link geminilake_dais[] = {
+ /* Front End DAI links */
+ [GLK_DPCM_AUDIO_PB] = {
+ .name = "Glk Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = geminilake_rt5682_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_CP] = {
+ .name = "Glk Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_HS_PB] = {
+ .name = "Glk Audio Headset Playback",
+ .stream_name = "Headset Audio",
+ .dpcm_playback = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(system2, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_ECHO_REF_CP] = {
+ .name = "Glk Audio Echo Reference cap",
+ .stream_name = "Echoreference Capture",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(echoref, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_REF_CP] = {
+ .name = "Glk Audio Reference cap",
+ .stream_name = "Refcap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &geminilake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Glk Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &geminilake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Glk HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Glk HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [GLK_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Glk HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+ /* Back End DAI links */
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = geminilake_ssp_fixup,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ /* SSP2 - Codec */
+ .name = "SSP2-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = geminilake_rt5682_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = geminilake_ssp_fixup,
+ .ops = &geminilake_rt5682_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .ignore_suspend = 1,
+ .be_hw_params_fixup = geminilake_dmic_fixup,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .init = geminilake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = geminilake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = geminilake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+static int glk_card_late_probe(struct snd_soc_card *card)
+{
+ struct glk_card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = NULL;
+ char jack_name[NAME_SIZE];
+ struct glk_hdmi_pcm *pcm;
+ int err;
+ int i = 0;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (ctx->common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct glk_hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ return hda_dsp_hdmi_build_controls(card, component);
+ }
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &geminilake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &geminilake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* geminilake audio machine driver for SPT + RT5682 */
+static struct snd_soc_card glk_audio_card_rt5682_m98357a = {
+ .name = "glkrt5682max",
+ .owner = THIS_MODULE,
+ .dai_link = geminilake_dais,
+ .num_links = ARRAY_SIZE(geminilake_dais),
+ .controls = geminilake_controls,
+ .num_controls = ARRAY_SIZE(geminilake_controls),
+ .dapm_widgets = geminilake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(geminilake_widgets),
+ .dapm_routes = geminilake_map,
+ .num_dapm_routes = ARRAY_SIZE(geminilake_map),
+ .fully_routed = true,
+ .late_probe = glk_card_late_probe,
+};
+
+static int geminilake_audio_probe(struct platform_device *pdev)
+{
+ struct glk_card_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+ const char *platform_name;
+ struct snd_soc_card *card;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ card = &glk_audio_card_rt5682_m98357a;
+ card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(card, ctx);
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ platform_name = mach->mach_params.platform;
+
+ ret = snd_soc_fixup_dai_links_platform_name(card, platform_name);
+ if (ret)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct platform_device_id glk_board_ids[] = {
+ {
+ .name = "glk_rt5682_max98357a",
+ .driver_data =
+ (kernel_ulong_t)&glk_audio_card_rt5682_m98357a,
+ },
+ { }
+};
+
+static struct platform_driver geminilake_audio = {
+ .probe = geminilake_audio_probe,
+ .driver = {
+ .name = "glk_rt5682_max98357a",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = glk_board_ids,
+};
+module_platform_driver(geminilake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Geminilake Audio Machine driver-RT5682 & MAX98357A in I2S mode");
+MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>");
+MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:glk_rt5682_max98357a");
diff --git a/sound/soc/intel/boards/haswell.c b/sound/soc/intel/boards/haswell.c
new file mode 100644
index 000000000..c763bfeb1
--- /dev/null
+++ b/sound/soc/intel/boards/haswell.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Haswell Lynxpoint SST Audio
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/pcm_params.h>
+
+#include "../../codecs/rt5640.h"
+
+/* Haswell ULT platforms have a Headphone and Mic jack */
+static const struct snd_soc_dapm_widget haswell_widgets[] = {
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route haswell_rt5640_map[] = {
+
+ {"Headphones", NULL, "HPOR"},
+ {"Headphones", NULL, "HPOL"},
+ {"IN2P", NULL, "Mic"},
+
+ /* CODEC BE connections */
+ {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
+ {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
+};
+
+static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ /* set SSP0 to 16 bit */
+ params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
+ return 0;
+}
+
+static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000,
+ SND_SOC_CLOCK_IN);
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ /* set correct codec filter for DAI format and clock config */
+ snd_soc_component_update_bits(codec_dai->component, 0x83, 0xffff, 0x8000);
+
+ return ret;
+}
+
+static const struct snd_soc_ops haswell_rt5640_ops = {
+ .hw_params = haswell_rt5640_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(offload0,
+ DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
+
+SND_SOC_DAILINK_DEF(offload1,
+ DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
+
+SND_SOC_DAILINK_DEF(loopback,
+ DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
+
+SND_SOC_DAILINK_DEF(codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT33CA:00", "rt5640-aif1")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
+
+SND_SOC_DAILINK_DEF(ssp0_port,
+ DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
+
+static struct snd_soc_dai_link haswell_rt5640_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "System",
+ .stream_name = "System Playback/Capture",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ {
+ .name = "Offload0",
+ .stream_name = "Offload0 Playback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(offload0, dummy, platform),
+ },
+ {
+ .name = "Offload1",
+ .stream_name = "Offload1 Playback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(offload1, dummy, platform),
+ },
+ {
+ .name = "Loopback",
+ .stream_name = "Loopback",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(loopback, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = haswell_ssp0_fixup,
+ .ops = &haswell_rt5640_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
+ },
+};
+
+/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */
+static struct snd_soc_card haswell_rt5640 = {
+ .name = "haswell-rt5640",
+ .owner = THIS_MODULE,
+ .dai_link = haswell_rt5640_dais,
+ .num_links = ARRAY_SIZE(haswell_rt5640_dais),
+ .dapm_widgets = haswell_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(haswell_widgets),
+ .dapm_routes = haswell_rt5640_map,
+ .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map),
+ .fully_routed = true,
+};
+
+static int haswell_audio_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ haswell_rt5640.dev = &pdev->dev;
+
+ /* override plaform name, if required */
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(&haswell_rt5640,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640);
+}
+
+static struct platform_driver haswell_audio = {
+ .probe = haswell_audio_probe,
+ .driver = {
+ .name = "haswell-audio",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+
+module_platform_driver(haswell_audio)
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
+MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:haswell-audio");
diff --git a/sound/soc/intel/boards/hda_dsp_common.c b/sound/soc/intel/boards/hda_dsp_common.c
new file mode 100644
index 000000000..91ad2a0ad
--- /dev/null
+++ b/sound/soc/intel/boards/hda_dsp_common.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2019 Intel Corporation. All rights reserved.
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/hda_codec.h>
+#include <sound/hda_i915.h>
+#include "../../codecs/hdac_hda.h"
+
+#include "hda_dsp_common.h"
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+
+/*
+ * Search card topology and return PCM device number
+ * matching Nth HDMI device (zero-based index).
+ */
+static struct snd_pcm *hda_dsp_hdmi_pcm_handle(struct snd_soc_card *card,
+ int hdmi_idx)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_pcm *spcm;
+ int i = 0;
+
+ for_each_card_rtds(card, rtd) {
+ spcm = rtd->pcm ?
+ rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].pcm : NULL;
+ if (spcm && strstr(spcm->id, "HDMI")) {
+ if (i == hdmi_idx)
+ return rtd->pcm;
+ ++i;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Search card topology and register HDMI PCM related controls
+ * to codec driver.
+ */
+int hda_dsp_hdmi_build_controls(struct snd_soc_card *card,
+ struct snd_soc_component *comp)
+{
+ struct hdac_hda_priv *hda_pvt;
+ struct hda_codec *hcodec;
+ struct snd_pcm *spcm;
+ struct hda_pcm *hpcm;
+ int err = 0, i = 0;
+
+ if (!comp)
+ return -EINVAL;
+
+ hda_pvt = snd_soc_component_get_drvdata(comp);
+ hcodec = &hda_pvt->codec;
+
+ list_for_each_entry(hpcm, &hcodec->pcm_list_head, list) {
+ spcm = hda_dsp_hdmi_pcm_handle(card, i);
+ if (spcm) {
+ hpcm->pcm = spcm;
+ hpcm->device = spcm->device;
+ dev_dbg(card->dev,
+ "%s: mapping HDMI converter %d to PCM %d (%p)\n",
+ __func__, i, hpcm->device, spcm);
+ } else {
+ hpcm->pcm = NULL;
+ hpcm->device = SNDRV_PCM_INVALID_DEVICE;
+ dev_warn(card->dev,
+ "%s: no PCM in topology for HDMI converter %d\n\n",
+ __func__, i);
+ }
+ i++;
+ }
+ snd_hdac_display_power(hcodec->core.bus,
+ HDA_CODEC_IDX_CONTROLLER, true);
+ err = snd_hda_codec_build_controls(hcodec);
+ if (err < 0)
+ dev_err(card->dev, "unable to create controls %d\n", err);
+ snd_hdac_display_power(hcodec->core.bus,
+ HDA_CODEC_IDX_CONTROLLER, false);
+
+ return err;
+}
+
+#endif
diff --git a/sound/soc/intel/boards/hda_dsp_common.h b/sound/soc/intel/boards/hda_dsp_common.h
new file mode 100644
index 000000000..ea4ae9285
--- /dev/null
+++ b/sound/soc/intel/boards/hda_dsp_common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2019 Intel Corporation.
+ */
+
+/*
+ * This file defines helper functions used by multiple
+ * Intel HDA based machine drivers.
+ */
+
+#ifndef __HDA_DSP_COMMON_H
+#define __HDA_DSP_COMMON_H
+
+#include <sound/hda_codec.h>
+#include <sound/hda_i915.h>
+#include "../../codecs/hdac_hda.h"
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+int hda_dsp_hdmi_build_controls(struct snd_soc_card *card,
+ struct snd_soc_component *comp);
+#else
+static inline int hda_dsp_hdmi_build_controls(struct snd_soc_card *card,
+ struct snd_soc_component *comp)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif /* __HDA_DSP_COMMON_H */
diff --git a/sound/soc/intel/boards/kbl_da7219_max98357a.c b/sound/soc/intel/boards/kbl_da7219_max98357a.c
new file mode 100644
index 000000000..36f1f49e0
--- /dev/null
+++ b/sound/soc/intel/boards/kbl_da7219_max98357a.c
@@ -0,0 +1,619 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2017-18 Intel Corporation.
+
+/*
+ * Intel Kabylake I2S Machine Driver with MAX98357A & DA7219 Codecs
+ *
+ * Modified from:
+ * Intel Kabylake I2S Machine driver supporting MAXIM98927 and
+ * RT5663 codecs
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../codecs/da7219.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "../../codecs/da7219-aad.h"
+
+#define KBL_DIALOG_CODEC_DAI "da7219-hifi"
+#define KBL_MAXIM_CODEC_DAI "HiFi"
+#define MAXIM_DEV0_NAME "MX98357A:00"
+#define DUAL_CHANNEL 2
+#define QUAD_CHANNEL 4
+
+static struct snd_soc_card *kabylake_audio_card;
+static struct snd_soc_jack skylake_hdmi[3];
+
+struct kbl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct kbl_codec_private {
+ struct snd_soc_jack kabylake_headset;
+ struct list_head hdmi_pcm_list;
+};
+
+enum {
+ KBL_DPCM_AUDIO_PB = 0,
+ KBL_DPCM_AUDIO_CP,
+ KBL_DPCM_AUDIO_DMIC_CP,
+ KBL_DPCM_AUDIO_HDMI1_PB,
+ KBL_DPCM_AUDIO_HDMI2_PB,
+ KBL_DPCM_AUDIO_HDMI3_PB,
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ int ret = 0;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, KBL_DIALOG_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7219_SYSCLK_MCLK, 0, 0);
+ if (ret)
+ dev_err(card->dev, "failed to stop PLL: %d\n", ret);
+ } else if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM,
+ 0, DA7219_PLL_FREQ_OUT_98304);
+ if (ret)
+ dev_err(card->dev, "failed to start PLL: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new kabylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Spk"),
+};
+
+static const struct snd_soc_dapm_widget kabylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Spk", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("DP", NULL),
+ SND_SOC_DAPM_SPK("HDMI", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route kabylake_map[] = {
+ { "Headphone Jack", NULL, "HPL" },
+ { "Headphone Jack", NULL, "HPR" },
+
+ /* speaker */
+ { "Spk", NULL, "Speaker" },
+
+ /* other jacks */
+ { "MIC", NULL, "Headset Mic" },
+ { "DMic", NULL, "SoC DMIC" },
+
+ { "HDMI", NULL, "hif5 Output" },
+ { "DP", NULL, "hif6 Output" },
+
+ /* CODEC BE connections */
+ { "HiFi Playback", NULL, "ssp0 Tx" },
+ { "ssp0 Tx", NULL, "codec0_out" },
+
+ { "Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "codec0_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "Capture" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi1", NULL, "iDisp1 Tx" },
+ { "iDisp1 Tx", NULL, "iDisp1_out" },
+ { "hifi2", NULL, "iDisp2 Tx" },
+ { "iDisp2 Tx", NULL, "iDisp2_out" },
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+};
+
+static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will convert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = DUAL_CHANNEL;
+
+ /* set SSP to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_jack *jack;
+ int ret;
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, 24576000,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT,
+ &ctx->kabylake_headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->kabylake_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND);
+ da7219_aad_jack_det(component, &ctx->kabylake_headset);
+
+ ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+ if (ret)
+ dev_err(rtd->dev, "SoC DMIC - Ignore suspend failed %d\n", ret);
+
+ return ret;
+}
+
+static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct kbl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = device;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB);
+}
+
+static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB);
+}
+
+static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB);
+}
+
+static int kabylake_da7219_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static unsigned int channels_quad[] = {
+ QUAD_CHANNEL,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_channels_quad = {
+ .count = ARRAY_SIZE(channels_quad),
+ .list = channels_quad,
+ .mask = 0,
+};
+
+static int kbl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = DUAL_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops kabylake_da7219_fe_ops = {
+ .startup = kbl_fe_startup,
+};
+
+static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /*
+ * set BE channel constraint as user FE channels
+ */
+
+ if (params_channels(params) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int kabylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels_quad);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static struct snd_soc_ops kabylake_dmic_ops = {
+ .startup = kabylake_dmic_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC(MAXIM_DEV0_NAME,
+ KBL_MAXIM_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00",
+ KBL_DIALOG_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2",
+ "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* kabylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link kabylake_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = kabylake_da7219_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Kbl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &kabylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Kbl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = kabylake_da7219_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .be_hw_params_fixup = kabylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = kabylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int kabylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card);
+ struct kbl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* kabylake audio machine driver for SPT + DA7219 */
+static struct snd_soc_card kabylake_audio_card_da7219_m98357a = {
+ .name = "kblda7219max",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_dais,
+ .num_links = ARRAY_SIZE(kabylake_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static int kabylake_audio_probe(struct platform_device *pdev)
+{
+ struct kbl_codec_private *ctx;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ kabylake_audio_card =
+ (struct snd_soc_card *)pdev->id_entry->driver_data;
+
+ kabylake_audio_card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(kabylake_audio_card, ctx);
+ return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card);
+}
+
+static const struct platform_device_id kbl_board_ids[] = {
+ {
+ .name = "kbl_da7219_mx98357a",
+ .driver_data =
+ (kernel_ulong_t)&kabylake_audio_card_da7219_m98357a,
+ },
+ { }
+};
+
+static struct platform_driver kabylake_audio = {
+ .probe = kabylake_audio_probe,
+ .driver = {
+ .name = "kbl_da7219_max98357a",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = kbl_board_ids,
+};
+
+module_platform_driver(kabylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode");
+MODULE_AUTHOR("Naveen Manohar <naveen.m@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kbl_da7219_mx98357a");
diff --git a/sound/soc/intel/boards/kbl_da7219_max98927.c b/sound/soc/intel/boards/kbl_da7219_max98927.c
new file mode 100644
index 000000000..884741aa4
--- /dev/null
+++ b/sound/soc/intel/boards/kbl_da7219_max98927.c
@@ -0,0 +1,1154 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2018 Intel Corporation.
+
+/*
+ * Intel Kabylake I2S Machine Driver with MAX98927, MAX98373 & DA7219 Codecs
+ *
+ * Modified from:
+ * Intel Kabylake I2S Machine driver supporting MAX98927 and
+ * RT5663 codecs
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../codecs/da7219.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "../../codecs/da7219-aad.h"
+
+#define KBL_DIALOG_CODEC_DAI "da7219-hifi"
+#define MAX98927_CODEC_DAI "max98927-aif1"
+#define MAX98927_DEV0_NAME "i2c-MX98927:00"
+#define MAX98927_DEV1_NAME "i2c-MX98927:01"
+
+#define MAX98373_CODEC_DAI "max98373-aif1"
+#define MAX98373_DEV0_NAME "i2c-MX98373:00"
+#define MAX98373_DEV1_NAME "i2c-MX98373:01"
+
+
+#define DUAL_CHANNEL 2
+#define QUAD_CHANNEL 4
+#define NAME_SIZE 32
+
+static struct snd_soc_card *kabylake_audio_card;
+static struct snd_soc_jack kabylake_hdmi[3];
+
+struct kbl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct kbl_codec_private {
+ struct snd_soc_jack kabylake_headset;
+ struct list_head hdmi_pcm_list;
+};
+
+enum {
+ KBL_DPCM_AUDIO_PB = 0,
+ KBL_DPCM_AUDIO_ECHO_REF_CP,
+ KBL_DPCM_AUDIO_REF_CP,
+ KBL_DPCM_AUDIO_DMIC_CP,
+ KBL_DPCM_AUDIO_HDMI1_PB,
+ KBL_DPCM_AUDIO_HDMI2_PB,
+ KBL_DPCM_AUDIO_HDMI3_PB,
+ KBL_DPCM_AUDIO_HS_PB,
+ KBL_DPCM_AUDIO_CP,
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ int ret = 0;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, KBL_DIALOG_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n");
+ return -EIO;
+ }
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, 24576000,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(card->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ DA7219_SYSCLK_MCLK, 0, 0);
+ if (ret)
+ dev_err(card->dev, "failed to stop PLL: %d\n", ret);
+ } else if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM,
+ 0, DA7219_PLL_FREQ_OUT_98304);
+ if (ret)
+ dev_err(card->dev, "failed to start PLL: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new kabylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+};
+
+static const struct snd_soc_dapm_widget kabylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("DP", NULL),
+ SND_SOC_DAPM_SPK("HDMI", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route kabylake_map[] = {
+ /* speaker */
+ { "Left Spk", NULL, "Left BE_OUT" },
+ { "Right Spk", NULL, "Right BE_OUT" },
+
+ /* other jacks */
+ { "DMic", NULL, "SoC DMIC" },
+
+ { "HDMI", NULL, "hif5 Output" },
+ { "DP", NULL, "hif6 Output" },
+
+ /* CODEC BE connections */
+ { "Left HiFi Playback", NULL, "ssp0 Tx" },
+ { "Right HiFi Playback", NULL, "ssp0 Tx" },
+ { "ssp0 Tx", NULL, "spk_out" },
+
+ /* IV feedback path */
+ { "codec0_fb_in", NULL, "ssp0 Rx"},
+ { "ssp0 Rx", NULL, "Left HiFi Capture" },
+ { "ssp0 Rx", NULL, "Right HiFi Capture" },
+
+ /* AEC capture path */
+ { "echo_ref_out", NULL, "ssp0 Rx" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi1", NULL, "iDisp1 Tx" },
+ { "iDisp1 Tx", NULL, "iDisp1_out" },
+ { "hifi2", NULL, "iDisp2 Tx" },
+ { "iDisp2 Tx", NULL, "iDisp2_out" },
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+};
+
+static const struct snd_soc_dapm_route kabylake_ssp1_map[] = {
+ { "Headphone Jack", NULL, "HPL" },
+ { "Headphone Jack", NULL, "HPR" },
+
+ /* other jacks */
+ { "MIC", NULL, "Headset Mic" },
+
+ /* CODEC BE connections */
+ { "Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "hs_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "Capture" },
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+};
+
+static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int ret, j;
+
+ for_each_rtd_codec_dais(runtime, j, codec_dai) {
+
+ if (!strcmp(codec_dai->component->name, MAX98927_DEV0_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAX98927_DEV1_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev, "DEV1 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAX98373_DEV0_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0x30, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev,
+ "DEV0 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAX98373_DEV1_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ 0xC0, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev,
+ "DEV1 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int kabylake_ssp0_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int j, ret;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ const char *name = codec_dai->component->name;
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ char pin_name[20];
+
+ if (strcmp(name, MAX98927_DEV0_NAME) &&
+ strcmp(name, MAX98927_DEV1_NAME) &&
+ strcmp(name, MAX98373_DEV0_NAME) &&
+ strcmp(name, MAX98373_DEV1_NAME))
+ continue;
+
+ snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk",
+ codec_dai->component->name_prefix);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = snd_soc_dapm_enable_pin(dapm, pin_name);
+ if (ret) {
+ dev_err(rtd->dev, "failed to enable %s: %d\n",
+ pin_name, ret);
+ return ret;
+ }
+ snd_soc_dapm_sync(dapm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = snd_soc_dapm_disable_pin(dapm, pin_name);
+ if (ret) {
+ dev_err(rtd->dev, "failed to disable %s: %d\n",
+ pin_name, ret);
+ return ret;
+ }
+ snd_soc_dapm_sync(dapm);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops kabylake_ssp0_ops = {
+ .hw_params = kabylake_ssp0_hw_params,
+ .trigger = kabylake_ssp0_trigger,
+};
+
+static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL;
+
+ /*
+ * The following loop will be called only for playback stream
+ * In this platform, there is only one playback device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ /*
+ * This following loop will be called only for capture stream
+ * In this platform, there is only one capture device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ if (!rtd_dpcm)
+ return -EINVAL;
+
+ /*
+ * The above 2 loops are mutually exclusive based on the stream direction,
+ * thus rtd_dpcm variable will never be overwritten
+ */
+
+ /*
+ * The ADSP will convert the FE rate to 48k, stereo, 24 bit
+ */
+ if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) {
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ }
+
+ /*
+ * The speaker on the SSP0 supports S16_LE and not S24_LE.
+ * thus changing the mask here
+ */
+ if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec"))
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_jack *jack;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+
+ ret = snd_soc_dapm_add_routes(&card->dapm,
+ kabylake_ssp1_map,
+ ARRAY_SIZE(kabylake_ssp1_map));
+
+ if (ret)
+ return ret;
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT,
+ &ctx->kabylake_headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->kabylake_headset;
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND);
+
+ da7219_aad_jack_det(component, &ctx->kabylake_headset);
+
+ return 0;
+}
+
+static int kabylake_dmic_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+ if (ret)
+ dev_err(rtd->dev, "SoC DMIC - Ignore suspend failed %d\n", ret);
+
+ return ret;
+}
+
+static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct kbl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = device;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB);
+}
+
+static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB);
+}
+
+static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB);
+}
+
+static int kabylake_da7219_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static unsigned int channels_quad[] = {
+ QUAD_CHANNEL,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_channels_quad = {
+ .count = ARRAY_SIZE(channels_quad),
+ .list = channels_quad,
+ .mask = 0,
+};
+
+static int kbl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = DUAL_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops kabylake_da7219_fe_ops = {
+ .startup = kbl_fe_startup,
+};
+
+static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /*
+ * set BE channel constraint as user FE channels
+ */
+
+ if (params_channels(params) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int kabylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels_quad);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static struct snd_soc_ops kabylake_dmic_ops = {
+ .startup = kabylake_dmic_startup,
+};
+
+static const unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static const unsigned int ch_mono[] = {
+ 1,
+};
+static const struct snd_pcm_hw_constraint_list constraints_refcap = {
+ .count = ARRAY_SIZE(ch_mono),
+ .list = ch_mono,
+};
+
+static int kabylake_refcap_startup(struct snd_pcm_substream *substream)
+{
+ substream->runtime->hw.channels_max = 1;
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_refcap);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+}
+
+
+static struct snd_soc_ops skylake_refcap_ops = {
+ .startup = kabylake_refcap_startup,
+};
+
+static struct snd_soc_codec_conf max98927_codec_conf[] = {
+
+ {
+ .dlc = COMP_CODEC_CONF(MAX98927_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+
+ {
+ .dlc = COMP_CODEC_CONF(MAX98927_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+static struct snd_soc_codec_conf max98373_codec_conf[] = {
+
+ {
+ .dlc = COMP_CODEC_CONF(MAX98373_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+
+ {
+ .dlc = COMP_CODEC_CONF(MAX98373_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+static struct snd_soc_dai_link_component max98373_ssp0_codec_components[] = {
+ { /* Left */
+ .name = MAX98373_DEV0_NAME,
+ .dai_name = MAX98373_CODEC_DAI,
+ },
+
+ { /* For Right */
+ .name = MAX98373_DEV1_NAME,
+ .dai_name = MAX98373_CODEC_DAI,
+ },
+
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(echoref,
+ DAILINK_COMP_ARRAY(COMP_CPU("Echoref Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(system2,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin2")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC(MAX98927_DEV0_NAME, MAX98927_CODEC_DAI),
+ /* For Right */ COMP_CODEC(MAX98927_DEV1_NAME, MAX98927_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00",
+ KBL_DIALOG_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* kabylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link kabylake_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = kabylake_da7219_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_ECHO_REF_CP] = {
+ .name = "Kbl Audio Echo Reference cap",
+ .stream_name = "Echoreference Capture",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ SND_SOC_DAILINK_REG(echoref, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_REF_CP] = {
+ .name = "Kbl Audio Reference cap",
+ .stream_name = "Wake on Voice",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Kbl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &kabylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Kbl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HS_PB] = {
+ .name = "Kbl Audio Headset Playback",
+ .stream_name = "Headset Audio",
+ .dpcm_playback = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .init = kabylake_da7219_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .ops = &kabylake_ssp0_ops,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = kabylake_da7219_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .init = kabylake_dmic_init,
+ .be_hw_params_fixup = kabylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = kabylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+/* kabylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = kabylake_da7219_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_da7219_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_ECHO_REF_CP] = {
+ .name = "Kbl Audio Echo Reference cap",
+ .stream_name = "Echoreference Capture",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ SND_SOC_DAILINK_REG(echoref, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_REF_CP] = {
+ .name = "Kbl Audio Reference cap",
+ .stream_name = "Wake on Voice",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Kbl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &kabylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Kbl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .ops = &kabylake_ssp0_ops,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec),
+ },
+ {
+ .name = "dmic01",
+ .id = 1,
+ .init = kabylake_dmic_init,
+ .be_hw_params_fixup = kabylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 2,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 3,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 4,
+ .init = kabylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+static int kabylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card);
+ struct kbl_hdmi_pcm *pcm;
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &kabylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &kabylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+
+ err = hdac_hdmi_jack_port_init(component, &card->dapm);
+
+ if (err < 0)
+ return err;
+
+ err = snd_soc_dapm_disable_pin(dapm, "Left Spk");
+ if (err) {
+ dev_err(card->dev, "failed to disable Left Spk: %d\n", err);
+ return err;
+ }
+
+ err = snd_soc_dapm_disable_pin(dapm, "Right Spk");
+ if (err) {
+ dev_err(card->dev, "failed to disable Right Spk: %d\n", err);
+ return err;
+ }
+
+ return snd_soc_dapm_sync(dapm);
+}
+
+/* kabylake audio machine driver for SPT + DA7219 */
+static struct snd_soc_card kbl_audio_card_da7219_m98927 = {
+ .name = "kblda7219m98927",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_dais,
+ .num_links = ARRAY_SIZE(kabylake_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98927_codec_conf,
+ .num_configs = ARRAY_SIZE(max98927_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+/* kabylake audio machine driver for Maxim98927 */
+static struct snd_soc_card kbl_audio_card_max98927 = {
+ .name = "kblmax98927",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_max98_927_373_dais,
+ .num_links = ARRAY_SIZE(kabylake_max98_927_373_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98927_codec_conf,
+ .num_configs = ARRAY_SIZE(max98927_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static struct snd_soc_card kbl_audio_card_da7219_m98373 = {
+ .name = "kblda7219m98373",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_dais,
+ .num_links = ARRAY_SIZE(kabylake_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98373_codec_conf,
+ .num_configs = ARRAY_SIZE(max98373_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static struct snd_soc_card kbl_audio_card_max98373 = {
+ .name = "kblmax98373",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_max98_927_373_dais,
+ .num_links = ARRAY_SIZE(kabylake_max98_927_373_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98373_codec_conf,
+ .num_configs = ARRAY_SIZE(max98373_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static int kabylake_audio_probe(struct platform_device *pdev)
+{
+ struct kbl_codec_private *ctx;
+ struct snd_soc_dai_link *kbl_dai_link;
+ struct snd_soc_dai_link_component **codecs;
+ int i;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ kabylake_audio_card =
+ (struct snd_soc_card *)pdev->id_entry->driver_data;
+
+ kbl_dai_link = kabylake_audio_card->dai_link;
+
+ /* Update codecs for SSP0 with max98373 codec info */
+ if (!strcmp(pdev->name, "kbl_da7219_max98373") ||
+ (!strcmp(pdev->name, "kbl_max98373"))) {
+ for (i = 0; i < kabylake_audio_card->num_links; ++i) {
+ if (strcmp(kbl_dai_link[i].name, "SSP0-Codec"))
+ continue;
+
+ codecs = &(kbl_dai_link[i].codecs);
+ *codecs = max98373_ssp0_codec_components;
+ kbl_dai_link[i].num_codecs =
+ ARRAY_SIZE(max98373_ssp0_codec_components);
+ break;
+ }
+ }
+ kabylake_audio_card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(kabylake_audio_card, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card);
+}
+
+static const struct platform_device_id kbl_board_ids[] = {
+ {
+ .name = "kbl_da7219_max98927",
+ .driver_data =
+ (kernel_ulong_t)&kbl_audio_card_da7219_m98927,
+ },
+ {
+ .name = "kbl_max98927",
+ .driver_data =
+ (kernel_ulong_t)&kbl_audio_card_max98927,
+ },
+ {
+ .name = "kbl_da7219_max98373",
+ .driver_data =
+ (kernel_ulong_t)&kbl_audio_card_da7219_m98373,
+ },
+ {
+ .name = "kbl_max98373",
+ .driver_data =
+ (kernel_ulong_t)&kbl_audio_card_max98373,
+ },
+ { }
+};
+
+static struct platform_driver kabylake_audio = {
+ .probe = kabylake_audio_probe,
+ .driver = {
+ .name = "kbl_da7219_max98_927_373",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = kbl_board_ids,
+};
+
+module_platform_driver(kabylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio KabyLake Machine driver for MAX98927/MAX98373 & DA7219");
+MODULE_AUTHOR("Mac Chiang <mac.chiang@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kbl_da7219_max98927");
+MODULE_ALIAS("platform:kbl_max98927");
+MODULE_ALIAS("platform:kbl_da7219_max98373");
+MODULE_ALIAS("platform:kbl_max98373");
diff --git a/sound/soc/intel/boards/kbl_rt5660.c b/sound/soc/intel/boards/kbl_rt5660.c
new file mode 100644
index 000000000..3a9f91b58
--- /dev/null
+++ b/sound/soc/intel/boards/kbl_rt5660.c
@@ -0,0 +1,567 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2018-19 Canonical Corporation.
+
+/*
+ * Intel Kabylake I2S Machine Driver with RT5660 Codec
+ *
+ * Modified from:
+ * Intel Kabylake I2S Machine driver supporting MAXIM98357a and
+ * DA7219 codecs
+ * Also referred to:
+ * Intel Broadwell I2S Machine driver supporting RT5677 codec
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/acpi.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../../codecs/hdac_hdmi.h"
+#include "../../codecs/rt5660.h"
+
+#define KBL_RT5660_CODEC_DAI "rt5660-aif1"
+#define DUAL_CHANNEL 2
+
+static struct snd_soc_card *kabylake_audio_card;
+static struct snd_soc_jack skylake_hdmi[3];
+static struct snd_soc_jack lineout_jack;
+static struct snd_soc_jack mic_jack;
+
+struct kbl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct kbl_codec_private {
+ struct gpio_desc *gpio_lo_mute;
+ struct list_head hdmi_pcm_list;
+};
+
+enum {
+ KBL_DPCM_AUDIO_PB = 0,
+ KBL_DPCM_AUDIO_CP,
+ KBL_DPCM_AUDIO_HDMI1_PB,
+ KBL_DPCM_AUDIO_HDMI2_PB,
+ KBL_DPCM_AUDIO_HDMI3_PB,
+};
+
+#define GPIO_LINEOUT_MUTE_INDEX 0
+#define GPIO_LINEOUT_DET_INDEX 3
+#define GPIO_LINEIN_DET_INDEX 4
+
+static const struct acpi_gpio_params lineout_mute_gpio = { GPIO_LINEOUT_MUTE_INDEX, 0, true };
+static const struct acpi_gpio_params lineout_det_gpio = { GPIO_LINEOUT_DET_INDEX, 0, false };
+static const struct acpi_gpio_params mic_det_gpio = { GPIO_LINEIN_DET_INDEX, 0, false };
+
+
+static const struct acpi_gpio_mapping acpi_rt5660_gpios[] = {
+ { "lineout-mute-gpios", &lineout_mute_gpio, 1 },
+ { "lineout-det-gpios", &lineout_det_gpio, 1 },
+ { "mic-det-gpios", &mic_det_gpio, 1 },
+ { NULL },
+};
+
+static struct snd_soc_jack_pin lineout_jack_pin = {
+ .pin = "Line Out",
+ .mask = SND_JACK_LINEOUT,
+};
+
+static struct snd_soc_jack_pin mic_jack_pin = {
+ .pin = "Line In",
+ .mask = SND_JACK_MICROPHONE,
+};
+
+static struct snd_soc_jack_gpio lineout_jack_gpio = {
+ .name = "lineout-det",
+ .report = SND_JACK_LINEOUT,
+ .debounce_time = 200,
+};
+
+static struct snd_soc_jack_gpio mic_jack_gpio = {
+ .name = "mic-det",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 200,
+};
+
+static int kabylake_5660_event_lineout(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct kbl_codec_private *priv = snd_soc_card_get_drvdata(dapm->card);
+
+ gpiod_set_value_cansleep(priv->gpio_lo_mute,
+ !(SND_SOC_DAPM_EVENT_ON(event)));
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new kabylake_rt5660_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Line In"),
+ SOC_DAPM_PIN_SWITCH("Line Out"),
+};
+
+static const struct snd_soc_dapm_widget kabylake_rt5660_widgets[] = {
+ SND_SOC_DAPM_MIC("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", kabylake_5660_event_lineout),
+};
+
+static const struct snd_soc_dapm_route kabylake_rt5660_map[] = {
+ /* other jacks */
+ {"IN1P", NULL, "Line In"},
+ {"IN2P", NULL, "Line In"},
+ {"Line Out", NULL, "LOUTR"},
+ {"Line Out", NULL, "LOUTL"},
+
+ /* CODEC BE connections */
+ { "AIF1 Playback", NULL, "ssp0 Tx"},
+ { "ssp0 Tx", NULL, "codec0_out"},
+
+ { "codec0_in", NULL, "ssp0 Rx" },
+ { "ssp0 Rx", NULL, "AIF1 Capture" },
+
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+};
+
+static int kabylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will convert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = DUAL_CHANNEL;
+
+ /* set SSP0 to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int kabylake_rt5660_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+ ret = devm_acpi_dev_add_driver_gpios(component->dev, acpi_rt5660_gpios);
+ if (ret)
+ dev_warn(component->dev, "Failed to add driver gpios\n");
+
+ /* Request rt5660 GPIO for lineout mute control, return if fails */
+ ctx->gpio_lo_mute = gpiod_get(component->dev, "lineout-mute",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->gpio_lo_mute)) {
+ dev_err(component->dev, "Can't find GPIO_MUTE# gpio\n");
+ return PTR_ERR(ctx->gpio_lo_mute);
+ }
+
+ /* Create and initialize headphone jack, this jack is not mandatory, don't return if fails */
+ ret = snd_soc_card_jack_new(rtd->card, "Lineout Jack",
+ SND_JACK_LINEOUT, &lineout_jack,
+ &lineout_jack_pin, 1);
+ if (ret)
+ dev_warn(component->dev, "Can't create Lineout jack\n");
+ else {
+ lineout_jack_gpio.gpiod_dev = component->dev;
+ ret = snd_soc_jack_add_gpios(&lineout_jack, 1,
+ &lineout_jack_gpio);
+ if (ret)
+ dev_warn(component->dev, "Can't add Lineout jack gpio\n");
+ }
+
+ /* Create and initialize mic jack, this jack is not mandatory, don't return if fails */
+ ret = snd_soc_card_jack_new(rtd->card, "Mic Jack",
+ SND_JACK_MICROPHONE, &mic_jack,
+ &mic_jack_pin, 1);
+ if (ret)
+ dev_warn(component->dev, "Can't create mic jack\n");
+ else {
+ mic_jack_gpio.gpiod_dev = component->dev;
+ ret = snd_soc_jack_add_gpios(&mic_jack, 1, &mic_jack_gpio);
+ if (ret)
+ dev_warn(component->dev, "Can't add mic jack gpio\n");
+ }
+
+ /* Here we enable some dapms in advance to reduce the pop noise for recording via line-in */
+ snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1");
+ snd_soc_dapm_force_enable_pin(dapm, "BST1");
+ snd_soc_dapm_force_enable_pin(dapm, "BST2");
+
+ return 0;
+}
+
+static void kabylake_rt5660_codec_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+
+ /*
+ * The .exit() can be reached without going through the .init()
+ * so explicitly test if the gpiod is valid
+ */
+ if (!IS_ERR_OR_NULL(ctx->gpio_lo_mute))
+ gpiod_put(ctx->gpio_lo_mute);
+}
+
+static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct kbl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = device;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB);
+}
+
+static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB);
+}
+
+static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB);
+}
+
+static int kabylake_rt5660_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT5660_SCLK_S_PLL1, params_rate(params) * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ RT5660_PLL1_S_BCLK,
+ params_rate(params) * 50,
+ params_rate(params) * 512);
+ if (ret < 0)
+ dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_ops kabylake_rt5660_ops = {
+ .hw_params = kabylake_rt5660_hw_params,
+};
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int kbl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = DUAL_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops kabylake_rt5660_fe_ops = {
+ .startup = kbl_fe_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC3277:00", KBL_RT5660_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* kabylake digital audio interface glue - connects rt5660 codec <--> CPU */
+static struct snd_soc_dai_link kabylake_rt5660_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_rt5660_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_rt5660_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Kbl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = kabylake_rt5660_codec_init,
+ .exit = kabylake_rt5660_codec_exit,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp0_fixup,
+ .ops = &kabylake_rt5660_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 1,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 2,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 3,
+ .init = kabylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+
+#define NAME_SIZE 32
+static int kabylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card);
+ struct kbl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* kabylake audio machine driver for rt5660 */
+static struct snd_soc_card kabylake_audio_card_rt5660 = {
+ .name = "kblrt5660",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_rt5660_dais,
+ .num_links = ARRAY_SIZE(kabylake_rt5660_dais),
+ .controls = kabylake_rt5660_controls,
+ .num_controls = ARRAY_SIZE(kabylake_rt5660_controls),
+ .dapm_widgets = kabylake_rt5660_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_rt5660_widgets),
+ .dapm_routes = kabylake_rt5660_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_rt5660_map),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static int kabylake_audio_probe(struct platform_device *pdev)
+{
+ struct kbl_codec_private *ctx;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ kabylake_audio_card =
+ (struct snd_soc_card *)pdev->id_entry->driver_data;
+
+ kabylake_audio_card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(kabylake_audio_card, ctx);
+ return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card);
+}
+
+static const struct platform_device_id kbl_board_ids[] = {
+ {
+ .name = "kbl_rt5660",
+ .driver_data =
+ (kernel_ulong_t)&kabylake_audio_card_rt5660,
+ },
+ { }
+};
+
+static struct platform_driver kabylake_audio = {
+ .probe = kabylake_audio_probe,
+ .driver = {
+ .name = "kbl_rt5660",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = kbl_board_ids,
+};
+
+module_platform_driver(kabylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-RT5660 in I2S mode");
+MODULE_AUTHOR("Hui Wang <hui.wang@canonical.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kbl_rt5660");
diff --git a/sound/soc/intel/boards/kbl_rt5663_max98927.c b/sound/soc/intel/boards/kbl_rt5663_max98927.c
new file mode 100644
index 000000000..9a4b3d097
--- /dev/null
+++ b/sound/soc/intel/boards/kbl_rt5663_max98927.c
@@ -0,0 +1,1054 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Kabylake I2S Machine Driver with MAXIM98927
+ * and RT5663 Codecs
+ *
+ * Copyright (C) 2017, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Skylake I2S Machine driver
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5663.h"
+#include "../../codecs/hdac_hdmi.h"
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+
+#define KBL_REALTEK_CODEC_DAI "rt5663-aif"
+#define KBL_MAXIM_CODEC_DAI "max98927-aif1"
+#define DMIC_CH(p) p->list[p->count-1]
+#define MAXIM_DEV0_NAME "i2c-MX98927:00"
+#define MAXIM_DEV1_NAME "i2c-MX98927:01"
+
+static struct snd_soc_card *kabylake_audio_card;
+static const struct snd_pcm_hw_constraint_list *dmic_constraints;
+static struct snd_soc_jack skylake_hdmi[3];
+
+struct kbl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct kbl_rt5663_private {
+ struct snd_soc_jack kabylake_headset;
+ struct list_head hdmi_pcm_list;
+ struct clk *mclk;
+ struct clk *sclk;
+};
+
+enum {
+ KBL_DPCM_AUDIO_PB = 0,
+ KBL_DPCM_AUDIO_CP,
+ KBL_DPCM_AUDIO_HS_PB,
+ KBL_DPCM_AUDIO_ECHO_REF_CP,
+ KBL_DPCM_AUDIO_REF_CP,
+ KBL_DPCM_AUDIO_DMIC_CP,
+ KBL_DPCM_AUDIO_HDMI1_PB,
+ KBL_DPCM_AUDIO_HDMI2_PB,
+ KBL_DPCM_AUDIO_HDMI3_PB,
+};
+
+static const struct snd_kcontrol_new kabylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct kbl_rt5663_private *priv = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ /*
+ * MCLK/SCLK need to be ON early for a successful synchronization of
+ * codec internal clock. And the clocks are turned off during
+ * POST_PMD after the stream is stopped.
+ */
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Enable MCLK */
+ ret = clk_set_rate(priv->mclk, 24000000);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't set rate for mclk, err: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't enable mclk, err: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable SCLK */
+ ret = clk_set_rate(priv->sclk, 3072000);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't set rate for sclk, err: %d\n",
+ ret);
+ clk_disable_unprepare(priv->mclk);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->sclk);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't enable sclk, err: %d\n", ret);
+ clk_disable_unprepare(priv->mclk);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ clk_disable_unprepare(priv->mclk);
+ clk_disable_unprepare(priv->sclk);
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget kabylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SPK("HDMI3", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route kabylake_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* speaker */
+ { "Left Spk", NULL, "Left BE_OUT" },
+ { "Right Spk", NULL, "Right BE_OUT" },
+
+ /* other jacks */
+ { "Headset Mic", NULL, "Platform Clock" },
+ { "IN1P", NULL, "Headset Mic" },
+ { "IN1N", NULL, "Headset Mic" },
+ { "DMic", NULL, "SoC DMIC" },
+
+ /* CODEC BE connections */
+ { "Left HiFi Playback", NULL, "ssp0 Tx" },
+ { "Right HiFi Playback", NULL, "ssp0 Tx" },
+ { "ssp0 Tx", NULL, "spk_out" },
+
+ { "AIF Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "hs_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "AIF Capture" },
+
+ /* IV feedback path */
+ { "codec0_fb_in", NULL, "ssp0 Rx"},
+ { "ssp0 Rx", NULL, "Left HiFi Capture" },
+ { "ssp0 Rx", NULL, "Right HiFi Capture" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+};
+
+enum {
+ KBL_DPCM_AUDIO_5663_PB = 0,
+ KBL_DPCM_AUDIO_5663_CP,
+ KBL_DPCM_AUDIO_5663_HDMI1_PB,
+ KBL_DPCM_AUDIO_5663_HDMI2_PB,
+};
+
+static const struct snd_kcontrol_new kabylake_5663_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_widget kabylake_5663_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("DP", NULL),
+ SND_SOC_DAPM_SPK("HDMI", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route kabylake_5663_map[] = {
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* other jacks */
+ { "Headset Mic", NULL, "Platform Clock" },
+ { "IN1P", NULL, "Headset Mic" },
+ { "IN1N", NULL, "Headset Mic" },
+
+ { "HDMI", NULL, "hif5 Output" },
+ { "DP", NULL, "hif6 Output" },
+
+ /* CODEC BE connections */
+ { "AIF Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "codec0_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "AIF Capture" },
+
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+};
+
+static struct snd_soc_codec_conf max98927_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+ if (ret) {
+ dev_err(rtd->dev, "Ref Cap ignore suspend failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_jack *jack;
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset,
+ NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->kabylake_headset;
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ snd_soc_component_set_jack(component, &ctx->kabylake_headset, NULL);
+
+ return ret;
+}
+
+static int kabylake_rt5663_max98927_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+
+ ret = kabylake_rt5663_codec_init(rtd);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+ if (ret) {
+ dev_err(rtd->dev, "SoC DMIC ignore suspend failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device)
+{
+ struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct kbl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = device;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB);
+}
+
+static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB);
+}
+
+static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB);
+}
+
+static int kabylake_5663_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_5663_HDMI1_PB);
+}
+
+static int kabylake_5663_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_5663_HDMI2_PB);
+}
+
+static unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int kbl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops kabylake_rt5663_fe_ops = {
+ .startup = kbl_fe_startup,
+};
+
+static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL;
+
+ /*
+ * The following loop will be called only for playback stream
+ * In this platform, there is only one playback device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ /*
+ * This following loop will be called only for capture stream
+ * In this platform, there is only one capture device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ if (!rtd_dpcm)
+ return -EINVAL;
+
+ /*
+ * The above 2 loops are mutually exclusive based on the stream direction,
+ * thus rtd_dpcm variable will never be overwritten
+ */
+
+ /*
+ * The ADSP will convert the FE rate to 48k, stereo, 24 bit
+ */
+ if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) {
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ }
+ /*
+ * The speaker on the SSP0 supports S16_LE and not S24_LE.
+ * thus changing the mask here
+ */
+ if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec"))
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */
+ rt5663_sel_asrc_clk_src(codec_dai->component,
+ RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER,
+ RT5663_CLK_SEL_I2S1_ASRC);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT5663_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_ops kabylake_rt5663_ops = {
+ .hw_params = kabylake_rt5663_hw_params,
+};
+
+static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int ret = 0, j;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) {
+ /*
+ * Use channel 4 and 5 for the first amp
+ */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV1_NAME)) {
+ /*
+ * Use channel 6 and 7 for the second amp
+ */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+static struct snd_soc_ops kabylake_ssp0_ops = {
+ .hw_params = kabylake_ssp0_hw_params,
+};
+
+static unsigned int channels_dmic[] = {
+ 2, 4,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static const unsigned int dmic_2ch[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = {
+ .count = ARRAY_SIZE(dmic_2ch),
+ .list = dmic_2ch,
+ .mask = 0,
+};
+
+static int kabylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = DMIC_CH(dmic_constraints);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ dmic_constraints);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static struct snd_soc_ops kabylake_dmic_ops = {
+ .startup = kabylake_dmic_startup,
+};
+
+static unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static const unsigned int ch_mono[] = {
+ 1,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_refcap = {
+ .count = ARRAY_SIZE(ch_mono),
+ .list = ch_mono,
+};
+
+static int kabylake_refcap_startup(struct snd_pcm_substream *substream)
+{
+ substream->runtime->hw.channels_max = 1;
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_refcap);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+}
+
+static struct snd_soc_ops skylake_refcap_ops = {
+ .startup = kabylake_refcap_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(system2,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin2")));
+
+SND_SOC_DAILINK_DEF(echoref,
+ DAILINK_COMP_ARRAY(COMP_CPU("Echoref Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC(MAXIM_DEV0_NAME, KBL_MAXIM_CODEC_DAI),
+ /* Right */ COMP_CODEC(MAXIM_DEV1_NAME, KBL_MAXIM_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5663:00",
+ KBL_REALTEK_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic01_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* kabylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link kabylake_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = kabylake_rt5663_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HS_PB] = {
+ .name = "Kbl Audio Headset Playback",
+ .stream_name = "Headset Audio",
+ .dpcm_playback = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(system2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_ECHO_REF_CP] = {
+ .name = "Kbl Audio Echo Reference cap",
+ .stream_name = "Echoreference Capture",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ SND_SOC_DAILINK_REG(echoref, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_REF_CP] = {
+ .name = "Kbl Audio Reference cap",
+ .stream_name = "Wake on Voice",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Kbl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &kabylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Kbl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .dpcm_playback = 1,
+ .ops = &kabylake_ssp0_ops,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = kabylake_rt5663_max98927_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .ops = &kabylake_rt5663_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .be_hw_params_fixup = kabylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic01_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = kabylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+static struct snd_soc_dai_link kabylake_5663_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_5663_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_5663_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_5663_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_5663_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = kabylake_rt5663_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .ops = &kabylake_rt5663_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 1,
+ .dpcm_playback = 1,
+ .init = kabylake_5663_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 2,
+ .init = kabylake_5663_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int kabylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(card);
+ struct kbl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* kabylake audio machine driver for SPT + RT5663 */
+static struct snd_soc_card kabylake_audio_card_rt5663_m98927 = {
+ .name = "kblrt5663max",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_dais,
+ .num_links = ARRAY_SIZE(kabylake_dais),
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98927_codec_conf,
+ .num_configs = ARRAY_SIZE(max98927_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+/* kabylake audio machine driver for RT5663 */
+static struct snd_soc_card kabylake_audio_card_rt5663 = {
+ .name = "kblrt5663",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_5663_dais,
+ .num_links = ARRAY_SIZE(kabylake_5663_dais),
+ .controls = kabylake_5663_controls,
+ .num_controls = ARRAY_SIZE(kabylake_5663_controls),
+ .dapm_widgets = kabylake_5663_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_5663_widgets),
+ .dapm_routes = kabylake_5663_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_5663_map),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static int kabylake_audio_probe(struct platform_device *pdev)
+{
+ struct kbl_rt5663_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ kabylake_audio_card =
+ (struct snd_soc_card *)pdev->id_entry->driver_data;
+
+ kabylake_audio_card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(kabylake_audio_card, ctx);
+
+ mach = pdev->dev.platform_data;
+ if (mach)
+ dmic_constraints = mach->mach_params.dmic_num == 2 ?
+ &constraints_dmic_2ch : &constraints_dmic_channels;
+
+ ctx->mclk = devm_clk_get(&pdev->dev, "ssp1_mclk");
+ if (IS_ERR(ctx->mclk)) {
+ ret = PTR_ERR(ctx->mclk);
+ if (ret == -ENOENT) {
+ dev_info(&pdev->dev,
+ "Failed to get ssp1_sclk, defer probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_err(&pdev->dev, "Failed to get ssp1_mclk with err:%d\n",
+ ret);
+ return ret;
+ }
+
+ ctx->sclk = devm_clk_get(&pdev->dev, "ssp1_sclk");
+ if (IS_ERR(ctx->sclk)) {
+ ret = PTR_ERR(ctx->sclk);
+ if (ret == -ENOENT) {
+ dev_info(&pdev->dev,
+ "Failed to get ssp1_sclk, defer probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_err(&pdev->dev, "Failed to get ssp1_sclk with err:%d\n",
+ ret);
+ return ret;
+ }
+
+ return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card);
+}
+
+static const struct platform_device_id kbl_board_ids[] = {
+ {
+ .name = "kbl_rt5663",
+ .driver_data = (kernel_ulong_t)&kabylake_audio_card_rt5663,
+ },
+ {
+ .name = "kbl_rt5663_m98927",
+ .driver_data =
+ (kernel_ulong_t)&kabylake_audio_card_rt5663_m98927,
+ },
+ { }
+};
+
+static struct platform_driver kabylake_audio = {
+ .probe = kabylake_audio_probe,
+ .driver = {
+ .name = "kbl_rt5663_m98927",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = kbl_board_ids,
+};
+
+module_platform_driver(kabylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-RT5663 & MAX98927 in I2S mode");
+MODULE_AUTHOR("Naveen M <naveen.m@intel.com>");
+MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kbl_rt5663");
+MODULE_ALIAS("platform:kbl_rt5663_m98927");
diff --git a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c
new file mode 100644
index 000000000..f95546c18
--- /dev/null
+++ b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Kabylake I2S Machine Driver with MAXIM98927
+ * RT5514 and RT5663 Codecs
+ *
+ * Copyright (C) 2017, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Kabylake I2S Machine driver supporting MAXIM98927 and
+ * RT5663 codecs
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt5514.h"
+#include "../../codecs/rt5663.h"
+#include "../../codecs/hdac_hdmi.h"
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+
+#define KBL_REALTEK_CODEC_DAI "rt5663-aif"
+#define KBL_REALTEK_DMIC_CODEC_DAI "rt5514-aif1"
+#define KBL_MAXIM_CODEC_DAI "max98927-aif1"
+#define MAXIM_DEV0_NAME "i2c-MX98927:00"
+#define MAXIM_DEV1_NAME "i2c-MX98927:01"
+#define RT5514_DEV_NAME "i2c-10EC5514:00"
+#define RT5663_DEV_NAME "i2c-10EC5663:00"
+#define RT5514_AIF1_BCLK_FREQ (48000 * 8 * 16)
+#define RT5514_AIF1_SYSCLK_FREQ 12288000
+#define NAME_SIZE 32
+
+#define DMIC_CH(p) p->list[p->count-1]
+
+
+static struct snd_soc_card kabylake_audio_card;
+static const struct snd_pcm_hw_constraint_list *dmic_constraints;
+
+struct kbl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct kbl_codec_private {
+ struct snd_soc_jack kabylake_headset;
+ struct list_head hdmi_pcm_list;
+ struct snd_soc_jack kabylake_hdmi[2];
+ struct clk *mclk;
+ struct clk *sclk;
+};
+
+enum {
+ KBL_DPCM_AUDIO_PB = 0,
+ KBL_DPCM_AUDIO_CP,
+ KBL_DPCM_AUDIO_HS_PB,
+ KBL_DPCM_AUDIO_ECHO_REF_CP,
+ KBL_DPCM_AUDIO_DMIC_CP,
+ KBL_DPCM_AUDIO_RT5514_DSP,
+ KBL_DPCM_AUDIO_HDMI1_PB,
+ KBL_DPCM_AUDIO_HDMI2_PB,
+};
+
+static const struct snd_kcontrol_new kabylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+ SOC_DAPM_PIN_SWITCH("DMIC"),
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct kbl_codec_private *priv = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ /*
+ * MCLK/SCLK need to be ON early for a successful synchronization of
+ * codec internal clock. And the clocks are turned off during
+ * POST_PMD after the stream is stopped.
+ */
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Enable MCLK */
+ ret = clk_set_rate(priv->mclk, 24000000);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't set rate for mclk, err: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't enable mclk, err: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable SCLK */
+ ret = clk_set_rate(priv->sclk, 3072000);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't set rate for sclk, err: %d\n",
+ ret);
+ clk_disable_unprepare(priv->mclk);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->sclk);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't enable sclk, err: %d\n", ret);
+ clk_disable_unprepare(priv->mclk);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ clk_disable_unprepare(priv->mclk);
+ clk_disable_unprepare(priv->sclk);
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget kabylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+
+};
+
+static const struct snd_soc_dapm_route kabylake_map[] = {
+ /* Headphones */
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* speaker */
+ { "Left Spk", NULL, "Left BE_OUT" },
+ { "Right Spk", NULL, "Right BE_OUT" },
+
+ /* other jacks */
+ { "Headset Mic", NULL, "Platform Clock" },
+ { "IN1P", NULL, "Headset Mic" },
+ { "IN1N", NULL, "Headset Mic" },
+
+ /* CODEC BE connections */
+ { "Left HiFi Playback", NULL, "ssp0 Tx" },
+ { "Right HiFi Playback", NULL, "ssp0 Tx" },
+ { "ssp0 Tx", NULL, "spk_out" },
+
+ { "AIF Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "hs_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "AIF Capture" },
+
+ { "codec1_in", NULL, "ssp0 Rx" },
+ { "ssp0 Rx", NULL, "AIF1 Capture" },
+
+ /* IV feedback path */
+ { "codec0_fb_in", NULL, "ssp0 Rx"},
+ { "ssp0 Rx", NULL, "Left HiFi Capture" },
+ { "ssp0 Rx", NULL, "Right HiFi Capture" },
+
+ /* DMIC */
+ { "DMIC1L", NULL, "DMIC" },
+ { "DMIC1R", NULL, "DMIC" },
+ { "DMIC2L", NULL, "DMIC" },
+ { "DMIC2R", NULL, "DMIC" },
+
+ { "hifi2", NULL, "iDisp2 Tx" },
+ { "iDisp2 Tx", NULL, "iDisp2_out" },
+ { "hifi1", NULL, "iDisp1 Tx" },
+ { "iDisp1 Tx", NULL, "iDisp1_out" },
+};
+
+static struct snd_soc_codec_conf max98927_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+
+static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+ int ret;
+
+ dapm = snd_soc_component_get_dapm(component);
+ ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+ if (ret)
+ dev_err(rtd->dev, "Ref Cap -Ignore suspend failed = %d\n", ret);
+
+ return ret;
+}
+
+static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_jack *jack;
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(&kabylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset,
+ NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->kabylake_headset;
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ snd_soc_component_set_jack(component, &ctx->kabylake_headset, NULL);
+
+ ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "DMIC");
+ if (ret)
+ dev_err(rtd->dev, "DMIC - Ignore suspend failed = %d\n", ret);
+
+ return ret;
+}
+
+static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct kbl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = device;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB);
+}
+
+static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB);
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int kbl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops kabylake_rt5663_fe_ops = {
+ .startup = kbl_fe_startup,
+};
+
+static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_soc_dpcm *dpcm, *rtd_dpcm = NULL;
+
+ /*
+ * The following loop will be called only for playback stream
+ * In this platform, there is only one playback device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ /*
+ * This following loop will be called only for capture stream
+ * In this platform, there is only one capture device on every SSP
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) {
+ rtd_dpcm = dpcm;
+ break;
+ }
+
+ if (!rtd_dpcm)
+ return -EINVAL;
+
+ /*
+ * The above 2 loops are mutually exclusive based on the stream direction,
+ * thus rtd_dpcm variable will never be overwritten
+ */
+
+ /*
+ * The ADSP will convert the FE rate to 48k, stereo, 24 bit
+ */
+ if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Port") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Headset Playback") ||
+ !strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio Capture Port")) {
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ } else if (!strcmp(rtd_dpcm->fe->dai_link->name, "Kbl Audio DMIC cap")) {
+ if (params_channels(params) == 2 ||
+ DMIC_CH(dmic_constraints) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+ }
+ /*
+ * The speaker on the SSP0 supports S16_LE and not S24_LE.
+ * thus changing the mask here
+ */
+ if (!strcmp(rtd_dpcm->be->dai_link->name, "SSP0-Codec"))
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */
+ rt5663_sel_asrc_clk_src(codec_dai->component,
+ RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER,
+ RT5663_CLK_SEL_I2S1_ASRC);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT5663_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_ops kabylake_rt5663_ops = {
+ .hw_params = kabylake_rt5663_hw_params,
+};
+
+static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int ret = 0, j;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ if (!strcmp(codec_dai->component->name, RT5514_DEV_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0, 8, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ RT5514_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "set sysclk err: %d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "DEV0 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV1_NAME)) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16);
+ if (ret < 0) {
+ dev_err(rtd->dev, "DEV1 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+static struct snd_soc_ops kabylake_ssp0_ops = {
+ .hw_params = kabylake_ssp0_hw_params,
+};
+
+static const unsigned int channels_dmic[] = {
+ 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static const unsigned int dmic_2ch[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = {
+ .count = ARRAY_SIZE(dmic_2ch),
+ .list = dmic_2ch,
+ .mask = 0,
+};
+
+static int kabylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = DMIC_CH(dmic_constraints);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ dmic_constraints);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static struct snd_soc_ops kabylake_dmic_ops = {
+ .startup = kabylake_dmic_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(system2,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin2")));
+
+SND_SOC_DAILINK_DEF(echoref,
+ DAILINK_COMP_ARRAY(COMP_CPU("Echoref Pin")));
+
+SND_SOC_DAILINK_DEF(spi_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("spi-PRP0001:00")));
+SND_SOC_DAILINK_DEF(spi_platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("spi-PRP0001:00")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC(MAXIM_DEV0_NAME, KBL_MAXIM_CODEC_DAI),
+ /* Right */COMP_CODEC(MAXIM_DEV1_NAME, KBL_MAXIM_CODEC_DAI),
+ /* dmic */ COMP_CODEC(RT5514_DEV_NAME, KBL_REALTEK_DMIC_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC(RT5663_DEV_NAME, KBL_REALTEK_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* kabylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link kabylake_dais[] = {
+ /* Front End DAI links */
+ [KBL_DPCM_AUDIO_PB] = {
+ .name = "Kbl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = kabylake_rt5663_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_CP] = {
+ .name = "Kbl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &kabylake_rt5663_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HS_PB] = {
+ .name = "Kbl Audio Headset Playback",
+ .stream_name = "Headset Audio",
+ .dpcm_playback = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(system2, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_ECHO_REF_CP] = {
+ .name = "Kbl Audio Echo Reference cap",
+ .stream_name = "Echoreference Capture",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ SND_SOC_DAILINK_REG(echoref, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_RT5514_DSP] = {
+ .name = "rt5514 dsp",
+ .stream_name = "Wake on Voice",
+ SND_SOC_DAILINK_REG(spi_cpu, dummy, spi_platform),
+ },
+ [KBL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Kbl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &kabylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Kbl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [KBL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Kbl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ /* Back End DAI links */
+ /* single Back end dai for both max speakers and dmic */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &kabylake_ssp0_ops,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = kabylake_rt5663_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = kabylake_ssp_fixup,
+ .ops = &kabylake_rt5663_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = kabylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = kabylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+};
+
+static int kabylake_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+ struct snd_soc_component *component = dapm->component;
+ struct kbl_codec_private *priv = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ if (!component || strcmp(component->name, RT5514_DEV_NAME))
+ return 0;
+
+ if (IS_ERR(priv->mclk))
+ return 0;
+
+ /*
+ * It's required to control mclk directly in the set_bias_level
+ * function for rt5514 codec or the recording function could
+ * break.
+ */
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level == SND_SOC_BIAS_ON) {
+ if (!__clk_is_enabled(priv->mclk))
+ return 0;
+ dev_dbg(card->dev, "Disable mclk");
+ clk_disable_unprepare(priv->mclk);
+ } else {
+ dev_dbg(card->dev, "Enable mclk");
+ ret = clk_set_rate(priv->mclk, 24000000);
+ if (ret) {
+ dev_err(card->dev, "Can't set rate for mclk, err: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret) {
+ dev_err(card->dev, "Can't enable mclk, err: %d\n",
+ ret);
+
+ /* mclk is already enabled in FW */
+ ret = 0;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int kabylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card);
+ struct kbl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP,pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &ctx->kabylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &ctx->kabylake_hdmi[i]);
+ if (err < 0)
+ return err;
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/*
+ * kabylake audio machine driver for MAX98927 + RT5514 + RT5663
+ */
+static struct snd_soc_card kabylake_audio_card = {
+ .name = "kbl-r5514-5663-max",
+ .owner = THIS_MODULE,
+ .dai_link = kabylake_dais,
+ .num_links = ARRAY_SIZE(kabylake_dais),
+ .set_bias_level = kabylake_set_bias_level,
+ .controls = kabylake_controls,
+ .num_controls = ARRAY_SIZE(kabylake_controls),
+ .dapm_widgets = kabylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets),
+ .dapm_routes = kabylake_map,
+ .num_dapm_routes = ARRAY_SIZE(kabylake_map),
+ .codec_conf = max98927_codec_conf,
+ .num_configs = ARRAY_SIZE(max98927_codec_conf),
+ .fully_routed = true,
+ .late_probe = kabylake_card_late_probe,
+};
+
+static int kabylake_audio_probe(struct platform_device *pdev)
+{
+ struct kbl_codec_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ kabylake_audio_card.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&kabylake_audio_card, ctx);
+
+ mach = pdev->dev.platform_data;
+ if (mach)
+ dmic_constraints = mach->mach_params.dmic_num == 2 ?
+ &constraints_dmic_2ch : &constraints_dmic_channels;
+
+ ctx->mclk = devm_clk_get(&pdev->dev, "ssp1_mclk");
+ if (IS_ERR(ctx->mclk)) {
+ ret = PTR_ERR(ctx->mclk);
+ if (ret == -ENOENT) {
+ dev_info(&pdev->dev,
+ "Failed to get ssp1_mclk, defer probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_err(&pdev->dev, "Failed to get ssp1_mclk with err:%d\n",
+ ret);
+ return ret;
+ }
+
+ ctx->sclk = devm_clk_get(&pdev->dev, "ssp1_sclk");
+ if (IS_ERR(ctx->sclk)) {
+ ret = PTR_ERR(ctx->sclk);
+ if (ret == -ENOENT) {
+ dev_info(&pdev->dev,
+ "Failed to get ssp1_sclk, defer probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_err(&pdev->dev, "Failed to get ssp1_sclk with err:%d\n",
+ ret);
+ return ret;
+ }
+
+ return devm_snd_soc_register_card(&pdev->dev, &kabylake_audio_card);
+}
+
+static const struct platform_device_id kbl_board_ids[] = {
+ { .name = "kbl_r5514_5663_max" },
+ { }
+};
+
+static struct platform_driver kabylake_audio = {
+ .probe = kabylake_audio_probe,
+ .driver = {
+ .name = "kbl_r5514_5663_max",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = kbl_board_ids,
+};
+
+module_platform_driver(kabylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-RT5663 RT5514 & MAX98927");
+MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kbl_r5514_5663_max");
diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.c b/sound/soc/intel/boards/skl_hda_dsp_common.c
new file mode 100644
index 000000000..07bfb2e64
--- /dev/null
+++ b/sound/soc/intel/boards/skl_hda_dsp_common.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-18 Intel Corporation.
+
+/*
+ * Common functions used in different Intel machine drivers
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../codecs/hdac_hdmi.h"
+#include "skl_hda_dsp_common.h"
+
+#include <sound/hda_codec.h>
+#include "../../codecs/hdac_hda.h"
+
+#define NAME_SIZE 32
+
+int skl_hda_hdmi_add_pcm(struct snd_soc_card *card, int device)
+{
+ struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card);
+ struct skl_hda_hdmi_pcm *pcm;
+ char dai_name[NAME_SIZE];
+
+ pcm = devm_kzalloc(card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ snprintf(dai_name, sizeof(dai_name), "intel-hdmi-hifi%d",
+ ctx->dai_index);
+ pcm->codec_dai = snd_soc_card_get_codec_dai(card, dai_name);
+ if (!pcm->codec_dai)
+ return -EINVAL;
+
+ pcm->device = device;
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEF(idisp1_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(analog_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("Analog CPU DAI")));
+SND_SOC_DAILINK_DEF(analog_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D0", "Analog Codec DAI")));
+
+SND_SOC_DAILINK_DEF(digital_cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("Digital CPU DAI")));
+SND_SOC_DAILINK_DEF(digital_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D0", "Digital Codec DAI")));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(dmic16k,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* skl_hda_digital audio interface glue - connects codec <--> CPU */
+struct snd_soc_dai_link skl_hda_be_dai_links[HDA_DSP_MAX_BE_DAI_LINKS] = {
+ /* Back End DAI links */
+ {
+ .name = "iDisp1",
+ .id = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_cpu, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 2,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_cpu, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 3,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_cpu, idisp3_codec, platform),
+ },
+ {
+ .name = "Analog Playback and Capture",
+ .id = 4,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(analog_cpu, analog_codec, platform),
+ },
+ {
+ .name = "Digital Playback and Capture",
+ .id = 5,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(digital_cpu, digital_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 6,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 7,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k, dmic_codec, platform),
+ },
+};
+
+int skl_hda_hdmi_jack_init(struct snd_soc_card *card)
+{
+ struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = NULL;
+ struct skl_hda_hdmi_pcm *pcm;
+ char jack_name[NAME_SIZE];
+ int err;
+
+ if (ctx->common_hdmi_codec_drv)
+ return skl_hda_hdmi_build_controls(card);
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &pcm->hdmi_jack,
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = snd_jack_add_new_kctl(pcm->hdmi_jack.jack,
+ jack_name, SND_JACK_AVOUT);
+ if (err)
+ dev_warn(component->dev, "failed creating Jack kctl\n");
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &pcm->hdmi_jack);
+ if (err < 0)
+ return err;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.h b/sound/soc/intel/boards/skl_hda_dsp_common.h
new file mode 100644
index 000000000..4b0b39591
--- /dev/null
+++ b/sound/soc/intel/boards/skl_hda_dsp_common.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2015-18 Intel Corporation.
+ */
+
+/*
+ * This file defines data structures used in Machine Driver for Intel
+ * platforms with HDA Codecs.
+ */
+
+#ifndef __SKL_HDA_DSP_COMMON_H
+#define __SKL_HDA_DSP_COMMON_H
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/hda_codec.h>
+#include "../../codecs/hdac_hda.h"
+#include "hda_dsp_common.h"
+
+#define HDA_DSP_MAX_BE_DAI_LINKS 7
+
+struct skl_hda_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_jack hdmi_jack;
+ int device;
+};
+
+struct skl_hda_private {
+ struct list_head hdmi_pcm_list;
+ int pcm_count;
+ int dai_index;
+ const char *platform_name;
+ bool common_hdmi_codec_drv;
+ bool idisp_codec;
+};
+
+extern struct snd_soc_dai_link skl_hda_be_dai_links[HDA_DSP_MAX_BE_DAI_LINKS];
+int skl_hda_hdmi_jack_init(struct snd_soc_card *card);
+int skl_hda_hdmi_add_pcm(struct snd_soc_card *card, int device);
+
+/*
+ * Search card topology and register HDMI PCM related controls
+ * to codec driver.
+ */
+static inline int skl_hda_hdmi_build_controls(struct snd_soc_card *card)
+{
+ struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+ struct skl_hda_hdmi_pcm *pcm;
+
+ /* HDMI disabled, do not create controls */
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return 0;
+
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct skl_hda_hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ if (!component)
+ return -EINVAL;
+
+ return hda_dsp_hdmi_build_controls(card, component);
+}
+
+#endif /* __SOUND_SOC_HDA_DSP_COMMON_H */
diff --git a/sound/soc/intel/boards/skl_hda_dsp_generic.c b/sound/soc/intel/boards/skl_hda_dsp_generic.c
new file mode 100644
index 000000000..bc50eda29
--- /dev/null
+++ b/sound/soc/intel/boards/skl_hda_dsp_generic.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-18 Intel Corporation.
+
+/*
+ * Machine Driver for SKL+ platforms with DSP and iDisp, HDA Codecs
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/hdac_hdmi.h"
+#include "skl_hda_dsp_common.h"
+
+static const struct snd_soc_dapm_widget skl_hda_widgets[] = {
+ SND_SOC_DAPM_HP("Analog Out", NULL),
+ SND_SOC_DAPM_MIC("Analog In", NULL),
+ SND_SOC_DAPM_HP("Alt Analog Out", NULL),
+ SND_SOC_DAPM_MIC("Alt Analog In", NULL),
+ SND_SOC_DAPM_SPK("Digital Out", NULL),
+ SND_SOC_DAPM_MIC("Digital In", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route skl_hda_map[] = {
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+
+ { "Analog Out", NULL, "Codec Output Pin1" },
+ { "Digital Out", NULL, "Codec Output Pin2" },
+ { "Alt Analog Out", NULL, "Codec Output Pin3" },
+
+ { "Codec Input Pin1", NULL, "Analog In" },
+ { "Codec Input Pin2", NULL, "Digital In" },
+ { "Codec Input Pin3", NULL, "Alt Analog In" },
+
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+
+ /* CODEC BE connections */
+ { "Analog Codec Playback", NULL, "Analog CPU Playback" },
+ { "Analog CPU Playback", NULL, "codec0_out" },
+ { "Digital Codec Playback", NULL, "Digital CPU Playback" },
+ { "Digital CPU Playback", NULL, "codec1_out" },
+ { "Alt Analog Codec Playback", NULL, "Alt Analog CPU Playback" },
+ { "Alt Analog CPU Playback", NULL, "codec2_out" },
+
+ { "codec0_in", NULL, "Analog CPU Capture" },
+ { "Analog CPU Capture", NULL, "Analog Codec Capture" },
+ { "codec1_in", NULL, "Digital CPU Capture" },
+ { "Digital CPU Capture", NULL, "Digital Codec Capture" },
+ { "codec2_in", NULL, "Alt Analog CPU Capture" },
+ { "Alt Analog CPU Capture", NULL, "Alt Analog Codec Capture" },
+};
+
+SND_SOC_DAILINK_DEF(dummy_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")));
+
+static int skl_hda_card_late_probe(struct snd_soc_card *card)
+{
+ return skl_hda_hdmi_jack_init(card);
+}
+
+static int
+skl_hda_add_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *link)
+{
+ struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ dev_dbg(card->dev, "%s: dai link name - %s\n", __func__, link->name);
+ link->platforms->name = ctx->platform_name;
+ link->nonatomic = 1;
+
+ if (!ctx->idisp_codec)
+ return 0;
+
+ if (strstr(link->name, "HDMI")) {
+ ret = skl_hda_hdmi_add_pcm(card, ctx->pcm_count);
+
+ if (ret < 0)
+ return ret;
+
+ ctx->dai_index++;
+ }
+
+ ctx->pcm_count++;
+ return ret;
+}
+
+static struct snd_soc_card hda_soc_card = {
+ .name = "hda-dsp",
+ .owner = THIS_MODULE,
+ .dai_link = skl_hda_be_dai_links,
+ .dapm_widgets = skl_hda_widgets,
+ .dapm_routes = skl_hda_map,
+ .add_dai_link = skl_hda_add_dai_link,
+ .fully_routed = true,
+ .late_probe = skl_hda_card_late_probe,
+};
+
+static char hda_soc_components[30];
+
+#define IDISP_DAI_COUNT 3
+#define HDAC_DAI_COUNT 2
+#define DMIC_DAI_COUNT 2
+
+/* there are two routes per iDisp output */
+#define IDISP_ROUTE_COUNT (IDISP_DAI_COUNT * 2)
+#define IDISP_CODEC_MASK 0x4
+
+#define HDA_CODEC_AUTOSUSPEND_DELAY_MS 1000
+
+static int skl_hda_fill_card_info(struct snd_soc_acpi_mach_params *mach_params)
+{
+ struct snd_soc_card *card = &hda_soc_card;
+ struct skl_hda_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai_link *dai_link;
+ u32 codec_count, codec_mask;
+ int i, num_links, num_route;
+
+ codec_mask = mach_params->codec_mask;
+ codec_count = hweight_long(codec_mask);
+ ctx->idisp_codec = !!(codec_mask & IDISP_CODEC_MASK);
+
+ if (!codec_count || codec_count > 2 ||
+ (codec_count == 2 && !ctx->idisp_codec))
+ return -EINVAL;
+
+ if (codec_mask == IDISP_CODEC_MASK) {
+ /* topology with iDisp as the only HDA codec */
+ num_links = IDISP_DAI_COUNT + DMIC_DAI_COUNT;
+ num_route = IDISP_ROUTE_COUNT;
+
+ /*
+ * rearrange the dai link array and make the
+ * dmic dai links follow idsp dai links for only
+ * num_links of dai links need to be registered
+ * to ASoC.
+ */
+ for (i = 0; i < DMIC_DAI_COUNT; i++) {
+ skl_hda_be_dai_links[IDISP_DAI_COUNT + i] =
+ skl_hda_be_dai_links[IDISP_DAI_COUNT +
+ HDAC_DAI_COUNT + i];
+ }
+ } else {
+ /* topology with external and iDisp HDA codecs */
+ num_links = ARRAY_SIZE(skl_hda_be_dai_links);
+ num_route = ARRAY_SIZE(skl_hda_map);
+ card->dapm_widgets = skl_hda_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(skl_hda_widgets);
+ if (!ctx->idisp_codec) {
+ for (i = 0; i < IDISP_DAI_COUNT; i++) {
+ skl_hda_be_dai_links[i].codecs = dummy_codec;
+ skl_hda_be_dai_links[i].num_codecs =
+ ARRAY_SIZE(dummy_codec);
+ }
+ }
+ }
+
+ card->num_links = num_links;
+ card->num_dapm_routes = num_route;
+
+ for_each_card_prelinks(card, i, dai_link)
+ dai_link->platforms->name = mach_params->platform;
+
+ return 0;
+}
+
+static void skl_set_hda_codec_autosuspend_delay(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct hdac_hda_priv *hda_pvt;
+ struct snd_soc_dai *dai;
+
+ for_each_card_rtds(card, rtd) {
+ if (!strstr(rtd->dai_link->codecs->name, "ehdaudio0D0"))
+ continue;
+ dai = asoc_rtd_to_codec(rtd, 0);
+ hda_pvt = snd_soc_component_get_drvdata(dai->component);
+ if (hda_pvt) {
+ /*
+ * all codecs are on the same bus, so it's sufficient
+ * to look up only the first one
+ */
+ snd_hda_set_power_save(hda_pvt->codec.bus,
+ HDA_CODEC_AUTOSUSPEND_DELAY_MS);
+ break;
+ }
+ }
+}
+
+static int skl_hda_audio_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ struct skl_hda_private *ctx;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s: entry\n", __func__);
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ mach = pdev->dev.platform_data;
+ if (!mach)
+ return -EINVAL;
+
+ snd_soc_card_set_drvdata(&hda_soc_card, ctx);
+
+ ret = skl_hda_fill_card_info(&mach->mach_params);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Unsupported HDAudio/iDisp configuration found\n");
+ return ret;
+ }
+
+ ctx->pcm_count = hda_soc_card.num_links;
+ ctx->dai_index = 1; /* hdmi codec dai name starts from index 1 */
+ ctx->platform_name = mach->mach_params.platform;
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ hda_soc_card.dev = &pdev->dev;
+
+ if (mach->mach_params.dmic_num > 0) {
+ snprintf(hda_soc_components, sizeof(hda_soc_components),
+ "cfg-dmics:%d", mach->mach_params.dmic_num);
+ hda_soc_card.components = hda_soc_components;
+ }
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &hda_soc_card);
+ if (!ret)
+ skl_set_hda_codec_autosuspend_delay(&hda_soc_card);
+
+ return ret;
+}
+
+static struct platform_driver skl_hda_audio = {
+ .probe = skl_hda_audio_probe,
+ .driver = {
+ .name = "skl_hda_dsp_generic",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+
+module_platform_driver(skl_hda_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("SKL/KBL/BXT/APL HDA Generic Machine driver");
+MODULE_AUTHOR("Rakesh Ughreja <rakesh.a.ughreja@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl_hda_dsp_generic");
diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c
new file mode 100644
index 000000000..558029000
--- /dev/null
+++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c
@@ -0,0 +1,693 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Skylake I2S Machine Driver with MAXIM98357A
+ * and NAU88L25
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/nau8825.h"
+#include "../../codecs/hdac_hdmi.h"
+
+#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi"
+#define SKL_MAXIM_CODEC_DAI "HiFi"
+#define DMIC_CH(p) p->list[p->count-1]
+
+static struct snd_soc_jack skylake_headset;
+static struct snd_soc_card skylake_audio_card;
+static const struct snd_pcm_hw_constraint_list *dmic_constraints;
+static struct snd_soc_jack skylake_hdmi[3];
+
+struct skl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct skl_nau8825_private {
+ struct list_head hdmi_pcm_list;
+};
+
+enum {
+ SKL_DPCM_AUDIO_PB = 0,
+ SKL_DPCM_AUDIO_CP,
+ SKL_DPCM_AUDIO_REF_CP,
+ SKL_DPCM_AUDIO_DMIC_CP,
+ SKL_DPCM_AUDIO_HDMI1_PB,
+ SKL_DPCM_AUDIO_HDMI2_PB,
+ SKL_DPCM_AUDIO_HDMI3_PB,
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "set sysclk err = %d\n", ret);
+ return -EIO;
+ }
+ } else {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "set sysclk err = %d\n", ret);
+ return -EIO;
+ }
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new skylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Spk"),
+};
+
+static const struct snd_soc_dapm_widget skylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Spk", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("DP1", NULL),
+ SND_SOC_DAPM_SPK("DP2", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route skylake_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* speaker */
+ { "Spk", NULL, "Speaker" },
+
+ /* other jacks */
+ { "MIC", NULL, "Headset Mic" },
+ { "DMic", NULL, "SoC DMIC" },
+
+ /* CODEC BE connections */
+ { "HiFi Playback", NULL, "ssp0 Tx" },
+ { "ssp0 Tx", NULL, "codec0_out" },
+
+ { "Playback", NULL, "ssp1 Tx" },
+ { "ssp1 Tx", NULL, "codec1_out" },
+
+ { "codec0_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "Capture" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+};
+
+static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP0 to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+
+ return 0;
+}
+
+static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset,
+ NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ nau8825_enable_jack_detect(component, &skylake_headset);
+
+ snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+
+ return ret;
+}
+
+static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI1_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI2_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI3_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int skl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops skylake_nau8825_fe_ops = {
+ .startup = skl_fe_startup,
+};
+
+static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_ops skylake_nau8825_ops = {
+ .hw_params = skylake_nau8825_hw_params,
+};
+
+static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static const unsigned int channels_dmic[] = {
+ 2, 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static const unsigned int dmic_2ch[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = {
+ .count = ARRAY_SIZE(dmic_2ch),
+ .list = dmic_2ch,
+ .mask = 0,
+};
+
+static int skylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = DMIC_CH(dmic_constraints);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ dmic_constraints);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops skylake_dmic_ops = {
+ .startup = skylake_dmic_startup,
+};
+
+static const unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static const unsigned int ch_mono[] = {
+ 1,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_refcap = {
+ .count = ARRAY_SIZE(ch_mono),
+ .list = ch_mono,
+};
+
+static int skylake_refcap_startup(struct snd_pcm_substream *substream)
+{
+ substream->runtime->hw.channels_max = 1;
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_refcap);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+}
+
+static const struct snd_soc_ops skylake_refcap_ops = {
+ .startup = skylake_refcap_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", SKL_MAXIM_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10508825:00",
+ SKL_NUVOTON_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* skylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link skylake_dais[] = {
+ /* Front End DAI links */
+ [SKL_DPCM_AUDIO_PB] = {
+ .name = "Skl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = skylake_nau8825_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &skylake_nau8825_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_CP] = {
+ .name = "Skl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &skylake_nau8825_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_REF_CP] = {
+ .name = "Skl Audio Reference cap",
+ .stream_name = "Wake on Voice",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Skl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Skl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Skl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Skl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = skylake_ssp_fixup,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = skylake_nau8825_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = skylake_ssp_fixup,
+ .ops = &skylake_nau8825_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .be_hw_params_fixup = skylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = skylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = skylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = skylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(card);
+ struct skl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT,
+ &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* skylake audio machine driver for SPT + NAU88L25 */
+static struct snd_soc_card skylake_audio_card = {
+ .name = "sklnau8825max",
+ .owner = THIS_MODULE,
+ .dai_link = skylake_dais,
+ .num_links = ARRAY_SIZE(skylake_dais),
+ .controls = skylake_controls,
+ .num_controls = ARRAY_SIZE(skylake_controls),
+ .dapm_widgets = skylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(skylake_widgets),
+ .dapm_routes = skylake_map,
+ .num_dapm_routes = ARRAY_SIZE(skylake_map),
+ .fully_routed = true,
+ .late_probe = skylake_card_late_probe,
+};
+
+static int skylake_audio_probe(struct platform_device *pdev)
+{
+ struct skl_nau8825_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ skylake_audio_card.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&skylake_audio_card, ctx);
+
+ mach = pdev->dev.platform_data;
+ if (mach)
+ dmic_constraints = mach->mach_params.dmic_num == 2 ?
+ &constraints_dmic_2ch : &constraints_dmic_channels;
+
+ return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
+}
+
+static const struct platform_device_id skl_board_ids[] = {
+ { .name = "skl_n88l25_m98357a" },
+ { .name = "kbl_n88l25_m98357a" },
+ { }
+};
+
+static struct platform_driver skylake_audio = {
+ .probe = skylake_audio_probe,
+ .driver = {
+ .name = "skl_n88l25_m98357a",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = skl_board_ids,
+};
+
+module_platform_driver(skylake_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode");
+MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl_n88l25_m98357a");
+MODULE_ALIAS("platform:kbl_n88l25_m98357a");
diff --git a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c
new file mode 100644
index 000000000..0c734f3a9
--- /dev/null
+++ b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c
@@ -0,0 +1,741 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Skylake I2S Machine Driver for NAU88L25+SSM4567
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Skylake I2S Machine Driver for NAU88L25 and SSM4567
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include "../../codecs/nau8825.h"
+#include "../../codecs/hdac_hdmi.h"
+
+#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi"
+#define SKL_SSM_CODEC_DAI "ssm4567-hifi"
+#define DMIC_CH(p) p->list[p->count-1]
+
+static struct snd_soc_jack skylake_headset;
+static struct snd_soc_card skylake_audio_card;
+static const struct snd_pcm_hw_constraint_list *dmic_constraints;
+static struct snd_soc_jack skylake_hdmi[3];
+
+struct skl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct skl_nau88125_private {
+ struct list_head hdmi_pcm_list;
+};
+enum {
+ SKL_DPCM_AUDIO_PB = 0,
+ SKL_DPCM_AUDIO_CP,
+ SKL_DPCM_AUDIO_REF_CP,
+ SKL_DPCM_AUDIO_DMIC_CP,
+ SKL_DPCM_AUDIO_HDMI1_PB,
+ SKL_DPCM_AUDIO_HDMI2_PB,
+ SKL_DPCM_AUDIO_HDMI3_PB,
+};
+
+static const struct snd_kcontrol_new skylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Left Speaker"),
+ SOC_DAPM_PIN_SWITCH("Right Speaker"),
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "set sysclk err = %d\n", ret);
+ return -EIO;
+ }
+ } else {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "set sysclk err = %d\n", ret);
+ return -EIO;
+ }
+ }
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget skylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Left Speaker", NULL),
+ SND_SOC_DAPM_SPK("Right Speaker", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("DP1", NULL),
+ SND_SOC_DAPM_SPK("DP2", NULL),
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route skylake_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ {"Headphone Jack", NULL, "HPOL"},
+ {"Headphone Jack", NULL, "HPOR"},
+
+ /* speaker */
+ {"Left Speaker", NULL, "Left OUT"},
+ {"Right Speaker", NULL, "Right OUT"},
+
+ /* other jacks */
+ {"MIC", NULL, "Headset Mic"},
+ {"DMic", NULL, "SoC DMIC"},
+
+ /* CODEC BE connections */
+ { "Left Playback", NULL, "ssp0 Tx"},
+ { "Right Playback", NULL, "ssp0 Tx"},
+ { "ssp0 Tx", NULL, "codec0_out"},
+
+ /* IV feedback path */
+ { "codec0_lp_in", NULL, "ssp0 Rx"},
+ { "ssp0 Rx", NULL, "Left Capture Sense" },
+ { "ssp0 Rx", NULL, "Right Capture Sense" },
+
+ { "Playback", NULL, "ssp1 Tx"},
+ { "ssp1 Tx", NULL, "codec1_out"},
+
+ { "codec0_in", NULL, "ssp1 Rx" },
+ { "ssp1 Rx", NULL, "Capture" },
+
+ /* DMIC */
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+};
+
+static struct snd_soc_codec_conf ssm4567_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("i2c-INT343B:00"),
+ .name_prefix = "Left",
+ },
+ {
+ .dlc = COMP_CODEC_CONF("i2c-INT343B:01"),
+ .name_prefix = "Right",
+ },
+};
+
+static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+
+ /* Slot 1 for left */
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 0), 0x01, 0x01, 2, 48);
+ if (ret < 0)
+ return ret;
+
+ /* Slot 2 for right */
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(rtd, 1), 0x02, 0x02, 2, 48);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ /*
+ * 4 buttons here map to the google Reference headset
+ * The use of these buttons can be decided by the user space.
+ */
+ ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset,
+ NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ nau8825_enable_jack_detect(component, &skylake_headset);
+
+ snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+
+ return ret;
+}
+
+static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI1_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI2_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+
+static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI3_PB;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int skl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * on this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops skylake_nau8825_fe_ops = {
+ .startup = skl_fe_startup,
+};
+
+static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The ADSP will covert the FE rate to 48k, stereo */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP0 to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ return 0;
+}
+
+static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN);
+
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_ops skylake_nau8825_ops = {
+ .hw_params = skylake_nau8825_hw_params,
+};
+
+static const unsigned int channels_dmic[] = {
+ 2, 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static const unsigned int dmic_2ch[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = {
+ .count = ARRAY_SIZE(dmic_2ch),
+ .list = dmic_2ch,
+ .mask = 0,
+};
+
+static int skylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = DMIC_CH(dmic_constraints);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ dmic_constraints);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops skylake_dmic_ops = {
+ .startup = skylake_dmic_startup,
+};
+
+static const unsigned int rates_16000[] = {
+ 16000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_16000 = {
+ .count = ARRAY_SIZE(rates_16000),
+ .list = rates_16000,
+};
+
+static const unsigned int ch_mono[] = {
+ 1,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_refcap = {
+ .count = ARRAY_SIZE(ch_mono),
+ .list = ch_mono,
+};
+
+static int skylake_refcap_startup(struct snd_pcm_substream *substream)
+{
+ substream->runtime->hw.channels_max = 1;
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_refcap);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_16000);
+}
+
+static const struct snd_soc_ops skylake_refcap_ops = {
+ .startup = skylake_refcap_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC("i2c-INT343B:00", SKL_SSM_CODEC_DAI),
+ /* Right */ COMP_CODEC("i2c-INT343B:01", SKL_SSM_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10508825:00", SKL_NUVOTON_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(dmic01_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* skylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link skylake_dais[] = {
+ /* Front End DAI links */
+ [SKL_DPCM_AUDIO_PB] = {
+ .name = "Skl Audio Port",
+ .stream_name = "Audio",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .init = skylake_nau8825_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .ops = &skylake_nau8825_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_CP] = {
+ .name = "Skl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .dynamic = 1,
+ .nonatomic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ .ops = &skylake_nau8825_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_REF_CP] = {
+ .name = "Skl Audio Reference cap",
+ .stream_name = "Wake on Voice",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_refcap_ops,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Skl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Skl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Skl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Skl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dai_fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .init = skylake_ssm4567_codec_init,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = skylake_ssp_fixup,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ /* SSP1 - Codec */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = skylake_nau8825_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = skylake_ssp_fixup,
+ .ops = &skylake_nau8825_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .ignore_suspend = 1,
+ .be_hw_params_fixup = skylake_dmic_fixup,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic01_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .dpcm_playback = 1,
+ .init = skylake_hdmi1_init,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = skylake_hdmi2_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = skylake_hdmi3_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(card);
+ struct skl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT,
+ &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* skylake audio machine driver for SPT + NAU88L25 */
+static struct snd_soc_card skylake_audio_card = {
+ .name = "sklnau8825adi",
+ .owner = THIS_MODULE,
+ .dai_link = skylake_dais,
+ .num_links = ARRAY_SIZE(skylake_dais),
+ .controls = skylake_controls,
+ .num_controls = ARRAY_SIZE(skylake_controls),
+ .dapm_widgets = skylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(skylake_widgets),
+ .dapm_routes = skylake_map,
+ .num_dapm_routes = ARRAY_SIZE(skylake_map),
+ .codec_conf = ssm4567_codec_conf,
+ .num_configs = ARRAY_SIZE(ssm4567_codec_conf),
+ .fully_routed = true,
+ .disable_route_checks = true,
+ .late_probe = skylake_card_late_probe,
+};
+
+static int skylake_audio_probe(struct platform_device *pdev)
+{
+ struct skl_nau88125_private *ctx;
+ struct snd_soc_acpi_mach *mach;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ skylake_audio_card.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&skylake_audio_card, ctx);
+
+ mach = pdev->dev.platform_data;
+ if (mach)
+ dmic_constraints = mach->mach_params.dmic_num == 2 ?
+ &constraints_dmic_2ch : &constraints_dmic_channels;
+
+ return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
+}
+
+static const struct platform_device_id skl_board_ids[] = {
+ { .name = "skl_n88l25_s4567" },
+ { .name = "kbl_n88l25_s4567" },
+ { }
+};
+
+static struct platform_driver skylake_audio = {
+ .probe = skylake_audio_probe,
+ .driver = {
+ .name = "skl_n88l25_s4567",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = skl_board_ids,
+};
+
+module_platform_driver(skylake_audio)
+
+/* Module information */
+MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>");
+MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>");
+MODULE_AUTHOR("Naveen M <naveen.m@intel.com>");
+MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>");
+MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
+MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl_n88l25_s4567");
+MODULE_ALIAS("platform:kbl_n88l25_s4567");
diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c
new file mode 100644
index 000000000..5a0c64a83
--- /dev/null
+++ b/sound/soc/intel/boards/skl_rt286.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Skylake I2S Machine Driver
+ *
+ * Copyright (C) 2014-2015, Intel Corporation. All rights reserved.
+ *
+ * Modified from:
+ * Intel Broadwell Wildcatpoint SST Audio
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include "../../codecs/rt286.h"
+#include "../../codecs/hdac_hdmi.h"
+
+static struct snd_soc_jack skylake_headset;
+static struct snd_soc_jack skylake_hdmi[3];
+
+struct skl_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct skl_rt286_private {
+ struct list_head hdmi_pcm_list;
+};
+
+enum {
+ SKL_DPCM_AUDIO_PB = 0,
+ SKL_DPCM_AUDIO_DB_PB,
+ SKL_DPCM_AUDIO_CP,
+ SKL_DPCM_AUDIO_REF_CP,
+ SKL_DPCM_AUDIO_DMIC_CP,
+ SKL_DPCM_AUDIO_HDMI1_PB,
+ SKL_DPCM_AUDIO_HDMI2_PB,
+ SKL_DPCM_AUDIO_HDMI3_PB,
+};
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin skylake_headset_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static const struct snd_kcontrol_new skylake_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Mic Jack"),
+};
+
+static const struct snd_soc_dapm_widget skylake_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("DMIC2", NULL),
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+ SND_SOC_DAPM_SPK("HDMI1", NULL),
+ SND_SOC_DAPM_SPK("HDMI2", NULL),
+ SND_SOC_DAPM_SPK("HDMI3", NULL),
+};
+
+static const struct snd_soc_dapm_route skylake_rt286_map[] = {
+ /* speaker */
+ {"Speaker", NULL, "SPOR"},
+ {"Speaker", NULL, "SPOL"},
+
+ /* HP jack connectors - unknown if we have jack deteck */
+ {"Headphone Jack", NULL, "HPO Pin"},
+
+ /* other jacks */
+ {"MIC1", NULL, "Mic Jack"},
+
+ /* digital mics */
+ {"DMIC1 Pin", NULL, "DMIC2"},
+ {"DMic", NULL, "SoC DMIC"},
+
+ /* CODEC BE connections */
+ { "AIF1 Playback", NULL, "ssp0 Tx"},
+ { "ssp0 Tx", NULL, "codec0_out"},
+ { "ssp0 Tx", NULL, "codec1_out"},
+
+ { "codec0_in", NULL, "ssp0 Rx" },
+ { "codec1_in", NULL, "ssp0 Rx" },
+ { "ssp0 Rx", NULL, "AIF1 Capture" },
+
+ { "dmic01_hifi", NULL, "DMIC01 Rx" },
+ { "DMIC01 Rx", NULL, "DMIC AIF" },
+
+ { "hifi3", NULL, "iDisp3 Tx"},
+ { "iDisp3 Tx", NULL, "iDisp3_out"},
+ { "hifi2", NULL, "iDisp2 Tx"},
+ { "iDisp2 Tx", NULL, "iDisp2_out"},
+ { "hifi1", NULL, "iDisp1 Tx"},
+ { "iDisp1 Tx", NULL, "iDisp1_out"},
+
+};
+
+static int skylake_rt286_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_component *component = asoc_rtd_to_cpu(rtd, 0)->component;
+
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_ignore_suspend(dapm, "Reference Capture");
+
+ return 0;
+}
+
+static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret;
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &skylake_headset,
+ skylake_headset_pins, ARRAY_SIZE(skylake_headset_pins));
+
+ if (ret)
+ return ret;
+
+ rt286_mic_detect(component, &skylake_headset);
+
+ snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC");
+
+ return 0;
+}
+
+static int skylake_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = SKL_DPCM_AUDIO_HDMI1_PB + dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static const unsigned int rates[] = {
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const unsigned int channels[] = {
+ 2,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+static int skl_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * on this platform for PCM device we support,
+ * 48Khz
+ * stereo
+ * 16 bit audio
+ */
+
+ runtime->hw.channels_max = 2;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+
+ return 0;
+}
+
+static const struct snd_soc_ops skylake_rt286_fe_ops = {
+ .startup = skl_fe_startup,
+};
+
+static int skylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ /* The output is 48KHz, stereo, 16bits */
+ rate->min = rate->max = 48000;
+ chan->min = chan->max = 2;
+
+ /* set SSP0 to 24 bit */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ return 0;
+}
+
+static int skylake_rt286_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_ops skylake_rt286_ops = {
+ .hw_params = skylake_rt286_hw_params,
+};
+
+static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *chan = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ if (params_channels(params) == 2)
+ chan->min = chan->max = 2;
+ else
+ chan->min = chan->max = 4;
+
+ return 0;
+}
+
+static const unsigned int channels_dmic[] = {
+ 2, 4,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = {
+ .count = ARRAY_SIZE(channels_dmic),
+ .list = channels_dmic,
+ .mask = 0,
+};
+
+static int skylake_dmic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.channels_max = 4;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_dmic_channels);
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);
+}
+
+static const struct snd_soc_ops skylake_dmic_ops = {
+ .startup = skylake_dmic_startup,
+};
+
+SND_SOC_DAILINK_DEF(dummy,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEF(system,
+ DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
+
+SND_SOC_DAILINK_DEF(deepbuffer,
+ DAILINK_COMP_ARRAY(COMP_CPU("Deepbuffer Pin")));
+
+SND_SOC_DAILINK_DEF(reference,
+ DAILINK_COMP_ARRAY(COMP_CPU("Reference Pin")));
+
+SND_SOC_DAILINK_DEF(dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi1,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI1 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi2,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI2 Pin")));
+
+SND_SOC_DAILINK_DEF(hdmi3,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI3 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1")));
+
+SND_SOC_DAILINK_DEF(dmic01_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+/* skylake digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link skylake_rt286_dais[] = {
+ /* Front End DAI links */
+ [SKL_DPCM_AUDIO_PB] = {
+ .name = "Skl Audio Port",
+ .stream_name = "Audio",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .init = skylake_rt286_fe_init,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST
+ },
+ .dpcm_playback = 1,
+ .ops = &skylake_rt286_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_DB_PB] = {
+ .name = "Skl Deepbuffer Port",
+ .stream_name = "Deep Buffer Audio",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST
+ },
+ .dpcm_playback = 1,
+ .ops = &skylake_rt286_fe_ops,
+ SND_SOC_DAILINK_REG(deepbuffer, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_CP] = {
+ .name = "Skl Audio Capture Port",
+ .stream_name = "Audio Record",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .trigger = {
+ SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST
+ },
+ .dpcm_capture = 1,
+ .ops = &skylake_rt286_fe_ops,
+ SND_SOC_DAILINK_REG(system, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_REF_CP] = {
+ .name = "Skl Audio Reference cap",
+ .stream_name = "refcap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(reference, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_DMIC_CP] = {
+ .name = "Skl Audio DMIC cap",
+ .stream_name = "dmiccap",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ .ops = &skylake_dmic_ops,
+ SND_SOC_DAILINK_REG(dmic, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI1_PB] = {
+ .name = "Skl HDMI Port1",
+ .stream_name = "Hdmi1",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi1, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI2_PB] = {
+ .name = "Skl HDMI Port2",
+ .stream_name = "Hdmi2",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi2, dummy, platform),
+ },
+ [SKL_DPCM_AUDIO_HDMI3_PB] = {
+ .name = "Skl HDMI Port3",
+ .stream_name = "Hdmi3",
+ .dpcm_playback = 1,
+ .init = NULL,
+ .nonatomic = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hdmi3, dummy, platform),
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .init = skylake_rt286_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = skylake_ssp0_fixup,
+ .ops = &skylake_rt286_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 1,
+ .be_hw_params_fixup = skylake_dmic_fixup,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic01_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 2,
+ .init = skylake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 3,
+ .init = skylake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 4,
+ .init = skylake_hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+};
+
+#define NAME_SIZE 32
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+ struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(card);
+ struct skl_hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &skylake_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &skylake_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+/* skylake audio machine driver for SPT + RT286S */
+static struct snd_soc_card skylake_rt286 = {
+ .name = "skylake-rt286",
+ .owner = THIS_MODULE,
+ .dai_link = skylake_rt286_dais,
+ .num_links = ARRAY_SIZE(skylake_rt286_dais),
+ .controls = skylake_controls,
+ .num_controls = ARRAY_SIZE(skylake_controls),
+ .dapm_widgets = skylake_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(skylake_widgets),
+ .dapm_routes = skylake_rt286_map,
+ .num_dapm_routes = ARRAY_SIZE(skylake_rt286_map),
+ .fully_routed = true,
+ .late_probe = skylake_card_late_probe,
+};
+
+static int skylake_audio_probe(struct platform_device *pdev)
+{
+ struct skl_rt286_private *ctx;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ skylake_rt286.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&skylake_rt286, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev, &skylake_rt286);
+}
+
+static const struct platform_device_id skl_board_ids[] = {
+ { .name = "skl_alc286s_i2s" },
+ { .name = "kbl_alc286s_i2s" },
+ { }
+};
+
+static struct platform_driver skylake_audio = {
+ .probe = skylake_audio_probe,
+ .driver = {
+ .name = "skl_alc286s_i2s",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = skl_board_ids,
+
+};
+
+module_platform_driver(skylake_audio)
+
+/* Module information */
+MODULE_AUTHOR("Omair Mohammed Abdullah <omair.m.abdullah@intel.com>");
+MODULE_DESCRIPTION("Intel SST Audio for Skylake");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl_alc286s_i2s");
+MODULE_ALIAS("platform:kbl_alc286s_i2s");
diff --git a/sound/soc/intel/boards/sof_da7219_max98373.c b/sound/soc/intel/boards/sof_da7219_max98373.c
new file mode 100644
index 000000000..8d1ad892e
--- /dev/null
+++ b/sound/soc/intel/boards/sof_da7219_max98373.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2019 Intel Corporation.
+
+/*
+ * Intel SOF Machine driver for DA7219 + MAX98373/MAX98360A codec
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/da7219.h"
+#include "../../codecs/da7219-aad.h"
+#include "hda_dsp_common.h"
+
+#define DIALOG_CODEC_DAI "da7219-hifi"
+#define MAX98373_CODEC_DAI "max98373-aif1"
+#define MAXIM_DEV0_NAME "i2c-MX98373:00"
+#define MAXIM_DEV1_NAME "i2c-MX98373:01"
+
+struct hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct card_private {
+ struct snd_soc_jack headset;
+ struct list_head hdmi_pcm_list;
+ struct snd_soc_jack hdmi[3];
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *codec_dai;
+ int ret = 0;
+
+ codec_dai = snd_soc_card_get_codec_dai(card, DIALOG_CODEC_DAI);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK,
+ 0, 0);
+ if (ret)
+ dev_err(card->dev, "failed to stop PLL: %d\n", ret);
+ } else if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM,
+ 0, DA7219_PLL_FREQ_OUT_98304);
+ if (ret)
+ dev_err(card->dev, "failed to start PLL: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+};
+
+static const struct snd_kcontrol_new m98360a_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Spk"),
+};
+
+/* For MAX98373 amp */
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_POST_PMD |
+ SND_SOC_DAPM_PRE_PMU),
+
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "Headphone Jack", NULL, "HPL" },
+ { "Headphone Jack", NULL, "HPR" },
+
+ { "MIC", NULL, "Headset Mic" },
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+
+ { "Left Spk", NULL, "Left BE_OUT" },
+ { "Right Spk", NULL, "Right BE_OUT" },
+
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+/* For MAX98360A amp */
+static const struct snd_soc_dapm_widget max98360a_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_SPK("Spk", NULL),
+
+ SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
+ platform_clock_control, SND_SOC_DAPM_POST_PMD |
+ SND_SOC_DAPM_PRE_PMU),
+
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route max98360a_map[] = {
+ { "Headphone Jack", NULL, "HPL" },
+ { "Headphone Jack", NULL, "HPR" },
+
+ { "MIC", NULL, "Headset Mic" },
+
+ { "Headphone Jack", NULL, "Platform Clock" },
+ { "Headset Mic", NULL, "Platform Clock" },
+
+ {"Spk", NULL, "Speaker"},
+
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+static struct snd_soc_jack headset;
+
+static int da7219_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, 24000000,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(rtd->dev, "can't set codec sysclk configuration\n");
+ return ret;
+ }
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3 | SND_JACK_LINEOUT,
+ &headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &headset;
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND);
+ da7219_aad_jack_det(component, jack);
+
+ return ret;
+}
+
+static int ssp1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream);
+ int ret, j;
+
+ for (j = 0; j < runtime->num_codecs; j++) {
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, j);
+
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) {
+ /* vmon_slot_no = 0 imon_slot_no = 1 for TX slots */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 3, 4, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ if (!strcmp(codec_dai->component->name, MAXIM_DEV1_NAME)) {
+ /* vmon_slot_no = 2 imon_slot_no = 3 for TX slots */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC, 3, 4, 16);
+ if (ret < 0) {
+ dev_err(runtime->dev, "DEV1 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops ssp1_ops = {
+ .hw_params = ssp1_hw_params,
+};
+
+static struct snd_soc_codec_conf max98373_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+ {
+ .dlc = COMP_CODEC_CONF(MAXIM_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+static int hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ pcm->device = dai->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int card_late_probe(struct snd_soc_card *card)
+{
+ struct card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_acpi_mach *mach = (card->dev)->platform_data;
+ struct hdmi_pcm *pcm;
+
+ if (mach->mach_params.common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm,
+ head);
+ return hda_dsp_hdmi_build_controls(card,
+ pcm->codec_dai->component);
+ }
+
+ return -EINVAL;
+}
+
+SND_SOC_DAILINK_DEF(ssp0_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin")));
+SND_SOC_DAILINK_DEF(ssp0_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00", DIALOG_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP1 Pin")));
+SND_SOC_DAILINK_DEF(ssp1_amps,
+ DAILINK_COMP_ARRAY(
+ /* Left */ COMP_CODEC(MAXIM_DEV0_NAME, MAX98373_CODEC_DAI),
+ /* Right */ COMP_CODEC(MAXIM_DEV1_NAME, MAX98373_CODEC_DAI)));
+
+SND_SOC_DAILINK_DEF(ssp1_m98360a,
+ DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi")));
+
+SND_SOC_DAILINK_DEF(dmic_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin")));
+SND_SOC_DAILINK_DEF(dmic_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+SND_SOC_DAILINK_DEF(dmic16k_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin")));
+
+SND_SOC_DAILINK_DEF(idisp1_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin")));
+SND_SOC_DAILINK_DEF(idisp1_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1")));
+
+SND_SOC_DAILINK_DEF(idisp2_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin")));
+SND_SOC_DAILINK_DEF(idisp2_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2")));
+
+SND_SOC_DAILINK_DEF(idisp3_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin")));
+SND_SOC_DAILINK_DEF(idisp3_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3")));
+
+SND_SOC_DAILINK_DEF(platform, /* subject to be overridden during probe */
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3")));
+
+static struct snd_soc_dai_link dais[] = {
+ /* Back End DAI links */
+ {
+ .name = "SSP1-Codec",
+ .id = 0,
+ .ignore_pmdown_time = 1,
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1, /* IV feedback */
+ .ops = &ssp1_ops,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_amps, platform),
+ },
+ {
+ .name = "SSP0-Codec",
+ .id = 1,
+ .no_pcm = 1,
+ .init = da7219_codec_init,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ SND_SOC_DAILINK_REG(ssp0_pin, ssp0_codec, platform),
+ },
+ {
+ .name = "dmic01",
+ .id = 2,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform),
+ },
+ {
+ .name = "iDisp1",
+ .id = 3,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform),
+ },
+ {
+ .name = "iDisp2",
+ .id = 4,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform),
+ },
+ {
+ .name = "iDisp3",
+ .id = 5,
+ .init = hdmi_init,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform),
+ },
+ {
+ .name = "dmic16k",
+ .id = 6,
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(dmic16k_pin, dmic_codec, platform),
+ }
+};
+
+static struct snd_soc_card card_da7219_m98373 = {
+ .name = "da7219max",
+ .owner = THIS_MODULE,
+ .dai_link = dais,
+ .num_links = ARRAY_SIZE(dais),
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .codec_conf = max98373_codec_conf,
+ .num_configs = ARRAY_SIZE(max98373_codec_conf),
+ .fully_routed = true,
+ .late_probe = card_late_probe,
+};
+
+static struct snd_soc_card card_da7219_m98360a = {
+ .name = "da7219max98360a",
+ .owner = THIS_MODULE,
+ .dai_link = dais,
+ .num_links = ARRAY_SIZE(dais),
+ .controls = m98360a_controls,
+ .num_controls = ARRAY_SIZE(m98360a_controls),
+ .dapm_widgets = max98360a_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98360a_widgets),
+ .dapm_routes = max98360a_map,
+ .num_dapm_routes = ARRAY_SIZE(max98360a_map),
+ .fully_routed = true,
+ .late_probe = card_late_probe,
+};
+
+static int audio_probe(struct platform_device *pdev)
+{
+ static struct snd_soc_card *card;
+ struct snd_soc_acpi_mach *mach;
+ struct card_private *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ /* By default dais[0] is configured for max98373 */
+ if (!strcmp(pdev->name, "sof_da7219_max98360a")) {
+ dais[0] = (struct snd_soc_dai_link) {
+ .name = "SSP1-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .ignore_pmdown_time = 1,
+ SND_SOC_DAILINK_REG(ssp1_pin, ssp1_m98360a, platform) };
+ }
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+ card = (struct snd_soc_card *)pdev->id_entry->driver_data;
+ card->dev = &pdev->dev;
+
+ mach = pdev->dev.platform_data;
+ ret = snd_soc_fixup_dai_links_platform_name(card,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(card, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct platform_device_id board_ids[] = {
+ {
+ .name = "sof_da7219_max98373",
+ .driver_data = (kernel_ulong_t)&card_da7219_m98373,
+ },
+ {
+ .name = "sof_da7219_max98360a",
+ .driver_data = (kernel_ulong_t)&card_da7219_m98360a,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, board_ids);
+
+static struct platform_driver audio = {
+ .probe = audio_probe,
+ .driver = {
+ .name = "sof_da7219_max98_360a_373",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = board_ids,
+};
+module_platform_driver(audio)
+
+/* Module information */
+MODULE_DESCRIPTION("ASoC Intel(R) SOF Machine driver");
+MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sof_da7219_max98360a");
+MODULE_ALIAS("platform:sof_da7219_max98373");
diff --git a/sound/soc/intel/boards/sof_maxim_common.c b/sound/soc/intel/boards/sof_maxim_common.c
new file mode 100644
index 000000000..c2a975718
--- /dev/null
+++ b/sound/soc/intel/boards/sof_maxim_common.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+#include <linux/string.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <uapi/sound/asound.h>
+#include "sof_maxim_common.h"
+
+#define MAX_98373_PIN_NAME 16
+
+const struct snd_soc_dapm_route max_98373_dapm_routes[] = {
+ /* speaker */
+ { "Left Spk", NULL, "Left BE_OUT" },
+ { "Right Spk", NULL, "Right BE_OUT" },
+};
+
+static struct snd_soc_codec_conf max_98373_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF(MAX_98373_DEV0_NAME),
+ .name_prefix = "Right",
+ },
+ {
+ .dlc = COMP_CODEC_CONF(MAX_98373_DEV1_NAME),
+ .name_prefix = "Left",
+ },
+};
+
+struct snd_soc_dai_link_component max_98373_components[] = {
+ { /* For Right */
+ .name = MAX_98373_DEV0_NAME,
+ .dai_name = MAX_98373_CODEC_DAI,
+ },
+ { /* For Left */
+ .name = MAX_98373_DEV1_NAME,
+ .dai_name = MAX_98373_CODEC_DAI,
+ },
+};
+
+static int max98373_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int j;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ if (!strcmp(codec_dai->component->name, MAX_98373_DEV0_NAME)) {
+ /* DEV0 tdm slot configuration */
+ snd_soc_dai_set_tdm_slot(codec_dai, 0x03, 3, 8, 32);
+ }
+ if (!strcmp(codec_dai->component->name, MAX_98373_DEV1_NAME)) {
+ /* DEV1 tdm slot configuration */
+ snd_soc_dai_set_tdm_slot(codec_dai, 0x0C, 3, 8, 32);
+ }
+ }
+ return 0;
+}
+
+int max98373_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ int j;
+ int ret = 0;
+
+ /* set spk pin by playback only */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return 0;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ char pin_name[MAX_98373_PIN_NAME];
+
+ snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk",
+ codec_dai->component->name_prefix);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = snd_soc_dapm_enable_pin(dapm, pin_name);
+ if (!ret)
+ snd_soc_dapm_sync(dapm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = snd_soc_dapm_disable_pin(dapm, pin_name);
+ if (!ret)
+ snd_soc_dapm_sync(dapm);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return ret;
+}
+
+struct snd_soc_ops max_98373_ops = {
+ .hw_params = max98373_hw_params,
+ .trigger = max98373_trigger,
+};
+
+int max98373_spk_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes,
+ ARRAY_SIZE(max_98373_dapm_routes));
+ if (ret)
+ dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret);
+ return ret;
+}
+
+void sof_max98373_codec_conf(struct snd_soc_card *card)
+{
+ card->codec_conf = max_98373_codec_conf;
+ card->num_configs = ARRAY_SIZE(max_98373_codec_conf);
+}
diff --git a/sound/soc/intel/boards/sof_maxim_common.h b/sound/soc/intel/boards/sof_maxim_common.h
new file mode 100644
index 000000000..5240b1c9d
--- /dev/null
+++ b/sound/soc/intel/boards/sof_maxim_common.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation.
+ */
+
+/*
+ * This file defines data structures used in Machine Driver for Intel
+ * platforms with Maxim Codecs.
+ */
+#ifndef __SOF_MAXIM_COMMON_H
+#define __SOF_MAXIM_COMMON_H
+
+#include <sound/soc.h>
+
+#define MAX_98373_CODEC_DAI "max98373-aif1"
+#define MAX_98373_DEV0_NAME "i2c-MX98373:00"
+#define MAX_98373_DEV1_NAME "i2c-MX98373:01"
+
+extern struct snd_soc_dai_link_component max_98373_components[2];
+extern struct snd_soc_ops max_98373_ops;
+extern const struct snd_soc_dapm_route max_98373_dapm_routes[];
+
+int max98373_spk_codec_init(struct snd_soc_pcm_runtime *rtd);
+void sof_max98373_codec_conf(struct snd_soc_card *card);
+int max98373_trigger(struct snd_pcm_substream *substream, int cmd);
+
+#endif /* __SOF_MAXIM_COMMON_H */
diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c
new file mode 100644
index 000000000..bdd671f07
--- /dev/null
+++ b/sound/soc/intel/boards/sof_pcm512x.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2018-2020 Intel Corporation.
+
+/*
+ * Intel SOF Machine Driver for Intel platforms with TI PCM512x codec,
+ * e.g. Up or Up2 with Hifiberry DAC+ HAT
+ */
+#include <linux/clk.h>
+#include <linux/dmi.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/pcm512x.h"
+#include "../common/soc-intel-quirks.h"
+#include "hda_dsp_common.h"
+
+#define NAME_SIZE 32
+
+#define SOF_PCM512X_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0))
+#define SOF_PCM512X_SSP_CODEC_MASK (GENMASK(3, 0))
+#define SOF_PCM512X_ENABLE_SSP_CAPTURE BIT(4)
+#define SOF_PCM512X_ENABLE_DMIC BIT(5)
+
+#define IDISP_CODEC_MASK 0x4
+
+/* Default: SSP5 */
+static unsigned long sof_pcm512x_quirk =
+ SOF_PCM512X_SSP_CODEC(5) |
+ SOF_PCM512X_ENABLE_SSP_CAPTURE |
+ SOF_PCM512X_ENABLE_DMIC;
+
+static bool is_legacy_cpu;
+
+struct sof_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct sof_card_private {
+ struct list_head hdmi_pcm_list;
+ bool idisp_codec;
+};
+
+static int sof_pcm512x_quirk_cb(const struct dmi_system_id *id)
+{
+ sof_pcm512x_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_pcm512x_quirk_table[] = {
+ {
+ .callback = sof_pcm512x_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "UP-CHT01"),
+ },
+ .driver_data = (void *)(SOF_PCM512X_SSP_CODEC(2)),
+ },
+ {}
+};
+
+static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct sof_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ /* dai_link id is 1:1 mapped to the PCM device */
+ pcm->device = rtd->dai_link->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_update_bits(codec, PCM512x_GPIO_EN, 0x08, 0x08);
+ snd_soc_component_update_bits(codec, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+ snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
+ 0x08, 0x08);
+
+ return 0;
+}
+
+static int aif1_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
+ 0x08, 0x08);
+
+ return 0;
+}
+
+static void aif1_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
+ 0x08, 0x00);
+}
+
+static const struct snd_soc_ops sof_pcm512x_ops = {
+ .startup = aif1_startup,
+ .shutdown = aif1_shutdown,
+};
+
+static struct snd_soc_dai_link_component platform_component[] = {
+ {
+ /* name might be overridden during probe */
+ .name = "0000:00:1f.3"
+ }
+};
+
+static int sof_card_late_probe(struct snd_soc_card *card)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct sof_hdmi_pcm *pcm;
+
+ /* HDMI is not supported by SOF on Baytrail/CherryTrail */
+ if (is_legacy_cpu)
+ return 0;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (!ctx->idisp_codec)
+ return 0;
+
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head);
+
+ return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component);
+}
+
+static const struct snd_kcontrol_new sof_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static const struct snd_soc_dapm_widget sof_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_widget dmic_widgets[] = {
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route sof_map[] = {
+ /* Speaker */
+ {"Ext Spk", NULL, "OUTR"},
+ {"Ext Spk", NULL, "OUTL"},
+};
+
+static const struct snd_soc_dapm_route dmic_map[] = {
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+static int dmic_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets,
+ ARRAY_SIZE(dmic_widgets));
+ if (ret) {
+ dev_err(card->dev, "DMic widget addition failed: %d\n", ret);
+ /* Don't need to add routes if widget addition failed */
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map,
+ ARRAY_SIZE(dmic_map));
+
+ if (ret)
+ dev_err(card->dev, "DMic map addition failed: %d\n", ret);
+
+ return ret;
+}
+
+/* sof audio machine driver for pcm512x codec */
+static struct snd_soc_card sof_audio_card_pcm512x = {
+ .name = "pcm512x",
+ .owner = THIS_MODULE,
+ .controls = sof_controls,
+ .num_controls = ARRAY_SIZE(sof_controls),
+ .dapm_widgets = sof_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sof_widgets),
+ .dapm_routes = sof_map,
+ .num_dapm_routes = ARRAY_SIZE(sof_map),
+ .fully_routed = true,
+ .late_probe = sof_card_late_probe,
+};
+
+SND_SOC_DAILINK_DEF(pcm512x_component,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-104C5122:00", "pcm512x-hifi")));
+SND_SOC_DAILINK_DEF(dmic_component,
+ DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
+
+static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev,
+ int ssp_codec,
+ int dmic_be_num,
+ int hdmi_num,
+ bool idisp_codec)
+{
+ struct snd_soc_dai_link_component *idisp_components;
+ struct snd_soc_dai_link_component *cpus;
+ struct snd_soc_dai_link *links;
+ int i, id = 0;
+
+ links = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links,
+ sizeof(struct snd_soc_dai_link), GFP_KERNEL);
+ cpus = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links,
+ sizeof(struct snd_soc_dai_link_component), GFP_KERNEL);
+ if (!links || !cpus)
+ goto devm_err;
+
+ /* codec SSP */
+ links[id].name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d-Codec", ssp_codec);
+ if (!links[id].name)
+ goto devm_err;
+
+ links[id].id = id;
+ links[id].codecs = pcm512x_component;
+ links[id].num_codecs = ARRAY_SIZE(pcm512x_component);
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].init = sof_pcm512x_codec_init;
+ links[id].ops = &sof_pcm512x_ops;
+ links[id].nonatomic = true;
+ links[id].dpcm_playback = 1;
+ /*
+ * capture only supported with specific versions of the Hifiberry DAC+
+ */
+ if (sof_pcm512x_quirk & SOF_PCM512X_ENABLE_SSP_CAPTURE)
+ links[id].dpcm_capture = 1;
+ links[id].no_pcm = 1;
+ links[id].cpus = &cpus[id];
+ links[id].num_cpus = 1;
+ if (is_legacy_cpu) {
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "ssp%d-port",
+ ssp_codec);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+ } else {
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d Pin",
+ ssp_codec);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+ }
+ id++;
+
+ /* dmic */
+ if (dmic_be_num > 0) {
+ /* at least we have dmic01 */
+ links[id].name = "dmic01";
+ links[id].cpus = &cpus[id];
+ links[id].cpus->dai_name = "DMIC01 Pin";
+ links[id].init = dmic_init;
+ if (dmic_be_num > 1) {
+ /* set up 2 BE links at most */
+ links[id + 1].name = "dmic16k";
+ links[id + 1].cpus = &cpus[id + 1];
+ links[id + 1].cpus->dai_name = "DMIC16k Pin";
+ dmic_be_num = 2;
+ }
+ }
+
+ for (i = 0; i < dmic_be_num; i++) {
+ links[id].id = id;
+ links[id].num_cpus = 1;
+ links[id].codecs = dmic_component;
+ links[id].num_codecs = ARRAY_SIZE(dmic_component);
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].ignore_suspend = 1;
+ links[id].dpcm_capture = 1;
+ links[id].no_pcm = 1;
+ id++;
+ }
+
+ /* HDMI */
+ if (hdmi_num > 0) {
+ idisp_components = devm_kcalloc(dev, hdmi_num,
+ sizeof(struct snd_soc_dai_link_component),
+ GFP_KERNEL);
+ if (!idisp_components)
+ goto devm_err;
+ }
+ for (i = 1; i <= hdmi_num; i++) {
+ links[id].name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d", i);
+ if (!links[id].name)
+ goto devm_err;
+
+ links[id].id = id;
+ links[id].cpus = &cpus[id];
+ links[id].num_cpus = 1;
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d Pin", i);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+
+ /*
+ * topology cannot be loaded if codec is missing, so
+ * use the dummy codec if needed
+ */
+ if (idisp_codec) {
+ idisp_components[i - 1].name = "ehdaudio0D2";
+ idisp_components[i - 1].dai_name =
+ devm_kasprintf(dev, GFP_KERNEL,
+ "intel-hdmi-hifi%d", i);
+ } else {
+ idisp_components[i - 1].name = "snd-soc-dummy";
+ idisp_components[i - 1].dai_name = "snd-soc-dummy-dai";
+ }
+ if (!idisp_components[i - 1].dai_name)
+ goto devm_err;
+
+ links[id].codecs = &idisp_components[i - 1];
+ links[id].num_codecs = 1;
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].init = sof_hdmi_init;
+ links[id].dpcm_playback = 1;
+ links[id].no_pcm = 1;
+ id++;
+ }
+
+ return links;
+devm_err:
+ return NULL;
+}
+
+static int sof_audio_probe(struct platform_device *pdev)
+{
+ struct snd_soc_acpi_mach *mach = pdev->dev.platform_data;
+ struct snd_soc_dai_link *dai_links;
+ struct sof_card_private *ctx;
+ int dmic_be_num, hdmi_num;
+ int ret, ssp_codec;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ hdmi_num = 0;
+ if (soc_intel_is_byt() || soc_intel_is_cht()) {
+ is_legacy_cpu = true;
+ dmic_be_num = 0;
+ /* default quirk for legacy cpu */
+ sof_pcm512x_quirk = SOF_PCM512X_SSP_CODEC(2);
+ } else {
+ dmic_be_num = 2;
+ if (mach->mach_params.common_hdmi_codec_drv &&
+ (mach->mach_params.codec_mask & IDISP_CODEC_MASK))
+ ctx->idisp_codec = true;
+
+ /* links are always present in topology */
+ hdmi_num = 3;
+ }
+
+ dmi_check_system(sof_pcm512x_quirk_table);
+
+ dev_dbg(&pdev->dev, "sof_pcm512x_quirk = %lx\n", sof_pcm512x_quirk);
+
+ ssp_codec = sof_pcm512x_quirk & SOF_PCM512X_SSP_CODEC_MASK;
+
+ if (!(sof_pcm512x_quirk & SOF_PCM512X_ENABLE_DMIC))
+ dmic_be_num = 0;
+
+ /* compute number of dai links */
+ sof_audio_card_pcm512x.num_links = 1 + dmic_be_num + hdmi_num;
+
+ dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec,
+ dmic_be_num, hdmi_num,
+ ctx->idisp_codec);
+ if (!dai_links)
+ return -ENOMEM;
+
+ sof_audio_card_pcm512x.dai_link = dai_links;
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ sof_audio_card_pcm512x.dev = &pdev->dev;
+
+ /* set platform name for each dailink */
+ ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_pcm512x,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(&sof_audio_card_pcm512x, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev,
+ &sof_audio_card_pcm512x);
+}
+
+static int sof_pcm512x_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_component *component = NULL;
+
+ for_each_card_components(card, component) {
+ if (!strcmp(component->name, pcm512x_component[0].name)) {
+ snd_soc_component_set_jack(component, NULL, NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver sof_audio = {
+ .probe = sof_audio_probe,
+ .remove = sof_pcm512x_remove,
+ .driver = {
+ .name = "sof_pcm512x",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(sof_audio)
+
+MODULE_DESCRIPTION("ASoC Intel(R) SOF + PCM512x Machine driver");
+MODULE_AUTHOR("Pierre-Louis Bossart");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sof_pcm512x");
diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c
new file mode 100644
index 000000000..5883d1fa3
--- /dev/null
+++ b/sound/soc/intel/boards/sof_rt5682.c
@@ -0,0 +1,904 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2019-2020 Intel Corporation.
+
+/*
+ * Intel SOF Machine Driver with Realtek rt5682 Codec
+ * and speaker codec MAX98357A or RT1015.
+ */
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/dmi.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/rt5682.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/rt1015.h"
+#include "../../codecs/rt5682.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "../common/soc-intel-quirks.h"
+#include "hda_dsp_common.h"
+#include "sof_maxim_common.h"
+
+#define NAME_SIZE 32
+
+#define SOF_RT5682_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0))
+#define SOF_RT5682_SSP_CODEC_MASK (GENMASK(2, 0))
+#define SOF_RT5682_MCLK_EN BIT(3)
+#define SOF_RT5682_MCLK_24MHZ BIT(4)
+#define SOF_SPEAKER_AMP_PRESENT BIT(5)
+#define SOF_RT5682_SSP_AMP_SHIFT 6
+#define SOF_RT5682_SSP_AMP_MASK (GENMASK(8, 6))
+#define SOF_RT5682_SSP_AMP(quirk) \
+ (((quirk) << SOF_RT5682_SSP_AMP_SHIFT) & SOF_RT5682_SSP_AMP_MASK)
+#define SOF_RT5682_MCLK_BYTCHT_EN BIT(9)
+#define SOF_RT5682_NUM_HDMIDEV_SHIFT 10
+#define SOF_RT5682_NUM_HDMIDEV_MASK (GENMASK(12, 10))
+#define SOF_RT5682_NUM_HDMIDEV(quirk) \
+ ((quirk << SOF_RT5682_NUM_HDMIDEV_SHIFT) & SOF_RT5682_NUM_HDMIDEV_MASK)
+#define SOF_RT1015_SPEAKER_AMP_PRESENT BIT(13)
+#define SOF_MAX98373_SPEAKER_AMP_PRESENT BIT(14)
+#define SOF_MAX98360A_SPEAKER_AMP_PRESENT BIT(15)
+
+/* Default: MCLK on, MCLK 19.2M, SSP0 */
+static unsigned long sof_rt5682_quirk = SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0);
+
+static int is_legacy_cpu;
+
+static struct snd_soc_jack sof_hdmi[3];
+
+struct sof_hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+struct sof_card_private {
+ struct clk *mclk;
+ struct snd_soc_jack sof_headset;
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+};
+
+static int sof_rt5682_quirk_cb(const struct dmi_system_id *id)
+{
+ sof_rt5682_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_rt5682_quirk_table[] = {
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max"),
+ },
+ .driver_data = (void *)(SOF_RT5682_SSP_CODEC(2)),
+ },
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "UP-CHT01"),
+ },
+ .driver_data = (void *)(SOF_RT5682_SSP_CODEC(2)),
+ },
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WhiskeyLake Client"),
+ },
+ .driver_data = (void *)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_MCLK_24MHZ |
+ SOF_RT5682_SSP_CODEC(1)),
+ },
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Hatch"),
+ },
+ .driver_data = (void *)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_MCLK_24MHZ |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1)),
+ },
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"),
+ },
+ .driver_data = (void *)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0)),
+ },
+ {
+ .callback = sof_rt5682_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Volteer"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98373_ALC5682I_I2S_UP4"),
+ },
+ .driver_data = (void *)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_MAX98373_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(2) |
+ SOF_RT5682_NUM_HDMIDEV(4)),
+ },
+ {}
+};
+
+static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct sof_hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ /* dai_link id is 1:1 mapped to the PCM device */
+ pcm->device = rtd->dai_link->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ /* need to enable ASRC function for 24MHz mclk rate */
+ if ((sof_rt5682_quirk & SOF_RT5682_MCLK_EN) &&
+ (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ)) {
+ rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER |
+ RT5682_AD_STEREO1_FILTER,
+ RT5682_CLK_SEL_I2S1_ASRC);
+ }
+
+ if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) {
+ /*
+ * The firmware might enable the clock at
+ * boot (this information may or may not
+ * be reflected in the enable clock register).
+ * To change the rate we must disable the clock
+ * first to cover these cases. Due to common
+ * clock framework restrictions that do not allow
+ * to disable a clock that has not been enabled,
+ * we need to enable the clock first.
+ */
+ ret = clk_prepare_enable(ctx->mclk);
+ if (!ret)
+ clk_disable_unprepare(ctx->mclk);
+
+ ret = clk_set_rate(ctx->mclk, 19200000);
+
+ if (ret)
+ dev_err(rtd->dev, "unable to set MCLK rate\n");
+ }
+
+ /*
+ * Headset buttons map to the google Reference headset.
+ * These can be configured by userspace.
+ */
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sof_headset, NULL, 0);
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->sof_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+};
+
+static void sof_rt5682_codec_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static int sof_rt5682_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int clk_id, clk_freq, pll_out, ret;
+
+ if (sof_rt5682_quirk & SOF_RT5682_MCLK_EN) {
+ if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) {
+ ret = clk_prepare_enable(ctx->mclk);
+ if (ret < 0) {
+ dev_err(rtd->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ }
+
+ clk_id = RT5682_PLL1_S_MCLK;
+ if (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ)
+ clk_freq = 24000000;
+ else
+ clk_freq = 19200000;
+ } else {
+ clk_id = RT5682_PLL1_S_BCLK1;
+ clk_freq = params_rate(params) * 50;
+ }
+
+ pll_out = params_rate(params) * 512;
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out);
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret);
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret);
+
+ /*
+ * slot_width should equal or large than data length, set them
+ * be the same
+ */
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2,
+ params_width(params));
+ if (ret < 0) {
+ dev_err(rtd->dev, "set TDM slot err:%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops sof_rt5682_ops = {
+ .hw_params = sof_rt5682_hw_params,
+};
+
+static int sof_rt1015_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *codec_dai;
+ int i, ret;
+
+ if (!snd_soc_card_get_codec_dai(card, "rt1015-aif"))
+ return 0;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ /* Set tdm/i2s1 master bclk ratio */
+ ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
+ if (ret < 0) {
+ dev_err(card->dev, "failed to set bclk ratio\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT1015_PLL_S_BCLK,
+ params_rate(params) * 64,
+ params_rate(params) * 256);
+ if (ret < 0) {
+ dev_err(card->dev, "failed to set pll\n");
+ return ret;
+ }
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT1015_SCLK_S_PLL,
+ params_rate(params) * 256,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "failed to set sysclk\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops sof_rt1015_ops = {
+ .hw_params = sof_rt1015_hw_params,
+};
+
+static struct snd_soc_dai_link_component platform_component[] = {
+ {
+ /* name might be overridden during probe */
+ .name = "0000:00:1f.3"
+ }
+};
+
+static int sof_card_late_probe(struct snd_soc_card *card)
+{
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = NULL;
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+ char jack_name[NAME_SIZE];
+ struct sof_hdmi_pcm *pcm;
+ int err;
+ int i = 0;
+
+ /* HDMI is not supported by SOF on Baytrail/CherryTrail */
+ if (is_legacy_cpu)
+ return 0;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ if (ctx->common_hdmi_codec_drv) {
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+ return hda_dsp_hdmi_build_controls(card, component);
+ }
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &sof_hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &sof_hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (sof_rt5682_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT) {
+ /* Disable Left and Right Spk pin after boot */
+ snd_soc_dapm_disable_pin(dapm, "Left Spk");
+ snd_soc_dapm_disable_pin(dapm, "Right Spk");
+ err = snd_soc_dapm_sync(dapm);
+ if (err < 0)
+ return err;
+ }
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
+
+static const struct snd_kcontrol_new sof_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Spk"),
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+
+};
+
+static const struct snd_soc_dapm_widget sof_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Spk", NULL),
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+};
+
+static const struct snd_soc_dapm_widget dmic_widgets[] = {
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route sof_map[] = {
+ /* HP jack connectors - unknown if we have jack detection */
+ { "Headphone Jack", NULL, "HPOL" },
+ { "Headphone Jack", NULL, "HPOR" },
+
+ /* other jacks */
+ { "IN1P", NULL, "Headset Mic" },
+};
+
+static const struct snd_soc_dapm_route speaker_map[] = {
+ /* speaker */
+ { "Spk", NULL, "Speaker" },
+};
+
+static const struct snd_soc_dapm_route speaker_map_lr[] = {
+ { "Left Spk", NULL, "Left SPO" },
+ { "Right Spk", NULL, "Right SPO" },
+};
+
+static const struct snd_soc_dapm_route dmic_map[] = {
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+static int speaker_codec_init_lr(struct snd_soc_pcm_runtime *rtd)
+{
+ return snd_soc_dapm_add_routes(&rtd->card->dapm, speaker_map_lr,
+ ARRAY_SIZE(speaker_map_lr));
+}
+
+static int speaker_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, speaker_map,
+ ARRAY_SIZE(speaker_map));
+
+ if (ret)
+ dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret);
+ return ret;
+}
+
+static int dmic_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets,
+ ARRAY_SIZE(dmic_widgets));
+ if (ret) {
+ dev_err(card->dev, "DMic widget addition failed: %d\n", ret);
+ /* Don't need to add routes if widget addition failed */
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map,
+ ARRAY_SIZE(dmic_map));
+
+ if (ret)
+ dev_err(card->dev, "DMic map addition failed: %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_codec_conf rt1015_amp_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1015:00"),
+ .name_prefix = "Left",
+ },
+ {
+ .dlc = COMP_CODEC_CONF("i2c-10EC1015:01"),
+ .name_prefix = "Right",
+ },
+};
+
+/* sof audio machine driver for rt5682 codec */
+static struct snd_soc_card sof_audio_card_rt5682 = {
+ .name = "rt5682", /* the sof- prefix is added by the core */
+ .owner = THIS_MODULE,
+ .controls = sof_controls,
+ .num_controls = ARRAY_SIZE(sof_controls),
+ .dapm_widgets = sof_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sof_widgets),
+ .dapm_routes = sof_map,
+ .num_dapm_routes = ARRAY_SIZE(sof_map),
+ .fully_routed = true,
+ .late_probe = sof_card_late_probe,
+};
+
+static struct snd_soc_dai_link_component rt5682_component[] = {
+ {
+ .name = "i2c-10EC5682:00",
+ .dai_name = "rt5682-aif1",
+ }
+};
+
+static struct snd_soc_dai_link_component dmic_component[] = {
+ {
+ .name = "dmic-codec",
+ .dai_name = "dmic-hifi",
+ }
+};
+
+static struct snd_soc_dai_link_component max98357a_component[] = {
+ {
+ .name = "MX98357A:00",
+ .dai_name = "HiFi",
+ }
+};
+
+static struct snd_soc_dai_link_component max98360a_component[] = {
+ {
+ .name = "MX98360A:00",
+ .dai_name = "HiFi",
+ }
+};
+
+static struct snd_soc_dai_link_component rt1015_components[] = {
+ {
+ .name = "i2c-10EC1015:00",
+ .dai_name = "rt1015-aif",
+ },
+ {
+ .name = "i2c-10EC1015:01",
+ .dai_name = "rt1015-aif",
+ },
+};
+
+static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev,
+ int ssp_codec,
+ int ssp_amp,
+ int dmic_be_num,
+ int hdmi_num)
+{
+ struct snd_soc_dai_link_component *idisp_components;
+ struct snd_soc_dai_link_component *cpus;
+ struct snd_soc_dai_link *links;
+ int i, id = 0;
+
+ links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) *
+ sof_audio_card_rt5682.num_links, GFP_KERNEL);
+ cpus = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link_component) *
+ sof_audio_card_rt5682.num_links, GFP_KERNEL);
+ if (!links || !cpus)
+ goto devm_err;
+
+ /* codec SSP */
+ links[id].name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d-Codec", ssp_codec);
+ if (!links[id].name)
+ goto devm_err;
+
+ links[id].id = id;
+ links[id].codecs = rt5682_component;
+ links[id].num_codecs = ARRAY_SIZE(rt5682_component);
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].init = sof_rt5682_codec_init;
+ links[id].exit = sof_rt5682_codec_exit;
+ links[id].ops = &sof_rt5682_ops;
+ links[id].nonatomic = true;
+ links[id].dpcm_playback = 1;
+ links[id].dpcm_capture = 1;
+ links[id].no_pcm = 1;
+ links[id].cpus = &cpus[id];
+ links[id].num_cpus = 1;
+ if (is_legacy_cpu) {
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "ssp%d-port",
+ ssp_codec);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+ } else {
+ /*
+ * Currently, On SKL+ platforms MCLK will be turned off in sof
+ * runtime suspended, and it will go into runtime suspended
+ * right after playback is stop. However, rt5682 will output
+ * static noise if sysclk turns off during playback. Set
+ * ignore_pmdown_time to power down rt5682 immediately and
+ * avoid the noise.
+ * It can be removed once we can control MCLK by driver.
+ */
+ links[id].ignore_pmdown_time = 1;
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d Pin",
+ ssp_codec);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+ }
+ id++;
+
+ /* dmic */
+ if (dmic_be_num > 0) {
+ /* at least we have dmic01 */
+ links[id].name = "dmic01";
+ links[id].cpus = &cpus[id];
+ links[id].cpus->dai_name = "DMIC01 Pin";
+ links[id].init = dmic_init;
+ if (dmic_be_num > 1) {
+ /* set up 2 BE links at most */
+ links[id + 1].name = "dmic16k";
+ links[id + 1].cpus = &cpus[id + 1];
+ links[id + 1].cpus->dai_name = "DMIC16k Pin";
+ dmic_be_num = 2;
+ }
+ }
+
+ for (i = 0; i < dmic_be_num; i++) {
+ links[id].id = id;
+ links[id].num_cpus = 1;
+ links[id].codecs = dmic_component;
+ links[id].num_codecs = ARRAY_SIZE(dmic_component);
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].ignore_suspend = 1;
+ links[id].dpcm_capture = 1;
+ links[id].no_pcm = 1;
+ id++;
+ }
+
+ /* HDMI */
+ if (hdmi_num > 0) {
+ idisp_components = devm_kzalloc(dev,
+ sizeof(struct snd_soc_dai_link_component) *
+ hdmi_num, GFP_KERNEL);
+ if (!idisp_components)
+ goto devm_err;
+ }
+ for (i = 1; i <= hdmi_num; i++) {
+ links[id].name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d", i);
+ if (!links[id].name)
+ goto devm_err;
+
+ links[id].id = id;
+ links[id].cpus = &cpus[id];
+ links[id].num_cpus = 1;
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d Pin", i);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+
+ idisp_components[i - 1].name = "ehdaudio0D2";
+ idisp_components[i - 1].dai_name = devm_kasprintf(dev,
+ GFP_KERNEL,
+ "intel-hdmi-hifi%d",
+ i);
+ if (!idisp_components[i - 1].dai_name)
+ goto devm_err;
+
+ links[id].codecs = &idisp_components[i - 1];
+ links[id].num_codecs = 1;
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].init = sof_hdmi_init;
+ links[id].dpcm_playback = 1;
+ links[id].no_pcm = 1;
+ id++;
+ }
+
+ /* speaker amp */
+ if (sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) {
+ links[id].name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d-Codec", ssp_amp);
+ if (!links[id].name)
+ goto devm_err;
+
+ links[id].id = id;
+ if (sof_rt5682_quirk & SOF_RT1015_SPEAKER_AMP_PRESENT) {
+ links[id].codecs = rt1015_components;
+ links[id].num_codecs = ARRAY_SIZE(rt1015_components);
+ links[id].init = speaker_codec_init_lr;
+ links[id].ops = &sof_rt1015_ops;
+ } else if (sof_rt5682_quirk &
+ SOF_MAX98373_SPEAKER_AMP_PRESENT) {
+ links[id].codecs = max_98373_components;
+ links[id].num_codecs = ARRAY_SIZE(max_98373_components);
+ links[id].init = max98373_spk_codec_init;
+ links[id].ops = &max_98373_ops;
+ } else if (sof_rt5682_quirk &
+ SOF_MAX98360A_SPEAKER_AMP_PRESENT) {
+ links[id].codecs = max98360a_component;
+ links[id].num_codecs = ARRAY_SIZE(max98360a_component);
+ links[id].init = speaker_codec_init;
+ } else {
+ links[id].codecs = max98357a_component;
+ links[id].num_codecs = ARRAY_SIZE(max98357a_component);
+ links[id].init = speaker_codec_init;
+ }
+ links[id].platforms = platform_component;
+ links[id].num_platforms = ARRAY_SIZE(platform_component);
+ links[id].nonatomic = true;
+ links[id].dpcm_playback = 1;
+ /* feedback stream or firmware-generated echo reference */
+ links[id].dpcm_capture = 1;
+
+ links[id].no_pcm = 1;
+ links[id].cpus = &cpus[id];
+ links[id].num_cpus = 1;
+ if (is_legacy_cpu) {
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "ssp%d-port",
+ ssp_amp);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+
+ } else {
+ links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d Pin",
+ ssp_amp);
+ if (!links[id].cpus->dai_name)
+ goto devm_err;
+ }
+ }
+
+ return links;
+devm_err:
+ return NULL;
+}
+
+static int sof_audio_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_link *dai_links;
+ struct snd_soc_acpi_mach *mach;
+ struct sof_card_private *ctx;
+ int dmic_be_num, hdmi_num;
+ int ret, ssp_amp, ssp_codec;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ if (pdev->id_entry && pdev->id_entry->driver_data)
+ sof_rt5682_quirk = (unsigned long)pdev->id_entry->driver_data;
+
+ dmi_check_system(sof_rt5682_quirk_table);
+
+ mach = pdev->dev.platform_data;
+
+ /* A speaker amp might not be present when the quirk claims one is.
+ * Detect this via whether the machine driver match includes quirk_data.
+ */
+ if ((sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) && !mach->quirk_data)
+ sof_rt5682_quirk &= ~SOF_SPEAKER_AMP_PRESENT;
+
+ if (soc_intel_is_byt() || soc_intel_is_cht()) {
+ is_legacy_cpu = 1;
+ dmic_be_num = 0;
+ hdmi_num = 0;
+ /* default quirk for legacy cpu */
+ sof_rt5682_quirk = SOF_RT5682_MCLK_EN |
+ SOF_RT5682_MCLK_BYTCHT_EN |
+ SOF_RT5682_SSP_CODEC(2);
+ } else {
+ dmic_be_num = 2;
+ hdmi_num = (sof_rt5682_quirk & SOF_RT5682_NUM_HDMIDEV_MASK) >>
+ SOF_RT5682_NUM_HDMIDEV_SHIFT;
+ /* default number of HDMI DAI's */
+ if (!hdmi_num)
+ hdmi_num = 3;
+ }
+
+ /* need to get main clock from pmc */
+ if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) {
+ ctx->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3");
+ if (IS_ERR(ctx->mclk)) {
+ ret = PTR_ERR(ctx->mclk);
+
+ dev_err(&pdev->dev,
+ "Failed to get MCLK from pmc_plt_clk_3: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ctx->mclk);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "could not configure MCLK state");
+ return ret;
+ }
+ }
+
+ dev_dbg(&pdev->dev, "sof_rt5682_quirk = %lx\n", sof_rt5682_quirk);
+
+ ssp_amp = (sof_rt5682_quirk & SOF_RT5682_SSP_AMP_MASK) >>
+ SOF_RT5682_SSP_AMP_SHIFT;
+
+ ssp_codec = sof_rt5682_quirk & SOF_RT5682_SSP_CODEC_MASK;
+
+ /* compute number of dai links */
+ sof_audio_card_rt5682.num_links = 1 + dmic_be_num + hdmi_num;
+
+ if (sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT)
+ sof_audio_card_rt5682.num_links++;
+
+ if (sof_rt5682_quirk & SOF_MAX98373_SPEAKER_AMP_PRESENT)
+ sof_max98373_codec_conf(&sof_audio_card_rt5682);
+
+ dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, ssp_amp,
+ dmic_be_num, hdmi_num);
+ if (!dai_links)
+ return -ENOMEM;
+
+ sof_audio_card_rt5682.dai_link = dai_links;
+
+ if (sof_rt5682_quirk & SOF_RT1015_SPEAKER_AMP_PRESENT) {
+ sof_audio_card_rt5682.codec_conf = rt1015_amp_conf;
+ sof_audio_card_rt5682.num_configs = ARRAY_SIZE(rt1015_amp_conf);
+ }
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ sof_audio_card_rt5682.dev = &pdev->dev;
+
+ /* set platform name for each dailink */
+ ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_rt5682,
+ mach->mach_params.platform);
+ if (ret)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ snd_soc_card_set_drvdata(&sof_audio_card_rt5682, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev,
+ &sof_audio_card_rt5682);
+}
+
+static const struct platform_device_id board_ids[] = {
+ {
+ .name = "sof_rt5682",
+ },
+ {
+ .name = "tgl_max98357a_rt5682",
+ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1) |
+ SOF_RT5682_NUM_HDMIDEV(4)),
+ },
+ {
+ .name = "jsl_rt5682_rt1015",
+ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_MCLK_24MHZ |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_RT1015_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1)),
+ },
+ {
+ .name = "tgl_max98373_rt5682",
+ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_MAX98373_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1) |
+ SOF_RT5682_NUM_HDMIDEV(4)),
+ },
+ {
+ .name = "jsl_rt5682_max98360a",
+ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_MCLK_24MHZ |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_MAX98360A_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1)),
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, board_ids);
+
+static struct platform_driver sof_audio = {
+ .probe = sof_audio_probe,
+ .driver = {
+ .name = "sof_rt5682",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = board_ids,
+};
+module_platform_driver(sof_audio)
+
+/* Module information */
+MODULE_DESCRIPTION("SOF Audio Machine driver");
+MODULE_AUTHOR("Bard Liao <bard.liao@intel.com>");
+MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sof_rt5682");
+MODULE_ALIAS("platform:tgl_max98357a_rt5682");
+MODULE_ALIAS("platform:jsl_rt5682_rt1015");
+MODULE_ALIAS("platform:tgl_max98373_rt5682");
+MODULE_ALIAS("platform:jsl_rt5682_max98360a");
diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c
new file mode 100644
index 000000000..f36a0fda1
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw.c
@@ -0,0 +1,1343 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw - ASOC Machine driver for Intel SoundWire platforms
+ */
+
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "sof_sdw_common.h"
+#include "../../codecs/rt711.h"
+
+unsigned long sof_sdw_quirk = RT711_JD1;
+static int quirk_override = -1;
+module_param_named(quirk, quirk_override, int, 0444);
+MODULE_PARM_DESC(quirk, "Board-specific quirk override");
+
+#define INC_ID(BE, CPU, LINK) do { (BE)++; (CPU)++; (LINK)++; } while (0)
+
+static void log_quirks(struct device *dev)
+{
+ if (SOF_RT711_JDSRC(sof_sdw_quirk))
+ dev_dbg(dev, "quirk realtek,jack-detect-source %ld\n",
+ SOF_RT711_JDSRC(sof_sdw_quirk));
+ if (sof_sdw_quirk & SOF_SDW_FOUR_SPK)
+ dev_dbg(dev, "quirk SOF_SDW_FOUR_SPK enabled\n");
+ if (sof_sdw_quirk & SOF_SDW_TGL_HDMI)
+ dev_dbg(dev, "quirk SOF_SDW_TGL_HDMI enabled\n");
+ if (sof_sdw_quirk & SOF_SDW_PCH_DMIC)
+ dev_dbg(dev, "quirk SOF_SDW_PCH_DMIC enabled\n");
+ if (SOF_SSP_GET_PORT(sof_sdw_quirk))
+ dev_dbg(dev, "SSP port %ld\n",
+ SOF_SSP_GET_PORT(sof_sdw_quirk));
+ if (sof_sdw_quirk & SOF_RT715_DAI_ID_FIX)
+ dev_dbg(dev, "quirk SOF_RT715_DAI_ID_FIX enabled\n");
+ if (sof_sdw_quirk & SOF_SDW_NO_AGGREGATION)
+ dev_dbg(dev, "quirk SOF_SDW_NO_AGGREGATION enabled\n");
+}
+
+static int sof_sdw_quirk_cb(const struct dmi_system_id *id)
+{
+ sof_sdw_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_sdw_quirk_table[] = {
+ /* CometLake devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CometLake Client"),
+ },
+ .driver_data = (void *)SOF_SDW_PCH_DMIC,
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "09C6")
+ },
+ .driver_data = (void *)(RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX),
+ },
+ {
+ /* early version of SKU 09C6 */
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0983")
+ },
+ .driver_data = (void *)(RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "098F"),
+ },
+ .driver_data = (void *)(RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX |
+ SOF_SDW_FOUR_SPK),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0990"),
+ },
+ .driver_data = (void *)(RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX |
+ SOF_SDW_FOUR_SPK),
+ },
+ /* IceLake devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"),
+ },
+ .driver_data = (void *)SOF_SDW_PCH_DMIC,
+ },
+ /* TigerLake devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME,
+ "Tiger Lake Client Platform"),
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ RT711_JD1 |
+ SOF_SDW_PCH_DMIC |
+ SOF_SSP_PORT(SOF_I2S_SSP2)),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3E")
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A5E")
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX |
+ SOF_SDW_FOUR_SPK),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Volteer"),
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ SOF_SDW_PCH_DMIC |
+ SOF_SDW_FOUR_SPK),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ripto"),
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ SOF_SDW_PCH_DMIC |
+ SOF_SDW_FOUR_SPK),
+ },
+ {
+ /*
+ * this entry covers multiple HP SKUs. The family name
+ * does not seem robust enough, so we use a partial
+ * match that ignores the product name suffix
+ * (e.g. 15-eb1xxx, 14t-ea000 or 13-aw2xxx)
+ */
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible"),
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ SOF_SDW_PCH_DMIC |
+ RT711_JD2),
+ },
+ /* TigerLake-SDCA devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A32")
+ },
+ .driver_data = (void *)(SOF_SDW_TGL_HDMI |
+ RT711_JD2 |
+ SOF_RT715_DAI_ID_FIX |
+ SOF_SDW_FOUR_SPK),
+ },
+ /* AlderLake devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alder Lake Client Platform"),
+ },
+ .driver_data = (void *)(RT711_JD1 |
+ SOF_SDW_TGL_HDMI |
+ SOF_RT715_DAI_ID_FIX |
+ SOF_SDW_PCH_DMIC),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Meteor Lake Client Platform"),
+ },
+ .driver_data = (void *)(RT711_JD2_100K),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Rex"),
+ },
+ .driver_data = (void *)(SOF_SDW_PCH_DMIC),
+ },
+ /* LunarLake devices */
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Lunar Lake Client Platform"),
+ },
+ .driver_data = (void *)(RT711_JD2_100K),
+ },
+ {}
+};
+
+static struct snd_soc_dai_link_component dmic_component[] = {
+ {
+ .name = "dmic-codec",
+ .dai_name = "dmic-hifi",
+ }
+};
+
+static struct snd_soc_dai_link_component platform_component[] = {
+ {
+ /* name might be overridden during probe */
+ .name = "0000:00:1f.3"
+ }
+};
+
+/* these wrappers are only needed to avoid typecast compilation errors */
+int sdw_startup(struct snd_pcm_substream *substream)
+{
+ return sdw_startup_stream(substream);
+}
+
+int sdw_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sdw_stream_runtime *sdw_stream;
+ struct snd_soc_dai *dai;
+
+ /* Find stream from first CPU DAI */
+ dai = asoc_rtd_to_cpu(rtd, 0);
+
+ sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);
+
+ if (IS_ERR(sdw_stream)) {
+ dev_err(rtd->dev, "no stream found for DAI %s", dai->name);
+ return PTR_ERR(sdw_stream);
+ }
+
+ return sdw_prepare_stream(sdw_stream);
+}
+
+int sdw_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sdw_stream_runtime *sdw_stream;
+ struct snd_soc_dai *dai;
+ int ret;
+
+ /* Find stream from first CPU DAI */
+ dai = asoc_rtd_to_cpu(rtd, 0);
+
+ sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);
+
+ if (IS_ERR(sdw_stream)) {
+ dev_err(rtd->dev, "no stream found for DAI %s", dai->name);
+ return PTR_ERR(sdw_stream);
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = sdw_enable_stream(sdw_stream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = sdw_disable_stream(sdw_stream);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ dev_err(rtd->dev, "%s trigger %d failed: %d", __func__, cmd, ret);
+
+ return ret;
+}
+
+int sdw_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sdw_stream_runtime *sdw_stream;
+ struct snd_soc_dai *dai;
+
+ /* Find stream from first CPU DAI */
+ dai = asoc_rtd_to_cpu(rtd, 0);
+
+ sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);
+
+ if (IS_ERR(sdw_stream)) {
+ dev_err(rtd->dev, "no stream found for DAI %s", dai->name);
+ return PTR_ERR(sdw_stream);
+ }
+
+ return sdw_deprepare_stream(sdw_stream);
+}
+
+void sdw_shutdown(struct snd_pcm_substream *substream)
+{
+ sdw_shutdown_stream(substream);
+}
+
+static const struct snd_soc_ops sdw_ops = {
+ .startup = sdw_startup,
+ .prepare = sdw_prepare,
+ .trigger = sdw_trigger,
+ .hw_free = sdw_hw_free,
+ .shutdown = sdw_shutdown,
+};
+
+static struct sof_sdw_codec_info codec_info_list[] = {
+ {
+ .part_id = 0x700,
+ .direction = {true, true},
+ .dai_name = "rt700-aif1",
+ .init = sof_sdw_rt700_init,
+ },
+ {
+ .part_id = 0x711,
+ .version_id = 3,
+ .direction = {true, true},
+ .dai_name = "rt711-sdca-aif1",
+ .init = sof_sdw_rt711_sdca_init,
+ .exit = sof_sdw_rt711_sdca_exit,
+ },
+ {
+ .part_id = 0x711,
+ .version_id = 2,
+ .direction = {true, true},
+ .dai_name = "rt711-aif1",
+ .init = sof_sdw_rt711_init,
+ .exit = sof_sdw_rt711_exit,
+ },
+ {
+ .part_id = 0x1308,
+ .acpi_id = "10EC1308",
+ .direction = {true, false},
+ .dai_name = "rt1308-aif",
+ .ops = &sof_sdw_rt1308_i2s_ops,
+ .init = sof_sdw_rt1308_init,
+ },
+ {
+ .part_id = 0x1316,
+ .direction = {true, true},
+ .dai_name = "rt1316-aif",
+ .init = sof_sdw_rt1316_init,
+ },
+ {
+ .part_id = 0x714,
+ .version_id = 3,
+ .direction = {false, true},
+ .ignore_pch_dmic = true,
+ .dai_name = "rt715-aif2",
+ .init = sof_sdw_rt715_sdca_init,
+ },
+ {
+ .part_id = 0x715,
+ .version_id = 3,
+ .direction = {false, true},
+ .ignore_pch_dmic = true,
+ .dai_name = "rt715-aif2",
+ .init = sof_sdw_rt715_sdca_init,
+ },
+ {
+ .part_id = 0x714,
+ .version_id = 2,
+ .direction = {false, true},
+ .ignore_pch_dmic = true,
+ .dai_name = "rt715-aif2",
+ .init = sof_sdw_rt715_init,
+ },
+ {
+ .part_id = 0x715,
+ .version_id = 2,
+ .direction = {false, true},
+ .ignore_pch_dmic = true,
+ .dai_name = "rt715-aif2",
+ .init = sof_sdw_rt715_init,
+ },
+ {
+ .part_id = 0x8373,
+ .direction = {true, true},
+ .dai_name = "max98373-aif1",
+ .init = sof_sdw_mx8373_init,
+ .codec_card_late_probe = sof_sdw_mx8373_late_probe,
+ },
+ {
+ .part_id = 0x5682,
+ .direction = {true, true},
+ .dai_name = "rt5682-sdw",
+ .init = sof_sdw_rt5682_init,
+ },
+};
+
+static inline int find_codec_info_part(u64 adr)
+{
+ unsigned int part_id, sdw_version;
+ int i;
+
+ part_id = SDW_PART_ID(adr);
+ sdw_version = SDW_VERSION(adr);
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)
+ /*
+ * A codec info is for all sdw version with the part id if
+ * version_id is not specified in the codec info.
+ */
+ if (part_id == codec_info_list[i].part_id &&
+ (!codec_info_list[i].version_id ||
+ sdw_version == codec_info_list[i].version_id))
+ return i;
+
+ return -EINVAL;
+
+}
+
+static inline int find_codec_info_acpi(const u8 *acpi_id)
+{
+ int i;
+
+ if (!acpi_id[0])
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)
+ if (!memcmp(codec_info_list[i].acpi_id, acpi_id,
+ ACPI_ID_LEN))
+ break;
+
+ if (i == ARRAY_SIZE(codec_info_list))
+ return -EINVAL;
+
+ return i;
+}
+
+/*
+ * get BE dailink number and CPU DAI number based on sdw link adr.
+ * Since some sdw slaves may be aggregated, the CPU DAI number
+ * may be larger than the number of BE dailinks.
+ */
+static int get_sdw_dailink_info(const struct snd_soc_acpi_link_adr *links,
+ int *sdw_be_num, int *sdw_cpu_dai_num)
+{
+ const struct snd_soc_acpi_link_adr *link;
+ bool group_visited[SDW_MAX_GROUPS];
+ bool no_aggregation;
+ int i;
+
+ no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION;
+ *sdw_cpu_dai_num = 0;
+ *sdw_be_num = 0;
+
+ if (!links)
+ return -EINVAL;
+
+ for (i = 0; i < SDW_MAX_GROUPS; i++)
+ group_visited[i] = false;
+
+ for (link = links; link->num_adr; link++) {
+ const struct snd_soc_acpi_endpoint *endpoint;
+ int codec_index;
+ int stream;
+ u64 adr;
+
+ adr = link->adr_d->adr;
+ codec_index = find_codec_info_part(adr);
+ if (codec_index < 0)
+ return codec_index;
+
+ endpoint = link->adr_d->endpoints;
+
+ /* count DAI number for playback and capture */
+ for_each_pcm_streams(stream) {
+ if (!codec_info_list[codec_index].direction[stream])
+ continue;
+
+ (*sdw_cpu_dai_num)++;
+
+ /* count BE for each non-aggregated slave or group */
+ if (!endpoint->aggregated || no_aggregation ||
+ !group_visited[endpoint->group_id])
+ (*sdw_be_num)++;
+ }
+
+ if (endpoint->aggregated)
+ group_visited[endpoint->group_id] = true;
+ }
+
+ return 0;
+}
+
+static void init_dai_link(struct snd_soc_dai_link *dai_links, int be_id,
+ char *name, int playback, int capture,
+ struct snd_soc_dai_link_component *cpus,
+ int cpus_num,
+ struct snd_soc_dai_link_component *codecs,
+ int codecs_num,
+ int (*init)(struct snd_soc_pcm_runtime *rtd),
+ const struct snd_soc_ops *ops)
+{
+ dai_links->id = be_id;
+ dai_links->name = name;
+ dai_links->platforms = platform_component;
+ dai_links->num_platforms = ARRAY_SIZE(platform_component);
+ dai_links->nonatomic = true;
+ dai_links->no_pcm = 1;
+ dai_links->cpus = cpus;
+ dai_links->num_cpus = cpus_num;
+ dai_links->codecs = codecs;
+ dai_links->num_codecs = codecs_num;
+ dai_links->dpcm_playback = playback;
+ dai_links->dpcm_capture = capture;
+ dai_links->init = init;
+ dai_links->ops = ops;
+}
+
+static bool is_unique_device(const struct snd_soc_acpi_link_adr *link,
+ unsigned int sdw_version,
+ unsigned int mfg_id,
+ unsigned int part_id,
+ unsigned int class_id,
+ int index_in_link
+ )
+{
+ int i;
+
+ for (i = 0; i < link->num_adr; i++) {
+ unsigned int sdw1_version, mfg1_id, part1_id, class1_id;
+ u64 adr;
+
+ /* skip itself */
+ if (i == index_in_link)
+ continue;
+
+ adr = link->adr_d[i].adr;
+
+ sdw1_version = SDW_VERSION(adr);
+ mfg1_id = SDW_MFG_ID(adr);
+ part1_id = SDW_PART_ID(adr);
+ class1_id = SDW_CLASS_ID(adr);
+
+ if (sdw_version == sdw1_version &&
+ mfg_id == mfg1_id &&
+ part_id == part1_id &&
+ class_id == class1_id)
+ return false;
+ }
+
+ return true;
+}
+
+static int create_codec_dai_name(struct device *dev,
+ const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link_component *codec,
+ int offset,
+ struct snd_soc_codec_conf *codec_conf,
+ int codec_count,
+ int *codec_conf_index)
+{
+ int i;
+
+ /* sanity check */
+ if (*codec_conf_index + link->num_adr > codec_count) {
+ dev_err(dev, "codec_conf: out-of-bounds access requested\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < link->num_adr; i++) {
+ unsigned int sdw_version, unique_id, mfg_id;
+ unsigned int link_id, part_id, class_id;
+ int codec_index, comp_index;
+ char *codec_str;
+ u64 adr;
+
+ adr = link->adr_d[i].adr;
+
+ sdw_version = SDW_VERSION(adr);
+ link_id = SDW_DISCO_LINK_ID(adr);
+ unique_id = SDW_UNIQUE_ID(adr);
+ mfg_id = SDW_MFG_ID(adr);
+ part_id = SDW_PART_ID(adr);
+ class_id = SDW_CLASS_ID(adr);
+
+ comp_index = i + offset;
+ if (is_unique_device(link, sdw_version, mfg_id, part_id,
+ class_id, i)) {
+ codec_str = "sdw:%x:%x:%x:%x";
+ codec[comp_index].name =
+ devm_kasprintf(dev, GFP_KERNEL, codec_str,
+ link_id, mfg_id, part_id,
+ class_id);
+ } else {
+ codec_str = "sdw:%x:%x:%x:%x:%x";
+ codec[comp_index].name =
+ devm_kasprintf(dev, GFP_KERNEL, codec_str,
+ link_id, mfg_id, part_id,
+ class_id, unique_id);
+ }
+
+ if (!codec[comp_index].name)
+ return -ENOMEM;
+
+ codec_index = find_codec_info_part(adr);
+ if (codec_index < 0)
+ return codec_index;
+
+ codec[comp_index].dai_name =
+ codec_info_list[codec_index].dai_name;
+
+ codec_conf[*codec_conf_index].dlc = codec[comp_index];
+ codec_conf[*codec_conf_index].name_prefix = link->adr_d[i].name_prefix;
+
+ ++*codec_conf_index;
+ }
+
+ return 0;
+}
+
+static int set_codec_init_func(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ bool playback, int group_id)
+{
+ int i;
+
+ do {
+ /*
+ * Initialize the codec. If codec is part of an aggregated
+ * group (group_id>0), initialize all codecs belonging to
+ * same group.
+ */
+ for (i = 0; i < link->num_adr; i++) {
+ int codec_index;
+
+ codec_index = find_codec_info_part(link->adr_d[i].adr);
+
+ if (codec_index < 0)
+ return codec_index;
+ /* The group_id is > 0 iff the codec is aggregated */
+ if (link->adr_d[i].endpoints->group_id != group_id)
+ continue;
+ if (codec_info_list[codec_index].init)
+ codec_info_list[codec_index].init(link,
+ dai_links,
+ &codec_info_list[codec_index],
+ playback);
+ }
+ link++;
+ } while (link->mask && group_id);
+
+ return 0;
+}
+
+/*
+ * check endpoint status in slaves and gather link ID for all slaves in
+ * the same group to generate different CPU DAI. Now only support
+ * one sdw link with all slaves set with only single group id.
+ *
+ * one slave on one sdw link with aggregated = 0
+ * one sdw BE DAI <---> one-cpu DAI <---> one-codec DAI
+ *
+ * two or more slaves on one sdw link with aggregated = 0
+ * one sdw BE DAI <---> one-cpu DAI <---> multi-codec DAIs
+ *
+ * multiple links with multiple slaves with aggregated = 1
+ * one sdw BE DAI <---> 1 .. N CPU DAIs <----> 1 .. N codec DAIs
+ */
+static int get_slave_info(const struct snd_soc_acpi_link_adr *adr_link,
+ struct device *dev, int *cpu_dai_id, int *cpu_dai_num,
+ int *codec_num, int *group_id,
+ bool *group_generated)
+{
+ const struct snd_soc_acpi_adr_device *adr_d;
+ const struct snd_soc_acpi_link_adr *adr_next;
+ bool no_aggregation;
+ int index = 0;
+
+ no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION;
+ *codec_num = adr_link->num_adr;
+ adr_d = adr_link->adr_d;
+
+ /* make sure the link mask has a single bit set */
+ if (!is_power_of_2(adr_link->mask))
+ return -EINVAL;
+
+ cpu_dai_id[index++] = ffs(adr_link->mask) - 1;
+ if (!adr_d->endpoints->aggregated || no_aggregation) {
+ *cpu_dai_num = 1;
+ *group_id = 0;
+ return 0;
+ }
+
+ *group_id = adr_d->endpoints->group_id;
+
+ /* gather other link ID of slaves in the same group */
+ for (adr_next = adr_link + 1; adr_next && adr_next->num_adr;
+ adr_next++) {
+ const struct snd_soc_acpi_endpoint *endpoint;
+
+ endpoint = adr_next->adr_d->endpoints;
+ if (!endpoint->aggregated ||
+ endpoint->group_id != *group_id)
+ continue;
+
+ /* make sure the link mask has a single bit set */
+ if (!is_power_of_2(adr_next->mask))
+ return -EINVAL;
+
+ if (index >= SDW_MAX_CPU_DAIS) {
+ dev_err(dev, " cpu_dai_id array overflows");
+ return -EINVAL;
+ }
+
+ cpu_dai_id[index++] = ffs(adr_next->mask) - 1;
+ *codec_num += adr_next->num_adr;
+ }
+
+ /*
+ * indicate CPU DAIs for this group have been generated
+ * to avoid generating CPU DAIs for this group again.
+ */
+ group_generated[*group_id] = true;
+ *cpu_dai_num = index;
+
+ return 0;
+}
+
+static int create_sdw_dailink(struct device *dev, int *be_index,
+ struct snd_soc_dai_link *dai_links,
+ int sdw_be_num, int sdw_cpu_dai_num,
+ struct snd_soc_dai_link_component *cpus,
+ const struct snd_soc_acpi_link_adr *link,
+ int *cpu_id, bool *group_generated,
+ struct snd_soc_codec_conf *codec_conf,
+ int codec_count,
+ int *codec_conf_index,
+ bool *ignore_pch_dmic)
+{
+ const struct snd_soc_acpi_link_adr *link_next;
+ struct snd_soc_dai_link_component *codecs;
+ int cpu_dai_id[SDW_MAX_CPU_DAIS];
+ int cpu_dai_num, cpu_dai_index;
+ unsigned int group_id;
+ int codec_idx = 0;
+ int i = 0, j = 0;
+ int codec_index;
+ int codec_num;
+ int stream;
+ int ret;
+ int k;
+
+ ret = get_slave_info(link, dev, cpu_dai_id, &cpu_dai_num, &codec_num,
+ &group_id, group_generated);
+ if (ret)
+ return ret;
+
+ codecs = devm_kcalloc(dev, codec_num, sizeof(*codecs), GFP_KERNEL);
+ if (!codecs)
+ return -ENOMEM;
+
+ /* generate codec name on different links in the same group */
+ for (link_next = link; link_next && link_next->num_adr &&
+ i < cpu_dai_num; link_next++) {
+ const struct snd_soc_acpi_endpoint *endpoints;
+
+ endpoints = link_next->adr_d->endpoints;
+ if (group_id && (!endpoints->aggregated ||
+ endpoints->group_id != group_id))
+ continue;
+
+ /* skip the link excluded by this processed group */
+ if (cpu_dai_id[i] != ffs(link_next->mask) - 1)
+ continue;
+
+ ret = create_codec_dai_name(dev, link_next, codecs, codec_idx,
+ codec_conf, codec_count, codec_conf_index);
+ if (ret < 0)
+ return ret;
+
+ /* check next link to create codec dai in the processed group */
+ i++;
+ codec_idx += link_next->num_adr;
+ }
+
+ /* find codec info to create BE DAI */
+ codec_index = find_codec_info_part(link->adr_d[0].adr);
+ if (codec_index < 0)
+ return codec_index;
+
+ if (codec_info_list[codec_index].ignore_pch_dmic)
+ *ignore_pch_dmic = true;
+
+ cpu_dai_index = *cpu_id;
+ for_each_pcm_streams(stream) {
+ char *name, *cpu_name;
+ int playback, capture;
+ static const char * const sdw_stream_name[] = {
+ "SDW%d-Playback",
+ "SDW%d-Capture",
+ };
+
+ if (!codec_info_list[codec_index].direction[stream])
+ continue;
+
+ /* create stream name according to first link id */
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ sdw_stream_name[stream], cpu_dai_id[0]);
+ if (!name)
+ return -ENOMEM;
+
+ /*
+ * generate CPU DAI name base on the sdw link ID and
+ * PIN ID with offset of 2 according to sdw dai driver.
+ */
+ for (k = 0; k < cpu_dai_num; k++) {
+ cpu_name = devm_kasprintf(dev, GFP_KERNEL,
+ "SDW%d Pin%d", cpu_dai_id[k],
+ j + SDW_INTEL_BIDIR_PDI_BASE);
+ if (!cpu_name)
+ return -ENOMEM;
+
+ if (cpu_dai_index >= sdw_cpu_dai_num) {
+ dev_err(dev, "invalid cpu dai index %d",
+ cpu_dai_index);
+ return -EINVAL;
+ }
+
+ cpus[cpu_dai_index++].dai_name = cpu_name;
+ }
+
+ if (*be_index >= sdw_be_num) {
+ dev_err(dev, " invalid be dai index %d", *be_index);
+ return -EINVAL;
+ }
+
+ if (*cpu_id >= sdw_cpu_dai_num) {
+ dev_err(dev, " invalid cpu dai index %d", *cpu_id);
+ return -EINVAL;
+ }
+
+ playback = (stream == SNDRV_PCM_STREAM_PLAYBACK);
+ capture = (stream == SNDRV_PCM_STREAM_CAPTURE);
+ init_dai_link(dai_links + *be_index, *be_index, name,
+ playback, capture,
+ cpus + *cpu_id, cpu_dai_num,
+ codecs, codec_num,
+ NULL, &sdw_ops);
+ /*
+ * SoundWire DAILINKs use 'stream' functions and Bank Switch operations
+ * based on wait_for_completion(), tag them as 'nonatomic'.
+ */
+ dai_links[*be_index].nonatomic = true;
+
+ ret = set_codec_init_func(link, dai_links + (*be_index)++,
+ playback, group_id);
+ if (ret < 0) {
+ dev_err(dev, "failed to init codec %d", codec_index);
+ return ret;
+ }
+
+ *cpu_id += cpu_dai_num;
+ j++;
+ }
+
+ return 0;
+}
+
+/*
+ * DAI link ID of SSP & DMIC & HDMI are based on last
+ * link ID used by sdw link. Since be_id may be changed
+ * in init func of sdw codec, it is not equal to be_id
+ */
+static inline int get_next_be_id(struct snd_soc_dai_link *links,
+ int be_id)
+{
+ return links[be_id - 1].id + 1;
+}
+
+#define IDISP_CODEC_MASK 0x4
+
+static int sof_card_codec_conf_alloc(struct device *dev,
+ struct snd_soc_acpi_mach_params *mach_params,
+ struct snd_soc_codec_conf **codec_conf,
+ int *codec_conf_count)
+{
+ const struct snd_soc_acpi_link_adr *adr_link;
+ struct snd_soc_codec_conf *c_conf;
+ int num_codecs = 0;
+ int i;
+
+ adr_link = mach_params->links;
+ if (!adr_link)
+ return -EINVAL;
+
+ /* generate DAI links by each sdw link */
+ for (; adr_link->num_adr; adr_link++) {
+ for (i = 0; i < adr_link->num_adr; i++) {
+ if (!adr_link->adr_d[i].name_prefix) {
+ dev_err(dev, "codec 0x%llx does not have a name prefix\n",
+ adr_link->adr_d[i].adr);
+ return -EINVAL;
+ }
+ }
+ num_codecs += adr_link->num_adr;
+ }
+
+ c_conf = devm_kzalloc(dev, num_codecs * sizeof(*c_conf), GFP_KERNEL);
+ if (!c_conf)
+ return -ENOMEM;
+
+ *codec_conf = c_conf;
+ *codec_conf_count = num_codecs;
+
+ return 0;
+}
+
+static int sof_card_dai_links_create(struct device *dev,
+ struct snd_soc_acpi_mach *mach,
+ struct snd_soc_card *card)
+{
+ int ssp_num, sdw_be_num = 0, hdmi_num = 0, dmic_num;
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai_link_component *idisp_components;
+ struct snd_soc_dai_link_component *ssp_components;
+ struct snd_soc_acpi_mach_params *mach_params;
+ const struct snd_soc_acpi_link_adr *adr_link;
+ struct snd_soc_dai_link_component *cpus;
+ struct snd_soc_codec_conf *codec_conf;
+ bool ignore_pch_dmic = false;
+ int codec_conf_count;
+ int codec_conf_index = 0;
+ bool group_generated[SDW_MAX_GROUPS];
+ int ssp_codec_index, ssp_mask;
+ struct snd_soc_dai_link *links;
+ int num_links, link_id = 0;
+ char *name, *cpu_name;
+ int total_cpu_dai_num;
+ int sdw_cpu_dai_num;
+ int i, j, be_id = 0;
+ int cpu_id = 0;
+ int comp_num;
+ int ret;
+
+ mach_params = &mach->mach_params;
+
+ /* allocate codec conf, will be populated when dailinks are created */
+ ret = sof_card_codec_conf_alloc(dev, mach_params, &codec_conf, &codec_conf_count);
+ if (ret < 0)
+ return ret;
+
+ /* reset amp_num to ensure amp_num++ starts from 0 in each probe */
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)
+ codec_info_list[i].amp_num = 0;
+
+ if (sof_sdw_quirk & SOF_SDW_TGL_HDMI)
+ hdmi_num = SOF_TGL_HDMI_COUNT;
+ else
+ hdmi_num = SOF_PRE_TGL_HDMI_COUNT;
+
+ ssp_mask = SOF_SSP_GET_PORT(sof_sdw_quirk);
+ /*
+ * on generic tgl platform, I2S or sdw mode is supported
+ * based on board rework. A ACPI device is registered in
+ * system only when I2S mode is supported, not sdw mode.
+ * Here check ACPI ID to confirm I2S is supported.
+ */
+ ssp_codec_index = find_codec_info_acpi(mach->id);
+ ssp_num = ssp_codec_index >= 0 ? hweight_long(ssp_mask) : 0;
+ comp_num = hdmi_num + ssp_num;
+
+ ret = get_sdw_dailink_info(mach_params->links,
+ &sdw_be_num, &sdw_cpu_dai_num);
+ if (ret < 0) {
+ dev_err(dev, "failed to get sdw link info %d", ret);
+ return ret;
+ }
+
+ if (mach_params->codec_mask & IDISP_CODEC_MASK)
+ ctx->idisp_codec = true;
+
+ /* enable dmic01 & dmic16k */
+ dmic_num = (sof_sdw_quirk & SOF_SDW_PCH_DMIC || mach_params->dmic_num) ? 2 : 0;
+ comp_num += dmic_num;
+
+ dev_dbg(dev, "sdw %d, ssp %d, dmic %d, hdmi %d", sdw_be_num, ssp_num,
+ dmic_num, ctx->idisp_codec ? hdmi_num : 0);
+
+ /* allocate BE dailinks */
+ num_links = comp_num + sdw_be_num;
+ links = devm_kcalloc(dev, num_links, sizeof(*links), GFP_KERNEL);
+
+ /* allocated CPU DAIs */
+ total_cpu_dai_num = comp_num + sdw_cpu_dai_num;
+ cpus = devm_kcalloc(dev, total_cpu_dai_num, sizeof(*cpus),
+ GFP_KERNEL);
+
+ if (!links || !cpus)
+ return -ENOMEM;
+
+ /* SDW */
+ if (!sdw_be_num)
+ goto SSP;
+
+ adr_link = mach_params->links;
+ if (!adr_link)
+ return -EINVAL;
+
+ /*
+ * SoundWire Slaves aggregated in the same group may be
+ * located on different hardware links. Clear array to indicate
+ * CPU DAIs for this group have not been generated.
+ */
+ for (i = 0; i < SDW_MAX_GROUPS; i++)
+ group_generated[i] = false;
+
+ /* generate DAI links by each sdw link */
+ for (; adr_link->num_adr; adr_link++) {
+ const struct snd_soc_acpi_endpoint *endpoint;
+
+ endpoint = adr_link->adr_d->endpoints;
+ if (endpoint->aggregated && !endpoint->group_id) {
+ dev_err(dev, "invalid group id on link %x",
+ adr_link->mask);
+ continue;
+ }
+
+ /* this group has been generated */
+ if (endpoint->aggregated &&
+ group_generated[endpoint->group_id])
+ continue;
+
+ ret = create_sdw_dailink(dev, &be_id, links, sdw_be_num,
+ sdw_cpu_dai_num, cpus, adr_link,
+ &cpu_id, group_generated,
+ codec_conf, codec_conf_count,
+ &codec_conf_index,
+ &ignore_pch_dmic);
+ if (ret < 0) {
+ dev_err(dev, "failed to create dai link %d", be_id);
+ return -ENOMEM;
+ }
+ }
+
+ /* non-sdw DAI follows sdw DAI */
+ link_id = be_id;
+
+ /* get BE ID for non-sdw DAI */
+ be_id = get_next_be_id(links, be_id);
+
+SSP:
+ /* SSP */
+ if (!ssp_num)
+ goto DMIC;
+
+ for (i = 0, j = 0; ssp_mask; i++, ssp_mask >>= 1) {
+ struct sof_sdw_codec_info *info;
+ int playback, capture;
+ char *codec_name;
+
+ if (!(ssp_mask & 0x1))
+ continue;
+
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ "SSP%d-Codec", i);
+ if (!name)
+ return -ENOMEM;
+
+ cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", i);
+ if (!cpu_name)
+ return -ENOMEM;
+
+ ssp_components = devm_kzalloc(dev, sizeof(*ssp_components),
+ GFP_KERNEL);
+ if (!ssp_components)
+ return -ENOMEM;
+
+ info = &codec_info_list[ssp_codec_index];
+ codec_name = devm_kasprintf(dev, GFP_KERNEL, "i2c-%s:0%d",
+ info->acpi_id, j++);
+ if (!codec_name)
+ return -ENOMEM;
+
+ ssp_components->name = codec_name;
+ ssp_components->dai_name = info->dai_name;
+ cpus[cpu_id].dai_name = cpu_name;
+
+ playback = info->direction[SNDRV_PCM_STREAM_PLAYBACK];
+ capture = info->direction[SNDRV_PCM_STREAM_CAPTURE];
+ init_dai_link(links + link_id, be_id, name,
+ playback, capture,
+ cpus + cpu_id, 1,
+ ssp_components, 1,
+ NULL, info->ops);
+
+ ret = info->init(NULL, links + link_id, info, 0);
+ if (ret < 0)
+ return ret;
+
+ INC_ID(be_id, cpu_id, link_id);
+ }
+
+DMIC:
+ /* dmic */
+ if (dmic_num > 0) {
+ if (ignore_pch_dmic) {
+ dev_warn(dev, "Ignoring PCH DMIC\n");
+ goto HDMI;
+ }
+ cpus[cpu_id].dai_name = "DMIC01 Pin";
+ init_dai_link(links + link_id, be_id, "dmic01",
+ 0, 1, // DMIC only supports capture
+ cpus + cpu_id, 1,
+ dmic_component, 1,
+ sof_sdw_dmic_init, NULL);
+ INC_ID(be_id, cpu_id, link_id);
+
+ cpus[cpu_id].dai_name = "DMIC16k Pin";
+ init_dai_link(links + link_id, be_id, "dmic16k",
+ 0, 1, // DMIC only supports capture
+ cpus + cpu_id, 1,
+ dmic_component, 1,
+ /* don't call sof_sdw_dmic_init() twice */
+ NULL, NULL);
+ INC_ID(be_id, cpu_id, link_id);
+ }
+
+HDMI:
+ /* HDMI */
+ if (hdmi_num > 0) {
+ idisp_components = devm_kcalloc(dev, hdmi_num,
+ sizeof(*idisp_components),
+ GFP_KERNEL);
+ if (!idisp_components)
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < hdmi_num; i++) {
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d", i + 1);
+ if (!name)
+ return -ENOMEM;
+
+ if (ctx->idisp_codec) {
+ idisp_components[i].name = "ehdaudio0D2";
+ idisp_components[i].dai_name = devm_kasprintf(dev,
+ GFP_KERNEL,
+ "intel-hdmi-hifi%d",
+ i + 1);
+ if (!idisp_components[i].dai_name)
+ return -ENOMEM;
+ } else {
+ idisp_components[i].name = "snd-soc-dummy";
+ idisp_components[i].dai_name = "snd-soc-dummy-dai";
+ }
+
+ cpu_name = devm_kasprintf(dev, GFP_KERNEL,
+ "iDisp%d Pin", i + 1);
+ if (!cpu_name)
+ return -ENOMEM;
+
+ cpus[cpu_id].dai_name = cpu_name;
+ init_dai_link(links + link_id, be_id, name,
+ 1, 0, // HDMI only supports playback
+ cpus + cpu_id, 1,
+ idisp_components + i, 1,
+ sof_sdw_hdmi_init, NULL);
+ INC_ID(be_id, cpu_id, link_id);
+ }
+
+ card->dai_link = links;
+ card->num_links = num_links;
+
+ card->codec_conf = codec_conf;
+ card->num_configs = codec_conf_count;
+
+ return 0;
+}
+
+static int sof_sdw_card_late_probe(struct snd_soc_card *card)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) {
+ if (!codec_info_list[i].late_probe)
+ continue;
+
+ ret = codec_info_list[i].codec_card_late_probe(card);
+ if (ret < 0)
+ return ret;
+ }
+
+ return sof_sdw_hdmi_card_late_probe(card);
+}
+
+/* SoC card */
+static const char sdw_card_long_name[] = "Intel Soundwire SOF";
+
+static struct snd_soc_card card_sof_sdw = {
+ .name = "soundwire",
+ .owner = THIS_MODULE,
+ .late_probe = sof_sdw_card_late_probe,
+};
+
+static int mc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &card_sof_sdw;
+ struct snd_soc_acpi_mach *mach;
+ struct mc_private *ctx;
+ int amp_num = 0, i;
+ int ret;
+
+ dev_dbg(&pdev->dev, "Entry %s\n", __func__);
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ dmi_check_system(sof_sdw_quirk_table);
+
+ if (quirk_override != -1) {
+ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n",
+ sof_sdw_quirk, quirk_override);
+ sof_sdw_quirk = quirk_override;
+ }
+ log_quirks(&pdev->dev);
+
+ INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
+ card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(card, ctx);
+
+ mach = pdev->dev.platform_data;
+ ret = sof_card_dai_links_create(&pdev->dev, mach,
+ card);
+ if (ret < 0)
+ return ret;
+
+ ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv;
+
+ /*
+ * the default amp_num is zero for each codec and
+ * amp_num will only be increased for active amp
+ * codecs on used platform
+ */
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)
+ amp_num += codec_info_list[i].amp_num;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "cfg-spk:%d cfg-amp:%d",
+ (sof_sdw_quirk & SOF_SDW_FOUR_SPK)
+ ? 4 : 2, amp_num);
+ if (!card->components)
+ return -ENOMEM;
+
+ card->long_name = sdw_card_long_name;
+
+ /* Register the card */
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret) {
+ dev_err(card->dev, "snd_soc_register_card failed %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, card);
+
+ return ret;
+}
+
+static int mc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_dai_link *link;
+ int ret;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) {
+ if (!codec_info_list[i].exit)
+ continue;
+ /*
+ * We don't need to call .exit function if there is no matched
+ * dai link found.
+ */
+ for_each_card_prelinks(card, j, link) {
+ if (!strcmp(link->codecs[0].dai_name,
+ codec_info_list[i].dai_name)) {
+ ret = codec_info_list[i].exit(&pdev->dev, link);
+ if (ret)
+ dev_warn(&pdev->dev,
+ "codec exit failed %d\n",
+ ret);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver sof_sdw_driver = {
+ .driver = {
+ .name = "sof_sdw",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = mc_probe,
+ .remove = mc_remove,
+};
+
+module_platform_driver(sof_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC SoundWire Generic Machine driver");
+MODULE_AUTHOR("Bard Liao <yung-chuan.liao@linux.intel.com>");
+MODULE_AUTHOR("Rander Wang <rander.wang@linux.intel.com>");
+MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sof_sdw");
diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h
new file mode 100644
index 000000000..801600522
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_common.h
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ * Copyright (c) 2020 Intel Corporation
+ */
+
+/*
+ * sof_sdw_common.h - prototypes for common helpers
+ */
+
+#ifndef SND_SOC_SOF_SDW_COMMON_H
+#define SND_SOC_SOF_SDW_COMMON_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+#include <sound/soc.h>
+
+#define MAX_NO_PROPS 2
+#define MAX_HDMI_NUM 4
+#define SDW_DMIC_DAI_ID 4
+#define SDW_MAX_CPU_DAIS 16
+#define SDW_INTEL_BIDIR_PDI_BASE 2
+
+/* 8 combinations with 4 links + unused group 0 */
+#define SDW_MAX_GROUPS 9
+
+enum {
+ SOF_PRE_TGL_HDMI_COUNT = 3,
+ SOF_TGL_HDMI_COUNT = 4,
+};
+
+enum {
+ SOF_I2S_SSP0 = BIT(0),
+ SOF_I2S_SSP1 = BIT(1),
+ SOF_I2S_SSP2 = BIT(2),
+ SOF_I2S_SSP3 = BIT(3),
+ SOF_I2S_SSP4 = BIT(4),
+ SOF_I2S_SSP5 = BIT(5),
+};
+
+#define SOF_RT711_JDSRC(quirk) ((quirk) & GENMASK(1, 0))
+#define SOF_SDW_FOUR_SPK BIT(2)
+#define SOF_SDW_TGL_HDMI BIT(3)
+#define SOF_SDW_PCH_DMIC BIT(4)
+#define SOF_SSP_PORT(x) (((x) & GENMASK(5, 0)) << 5)
+#define SOF_SSP_GET_PORT(quirk) (((quirk) >> 5) & GENMASK(5, 0))
+#define SOF_RT715_DAI_ID_FIX BIT(11)
+#define SOF_SDW_NO_AGGREGATION BIT(12)
+
+struct sof_sdw_codec_info {
+ const int part_id;
+ const int version_id;
+ int amp_num;
+ const u8 acpi_id[ACPI_ID_LEN];
+ const bool direction[2]; // playback & capture support
+ const bool ignore_pch_dmic;
+ const char *dai_name;
+ const struct snd_soc_ops *ops;
+
+ int (*init)(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+ int (*exit)(struct device *dev, struct snd_soc_dai_link *dai_link);
+ bool late_probe;
+ int (*codec_card_late_probe)(struct snd_soc_card *card);
+};
+
+struct mc_private {
+ struct list_head hdmi_pcm_list;
+ bool common_hdmi_codec_drv;
+ bool idisp_codec;
+ struct snd_soc_jack sdw_headset;
+};
+
+extern unsigned long sof_sdw_quirk;
+
+int sdw_startup(struct snd_pcm_substream *substream);
+int sdw_prepare(struct snd_pcm_substream *substream);
+int sdw_trigger(struct snd_pcm_substream *substream, int cmd);
+int sdw_hw_free(struct snd_pcm_substream *substream);
+void sdw_shutdown(struct snd_pcm_substream *substream);
+
+/* generic HDMI support */
+int sof_sdw_hdmi_init(struct snd_soc_pcm_runtime *rtd);
+
+int sof_sdw_hdmi_card_late_probe(struct snd_soc_card *card);
+
+/* DMIC support */
+int sof_sdw_dmic_init(struct snd_soc_pcm_runtime *rtd);
+
+/* RT711 support */
+int sof_sdw_rt711_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+int sof_sdw_rt711_exit(struct device *dev, struct snd_soc_dai_link *dai_link);
+
+/* RT711-SDCA support */
+int sof_sdw_rt711_sdca_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+int sof_sdw_rt711_sdca_exit(struct device *dev, struct snd_soc_dai_link *dai_link);
+
+/* RT700 support */
+int sof_sdw_rt700_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+/* RT1308 support */
+extern struct snd_soc_ops sof_sdw_rt1308_i2s_ops;
+
+int sof_sdw_rt1308_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+/* RT1316 support */
+int sof_sdw_rt1316_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+/* RT715 support */
+int sof_sdw_rt715_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+/* RT715-SDCA support */
+int sof_sdw_rt715_sdca_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+/* MAX98373 support */
+int sof_sdw_mx8373_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+int sof_sdw_mx8373_late_probe(struct snd_soc_card *card);
+
+/* RT5682 support */
+int sof_sdw_rt5682_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback);
+
+#endif
diff --git a/sound/soc/intel/boards/sof_sdw_dmic.c b/sound/soc/intel/boards/sof_sdw_dmic.c
new file mode 100644
index 000000000..19df0f7a1
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_dmic.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_dmic - Helpers to handle dmic from generic machine driver
+ */
+
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include "sof_sdw_common.h"
+
+static const struct snd_soc_dapm_widget dmic_widgets[] = {
+ SND_SOC_DAPM_MIC("SoC DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route dmic_map[] = {
+ /* digital mics */
+ {"DMic", NULL, "SoC DMIC"},
+};
+
+int sof_sdw_dmic_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets,
+ ARRAY_SIZE(dmic_widgets));
+ if (ret) {
+ dev_err(card->dev, "DMic widget addition failed: %d\n", ret);
+ /* Don't need to add routes if widget addition failed */
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map,
+ ARRAY_SIZE(dmic_map));
+
+ if (ret)
+ dev_err(card->dev, "DMic map addition failed: %d\n", ret);
+
+ return ret;
+}
+
diff --git a/sound/soc/intel/boards/sof_sdw_hdmi.c b/sound/soc/intel/boards/sof_sdw_hdmi.c
new file mode 100644
index 000000000..99b04bb2f
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_hdmi.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_hdmi - Helpers to handle HDMI from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/jack.h>
+#include "sof_sdw_common.h"
+#include "../../codecs/hdac_hdmi.h"
+#include "hda_dsp_common.h"
+
+static struct snd_soc_jack hdmi[MAX_HDMI_NUM];
+
+struct hdmi_pcm {
+ struct list_head head;
+ struct snd_soc_dai *codec_dai;
+ int device;
+};
+
+int sof_sdw_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct hdmi_pcm *pcm;
+
+ pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
+ /* dai_link id is 1:1 mapped to the PCM device */
+ pcm->device = rtd->dai_link->id;
+ pcm->codec_dai = dai;
+
+ list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+ return 0;
+}
+
+#define NAME_SIZE 32
+int sof_sdw_hdmi_card_late_probe(struct snd_soc_card *card)
+{
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct hdmi_pcm *pcm;
+ struct snd_soc_component *component = NULL;
+ int err, i = 0;
+ char jack_name[NAME_SIZE];
+
+ if (!ctx->idisp_codec)
+ return 0;
+
+ if (list_empty(&ctx->hdmi_pcm_list))
+ return -EINVAL;
+
+ pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm,
+ head);
+ component = pcm->codec_dai->component;
+
+ if (ctx->common_hdmi_codec_drv)
+ return hda_dsp_hdmi_build_controls(card, component);
+
+ list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+ component = pcm->codec_dai->component;
+ snprintf(jack_name, sizeof(jack_name),
+ "HDMI/DP, pcm=%d Jack", pcm->device);
+ err = snd_soc_card_jack_new(card, jack_name,
+ SND_JACK_AVOUT, &hdmi[i],
+ NULL, 0);
+
+ if (err)
+ return err;
+
+ err = snd_jack_add_new_kctl(hdmi[i].jack,
+ jack_name, SND_JACK_AVOUT);
+ if (err)
+ dev_warn(component->dev, "failed creating Jack kctl\n");
+
+ err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device,
+ &hdmi[i]);
+ if (err < 0)
+ return err;
+
+ i++;
+ }
+
+ if (!component)
+ return -EINVAL;
+
+ return hdac_hdmi_jack_port_init(component, &card->dapm);
+}
diff --git a/sound/soc/intel/boards/sof_sdw_max98373.c b/sound/soc/intel/boards/sof_sdw_max98373.c
new file mode 100644
index 000000000..cfdf970c5
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_max98373.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+//
+// sof_sdw_max98373 - Helpers to handle 2x MAX98373
+// codec devices from generic machine driver
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include "sof_sdw_common.h"
+#include "sof_maxim_common.h"
+
+static const struct snd_soc_dapm_widget mx8373_widgets[] = {
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+};
+
+static const struct snd_kcontrol_new mx8373_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Left Spk"),
+ SOC_DAPM_PIN_SWITCH("Right Spk"),
+};
+
+static int spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s spk:mx8373",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, mx8373_controls,
+ ARRAY_SIZE(mx8373_controls));
+ if (ret) {
+ dev_err(card->dev, "mx8373 ctrls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, mx8373_widgets,
+ ARRAY_SIZE(mx8373_widgets));
+ if (ret) {
+ dev_err(card->dev, "mx8373 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes, 2);
+ if (ret)
+ dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret);
+
+ return ret;
+}
+
+static int max98373_sdw_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /* enable max98373 first */
+ ret = max98373_trigger(substream, cmd);
+ if (ret < 0)
+ break;
+
+ ret = sdw_trigger(substream, cmd);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = sdw_trigger(substream, cmd);
+ if (ret < 0)
+ break;
+
+ ret = max98373_trigger(substream, cmd);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_ops max_98373_sdw_ops = {
+ .startup = sdw_startup,
+ .prepare = sdw_prepare,
+ .trigger = max98373_sdw_trigger,
+ .hw_free = sdw_hw_free,
+ .shutdown = sdw_shutdown,
+};
+
+int sof_sdw_mx8373_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ info->amp_num++;
+ if (info->amp_num == 2)
+ dai_links->init = spk_init;
+
+ info->late_probe = true;
+
+ dai_links->ops = &max_98373_sdw_ops;
+
+ return 0;
+}
+
+int sof_sdw_mx8373_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+
+ /* Disable Left and Right Spk pin after boot */
+ snd_soc_dapm_disable_pin(dapm, "Left Spk");
+ snd_soc_dapm_disable_pin(dapm, "Right Spk");
+ return snd_soc_dapm_sync(dapm);
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt1308.c b/sound/soc/intel/boards/sof_sdw_rt1308.c
new file mode 100644
index 000000000..0d476f6f6
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt1308.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt1308 - Helpers to handle RT1308 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include "sof_sdw_common.h"
+#include "../../codecs/rt1308.h"
+
+static const struct snd_soc_dapm_widget rt1308_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/*
+ * dapm routes for rt1308 will be registered dynamically according
+ * to the number of rt1308 used. The first two entries will be registered
+ * for one codec case, and the last two entries are also registered
+ * if two 1308s are used.
+ */
+static const struct snd_soc_dapm_route rt1308_map[] = {
+ { "Speaker", NULL, "rt1308-1 SPOL" },
+ { "Speaker", NULL, "rt1308-1 SPOR" },
+ { "Speaker", NULL, "rt1308-2 SPOL" },
+ { "Speaker", NULL, "rt1308-2 SPOR" },
+};
+
+static const struct snd_kcontrol_new rt1308_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static int first_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s spk:rt1308",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt1308_controls,
+ ARRAY_SIZE(rt1308_controls));
+ if (ret) {
+ dev_err(card->dev, "rt1308 controls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt1308_widgets,
+ ARRAY_SIZE(rt1308_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt1308 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt1308_map, 2);
+ if (ret)
+ dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret);
+
+ return ret;
+}
+
+static int second_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt1308_map + 2, 2);
+ if (ret)
+ dev_err(rtd->dev, "failed to add second SPK map: %d\n", ret);
+
+ return ret;
+}
+
+static int all_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+
+ ret = first_spk_init(rtd);
+ if (ret)
+ return ret;
+
+ return second_spk_init(rtd);
+}
+
+static int rt1308_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int clk_id, clk_freq, pll_out;
+ int err;
+
+ clk_id = RT1308_PLL_S_MCLK;
+ clk_freq = 38400000;
+
+ pll_out = params_rate(params) * 512;
+
+ /* Set rt1308 pll */
+ err = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out);
+ if (err < 0) {
+ dev_err(card->dev, "Failed to set RT1308 PLL: %d\n", err);
+ return err;
+ }
+
+ /* Set rt1308 sysclk */
+ err = snd_soc_dai_set_sysclk(codec_dai, RT1308_FS_SYS_S_PLL, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "Failed to set RT1308 SYSCLK: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+/* machine stream operations */
+struct snd_soc_ops sof_sdw_rt1308_i2s_ops = {
+ .hw_params = rt1308_i2s_hw_params,
+};
+
+int sof_sdw_rt1308_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /* Count amp number and do init on playback link only. */
+ if (!playback)
+ return 0;
+
+ info->amp_num++;
+ if (info->amp_num == 1)
+ dai_links->init = first_spk_init;
+
+ if (info->amp_num == 2) {
+ /*
+ * if two 1308s are in one dai link, the init function
+ * in this dai link will be first set for the first speaker,
+ * and it should be reset to initialize all speakers when
+ * the second speaker is found.
+ */
+ if (dai_links->init)
+ dai_links->init = all_spk_init;
+ else
+ dai_links->init = second_spk_init;
+ }
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt1316.c b/sound/soc/intel/boards/sof_sdw_rt1316.c
new file mode 100644
index 000000000..d6e1ebf18
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt1316.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt1316 - Helpers to handle RT1316 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include "sof_sdw_common.h"
+
+static const struct snd_soc_dapm_widget rt1316_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/*
+ * dapm routes for rt1316 will be registered dynamically according
+ * to the number of rt1316 used. The first two entries will be registered
+ * for one codec case, and the last two entries are also registered
+ * if two 1316s are used.
+ */
+static const struct snd_soc_dapm_route rt1316_map[] = {
+ { "Speaker", NULL, "rt1316-1 SPOL" },
+ { "Speaker", NULL, "rt1316-1 SPOR" },
+ { "Speaker", NULL, "rt1316-2 SPOL" },
+ { "Speaker", NULL, "rt1316-2 SPOR" },
+};
+
+static const struct snd_kcontrol_new rt1316_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static int first_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s spk:rt1316",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt1316_controls,
+ ARRAY_SIZE(rt1316_controls));
+ if (ret) {
+ dev_err(card->dev, "rt1316 controls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt1316_widgets,
+ ARRAY_SIZE(rt1316_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt1316 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt1316_map, 2);
+ if (ret)
+ dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret);
+
+ return ret;
+}
+
+static int second_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt1316_map + 2, 2);
+ if (ret)
+ dev_err(rtd->dev, "failed to add second SPK map: %d\n", ret);
+
+ return ret;
+}
+
+static int all_spk_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+
+ ret = first_spk_init(rtd);
+ if (ret)
+ return ret;
+
+ return second_spk_init(rtd);
+}
+
+int sof_sdw_rt1316_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /* Count amp number and do init on playback link only. */
+ if (!playback)
+ return 0;
+
+ info->amp_num++;
+ if (info->amp_num == 1)
+ dai_links->init = first_spk_init;
+
+ if (info->amp_num == 2) {
+ /*
+ * if two 1316s are in one dai link, the init function
+ * in this dai link will be first set for the first speaker,
+ * and it should be reset to initialize all speakers when
+ * the second speaker is found.
+ */
+ if (dai_links->init)
+ dai_links->init = all_spk_init;
+ else
+ dai_links->init = second_spk_init;
+ }
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt5682.c b/sound/soc/intel/boards/sof_sdw_rt5682.c
new file mode 100644
index 000000000..5fa1a5961
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt5682.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt5682 - Helpers to handle RT5682 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include "sof_sdw_common.h"
+
+static const struct snd_soc_dapm_widget rt5682_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route rt5682_map[] = {
+ /*Headphones*/
+ { "Headphone", NULL, "rt5682 HPOL" },
+ { "Headphone", NULL, "rt5682 HPOR" },
+ { "rt5682 IN1P", NULL, "Headset Mic" },
+};
+
+static const struct snd_kcontrol_new rt5682_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static struct snd_soc_jack_pin rt5682_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int rt5682_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:rt5682",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt5682_controls,
+ ARRAY_SIZE(rt5682_controls));
+ if (ret) {
+ dev_err(card->dev, "rt5682 control addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt5682_widgets,
+ ARRAY_SIZE(rt5682_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt5682 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt5682_map,
+ ARRAY_SIZE(rt5682_map));
+
+ if (ret) {
+ dev_err(card->dev, "rt5682 map addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sdw_headset,
+ rt5682_jack_pins,
+ ARRAY_SIZE(rt5682_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+int sof_sdw_rt5682_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /*
+ * headset should be initialized once.
+ * Do it with dai link for playback.
+ */
+ if (!playback)
+ return 0;
+
+ dai_links->init = rt5682_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt700.c b/sound/soc/intel/boards/sof_sdw_rt700.c
new file mode 100644
index 000000000..21e7e4a81
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt700.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt700 - Helpers to handle RT700 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include "sof_sdw_common.h"
+
+static const struct snd_soc_dapm_widget rt700_widgets[] = {
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route rt700_map[] = {
+ /* Headphones */
+ { "Headphones", NULL, "rt700 HP" },
+ { "Speaker", NULL, "rt700 SPK" },
+ { "rt700 MIC2", NULL, "AMIC" },
+};
+
+static const struct snd_kcontrol_new rt700_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphones"),
+ SOC_DAPM_PIN_SWITCH("AMIC"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static struct snd_soc_jack_pin rt700_jack_pins[] = {
+ {
+ .pin = "Headphones",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "AMIC",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int rt700_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:rt700",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt700_controls,
+ ARRAY_SIZE(rt700_controls));
+ if (ret) {
+ dev_err(card->dev, "rt700 controls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt700_widgets,
+ ARRAY_SIZE(rt700_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt700 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt700_map,
+ ARRAY_SIZE(rt700_map));
+
+ if (ret) {
+ dev_err(card->dev, "rt700 map addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sdw_headset,
+ rt700_jack_pins,
+ ARRAY_SIZE(rt700_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+int sof_sdw_rt700_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /*
+ * headset should be initialized once.
+ * Do it with dai link for playback.
+ */
+ if (!playback)
+ return 0;
+
+ dai_links->init = rt700_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt711.c b/sound/soc/intel/boards/sof_sdw_rt711.c
new file mode 100644
index 000000000..04074c09d
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt711.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt711 - Helpers to handle RT711 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include "sof_sdw_common.h"
+
+/*
+ * Note this MUST be called before snd_soc_register_card(), so that the props
+ * are in place before the codec component driver's probe function parses them.
+ */
+static int rt711_add_codec_device_props(const char *sdw_dev_name)
+{
+ struct property_entry props[MAX_NO_PROPS] = {};
+ struct device *sdw_dev;
+ int ret;
+
+ sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_dev_name);
+ if (!sdw_dev)
+ return -EPROBE_DEFER;
+
+ if (SOF_RT711_JDSRC(sof_sdw_quirk)) {
+ props[0] = PROPERTY_ENTRY_U32("realtek,jd-src",
+ SOF_RT711_JDSRC(sof_sdw_quirk));
+ }
+
+ ret = device_add_properties(sdw_dev, props);
+ put_device(sdw_dev);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget rt711_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route rt711_map[] = {
+ /* Headphones */
+ { "Headphone", NULL, "rt711 HP" },
+ { "rt711 MIC2", NULL, "Headset Mic" },
+};
+
+static const struct snd_kcontrol_new rt711_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static struct snd_soc_jack_pin rt711_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int rt711_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:rt711",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt711_controls,
+ ARRAY_SIZE(rt711_controls));
+ if (ret) {
+ dev_err(card->dev, "rt711 controls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt711_widgets,
+ ARRAY_SIZE(rt711_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt711 widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt711_map,
+ ARRAY_SIZE(rt711_map));
+
+ if (ret) {
+ dev_err(card->dev, "rt711 map addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sdw_headset,
+ rt711_jack_pins,
+ ARRAY_SIZE(rt711_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+int sof_sdw_rt711_exit(struct device *dev, struct snd_soc_dai_link *dai_link)
+{
+ struct device *sdw_dev;
+
+ sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL,
+ dai_link->codecs[0].name);
+ if (!sdw_dev)
+ return -EINVAL;
+
+ device_remove_properties(sdw_dev);
+ put_device(sdw_dev);
+
+ return 0;
+}
+
+int sof_sdw_rt711_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ int ret;
+
+ /*
+ * headset should be initialized once.
+ * Do it with dai link for playback.
+ */
+ if (!playback)
+ return 0;
+
+ ret = rt711_add_codec_device_props(dai_links->codecs[0].name);
+ if (ret < 0)
+ return ret;
+
+ dai_links->init = rt711_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c
new file mode 100644
index 000000000..19496f0f9
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt711_sdca - Helpers to handle RT711-SDCA from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include "sof_sdw_common.h"
+
+/*
+ * Note this MUST be called before snd_soc_register_card(), so that the props
+ * are in place before the codec component driver's probe function parses them.
+ */
+static int rt711_sdca_add_codec_device_props(const char *sdw_dev_name)
+{
+ struct property_entry props[MAX_NO_PROPS] = {};
+ struct device *sdw_dev;
+ int ret;
+
+ sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_dev_name);
+ if (!sdw_dev)
+ return -EPROBE_DEFER;
+
+ if (SOF_RT711_JDSRC(sof_sdw_quirk)) {
+ props[0] = PROPERTY_ENTRY_U32("realtek,jd-src",
+ SOF_RT711_JDSRC(sof_sdw_quirk));
+ }
+
+ ret = device_add_properties(sdw_dev, props);
+ put_device(sdw_dev);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget rt711_sdca_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route rt711_sdca_map[] = {
+ /* Headphones */
+ { "Headphone", NULL, "rt711 HP" },
+ { "rt711 MIC2", NULL, "Headset Mic" },
+};
+
+static const struct snd_kcontrol_new rt711_sdca_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static struct snd_soc_jack_pin rt711_sdca_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int rt711_sdca_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:rt711-sdca",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_add_card_controls(card, rt711_sdca_controls,
+ ARRAY_SIZE(rt711_sdca_controls));
+ if (ret) {
+ dev_err(card->dev, "rt711-sdca controls addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(&card->dapm, rt711_sdca_widgets,
+ ARRAY_SIZE(rt711_sdca_widgets));
+ if (ret) {
+ dev_err(card->dev, "rt711-sdca widgets addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, rt711_sdca_map,
+ ARRAY_SIZE(rt711_sdca_map));
+
+ if (ret) {
+ dev_err(card->dev, "rt711-sdca map addition failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sdw_headset,
+ rt711_sdca_jack_pins,
+ ARRAY_SIZE(rt711_sdca_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+int sof_sdw_rt711_sdca_exit(struct device *dev, struct snd_soc_dai_link *dai_link)
+{
+ struct device *sdw_dev;
+
+ sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL,
+ dai_link->codecs[0].name);
+ if (!sdw_dev)
+ return -EINVAL;
+
+ device_remove_properties(sdw_dev);
+ put_device(sdw_dev);
+
+ return 0;
+}
+
+int sof_sdw_rt711_sdca_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ int ret;
+
+ /*
+ * headset should be initialized once.
+ * Do it with dai link for playback.
+ */
+ if (!playback)
+ return 0;
+
+ ret = rt711_sdca_add_codec_device_props(dai_links->codecs[0].name);
+ if (ret < 0)
+ return ret;
+
+ dai_links->init = rt711_sdca_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt715.c b/sound/soc/intel/boards/sof_sdw_rt715.c
new file mode 100644
index 000000000..9b298f79e
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt715.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt715 - Helpers to handle RT715 from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "sof_sdw_common.h"
+
+static int rt715_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s mic:rt715",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int sof_sdw_rt715_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /*
+ * DAI ID is fixed at SDW_DMIC_DAI_ID for 715 to
+ * keep sdw DMIC and HDMI setting static in UCM
+ */
+ if (sof_sdw_quirk & SOF_RT715_DAI_ID_FIX)
+ dai_links->id = SDW_DMIC_DAI_ID;
+
+ dai_links->init = rt715_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_sdw_rt715_sdca.c b/sound/soc/intel/boards/sof_sdw_rt715_sdca.c
new file mode 100644
index 000000000..c056e56a1
--- /dev/null
+++ b/sound/soc/intel/boards/sof_sdw_rt715_sdca.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2020 Intel Corporation
+
+/*
+ * sof_sdw_rt715_sdca - Helpers to handle RT715-SDCA from generic machine driver
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "sof_sdw_common.h"
+
+static int rt715_sdca_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s mic:rt715-sdca",
+ card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int sof_sdw_rt715_sdca_init(const struct snd_soc_acpi_link_adr *link,
+ struct snd_soc_dai_link *dai_links,
+ struct sof_sdw_codec_info *info,
+ bool playback)
+{
+ /*
+ * DAI ID is fixed at SDW_DMIC_DAI_ID for 715-SDCA to
+ * keep sdw DMIC and HDMI setting static in UCM
+ */
+ if (sof_sdw_quirk & SOF_RT715_DAI_ID_FIX)
+ dai_links->id = SDW_DMIC_DAI_ID;
+
+ dai_links->init = rt715_sdca_rtd_init;
+
+ return 0;
+}
diff --git a/sound/soc/intel/boards/sof_wm8804.c b/sound/soc/intel/boards/sof_wm8804.c
new file mode 100644
index 000000000..6a181e451
--- /dev/null
+++ b/sound/soc/intel/boards/sof_wm8804.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2018-2020, Intel Corporation
+//
+// sof-wm8804.c - ASoC machine driver for Up and Up2 board
+// based on WM8804/Hifiberry Digi+
+
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include "../../codecs/wm8804.h"
+
+struct sof_card_private {
+ struct gpio_desc *gpio_44;
+ struct gpio_desc *gpio_48;
+ int sample_rate;
+};
+
+#define SOF_WM8804_UP2_QUIRK BIT(0)
+
+static unsigned long sof_wm8804_quirk;
+
+static int sof_wm8804_quirk_cb(const struct dmi_system_id *id)
+{
+ sof_wm8804_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_wm8804_quirk_table[] = {
+ {
+ .callback = sof_wm8804_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "UP-APL01"),
+ },
+ .driver_data = (void *)SOF_WM8804_UP2_QUIRK,
+ },
+ {}
+};
+
+static int sof_wm8804_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *codec = codec_dai->component;
+ const int sysclk = 27000000; /* This is fixed on this board */
+ int samplerate;
+ long mclk_freq;
+ int mclk_div;
+ int sampling_freq;
+ bool clk_44;
+ int ret;
+
+ samplerate = params_rate(params);
+ if (samplerate == ctx->sample_rate)
+ return 0;
+
+ ctx->sample_rate = 0;
+
+ if (samplerate <= 96000) {
+ mclk_freq = samplerate * 256;
+ mclk_div = WM8804_MCLKDIV_256FS;
+ } else {
+ mclk_freq = samplerate * 128;
+ mclk_div = WM8804_MCLKDIV_128FS;
+ }
+
+ switch (samplerate) {
+ case 32000:
+ sampling_freq = 0x03;
+ break;
+ case 44100:
+ sampling_freq = 0x00;
+ break;
+ case 48000:
+ sampling_freq = 0x02;
+ break;
+ case 88200:
+ sampling_freq = 0x08;
+ break;
+ case 96000:
+ sampling_freq = 0x0a;
+ break;
+ case 176400:
+ sampling_freq = 0x0c;
+ break;
+ case 192000:
+ sampling_freq = 0x0e;
+ break;
+ default:
+ dev_err(rtd->card->dev,
+ "unsupported samplerate %d\n", samplerate);
+ return -EINVAL;
+ }
+
+ if (samplerate % 16000)
+ clk_44 = true; /* use 44.1 kHz root frequency */
+ else
+ clk_44 = false;
+
+ if (!(IS_ERR_OR_NULL(ctx->gpio_44) ||
+ IS_ERR_OR_NULL(ctx->gpio_48))) {
+ /*
+ * ensure both GPIOs are LOW first, then drive the
+ * relevant one to HIGH
+ */
+ if (clk_44) {
+ gpiod_set_value_cansleep(ctx->gpio_48, !clk_44);
+ gpiod_set_value_cansleep(ctx->gpio_44, clk_44);
+ } else {
+ gpiod_set_value_cansleep(ctx->gpio_44, clk_44);
+ gpiod_set_value_cansleep(ctx->gpio_48, !clk_44);
+ }
+ }
+
+ snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
+ ret = snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "Failed to set WM8804 PLL\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+ sysclk, SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(rtd->card->dev,
+ "Failed to set WM8804 SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ /* set sampling frequency status bits */
+ snd_soc_component_update_bits(codec, WM8804_SPDTX4, 0x0f,
+ sampling_freq);
+
+ ctx->sample_rate = samplerate;
+
+ return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops sof_wm8804_ops = {
+ .hw_params = sof_wm8804_hw_params,
+};
+
+SND_SOC_DAILINK_DEF(ssp5_pin,
+ DAILINK_COMP_ARRAY(COMP_CPU("SSP5 Pin")));
+
+SND_SOC_DAILINK_DEF(ssp5_codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-1AEC8804:00", "wm8804-spdif")));
+
+SND_SOC_DAILINK_DEF(platform,
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:0e.0")));
+
+static struct snd_soc_dai_link dailink[] = {
+ /* back ends */
+ {
+ .name = "SSP5-Codec",
+ .id = 0,
+ .no_pcm = 1,
+ .nonatomic = true,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &sof_wm8804_ops,
+ SND_SOC_DAILINK_REG(ssp5_pin, ssp5_codec, platform),
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card sof_wm8804_card = {
+ .name = "wm8804", /* sof- prefix added automatically */
+ .owner = THIS_MODULE,
+ .dai_link = dailink,
+ .num_links = ARRAY_SIZE(dailink),
+};
+
+ /* i2c-<HID>:00 with HID being 8 chars */
+static char codec_name[SND_ACPI_I2C_ID_LEN];
+
+/*
+ * to control the HifiBerry Digi+ PRO, it's required to toggle GPIO to
+ * select the clock source. On the Up2 board, this means
+ * Pin29/BCM5/Linux GPIO 430 and Pin 31/BCM6/ Linux GPIO 404.
+ *
+ * Using the ACPI device name is not very nice, but since we only use
+ * the value for the Up2 board there is no risk of conflict with other
+ * platforms.
+ */
+
+static struct gpiod_lookup_table up2_gpios_table = {
+ /* .dev_id is set during probe */
+ .table = {
+ GPIO_LOOKUP("INT3452:01", 73, "BCM-GPIO5", GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP("INT3452:01", 74, "BCM-GPIO6", GPIO_ACTIVE_HIGH),
+ { },
+ },
+};
+
+static int sof_wm8804_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct snd_soc_acpi_mach *mach;
+ struct sof_card_private *ctx;
+ struct acpi_device *adev;
+ int dai_index = 0;
+ int ret;
+ int i;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mach = pdev->dev.platform_data;
+ card = &sof_wm8804_card;
+ card->dev = &pdev->dev;
+
+ dmi_check_system(sof_wm8804_quirk_table);
+
+ if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK) {
+ up2_gpios_table.dev_id = dev_name(&pdev->dev);
+ gpiod_add_lookup_table(&up2_gpios_table);
+
+ /*
+ * The gpios are required for specific boards with
+ * local oscillators, and optional in other cases.
+ * Since we can't identify when they are needed, use
+ * the GPIO as non-optional
+ */
+
+ ctx->gpio_44 = devm_gpiod_get(&pdev->dev, "BCM-GPIO5",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->gpio_44)) {
+ ret = PTR_ERR(ctx->gpio_44);
+ dev_err(&pdev->dev,
+ "could not get BCM-GPIO5: %d\n",
+ ret);
+ return ret;
+ }
+
+ ctx->gpio_48 = devm_gpiod_get(&pdev->dev, "BCM-GPIO6",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->gpio_48)) {
+ ret = PTR_ERR(ctx->gpio_48);
+ dev_err(&pdev->dev,
+ "could not get BCM-GPIO6: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* fix index of codec dai */
+ for (i = 0; i < ARRAY_SIZE(dailink); i++) {
+ if (!strcmp(dailink[i].codecs->name, "i2c-1AEC8804:00")) {
+ dai_index = i;
+ break;
+ }
+ }
+
+ /* fixup codec name based on HID */
+ adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
+ if (adev) {
+ snprintf(codec_name, sizeof(codec_name),
+ "%s%s", "i2c-", acpi_dev_name(adev));
+ put_device(&adev->dev);
+ dailink[dai_index].codecs->name = codec_name;
+ }
+
+ snd_soc_card_set_drvdata(card, ctx);
+
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static int sof_wm8804_remove(struct platform_device *pdev)
+{
+ if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK)
+ gpiod_remove_lookup_table(&up2_gpios_table);
+ return 0;
+}
+
+static struct platform_driver sof_wm8804_driver = {
+ .driver = {
+ .name = "sof-wm8804",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = sof_wm8804_probe,
+ .remove = sof_wm8804_remove,
+};
+module_platform_driver(sof_wm8804_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) SOF + WM8804 Machine driver");
+MODULE_AUTHOR("Pierre-Louis Bossart");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sof-wm8804");
diff --git a/sound/soc/intel/catpt/Makefile b/sound/soc/intel/catpt/Makefile
new file mode 100644
index 000000000..c393a4579
--- /dev/null
+++ b/sound/soc/intel/catpt/Makefile
@@ -0,0 +1,6 @@
+snd-soc-catpt-objs := device.o dsp.o loader.o ipc.o messages.o pcm.o sysfs.o
+
+# tell define_trace.h where to find the trace header
+CFLAGS_device.o := -I$(src)
+
+obj-$(CONFIG_SND_SOC_INTEL_CATPT) += snd-soc-catpt.o
diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h
new file mode 100644
index 000000000..88dc3fb63
--- /dev/null
+++ b/sound/soc/intel/catpt/core.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_CORE_H
+#define __SND_SOC_INTEL_CATPT_CORE_H
+
+#include <linux/dma/dw.h>
+#include <linux/irqreturn.h>
+#include "messages.h"
+#include "registers.h"
+
+struct catpt_dev;
+
+extern const struct attribute_group *catpt_attr_groups[];
+
+void catpt_sram_init(struct resource *sram, u32 start, u32 size);
+void catpt_sram_free(struct resource *sram);
+struct resource *
+catpt_request_region(struct resource *root, resource_size_t size);
+
+static inline bool catpt_resource_overlapping(struct resource *r1,
+ struct resource *r2,
+ struct resource *ret)
+{
+ if (!resource_overlaps(r1, r2))
+ return false;
+ ret->start = max(r1->start, r2->start);
+ ret->end = min(r1->end, r2->end);
+ return true;
+}
+
+struct catpt_ipc_msg {
+ union {
+ u32 header;
+ union catpt_global_msg rsp;
+ };
+ void *data;
+ size_t size;
+};
+
+struct catpt_ipc {
+ struct device *dev;
+
+ struct catpt_ipc_msg rx;
+ struct catpt_fw_ready config;
+ u32 default_timeout;
+ bool ready;
+
+ spinlock_t lock;
+ struct mutex mutex;
+ struct completion done_completion;
+ struct completion busy_completion;
+};
+
+void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev);
+
+struct catpt_module_type {
+ bool loaded;
+ u32 entry_point;
+ u32 persistent_size;
+ u32 scratch_size;
+ /* DRAM, initial module state */
+ u32 state_offset;
+ u32 state_size;
+
+ struct list_head node;
+};
+
+struct catpt_spec {
+ struct snd_soc_acpi_mach *machines;
+ u8 core_id;
+ u32 host_dram_offset;
+ u32 host_iram_offset;
+ u32 host_shim_offset;
+ u32 host_dma_offset[CATPT_DMA_COUNT];
+ u32 host_ssp_offset[CATPT_SSP_COUNT];
+ u32 dram_mask;
+ u32 iram_mask;
+ void (*pll_shutdown)(struct catpt_dev *cdev, bool enable);
+ int (*power_up)(struct catpt_dev *cdev);
+ int (*power_down)(struct catpt_dev *cdev);
+};
+
+struct catpt_dev {
+ struct device *dev;
+ struct dw_dma_chip *dmac;
+ struct catpt_ipc ipc;
+
+ void __iomem *pci_ba;
+ void __iomem *lpe_ba;
+ u32 lpe_base;
+ int irq;
+
+ const struct catpt_spec *spec;
+ struct completion fw_ready;
+
+ struct resource dram;
+ struct resource iram;
+ struct resource *scratch;
+
+ struct catpt_mixer_stream_info mixer;
+ struct catpt_module_type modules[CATPT_MODULE_COUNT];
+ struct catpt_ssp_device_format devfmt[CATPT_SSP_COUNT];
+ struct list_head stream_list;
+ spinlock_t list_lock;
+ struct mutex clk_mutex;
+
+ struct catpt_dx_context dx_ctx;
+ void *dxbuf_vaddr;
+ dma_addr_t dxbuf_paddr;
+};
+
+int catpt_dmac_probe(struct catpt_dev *cdev);
+void catpt_dmac_remove(struct catpt_dev *cdev);
+struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev);
+int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size);
+int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size);
+
+void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable);
+void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable);
+int lpt_dsp_power_up(struct catpt_dev *cdev);
+int lpt_dsp_power_down(struct catpt_dev *cdev);
+int wpt_dsp_power_up(struct catpt_dev *cdev);
+int wpt_dsp_power_down(struct catpt_dev *cdev);
+int catpt_dsp_stall(struct catpt_dev *cdev, bool stall);
+void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask);
+int catpt_dsp_update_lpclock(struct catpt_dev *cdev);
+irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id);
+irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id);
+
+/*
+ * IPC handlers may return positive values which denote successful
+ * HOST <-> DSP communication yet failure to process specific request.
+ * Use below macro to convert returned non-zero values appropriately
+ */
+#define CATPT_IPC_ERROR(err) (((err) < 0) ? (err) : -EREMOTEIO)
+
+int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout);
+int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply);
+
+int catpt_first_boot_firmware(struct catpt_dev *cdev);
+int catpt_boot_firmware(struct catpt_dev *cdev, bool restore);
+int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_coredump(struct catpt_dev *cdev);
+
+#include <sound/memalloc.h>
+#include <uapi/sound/asound.h>
+
+struct snd_pcm_substream;
+struct catpt_stream_template;
+
+struct catpt_stream_runtime {
+ struct snd_pcm_substream *substream;
+
+ struct catpt_stream_template *template;
+ struct catpt_stream_info info;
+ struct resource *persistent;
+ struct snd_dma_buffer pgtbl;
+
+ bool allocated;
+ bool prepared;
+
+ struct list_head node;
+};
+
+int catpt_register_plat_component(struct catpt_dev *cdev);
+void catpt_stream_update_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream,
+ struct catpt_notify_position *pos);
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_arm_stream_templates(struct catpt_dev *cdev);
+
+#endif
diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c
new file mode 100644
index 000000000..a70179959
--- /dev/null
+++ b/sound/soc/intel/catpt/device.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+// Special thanks to:
+// Marcin Barlik <marcin.barlik@intel.com>
+// Piotr Papierkowski <piotr.papierkowski@intel.com>
+//
+// for sharing LPT-LP and WTP-LP AudioDSP architecture expertise and
+// helping backtrack its historical background
+//
+
+#include <linux/acpi.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "core.h"
+#include "registers.h"
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
+static int __maybe_unused catpt_suspend(struct device *dev)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ struct dma_chan *chan;
+ int ret;
+
+ chan = catpt_dma_request_config_chan(cdev);
+ if (IS_ERR(chan))
+ return PTR_ERR(chan);
+
+ memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx));
+ ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx);
+ if (ret) {
+ ret = CATPT_IPC_ERROR(ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_dsp_stall(cdev, true);
+ if (ret)
+ goto release_dma_chan;
+
+ ret = catpt_store_memdumps(cdev, chan);
+ if (ret) {
+ dev_err(cdev->dev, "store memdumps failed: %d\n", ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_store_module_states(cdev, chan);
+ if (ret) {
+ dev_err(cdev->dev, "store module states failed: %d\n", ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_store_streams_context(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret);
+
+release_dma_chan:
+ dma_release_channel(chan);
+ if (ret)
+ return ret;
+ return cdev->spec->power_down(cdev);
+}
+
+static int __maybe_unused catpt_resume(struct device *dev)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ int ret, i;
+
+ ret = cdev->spec->power_up(cdev);
+ if (ret)
+ return ret;
+
+ if (!try_module_get(dev->driver->owner)) {
+ dev_info(dev, "module unloading, skipping fw boot\n");
+ return 0;
+ }
+ module_put(dev->driver->owner);
+
+ ret = catpt_boot_firmware(cdev, true);
+ if (ret) {
+ dev_err(cdev->dev, "boot firmware failed: %d\n", ret);
+ return ret;
+ }
+
+ /* reconfigure SSP devices after Dx transition */
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ if (cdev->devfmt[i].iface == UINT_MAX)
+ continue;
+
+ ret = catpt_ipc_set_device_format(cdev, &cdev->devfmt[i]);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused catpt_runtime_suspend(struct device *dev)
+{
+ if (!try_module_get(dev->driver->owner)) {
+ dev_info(dev, "module unloading, skipping suspend\n");
+ return 0;
+ }
+ module_put(dev->driver->owner);
+
+ return catpt_suspend(dev);
+}
+
+static int __maybe_unused catpt_runtime_resume(struct device *dev)
+{
+ return catpt_resume(dev);
+}
+
+static const struct dev_pm_ops catpt_dev_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(catpt_suspend, catpt_resume)
+ SET_RUNTIME_PM_OPS(catpt_runtime_suspend, catpt_runtime_resume, NULL)
+};
+
+/* machine board owned by CATPT is removed with this hook */
+static void board_pdev_unregister(void *data)
+{
+ platform_device_unregister(data);
+}
+
+static int catpt_register_board(struct catpt_dev *cdev)
+{
+ const struct catpt_spec *spec = cdev->spec;
+ struct snd_soc_acpi_mach *mach;
+ struct platform_device *board;
+
+ mach = snd_soc_acpi_find_machine(spec->machines);
+ if (!mach) {
+ dev_info(cdev->dev, "no machines present\n");
+ return 0;
+ }
+
+ mach->mach_params.platform = "catpt-platform";
+ board = platform_device_register_data(NULL, mach->drv_name,
+ PLATFORM_DEVID_NONE,
+ (const void *)mach, sizeof(*mach));
+ if (IS_ERR(board)) {
+ dev_err(cdev->dev, "board register failed\n");
+ return PTR_ERR(board);
+ }
+
+ return devm_add_action_or_reset(cdev->dev, board_pdev_unregister,
+ board);
+}
+
+static int catpt_probe_components(struct catpt_dev *cdev)
+{
+ int ret;
+
+ ret = cdev->spec->power_up(cdev);
+ if (ret)
+ return ret;
+
+ ret = catpt_dmac_probe(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "DMAC probe failed: %d\n", ret);
+ goto err_dmac_probe;
+ }
+
+ ret = catpt_first_boot_firmware(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "first fw boot failed: %d\n", ret);
+ goto err_boot_fw;
+ }
+
+ ret = catpt_register_plat_component(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "register plat comp failed: %d\n", ret);
+ goto err_boot_fw;
+ }
+
+ ret = catpt_register_board(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "register board failed: %d\n", ret);
+ goto err_reg_board;
+ }
+
+ /* reflect actual ADSP state in pm_runtime */
+ pm_runtime_set_active(cdev->dev);
+
+ pm_runtime_set_autosuspend_delay(cdev->dev, 2000);
+ pm_runtime_use_autosuspend(cdev->dev);
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_enable(cdev->dev);
+ return 0;
+
+err_reg_board:
+ snd_soc_unregister_component(cdev->dev);
+err_boot_fw:
+ catpt_dmac_remove(cdev);
+err_dmac_probe:
+ cdev->spec->power_down(cdev);
+
+ return ret;
+}
+
+static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev,
+ const struct catpt_spec *spec)
+{
+ cdev->dev = dev;
+ cdev->spec = spec;
+ init_completion(&cdev->fw_ready);
+ INIT_LIST_HEAD(&cdev->stream_list);
+ spin_lock_init(&cdev->list_lock);
+ mutex_init(&cdev->clk_mutex);
+
+ /*
+ * Mark both device formats as uninitialized. Once corresponding
+ * cpu_dai's pcm is created, proper values are assigned.
+ */
+ cdev->devfmt[CATPT_SSP_IFACE_0].iface = UINT_MAX;
+ cdev->devfmt[CATPT_SSP_IFACE_1].iface = UINT_MAX;
+
+ catpt_ipc_init(&cdev->ipc, dev);
+
+ catpt_sram_init(&cdev->dram, spec->host_dram_offset,
+ catpt_dram_size(cdev));
+ catpt_sram_init(&cdev->iram, spec->host_iram_offset,
+ catpt_iram_size(cdev));
+}
+
+static int catpt_acpi_probe(struct platform_device *pdev)
+{
+ const struct catpt_spec *spec;
+ struct catpt_dev *cdev;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret;
+
+ spec = device_get_match_data(dev);
+ if (!spec)
+ return -ENODEV;
+
+ cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ catpt_dev_init(cdev, dev, spec);
+
+ /* map DSP bar address */
+ cdev->lpe_ba = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(cdev->lpe_ba))
+ return PTR_ERR(cdev->lpe_ba);
+ cdev->lpe_base = res->start;
+
+ /* map PCI bar address */
+ cdev->pci_ba = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(cdev->pci_ba))
+ return PTR_ERR(cdev->pci_ba);
+
+ /* alloc buffer for storing DRAM context during dx transitions */
+ cdev->dxbuf_vaddr = dmam_alloc_coherent(dev, catpt_dram_size(cdev),
+ &cdev->dxbuf_paddr, GFP_KERNEL);
+ if (!cdev->dxbuf_vaddr)
+ return -ENOMEM;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ cdev->irq = ret;
+
+ platform_set_drvdata(pdev, cdev);
+
+ ret = devm_request_threaded_irq(dev, cdev->irq, catpt_dsp_irq_handler,
+ catpt_dsp_irq_thread,
+ IRQF_SHARED, "AudioDSP", cdev);
+ if (ret)
+ return ret;
+
+ return catpt_probe_components(cdev);
+}
+
+static int catpt_acpi_remove(struct platform_device *pdev)
+{
+ struct catpt_dev *cdev = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(cdev->dev);
+
+ snd_soc_unregister_component(cdev->dev);
+ catpt_dmac_remove(cdev);
+ cdev->spec->power_down(cdev);
+
+ catpt_sram_free(&cdev->iram);
+ catpt_sram_free(&cdev->dram);
+
+ return 0;
+}
+
+static struct catpt_spec lpt_desc = {
+ .machines = snd_soc_acpi_intel_haswell_machines,
+ .core_id = 0x01,
+ .host_dram_offset = 0x000000,
+ .host_iram_offset = 0x080000,
+ .host_shim_offset = 0x0E7000,
+ .host_dma_offset = { 0x0F0000, 0x0F8000 },
+ .host_ssp_offset = { 0x0E8000, 0x0E9000 },
+ .dram_mask = LPT_VDRTCTL0_DSRAMPGE_MASK,
+ .iram_mask = LPT_VDRTCTL0_ISRAMPGE_MASK,
+ .pll_shutdown = lpt_dsp_pll_shutdown,
+ .power_up = lpt_dsp_power_up,
+ .power_down = lpt_dsp_power_down,
+};
+
+static struct catpt_spec wpt_desc = {
+ .machines = snd_soc_acpi_intel_broadwell_machines,
+ .core_id = 0x02,
+ .host_dram_offset = 0x000000,
+ .host_iram_offset = 0x0A0000,
+ .host_shim_offset = 0x0FB000,
+ .host_dma_offset = { 0x0FE000, 0x0FF000 },
+ .host_ssp_offset = { 0x0FC000, 0x0FD000 },
+ .dram_mask = WPT_VDRTCTL0_DSRAMPGE_MASK,
+ .iram_mask = WPT_VDRTCTL0_ISRAMPGE_MASK,
+ .pll_shutdown = wpt_dsp_pll_shutdown,
+ .power_up = wpt_dsp_power_up,
+ .power_down = wpt_dsp_power_down,
+};
+
+static const struct acpi_device_id catpt_ids[] = {
+ { "INT33C8", (unsigned long)&lpt_desc },
+ { "INT3438", (unsigned long)&wpt_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, catpt_ids);
+
+static struct platform_driver catpt_acpi_driver = {
+ .probe = catpt_acpi_probe,
+ .remove = catpt_acpi_remove,
+ .driver = {
+ .name = "intel_catpt",
+ .acpi_match_table = catpt_ids,
+ .pm = &catpt_dev_pm,
+ .dev_groups = catpt_attr_groups,
+ },
+};
+module_platform_driver(catpt_acpi_driver);
+
+MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>");
+MODULE_DESCRIPTION("Intel LPT/WPT AudioDSP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/intel/catpt/dsp.c b/sound/soc/intel/catpt/dsp.c
new file mode 100644
index 000000000..38a92bbc1
--- /dev/null
+++ b/sound/soc/intel/catpt/dsp.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/devcoredump.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/pxa2xx_ssp.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+
+static bool catpt_dma_filter(struct dma_chan *chan, void *param)
+{
+ return param == chan->device->dev;
+}
+
+/*
+ * Either engine 0 or 1 can be used for image loading.
+ * Align with Windows driver equivalent and stick to engine 1.
+ */
+#define CATPT_DMA_DEVID 1
+#define CATPT_DMA_DSP_ADDR_MASK GENMASK(31, 20)
+
+struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev)
+{
+ struct dma_slave_config config;
+ struct dma_chan *chan;
+ dma_cap_mask_t mask;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ chan = dma_request_channel(mask, catpt_dma_filter, cdev->dev);
+ if (!chan) {
+ dev_err(cdev->dev, "request channel failed\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ memset(&config, 0, sizeof(config));
+ config.direction = DMA_MEM_TO_DEV;
+ config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.src_maxburst = 16;
+ config.dst_maxburst = 16;
+
+ ret = dmaengine_slave_config(chan, &config);
+ if (ret) {
+ dev_err(cdev->dev, "slave config failed: %d\n", ret);
+ dma_release_channel(chan);
+ return ERR_PTR(ret);
+ }
+
+ return chan;
+}
+
+static int catpt_dma_memcpy(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ struct dma_async_tx_descriptor *desc;
+ enum dma_status status;
+ int ret;
+
+ desc = dmaengine_prep_dma_memcpy(chan, dst_addr, src_addr, size,
+ DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(cdev->dev, "prep dma memcpy failed\n");
+ return -EIO;
+ }
+
+ /* enable demand mode for dma channel */
+ catpt_updatel_shim(cdev, HMDC,
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id),
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id));
+
+ ret = dma_submit_error(dmaengine_submit(desc));
+ if (ret) {
+ dev_err(cdev->dev, "submit tx failed: %d\n", ret);
+ goto clear_hdda;
+ }
+
+ status = dma_wait_for_async_tx(desc);
+ ret = (status == DMA_COMPLETE) ? 0 : -EPROTO;
+
+clear_hdda:
+ /* regardless of status, disable access to HOST memory in demand mode */
+ catpt_updatel_shim(cdev, HMDC,
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id), 0);
+
+ return ret;
+}
+
+int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ return catpt_dma_memcpy(cdev, chan, dst_addr | CATPT_DMA_DSP_ADDR_MASK,
+ src_addr, size);
+}
+
+int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ return catpt_dma_memcpy(cdev, chan, dst_addr,
+ src_addr | CATPT_DMA_DSP_ADDR_MASK, size);
+}
+
+int catpt_dmac_probe(struct catpt_dev *cdev)
+{
+ struct dw_dma_chip *dmac;
+ int ret;
+
+ dmac = devm_kzalloc(cdev->dev, sizeof(*dmac), GFP_KERNEL);
+ if (!dmac)
+ return -ENOMEM;
+
+ dmac->regs = cdev->lpe_ba + cdev->spec->host_dma_offset[CATPT_DMA_DEVID];
+ dmac->dev = cdev->dev;
+ dmac->irq = cdev->irq;
+
+ ret = dma_coerce_mask_and_coherent(cdev->dev, DMA_BIT_MASK(31));
+ if (ret)
+ return ret;
+ /*
+ * Caller is responsible for putting device in D0 to allow
+ * for I/O and memory access before probing DW.
+ */
+ ret = dw_dma_probe(dmac);
+ if (ret)
+ return ret;
+
+ cdev->dmac = dmac;
+ return 0;
+}
+
+void catpt_dmac_remove(struct catpt_dev *cdev)
+{
+ /*
+ * As do_dma_remove() juggles with pm_runtime_get_xxx() and
+ * pm_runtime_put_xxx() while both ADSP and DW 'devices' are part of
+ * the same module, caller makes sure pm_runtime_disable() is invoked
+ * before removing DW to prevent postmortem resume and suspend.
+ */
+ dw_dma_remove(cdev->dmac);
+}
+
+static void catpt_dsp_set_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask, unsigned long new)
+{
+ unsigned long old;
+ u32 off = sram->start;
+ u32 b = __ffs(mask);
+
+ old = catpt_readl_pci(cdev, VDRTCTL0) & mask;
+ dev_dbg(cdev->dev, "SRAMPGE [0x%08lx] 0x%08lx -> 0x%08lx",
+ mask, old, new);
+
+ if (old == new)
+ return;
+
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, new);
+ /* wait for SRAM power gating to propagate */
+ udelay(60);
+
+ /*
+ * Dummy read as the very first access after block enable
+ * to prevent byte loss in future operations.
+ */
+ for_each_clear_bit_from(b, &new, fls_long(mask)) {
+ u8 buf[4];
+
+ /* newly enabled: new bit=0 while old bit=1 */
+ if (test_bit(b, &old)) {
+ dev_dbg(cdev->dev, "sanitize block %ld: off 0x%08x\n",
+ b - __ffs(mask), off);
+ memcpy_fromio(buf, cdev->lpe_ba + off, sizeof(buf));
+ }
+ off += CATPT_MEMBLOCK_SIZE;
+ }
+}
+
+void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask)
+{
+ struct resource *res;
+ unsigned long new = 0;
+
+ /* flag all busy blocks */
+ for (res = sram->child; res; res = res->sibling) {
+ u32 h, l;
+
+ h = (res->end - sram->start) / CATPT_MEMBLOCK_SIZE;
+ l = (res->start - sram->start) / CATPT_MEMBLOCK_SIZE;
+ new |= GENMASK(h, l);
+ }
+
+ /* offset value given mask's start and invert it as ON=b0 */
+ new = ~(new << __ffs(mask)) & mask;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ catpt_dsp_set_srampge(cdev, sram, mask, new);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+}
+
+int catpt_dsp_stall(struct catpt_dev *cdev, bool stall)
+{
+ u32 reg, val;
+
+ val = stall ? CATPT_CS_STALL : 0;
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_STALL, val);
+
+ return catpt_readl_poll_shim(cdev, CS1,
+ reg, (reg & CATPT_CS_STALL) == val,
+ 500, 10000);
+}
+
+static int catpt_dsp_reset(struct catpt_dev *cdev, bool reset)
+{
+ u32 reg, val;
+
+ val = reset ? CATPT_CS_RST : 0;
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_RST, val);
+
+ return catpt_readl_poll_shim(cdev, CS1,
+ reg, (reg & CATPT_CS_RST) == val,
+ 500, 10000);
+}
+
+void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable)
+{
+ u32 val;
+
+ val = enable ? LPT_VDRTCTL0_APLLSE : 0;
+ catpt_updatel_pci(cdev, VDRTCTL0, LPT_VDRTCTL0_APLLSE, val);
+}
+
+void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable)
+{
+ u32 val;
+
+ val = enable ? WPT_VDRTCTL2_APLLSE : 0;
+ catpt_updatel_pci(cdev, VDRTCTL2, WPT_VDRTCTL2_APLLSE, val);
+}
+
+static int catpt_dsp_select_lpclock(struct catpt_dev *cdev, bool lp, bool waiti)
+{
+ u32 mask, reg, val;
+ int ret;
+
+ mutex_lock(&cdev->clk_mutex);
+
+ val = lp ? CATPT_CS_LPCS : 0;
+ reg = catpt_readl_shim(cdev, CS1) & CATPT_CS_LPCS;
+ dev_dbg(cdev->dev, "LPCS [0x%08lx] 0x%08x -> 0x%08x",
+ CATPT_CS_LPCS, reg, val);
+
+ if (reg == val) {
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+ }
+
+ if (waiti) {
+ /* wait for DSP to signal WAIT state */
+ ret = catpt_readl_poll_shim(cdev, ISD,
+ reg, (reg & CATPT_ISD_DCPWM),
+ 500, 10000);
+ if (ret) {
+ dev_warn(cdev->dev, "await WAITI timeout\n");
+ /* no signal - only high clock selection allowed */
+ if (lp) {
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+ }
+ }
+ }
+
+ ret = catpt_readl_poll_shim(cdev, CLKCTL,
+ reg, !(reg & CATPT_CLKCTL_CFCIP),
+ 500, 10000);
+ if (ret)
+ dev_warn(cdev->dev, "clock change still in progress\n");
+
+ /* default to DSP core & audio fabric high clock */
+ val |= CATPT_CS_DCS_HIGH;
+ mask = CATPT_CS_LPCS | CATPT_CS_DCS;
+ catpt_updatel_shim(cdev, CS1, mask, val);
+
+ ret = catpt_readl_poll_shim(cdev, CLKCTL,
+ reg, !(reg & CATPT_CLKCTL_CFCIP),
+ 500, 10000);
+ if (ret)
+ dev_warn(cdev->dev, "clock change still in progress\n");
+
+ /* update PLL accordingly */
+ cdev->spec->pll_shutdown(cdev, lp);
+
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+}
+
+int catpt_dsp_update_lpclock(struct catpt_dev *cdev)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node)
+ if (stream->prepared)
+ return catpt_dsp_select_lpclock(cdev, false, true);
+
+ return catpt_dsp_select_lpclock(cdev, true, true);
+}
+
+/* bring registers to their defaults as HW won't reset itself */
+static void catpt_dsp_set_regs_defaults(struct catpt_dev *cdev)
+{
+ int i;
+
+ catpt_writel_shim(cdev, CS1, CATPT_CS_DEFAULT);
+ catpt_writel_shim(cdev, ISC, CATPT_ISC_DEFAULT);
+ catpt_writel_shim(cdev, ISD, CATPT_ISD_DEFAULT);
+ catpt_writel_shim(cdev, IMC, CATPT_IMC_DEFAULT);
+ catpt_writel_shim(cdev, IMD, CATPT_IMD_DEFAULT);
+ catpt_writel_shim(cdev, IPCC, CATPT_IPCC_DEFAULT);
+ catpt_writel_shim(cdev, IPCD, CATPT_IPCD_DEFAULT);
+ catpt_writel_shim(cdev, CLKCTL, CATPT_CLKCTL_DEFAULT);
+ catpt_writel_shim(cdev, CS2, CATPT_CS2_DEFAULT);
+ catpt_writel_shim(cdev, LTRC, CATPT_LTRC_DEFAULT);
+ catpt_writel_shim(cdev, HMDC, CATPT_HMDC_DEFAULT);
+
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ catpt_writel_ssp(cdev, i, SSCR0, CATPT_SSC0_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSCR1, CATPT_SSC1_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSSR, CATPT_SSS_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSITR, CATPT_SSIT_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSDR, CATPT_SSD_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTO, CATPT_SSTO_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSPSP, CATPT_SSPSP_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTSA, CATPT_SSTSA_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSRSA, CATPT_SSRSA_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTSS, CATPT_SSTSS_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSCR2, CATPT_SSCR2_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSPSP2, CATPT_SSPSP2_DEFAULT);
+ }
+}
+
+int lpt_dsp_power_down(struct catpt_dev *cdev)
+{
+ catpt_dsp_reset(cdev, true);
+
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_select_lpclock(cdev, true, false);
+
+ /* DRAM power gating all */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask,
+ cdev->spec->dram_mask);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask,
+ cdev->spec->iram_mask);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D3hot);
+ /* give hw time to drop off */
+ udelay(50);
+
+ return 0;
+}
+
+int lpt_dsp_power_up(struct catpt_dev *cdev)
+{
+ /* SRAM power gating none */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, 0);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, 0);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D0);
+ /* give hw time to wake up */
+ udelay(100);
+
+ catpt_dsp_select_lpclock(cdev, false, false);
+ catpt_updatel_shim(cdev, CS1,
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ /* stagger DSP reset after clock selection */
+ udelay(50);
+
+ catpt_dsp_reset(cdev, false);
+ /* generate int deassert msg to fix inversed int logic */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB | CATPT_IMC_IPCCD, 0);
+
+ return 0;
+}
+
+int wpt_dsp_power_down(struct catpt_dev *cdev)
+{
+ u32 mask, val;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ catpt_dsp_reset(cdev, true);
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_select_lpclock(cdev, true, false);
+ /* disable MCLK */
+ catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, 0);
+
+ catpt_dsp_set_regs_defaults(cdev);
+
+ /* switch clock gating */
+ mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE);
+ val = mask & (~CATPT_VDRTCTL2_DTCGE);
+ catpt_updatel_pci(cdev, VDRTCTL2, mask, val);
+ /* enable DTCGE separatelly */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DTCGE,
+ CATPT_VDRTCTL2_DTCGE);
+
+ /* SRAM power gating all */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask,
+ cdev->spec->dram_mask);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask,
+ cdev->spec->iram_mask);
+ mask = WPT_VDRTCTL0_D3SRAMPGD | WPT_VDRTCTL0_D3PGD;
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, WPT_VDRTCTL0_D3PGD);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D3hot);
+ /* give hw time to drop off */
+ udelay(50);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+ udelay(50);
+
+ return 0;
+}
+
+int wpt_dsp_power_up(struct catpt_dev *cdev)
+{
+ u32 mask, val;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ /* switch clock gating */
+ mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE);
+ val = mask & (~CATPT_VDRTCTL2_DTCGE);
+ catpt_updatel_pci(cdev, VDRTCTL2, mask, val);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D0);
+
+ /* SRAM power gating none */
+ mask = WPT_VDRTCTL0_D3SRAMPGD | WPT_VDRTCTL0_D3PGD;
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, mask);
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, 0);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, 0);
+
+ catpt_dsp_set_regs_defaults(cdev);
+
+ /* restore MCLK */
+ catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, CATPT_CLKCTL_SMOS);
+ catpt_dsp_select_lpclock(cdev, false, false);
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_reset(cdev, false);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+
+ /* generate int deassert msg to fix inversed int logic */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB | CATPT_IMC_IPCCD, 0);
+
+ return 0;
+}
+
+#define CATPT_DUMP_MAGIC 0xcd42
+#define CATPT_DUMP_SECTION_ID_FILE 0x00
+#define CATPT_DUMP_SECTION_ID_IRAM 0x01
+#define CATPT_DUMP_SECTION_ID_DRAM 0x02
+#define CATPT_DUMP_SECTION_ID_REGS 0x03
+#define CATPT_DUMP_HASH_SIZE 20
+
+struct catpt_dump_section_hdr {
+ u16 magic;
+ u8 core_id;
+ u8 section_id;
+ u32 size;
+};
+
+int catpt_coredump(struct catpt_dev *cdev)
+{
+ struct catpt_dump_section_hdr *hdr;
+ size_t dump_size, regs_size;
+ u8 *dump, *pos;
+ const char *eof;
+ char *info;
+ int i;
+
+ regs_size = CATPT_SHIM_REGS_SIZE;
+ regs_size += CATPT_DMA_COUNT * CATPT_DMA_REGS_SIZE;
+ regs_size += CATPT_SSP_COUNT * CATPT_SSP_REGS_SIZE;
+ dump_size = resource_size(&cdev->dram);
+ dump_size += resource_size(&cdev->iram);
+ dump_size += regs_size;
+ /* account for header of each section and hash chunk */
+ dump_size += 4 * sizeof(*hdr) + CATPT_DUMP_HASH_SIZE;
+
+ dump = vzalloc(dump_size);
+ if (!dump)
+ return -ENOMEM;
+
+ pos = dump;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_FILE;
+ hdr->size = dump_size - sizeof(*hdr);
+ pos += sizeof(*hdr);
+
+ info = cdev->ipc.config.fw_info;
+ eof = info + FW_INFO_SIZE_MAX;
+ /* navigate to fifth info segment (fw hash) */
+ for (i = 0; i < 4 && info < eof; i++, info++) {
+ /* info segments are separated by space each */
+ info = strnchr(info, eof - info, ' ');
+ if (!info)
+ break;
+ }
+
+ if (i == 4 && info)
+ memcpy(pos, info, min_t(u32, eof - info, CATPT_DUMP_HASH_SIZE));
+ pos += CATPT_DUMP_HASH_SIZE;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_IRAM;
+ hdr->size = resource_size(&cdev->iram);
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, cdev->lpe_ba + cdev->iram.start, hdr->size);
+ pos += hdr->size;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_DRAM;
+ hdr->size = resource_size(&cdev->dram);
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, cdev->lpe_ba + cdev->dram.start, hdr->size);
+ pos += hdr->size;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_REGS;
+ hdr->size = regs_size;
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, catpt_shim_addr(cdev), CATPT_SHIM_REGS_SIZE);
+ pos += CATPT_SHIM_REGS_SIZE;
+
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ memcpy_fromio(pos, catpt_ssp_addr(cdev, i),
+ CATPT_SSP_REGS_SIZE);
+ pos += CATPT_SSP_REGS_SIZE;
+ }
+ for (i = 0; i < CATPT_DMA_COUNT; i++) {
+ memcpy_fromio(pos, catpt_dma_addr(cdev, i),
+ CATPT_DMA_REGS_SIZE);
+ pos += CATPT_DMA_REGS_SIZE;
+ }
+
+ dev_coredumpv(cdev->dev, dump, dump_size, GFP_KERNEL);
+
+ return 0;
+}
diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c
new file mode 100644
index 000000000..5b718a846
--- /dev/null
+++ b/sound/soc/intel/catpt/ipc.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/irqreturn.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+#include "trace.h"
+
+#define CATPT_IPC_TIMEOUT_MS 300
+
+void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev)
+{
+ ipc->dev = dev;
+ ipc->ready = false;
+ ipc->default_timeout = CATPT_IPC_TIMEOUT_MS;
+ init_completion(&ipc->done_completion);
+ init_completion(&ipc->busy_completion);
+ spin_lock_init(&ipc->lock);
+ mutex_init(&ipc->mutex);
+}
+
+static int catpt_ipc_arm(struct catpt_ipc *ipc, struct catpt_fw_ready *config)
+{
+ /*
+ * Both tx and rx are put into and received from outbox. Inbox is
+ * only used for notifications where payload size is known upfront,
+ * thus no separate buffer is allocated for it.
+ */
+ ipc->rx.data = devm_kzalloc(ipc->dev, config->outbox_size, GFP_KERNEL);
+ if (!ipc->rx.data)
+ return -ENOMEM;
+
+ memcpy(&ipc->config, config, sizeof(*config));
+ ipc->ready = true;
+
+ return 0;
+}
+
+static void catpt_ipc_msg_init(struct catpt_ipc *ipc,
+ struct catpt_ipc_msg *reply)
+{
+ lockdep_assert_held(&ipc->lock);
+
+ ipc->rx.header = 0;
+ ipc->rx.size = reply ? reply->size : 0;
+ reinit_completion(&ipc->done_completion);
+ reinit_completion(&ipc->busy_completion);
+}
+
+static void catpt_dsp_send_tx(struct catpt_dev *cdev,
+ const struct catpt_ipc_msg *tx)
+{
+ u32 header = tx->header | CATPT_IPCC_BUSY;
+
+ trace_catpt_ipc_request(header);
+ trace_catpt_ipc_payload(tx->data, tx->size);
+
+ memcpy_toio(catpt_outbox_addr(cdev), tx->data, tx->size);
+ catpt_writel_shim(cdev, IPCC, header);
+}
+
+static int catpt_wait_msg_completion(struct catpt_dev *cdev, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ int ret;
+
+ ret = wait_for_completion_timeout(&ipc->done_completion,
+ msecs_to_jiffies(timeout));
+ if (!ret)
+ return -ETIMEDOUT;
+ if (ipc->rx.rsp.status != CATPT_REPLY_PENDING)
+ return 0;
+
+ /* wait for delayed reply */
+ ret = wait_for_completion_timeout(&ipc->busy_completion,
+ msecs_to_jiffies(timeout));
+ return ret ? 0 : -ETIMEDOUT;
+}
+
+static int catpt_dsp_do_send_msg(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ unsigned long flags;
+ int ret;
+
+ if (!ipc->ready)
+ return -EPERM;
+ if (request.size > ipc->config.outbox_size ||
+ (reply && reply->size > ipc->config.outbox_size))
+ return -EINVAL;
+
+ spin_lock_irqsave(&ipc->lock, flags);
+ catpt_ipc_msg_init(ipc, reply);
+ catpt_dsp_send_tx(cdev, &request);
+ spin_unlock_irqrestore(&ipc->lock, flags);
+
+ ret = catpt_wait_msg_completion(cdev, timeout);
+ if (ret) {
+ dev_crit(cdev->dev, "communication severed: %d, rebooting dsp..\n",
+ ret);
+ ipc->ready = false;
+ /* TODO: attempt recovery */
+ return ret;
+ }
+
+ ret = ipc->rx.rsp.status;
+ if (reply) {
+ reply->header = ipc->rx.header;
+
+ if (!ret && reply->data)
+ memcpy(reply->data, ipc->rx.data, reply->size);
+ }
+
+ return ret;
+}
+
+int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ int ret;
+
+ mutex_lock(&ipc->mutex);
+ ret = catpt_dsp_do_send_msg(cdev, request, reply, timeout);
+ mutex_unlock(&ipc->mutex);
+
+ return ret;
+}
+
+int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply)
+{
+ return catpt_dsp_send_msg_timeout(cdev, request, reply,
+ cdev->ipc.default_timeout);
+}
+
+static void
+catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg)
+{
+ struct catpt_stream_runtime *stream;
+ struct catpt_notify_position pos;
+ struct catpt_notify_glitch glitch;
+
+ stream = catpt_stream_find(cdev, msg.stream_hw_id);
+ if (!stream) {
+ dev_warn(cdev->dev, "notify %d for non-existent stream %d\n",
+ msg.notify_reason, msg.stream_hw_id);
+ return;
+ }
+
+ switch (msg.notify_reason) {
+ case CATPT_NOTIFY_POSITION_CHANGED:
+ memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos));
+ trace_catpt_ipc_payload((u8 *)&pos, sizeof(pos));
+
+ catpt_stream_update_position(cdev, stream, &pos);
+ break;
+
+ case CATPT_NOTIFY_GLITCH_OCCURRED:
+ memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch));
+ trace_catpt_ipc_payload((u8 *)&glitch, sizeof(glitch));
+
+ dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n",
+ glitch.type, glitch.presentation_pos,
+ glitch.write_pos);
+ break;
+
+ default:
+ dev_warn(cdev->dev, "unknown notification: %d received\n",
+ msg.notify_reason);
+ break;
+ }
+}
+
+static void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+
+ ipc->rx.header = header;
+ if (ipc->rx.rsp.status != CATPT_REPLY_SUCCESS)
+ return;
+
+ memcpy_fromio(ipc->rx.data, catpt_outbox_addr(cdev), ipc->rx.size);
+ trace_catpt_ipc_payload(ipc->rx.data, ipc->rx.size);
+}
+
+static void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header)
+{
+ union catpt_notify_msg msg = CATPT_MSG(header);
+ struct catpt_ipc *ipc = &cdev->ipc;
+
+ if (msg.fw_ready) {
+ struct catpt_fw_ready config;
+ /* to fit 32b header original address is shifted right by 3 */
+ u32 off = msg.mailbox_address << 3;
+
+ memcpy_fromio(&config, cdev->lpe_ba + off, sizeof(config));
+ trace_catpt_ipc_payload((u8 *)&config, sizeof(config));
+
+ catpt_ipc_arm(ipc, &config);
+ complete(&cdev->fw_ready);
+ return;
+ }
+
+ switch (msg.global_msg_type) {
+ case CATPT_GLB_REQUEST_CORE_DUMP:
+ dev_err(cdev->dev, "ADSP device coredump received\n");
+ ipc->ready = false;
+ catpt_coredump(cdev);
+ /* TODO: attempt recovery */
+ break;
+
+ case CATPT_GLB_STREAM_MESSAGE:
+ switch (msg.stream_msg_type) {
+ case CATPT_STRM_NOTIFICATION:
+ catpt_dsp_notify_stream(cdev, msg);
+ break;
+ default:
+ catpt_dsp_copy_rx(cdev, header);
+ /* signal completion of delayed reply */
+ complete(&ipc->busy_completion);
+ break;
+ }
+ break;
+
+ default:
+ dev_warn(cdev->dev, "unknown response: %d received\n",
+ msg.global_msg_type);
+ break;
+ }
+}
+
+irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id)
+{
+ struct catpt_dev *cdev = dev_id;
+ u32 ipcd;
+
+ ipcd = catpt_readl_shim(cdev, IPCD);
+ trace_catpt_ipc_notify(ipcd);
+
+ /* ensure there is delayed reply or notification to process */
+ if (!(ipcd & CATPT_IPCD_BUSY))
+ return IRQ_NONE;
+
+ catpt_dsp_process_response(cdev, ipcd);
+
+ /* tell DSP processing is completed */
+ catpt_updatel_shim(cdev, IPCD, CATPT_IPCD_BUSY | CATPT_IPCD_DONE,
+ CATPT_IPCD_DONE);
+ /* unmask dsp BUSY interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, 0);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id)
+{
+ struct catpt_dev *cdev = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ u32 isc, ipcc;
+
+ isc = catpt_readl_shim(cdev, ISC);
+ trace_catpt_irq(isc);
+
+ /* immediate reply */
+ if (isc & CATPT_ISC_IPCCD) {
+ /* mask host DONE interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, CATPT_IMC_IPCCD);
+
+ ipcc = catpt_readl_shim(cdev, IPCC);
+ trace_catpt_ipc_reply(ipcc);
+ catpt_dsp_copy_rx(cdev, ipcc);
+ complete(&cdev->ipc.done_completion);
+
+ /* tell DSP processing is completed */
+ catpt_updatel_shim(cdev, IPCC, CATPT_IPCC_DONE, 0);
+ /* unmask host DONE interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, 0);
+ ret = IRQ_HANDLED;
+ }
+
+ /* delayed reply or notification */
+ if (isc & CATPT_ISC_IPCDB) {
+ /* mask dsp BUSY interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, CATPT_IMC_IPCDB);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c
new file mode 100644
index 000000000..8a5f20abc
--- /dev/null
+++ b/sound/soc/intel/catpt/loader.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include "core.h"
+#include "registers.h"
+
+/* FW load (200ms) plus operational delays */
+#define FW_READY_TIMEOUT_MS 250
+
+#define FW_SIGNATURE "$SST"
+#define FW_SIGNATURE_SIZE 4
+
+struct catpt_fw_hdr {
+ char signature[FW_SIGNATURE_SIZE];
+ u32 file_size;
+ u32 modules;
+ u32 file_format;
+ u32 reserved[4];
+} __packed;
+
+struct catpt_fw_mod_hdr {
+ char signature[FW_SIGNATURE_SIZE];
+ u32 mod_size;
+ u32 blocks;
+ u16 slot;
+ u16 module_id;
+ u32 entry_point;
+ u32 persistent_size;
+ u32 scratch_size;
+} __packed;
+
+enum catpt_ram_type {
+ CATPT_RAM_TYPE_IRAM = 1,
+ CATPT_RAM_TYPE_DRAM = 2,
+ /* DRAM with module's initial state */
+ CATPT_RAM_TYPE_INSTANCE = 3,
+};
+
+struct catpt_fw_block_hdr {
+ u32 ram_type;
+ u32 size;
+ u32 ram_offset;
+ u32 rsvd;
+} __packed;
+
+void catpt_sram_init(struct resource *sram, u32 start, u32 size)
+{
+ sram->start = start;
+ sram->end = start + size - 1;
+}
+
+void catpt_sram_free(struct resource *sram)
+{
+ struct resource *res, *save;
+
+ for (res = sram->child; res;) {
+ save = res->sibling;
+ release_resource(res);
+ kfree(res);
+ res = save;
+ }
+}
+
+struct resource *
+catpt_request_region(struct resource *root, resource_size_t size)
+{
+ struct resource *res = root->child;
+ resource_size_t addr = root->start;
+
+ for (;;) {
+ if (res->start - addr >= size)
+ break;
+ addr = res->end + 1;
+ res = res->sibling;
+ if (!res)
+ return NULL;
+ }
+
+ return __request_region(root, addr, size, NULL, 0);
+}
+
+int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node) {
+ u32 off, size;
+ int ret;
+
+ off = stream->persistent->start;
+ size = resource_size(stream->persistent);
+ dev_dbg(cdev->dev, "storing stream %d ctx: off 0x%08x size %d\n",
+ stream->info.stream_hw_id, off, size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cdev->modules); i++) {
+ struct catpt_module_type *type;
+ u32 off;
+ int ret;
+
+ type = &cdev->modules[i];
+ if (!type->loaded || !type->state_size)
+ continue;
+
+ off = type->state_offset;
+ dev_dbg(cdev->dev, "storing mod %d state: off 0x%08x size %d\n",
+ i, off, type->state_size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(type->state_size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+ if (info->source != CATPT_DX_TYPE_MEMORY_DUMP)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ dev_dbg(cdev->dev, "storing memdump: off 0x%08x size %d\n",
+ off, info->size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(info->size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node) {
+ u32 off, size;
+ int ret;
+
+ off = stream->persistent->start;
+ size = resource_size(stream->persistent);
+ dev_dbg(cdev->dev, "restoring stream %d ctx: off 0x%08x size %d\n",
+ stream->info.stream_hw_id, off, size);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + off,
+ cdev->dxbuf_paddr + off,
+ ALIGN(size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+ if (info->source != CATPT_DX_TYPE_MEMORY_DUMP)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ dev_dbg(cdev->dev, "restoring memdump: off 0x%08x size %d\n",
+ off, info->size);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + off,
+ cdev->dxbuf_paddr + off,
+ ALIGN(info->size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_restore_fwimage(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_block_hdr *blk)
+{
+ struct resource r1, r2, common;
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ blk, sizeof(*blk), false);
+
+ r1.start = cdev->dram.start + blk->ram_offset;
+ r1.end = r1.start + blk->size - 1;
+ /* advance to data area */
+ paddr += sizeof(*blk);
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+
+ if (info->source != CATPT_DX_TYPE_FW_IMAGE)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ r2.start = off;
+ r2.end = r2.start + info->size - 1;
+
+ if (!catpt_resource_overlapping(&r2, &r1, &common))
+ continue;
+ /* calculate start offset of common data area */
+ off = common.start - r1.start;
+
+ dev_dbg(cdev->dev, "restoring fwimage: %pr\n", &common);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan, common.start,
+ paddr + off,
+ resource_size(&common));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy todsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_load_block(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_block_hdr *blk, bool alloc)
+{
+ struct resource *sram, *res;
+ dma_addr_t dst_addr;
+ int ret;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ blk, sizeof(*blk), false);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_IRAM:
+ sram = &cdev->iram;
+ break;
+ default:
+ sram = &cdev->dram;
+ break;
+ };
+
+ dst_addr = sram->start + blk->ram_offset;
+ if (alloc) {
+ res = __request_region(sram, dst_addr, blk->size, NULL, 0);
+ if (!res)
+ return -EBUSY;
+ }
+
+ /* advance to data area */
+ paddr += sizeof(*blk);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan, dst_addr, paddr, blk->size);
+ if (ret) {
+ dev_err(cdev->dev, "memcpy error: %d\n", ret);
+ __release_region(sram, dst_addr, blk->size);
+ }
+
+ return ret;
+}
+
+static int catpt_restore_basefw(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *basefw)
+{
+ u32 offset = sizeof(*basefw);
+ int ret, i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ basefw, sizeof(*basefw), false);
+
+ /* restore basefw image */
+ for (i = 0; i < basefw->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_IRAM:
+ ret = catpt_load_block(cdev, chan, paddr + offset,
+ blk, false);
+ break;
+ default:
+ ret = catpt_restore_fwimage(cdev, chan, paddr + offset,
+ blk);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ /* then proceed with memory dumps */
+ ret = catpt_restore_memdumps(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "restore memdumps failed: %d\n", ret);
+
+ return ret;
+}
+
+static int catpt_restore_module(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *mod)
+{
+ u32 offset = sizeof(*mod);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ mod, sizeof(*mod), false);
+
+ for (i = 0; i < mod->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+ int ret;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_INSTANCE:
+ /* restore module state */
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + blk->ram_offset,
+ cdev->dxbuf_paddr + blk->ram_offset,
+ ALIGN(blk->size, 4));
+ break;
+ default:
+ ret = catpt_load_block(cdev, chan, paddr + offset,
+ blk, false);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_module(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *mod)
+{
+ struct catpt_module_type *type;
+ u32 offset = sizeof(*mod);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ mod, sizeof(*mod), false);
+
+ type = &cdev->modules[mod->module_id];
+
+ for (i = 0; i < mod->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+ int ret;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset);
+
+ ret = catpt_load_block(cdev, chan, paddr + offset, blk, true);
+ if (ret) {
+ dev_err(cdev->dev, "load block failed: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Save state window coordinates - these will be
+ * used to capture module state on D0 exit.
+ */
+ if (blk->ram_type == CATPT_RAM_TYPE_INSTANCE) {
+ type->state_offset = blk->ram_offset;
+ type->state_size = blk->size;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ /* init module type static info */
+ type->loaded = true;
+ /* DSP expects address from module header substracted by 4 */
+ type->entry_point = mod->entry_point - 4;
+ type->persistent_size = mod->persistent_size;
+ type->scratch_size = mod->scratch_size;
+
+ return 0;
+}
+
+static int catpt_restore_firmware(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_hdr *fw)
+{
+ u32 offset = sizeof(*fw);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ fw, sizeof(*fw), false);
+
+ for (i = 0; i < fw->modules; i++) {
+ struct catpt_fw_mod_hdr *mod;
+ int ret;
+
+ mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset);
+ if (strncmp(fw->signature, mod->signature,
+ FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "module signature mismatch\n");
+ return -EINVAL;
+ }
+
+ if (mod->module_id > CATPT_MODID_LAST)
+ return -EINVAL;
+
+ switch (mod->module_id) {
+ case CATPT_MODID_BASE_FW:
+ ret = catpt_restore_basefw(cdev, chan, paddr + offset,
+ mod);
+ break;
+ default:
+ ret = catpt_restore_module(cdev, chan, paddr + offset,
+ mod);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore module failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*mod) + mod->mod_size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_firmware(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_hdr *fw)
+{
+ u32 offset = sizeof(*fw);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ fw, sizeof(*fw), false);
+
+ for (i = 0; i < fw->modules; i++) {
+ struct catpt_fw_mod_hdr *mod;
+ int ret;
+
+ mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset);
+ if (strncmp(fw->signature, mod->signature,
+ FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "module signature mismatch\n");
+ return -EINVAL;
+ }
+
+ if (mod->module_id > CATPT_MODID_LAST)
+ return -EINVAL;
+
+ ret = catpt_load_module(cdev, chan, paddr + offset, mod);
+ if (ret) {
+ dev_err(cdev->dev, "load module failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*mod) + mod->mod_size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan,
+ const char *name, const char *signature,
+ bool restore)
+{
+ struct catpt_fw_hdr *fw;
+ struct firmware *img;
+ dma_addr_t paddr;
+ void *vaddr;
+ int ret;
+
+ ret = request_firmware((const struct firmware **)&img, name, cdev->dev);
+ if (ret)
+ return ret;
+
+ fw = (struct catpt_fw_hdr *)img->data;
+ if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "firmware signature mismatch\n");
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL);
+ if (!vaddr) {
+ ret = -ENOMEM;
+ goto release_fw;
+ }
+
+ memcpy(vaddr, img->data, img->size);
+ fw = (struct catpt_fw_hdr *)vaddr;
+ if (restore)
+ ret = catpt_restore_firmware(cdev, chan, paddr, fw);
+ else
+ ret = catpt_load_firmware(cdev, chan, paddr, fw);
+
+ dma_free_coherent(cdev->dev, img->size, vaddr, paddr);
+release_fw:
+ release_firmware(img);
+ return ret;
+}
+
+static int catpt_load_images(struct catpt_dev *cdev, bool restore)
+{
+ static const char *const names[] = {
+ "intel/IntcSST1.bin",
+ "intel/IntcSST2.bin",
+ };
+ struct dma_chan *chan;
+ int ret;
+
+ chan = catpt_dma_request_config_chan(cdev);
+ if (IS_ERR(chan))
+ return PTR_ERR(chan);
+
+ ret = catpt_load_image(cdev, chan, names[cdev->spec->core_id - 1],
+ FW_SIGNATURE, restore);
+ if (ret)
+ goto release_dma_chan;
+
+ if (!restore)
+ goto release_dma_chan;
+ ret = catpt_restore_streams_context(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "restore streams ctx failed: %d\n", ret);
+release_dma_chan:
+ dma_release_channel(chan);
+ return ret;
+}
+
+int catpt_boot_firmware(struct catpt_dev *cdev, bool restore)
+{
+ int ret;
+
+ catpt_dsp_stall(cdev, true);
+
+ ret = catpt_load_images(cdev, restore);
+ if (ret) {
+ dev_err(cdev->dev, "load binaries failed: %d\n", ret);
+ return ret;
+ }
+
+ reinit_completion(&cdev->fw_ready);
+ catpt_dsp_stall(cdev, false);
+
+ ret = wait_for_completion_timeout(&cdev->fw_ready,
+ msecs_to_jiffies(FW_READY_TIMEOUT_MS));
+ if (!ret) {
+ dev_err(cdev->dev, "firmware ready timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ /* update sram pg & clock once done booting */
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+ catpt_dsp_update_srampge(cdev, &cdev->iram, cdev->spec->iram_mask);
+
+ return catpt_dsp_update_lpclock(cdev);
+}
+
+int catpt_first_boot_firmware(struct catpt_dev *cdev)
+{
+ struct resource *res;
+ int ret;
+
+ ret = catpt_boot_firmware(cdev, false);
+ if (ret) {
+ dev_err(cdev->dev, "basefw boot failed: %d\n", ret);
+ return ret;
+ }
+
+ /* restrict FW Core dump area */
+ __request_region(&cdev->dram, 0, 0x200, NULL, 0);
+ /* restrict entire area following BASE_FW - highest offset in DRAM */
+ for (res = cdev->dram.child; res->sibling; res = res->sibling)
+ ;
+ __request_region(&cdev->dram, res->end + 1,
+ cdev->dram.end - res->end, NULL, 0);
+
+ ret = catpt_ipc_get_mixer_stream_info(cdev, &cdev->mixer);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_arm_stream_templates(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "arm templates failed: %d\n", ret);
+ return ret;
+ }
+
+ /* update dram pg for scratch and restricted regions */
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ return 0;
+}
diff --git a/sound/soc/intel/catpt/messages.c b/sound/soc/intel/catpt/messages.c
new file mode 100644
index 000000000..a793d114a
--- /dev/null
+++ b/sound/soc/intel/catpt/messages.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/slab.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+
+int catpt_ipc_get_fw_version(struct catpt_dev *cdev,
+ struct catpt_fw_version *version)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_FW_VERSION);
+ struct catpt_ipc_msg request = {{0}}, reply;
+ int ret;
+
+ request.header = msg.val;
+ reply.size = sizeof(*version);
+ reply.data = version;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "get fw version failed: %d\n", ret);
+
+ return ret;
+}
+
+struct catpt_alloc_stream_input {
+ enum catpt_path_id path_id:8;
+ enum catpt_stream_type stream_type:8;
+ enum catpt_format_id format_id:8;
+ u8 reserved;
+ struct catpt_audio_format input_format;
+ struct catpt_ring_info ring_info;
+ u8 num_entries;
+ /* flex array with entries here */
+ struct catpt_memory_info persistent_mem;
+ struct catpt_memory_info scratch_mem;
+ u32 num_notifications; /* obsolete */
+} __packed;
+
+int catpt_ipc_alloc_stream(struct catpt_dev *cdev,
+ enum catpt_path_id path_id,
+ enum catpt_stream_type type,
+ struct catpt_audio_format *afmt,
+ struct catpt_ring_info *rinfo,
+ u8 num_modules,
+ struct catpt_module_entry *modules,
+ struct resource *persistent,
+ struct resource *scratch,
+ struct catpt_stream_info *sinfo)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(ALLOCATE_STREAM);
+ struct catpt_alloc_stream_input input;
+ struct catpt_ipc_msg request, reply;
+ size_t size, arrsz;
+ u8 *payload;
+ off_t off;
+ int ret;
+
+ off = offsetof(struct catpt_alloc_stream_input, persistent_mem);
+ arrsz = sizeof(*modules) * num_modules;
+ size = sizeof(input) + arrsz;
+
+ payload = kzalloc(size, GFP_KERNEL);
+ if (!payload)
+ return -ENOMEM;
+
+ memset(&input, 0, sizeof(input));
+ input.path_id = path_id;
+ input.stream_type = type;
+ input.format_id = CATPT_FORMAT_PCM;
+ input.input_format = *afmt;
+ input.ring_info = *rinfo;
+ input.num_entries = num_modules;
+ input.persistent_mem.offset = catpt_to_dsp_offset(persistent->start);
+ input.persistent_mem.size = resource_size(persistent);
+ if (scratch) {
+ input.scratch_mem.offset = catpt_to_dsp_offset(scratch->start);
+ input.scratch_mem.size = resource_size(scratch);
+ }
+
+ /* re-arrange the input: account for flex array 'entries' */
+ memcpy(payload, &input, sizeof(input));
+ memmove(payload + off + arrsz, payload + off, sizeof(input) - off);
+ memcpy(payload + off, modules, arrsz);
+
+ request.header = msg.val;
+ request.size = size;
+ request.data = payload;
+ reply.size = sizeof(*sinfo);
+ reply.data = sinfo;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "alloc stream type %d failed: %d\n",
+ type, ret);
+
+ kfree(payload);
+ return ret;
+}
+
+int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(FREE_STREAM);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(stream_hw_id);
+ request.data = &stream_hw_id;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "free stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_set_device_format(struct catpt_dev *cdev,
+ struct catpt_ssp_device_format *devfmt)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(SET_DEVICE_FORMATS);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(*devfmt);
+ request.data = devfmt;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set device format failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state,
+ struct catpt_dx_context *context)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(ENTER_DX_STATE);
+ struct catpt_ipc_msg request, reply;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(state);
+ request.data = &state;
+ reply.size = sizeof(*context);
+ reply.data = context;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "enter dx state failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_MIXER_STREAM_INFO);
+ struct catpt_ipc_msg request = {{0}}, reply;
+ int ret;
+
+ request.header = msg.val;
+ reply.size = sizeof(*info);
+ reply.data = info;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "get mixer info failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(RESET_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "reset stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(PAUSE_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "pause stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(RESUME_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "resume stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+struct catpt_set_volume_input {
+ u32 channel;
+ u32 target_volume;
+ u64 curve_duration;
+ u32 curve_type;
+} __packed;
+
+int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 channel, u32 volume,
+ u32 curve_duration,
+ enum catpt_audio_curve_type curve_type)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_VOLUME);
+ struct catpt_ipc_msg request;
+ struct catpt_set_volume_input input;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ input.channel = channel;
+ input.target_volume = volume;
+ input.curve_duration = curve_duration;
+ input.curve_type = curve_type;
+
+ request.header = msg.val;
+ request.size = sizeof(input);
+ request.data = &input;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set stream %d volume failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+struct catpt_set_write_pos_input {
+ u32 new_write_pos;
+ bool end_of_buffer;
+ bool low_latency;
+} __packed;
+
+int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 pos, bool eob, bool ll)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_WRITE_POSITION);
+ struct catpt_ipc_msg request;
+ struct catpt_set_write_pos_input input;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ input.new_write_pos = pos;
+ input.end_of_buffer = eob;
+ input.low_latency = ll;
+
+ request.header = msg.val;
+ request.size = sizeof(input);
+ request.data = &input;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set stream %d write pos failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(MUTE_LOOPBACK);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+ request.size = sizeof(mute);
+ request.data = &mute;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "mute loopback failed: %d\n", ret);
+
+ return ret;
+}
diff --git a/sound/soc/intel/catpt/messages.h b/sound/soc/intel/catpt/messages.h
new file mode 100644
index 000000000..978a20b3f
--- /dev/null
+++ b/sound/soc/intel/catpt/messages.h
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_MSG_H
+#define __SND_SOC_INTEL_CATPT_MSG_H
+
+struct catpt_dev;
+
+/* IPC messages base types */
+
+enum catpt_reply_status {
+ CATPT_REPLY_SUCCESS = 0,
+ CATPT_REPLY_ERROR_INVALID_PARAM = 1,
+ CATPT_REPLY_UNKNOWN_MESSAGE_TYPE = 2,
+ CATPT_REPLY_OUT_OF_RESOURCES = 3,
+ CATPT_REPLY_BUSY = 4,
+ CATPT_REPLY_PENDING = 5,
+ CATPT_REPLY_FAILURE = 6,
+ CATPT_REPLY_INVALID_REQUEST = 7,
+ CATPT_REPLY_UNINITIALIZED = 8,
+ CATPT_REPLY_NOT_FOUND = 9,
+ CATPT_REPLY_SOURCE_NOT_STARTED = 10,
+};
+
+/* GLOBAL messages */
+
+enum catpt_global_msg_type {
+ CATPT_GLB_GET_FW_VERSION = 0,
+ CATPT_GLB_ALLOCATE_STREAM = 3,
+ CATPT_GLB_FREE_STREAM = 4,
+ CATPT_GLB_STREAM_MESSAGE = 6,
+ CATPT_GLB_REQUEST_CORE_DUMP = 7,
+ CATPT_GLB_SET_DEVICE_FORMATS = 10,
+ CATPT_GLB_ENTER_DX_STATE = 12,
+ CATPT_GLB_GET_MIXER_STREAM_INFO = 13,
+};
+
+union catpt_global_msg {
+ u32 val;
+ struct {
+ u32 status:5;
+ u32 context:19; /* stream or module specific */
+ u32 global_msg_type:5;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+} __packed;
+
+#define CATPT_MSG(hdr) { .val = hdr }
+#define CATPT_GLOBAL_MSG(msg_type) \
+ { .global_msg_type = CATPT_GLB_##msg_type }
+
+#define BUILD_HASH_SIZE 40
+
+struct catpt_fw_version {
+ u8 build;
+ u8 minor;
+ u8 major;
+ u8 type;
+ u8 build_hash[BUILD_HASH_SIZE];
+ u32 log_providers_hash;
+} __packed;
+
+int catpt_ipc_get_fw_version(struct catpt_dev *cdev,
+ struct catpt_fw_version *version);
+
+enum catpt_pin_id {
+ CATPT_PIN_ID_SYSTEM = 0,
+ CATPT_PIN_ID_REFERENCE = 1,
+ CATPT_PIN_ID_CAPTURE1 = 2,
+ CATPT_PIN_ID_CAPTURE2 = 3,
+ CATPT_PIN_ID_OFFLOAD1 = 4,
+ CATPT_PIN_ID_OFFLOAD2 = 5,
+ CATPT_PIN_ID_MIXER = 7,
+ CATPT_PIN_ID_BLUETOOTH_CAPTURE = 8,
+ CATPT_PIN_ID_BLUETOOTH_RENDER = 9,
+};
+
+enum catpt_path_id {
+ CATPT_PATH_SSP0_OUT = 0,
+ CATPT_PATH_SSP0_IN = 1,
+ CATPT_PATH_SSP1_OUT = 2,
+ CATPT_PATH_SSP1_IN = 3,
+ /* duplicated audio in capture path */
+ CATPT_PATH_SSP0_IN_DUP = 4,
+};
+
+enum catpt_stream_type {
+ CATPT_STRM_TYPE_RENDER = 0, /* offload */
+ CATPT_STRM_TYPE_SYSTEM = 1,
+ CATPT_STRM_TYPE_CAPTURE = 2,
+ CATPT_STRM_TYPE_LOOPBACK = 3,
+ CATPT_STRM_TYPE_BLUETOOTH_RENDER = 4,
+ CATPT_STRM_TYPE_BLUETOOTH_CAPTURE = 5,
+};
+
+enum catpt_format_id {
+ CATPT_FORMAT_PCM = 0,
+ CATPT_FORMAT_MP3 = 1,
+ CATPT_FORMAT_AAC = 2,
+ CATPT_FORMAT_WMA = 3,
+};
+
+enum catpt_channel_index {
+ CATPT_CHANNEL_LEFT = 0x0,
+ CATPT_CHANNEL_CENTER = 0x1,
+ CATPT_CHANNEL_RIGHT = 0x2,
+ CATPT_CHANNEL_LEFT_SURROUND = 0x3,
+ CATPT_CHANNEL_CENTER_SURROUND = 0x3,
+ CATPT_CHANNEL_RIGHT_SURROUND = 0x4,
+ CATPT_CHANNEL_LFE = 0x7,
+ CATPT_CHANNEL_INVALID = 0xF,
+};
+
+enum catpt_channel_config {
+ CATPT_CHANNEL_CONFIG_MONO = 0, /* One channel only */
+ CATPT_CHANNEL_CONFIG_STEREO = 1, /* L & R */
+ CATPT_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only */
+ CATPT_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only */
+ CATPT_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only */
+ CATPT_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only */
+ CATPT_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only */
+ CATPT_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs */
+ CATPT_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE */
+ CATPT_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two */
+ CATPT_CHANNEL_CONFIG_INVALID = 10,
+};
+
+enum catpt_interleaving_style {
+ CATPT_INTERLEAVING_PER_CHANNEL = 0,
+ CATPT_INTERLEAVING_PER_SAMPLE = 1,
+};
+
+struct catpt_audio_format {
+ u32 sample_rate;
+ u32 bit_depth;
+ u32 channel_map;
+ u32 channel_config;
+ u32 interleaving;
+ u8 num_channels;
+ u8 valid_bit_depth;
+ u8 reserved[2];
+} __packed;
+
+struct catpt_ring_info {
+ u32 page_table_addr;
+ u32 num_pages;
+ u32 size;
+ u32 offset;
+ u32 ring_first_page_pfn;
+} __packed;
+
+#define CATPT_MODULE_COUNT (CATPT_MODID_LAST + 1)
+
+enum catpt_module_id {
+ CATPT_MODID_BASE_FW = 0x0,
+ CATPT_MODID_MP3 = 0x1,
+ CATPT_MODID_AAC_5_1 = 0x2,
+ CATPT_MODID_AAC_2_0 = 0x3,
+ CATPT_MODID_SRC = 0x4,
+ CATPT_MODID_WAVES = 0x5,
+ CATPT_MODID_DOLBY = 0x6,
+ CATPT_MODID_BOOST = 0x7,
+ CATPT_MODID_LPAL = 0x8,
+ CATPT_MODID_DTS = 0x9,
+ CATPT_MODID_PCM_CAPTURE = 0xA,
+ CATPT_MODID_PCM_SYSTEM = 0xB,
+ CATPT_MODID_PCM_REFERENCE = 0xC,
+ CATPT_MODID_PCM = 0xD, /* offload */
+ CATPT_MODID_BLUETOOTH_RENDER = 0xE,
+ CATPT_MODID_BLUETOOTH_CAPTURE = 0xF,
+ CATPT_MODID_LAST = CATPT_MODID_BLUETOOTH_CAPTURE,
+};
+
+struct catpt_module_entry {
+ u32 module_id;
+ u32 entry_point;
+} __packed;
+
+struct catpt_module_map {
+ u8 num_entries;
+ struct catpt_module_entry entries[];
+} __packed;
+
+struct catpt_memory_info {
+ u32 offset;
+ u32 size;
+} __packed;
+
+#define CATPT_CHANNELS_MAX 4
+#define CATPT_ALL_CHANNELS_MASK UINT_MAX
+
+struct catpt_stream_info {
+ u32 stream_hw_id;
+ u32 reserved;
+ u32 read_pos_regaddr;
+ u32 pres_pos_regaddr;
+ u32 peak_meter_regaddr[CATPT_CHANNELS_MAX];
+ u32 volume_regaddr[CATPT_CHANNELS_MAX];
+} __packed;
+
+int catpt_ipc_alloc_stream(struct catpt_dev *cdev,
+ enum catpt_path_id path_id,
+ enum catpt_stream_type type,
+ struct catpt_audio_format *afmt,
+ struct catpt_ring_info *rinfo,
+ u8 num_modules,
+ struct catpt_module_entry *modules,
+ struct resource *persistent,
+ struct resource *scratch,
+ struct catpt_stream_info *sinfo);
+int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+
+enum catpt_ssp_iface {
+ CATPT_SSP_IFACE_0 = 0,
+ CATPT_SSP_IFACE_1 = 1,
+ CATPT_SSP_IFACE_LAST = CATPT_SSP_IFACE_1,
+};
+
+#define CATPT_SSP_COUNT (CATPT_SSP_IFACE_LAST + 1)
+
+enum catpt_mclk_frequency {
+ CATPT_MCLK_OFF = 0,
+ CATPT_MCLK_FREQ_6_MHZ = 1,
+ CATPT_MCLK_FREQ_21_MHZ = 2,
+ CATPT_MCLK_FREQ_24_MHZ = 3,
+};
+
+enum catpt_ssp_mode {
+ CATPT_SSP_MODE_I2S_CONSUMER = 0,
+ CATPT_SSP_MODE_I2S_PROVIDER = 1,
+ CATPT_SSP_MODE_TDM_PROVIDER = 2,
+};
+
+struct catpt_ssp_device_format {
+ u32 iface;
+ u32 mclk;
+ u32 mode;
+ u16 clock_divider;
+ u8 channels;
+} __packed;
+
+int catpt_ipc_set_device_format(struct catpt_dev *cdev,
+ struct catpt_ssp_device_format *devfmt);
+
+enum catpt_dx_state {
+ CATPT_DX_STATE_D3 = 3,
+};
+
+enum catpt_dx_type {
+ CATPT_DX_TYPE_FW_IMAGE = 0,
+ CATPT_DX_TYPE_MEMORY_DUMP = 1,
+};
+
+struct catpt_save_meminfo {
+ u32 offset;
+ u32 size;
+ u32 source;
+} __packed;
+
+#define SAVE_MEMINFO_MAX 14
+
+struct catpt_dx_context {
+ u32 num_meminfo;
+ struct catpt_save_meminfo meminfo[SAVE_MEMINFO_MAX];
+} __packed;
+
+int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state,
+ struct catpt_dx_context *context);
+
+struct catpt_mixer_stream_info {
+ u32 mixer_hw_id;
+ u32 peak_meter_regaddr[CATPT_CHANNELS_MAX];
+ u32 volume_regaddr[CATPT_CHANNELS_MAX];
+} __packed;
+
+int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info);
+
+/* STREAM messages */
+
+enum catpt_stream_msg_type {
+ CATPT_STRM_RESET_STREAM = 0,
+ CATPT_STRM_PAUSE_STREAM = 1,
+ CATPT_STRM_RESUME_STREAM = 2,
+ CATPT_STRM_STAGE_MESSAGE = 3,
+ CATPT_STRM_NOTIFICATION = 4,
+};
+
+enum catpt_stage_action {
+ CATPT_STG_SET_VOLUME = 1,
+ CATPT_STG_SET_WRITE_POSITION = 2,
+ CATPT_STG_MUTE_LOOPBACK = 3,
+};
+
+union catpt_stream_msg {
+ u32 val;
+ struct {
+ u32 status:5;
+ u32 reserved:7;
+ u32 stage_action:4;
+ u32 stream_hw_id:4;
+ u32 stream_msg_type:4;
+ u32 global_msg_type:5;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+} __packed;
+
+#define CATPT_STREAM_MSG(msg_type) \
+{ \
+ .stream_msg_type = CATPT_STRM_##msg_type, \
+ .global_msg_type = CATPT_GLB_STREAM_MESSAGE }
+#define CATPT_STAGE_MSG(msg_type) \
+{ \
+ .stage_action = CATPT_STG_##msg_type, \
+ .stream_msg_type = CATPT_STRM_STAGE_MESSAGE, \
+ .global_msg_type = CATPT_GLB_STREAM_MESSAGE }
+
+int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+
+/* STREAM messages - STAGE subtype */
+
+enum catpt_audio_curve_type {
+ CATPT_AUDIO_CURVE_NONE = 0,
+ CATPT_AUDIO_CURVE_WINDOWS_FADE = 1,
+};
+
+int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 channel, u32 volume,
+ u32 curve_duration,
+ enum catpt_audio_curve_type curve_type);
+
+int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 pos, bool eob, bool ll);
+
+int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute);
+
+/* NOTIFICATION messages */
+
+enum catpt_notify_reason {
+ CATPT_NOTIFY_POSITION_CHANGED = 0,
+ CATPT_NOTIFY_GLITCH_OCCURRED = 1,
+};
+
+union catpt_notify_msg {
+ u32 val;
+ struct {
+ u32 mailbox_address:29;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+ struct {
+ u32 status:5;
+ u32 reserved:7;
+ u32 notify_reason:4;
+ u32 stream_hw_id:4;
+ u32 stream_msg_type:4;
+ u32 global_msg_type:5;
+ u32 hdr:3; /* fw_ready, done, busy */
+ };
+} __packed;
+
+#define FW_INFO_SIZE_MAX 100
+
+struct catpt_fw_ready {
+ u32 inbox_offset;
+ u32 outbox_offset;
+ u32 inbox_size;
+ u32 outbox_size;
+ u32 fw_info_size;
+ char fw_info[FW_INFO_SIZE_MAX];
+} __packed;
+
+struct catpt_notify_position {
+ u32 stream_position;
+ u32 fw_cycle_count;
+} __packed;
+
+enum catpt_glitch_type {
+ CATPT_GLITCH_UNDERRUN = 1,
+ CATPT_GLITCH_DECODER_ERROR = 2,
+ CATPT_GLITCH_DOUBLED_WRITE_POS = 3,
+};
+
+struct catpt_notify_glitch {
+ u32 type;
+ u64 presentation_pos;
+ u32 write_pos;
+} __packed;
+
+#endif
diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c
new file mode 100644
index 000000000..408e64e3b
--- /dev/null
+++ b/sound/soc/intel/catpt/pcm.c
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <uapi/sound/tlv.h>
+#include "core.h"
+#include "messages.h"
+
+struct catpt_stream_template {
+ enum catpt_path_id path_id;
+ enum catpt_stream_type type;
+ u32 persistent_size;
+ u8 num_entries;
+ struct catpt_module_entry entries[];
+};
+
+static struct catpt_stream_template system_pb = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_SYSTEM,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_SYSTEM, 0 }},
+};
+
+static struct catpt_stream_template system_cp = {
+ .path_id = CATPT_PATH_SSP0_IN,
+ .type = CATPT_STRM_TYPE_CAPTURE,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template offload_pb = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_RENDER,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM, 0 }},
+};
+
+static struct catpt_stream_template loopback_cp = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_LOOPBACK,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_REFERENCE, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_pb = {
+ .path_id = CATPT_PATH_SSP1_OUT,
+ .type = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_BLUETOOTH_RENDER, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_cp = {
+ .path_id = CATPT_PATH_SSP1_IN,
+ .type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_BLUETOOTH_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template *catpt_topology[] = {
+ [CATPT_STRM_TYPE_RENDER] = &offload_pb,
+ [CATPT_STRM_TYPE_SYSTEM] = &system_pb,
+ [CATPT_STRM_TYPE_CAPTURE] = &system_cp,
+ [CATPT_STRM_TYPE_LOOPBACK] = &loopback_cp,
+ [CATPT_STRM_TYPE_BLUETOOTH_RENDER] = &bluetooth_pb,
+ [CATPT_STRM_TYPE_BLUETOOTH_CAPTURE] = &bluetooth_cp,
+};
+
+static struct catpt_stream_template *
+catpt_get_stream_template(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+ enum catpt_stream_type type;
+
+ type = cpu_dai->driver->id;
+
+ /* account for capture in bidirectional dais */
+ switch (type) {
+ case CATPT_STRM_TYPE_SYSTEM:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ type = CATPT_STRM_TYPE_CAPTURE;
+ break;
+ case CATPT_STRM_TYPE_BLUETOOTH_RENDER:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE;
+ break;
+ default:
+ break;
+ };
+
+ return catpt_topology[type];
+}
+
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ struct catpt_stream_runtime *pos, *result = NULL;
+
+ spin_lock(&cdev->list_lock);
+ list_for_each_entry(pos, &cdev->stream_list, node) {
+ if (pos->info.stream_hw_id == stream_hw_id) {
+ result = pos;
+ break;
+ }
+ }
+
+ spin_unlock(&cdev->list_lock);
+ return result;
+}
+
+static u32 catpt_stream_read_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream)
+{
+ u32 pos;
+
+ memcpy_fromio(&pos, cdev->lpe_ba + stream->info.read_pos_regaddr,
+ sizeof(pos));
+ return pos;
+}
+
+static u32 catpt_stream_volume(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream, u32 channel)
+{
+ u32 volume, offset;
+
+ if (channel >= CATPT_CHANNELS_MAX)
+ channel = 0;
+
+ offset = stream->info.volume_regaddr[channel];
+ memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+ return volume;
+}
+
+static u32 catpt_mixer_volume(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info, u32 channel)
+{
+ u32 volume, offset;
+
+ if (channel >= CATPT_CHANNELS_MAX)
+ channel = 0;
+
+ offset = info->volume_regaddr[channel];
+ memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+ return volume;
+}
+
+static void catpt_arrange_page_table(struct snd_pcm_substream *substream,
+ struct snd_dma_buffer *pgtbl)
+{
+ struct snd_pcm_runtime *rtm = substream->runtime;
+ struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream);
+ int i, pages;
+
+ pages = snd_sgbuf_aligned_pages(rtm->dma_bytes);
+
+ for (i = 0; i < pages; i++) {
+ u32 pfn, offset;
+ u32 *page_table;
+
+ pfn = PFN_DOWN(snd_sgbuf_get_addr(databuf, i * PAGE_SIZE));
+ /* incrementing by 2 on even and 3 on odd */
+ offset = ((i << 2) + i) >> 1;
+ page_table = (u32 *)(pgtbl->area + offset);
+
+ if (i & 1)
+ *page_table |= (pfn << 4);
+ else
+ *page_table |= pfn;
+ }
+}
+
+static u32 catpt_get_channel_map(enum catpt_channel_config config)
+{
+ switch (config) {
+ case CATPT_CHANNEL_CONFIG_MONO:
+ return GENMASK(31, 4) | CATPT_CHANNEL_CENTER;
+
+ case CATPT_CHANNEL_CONFIG_STEREO:
+ return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4);
+
+ case CATPT_CHANNEL_CONFIG_2_POINT_1:
+ return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4)
+ | (CATPT_CHANNEL_LFE << 8);
+
+ case CATPT_CHANNEL_CONFIG_3_POINT_0:
+ return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8);
+
+ case CATPT_CHANNEL_CONFIG_3_POINT_1:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LFE << 12);
+
+ case CATPT_CHANNEL_CONFIG_QUATRO:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 8)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 12);
+
+ case CATPT_CHANNEL_CONFIG_4_POINT_0:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_CENTER_SURROUND << 12);
+
+ case CATPT_CHANNEL_CONFIG_5_POINT_0:
+ return GENMASK(31, 20) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 12)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 16);
+
+ case CATPT_CHANNEL_CONFIG_5_POINT_1:
+ return GENMASK(31, 24) | CATPT_CHANNEL_CENTER
+ | (CATPT_CHANNEL_LEFT << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 12)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 16)
+ | (CATPT_CHANNEL_LFE << 20);
+
+ case CATPT_CHANNEL_CONFIG_DUAL_MONO:
+ return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_LEFT << 4);
+
+ default:
+ return U32_MAX;
+ }
+}
+
+static enum catpt_channel_config catpt_get_channel_config(u32 num_channels)
+{
+ switch (num_channels) {
+ case 6:
+ return CATPT_CHANNEL_CONFIG_5_POINT_1;
+ case 5:
+ return CATPT_CHANNEL_CONFIG_5_POINT_0;
+ case 4:
+ return CATPT_CHANNEL_CONFIG_QUATRO;
+ case 3:
+ return CATPT_CHANNEL_CONFIG_2_POINT_1;
+ case 1:
+ return CATPT_CHANNEL_CONFIG_MONO;
+ case 2:
+ default:
+ return CATPT_CHANNEL_CONFIG_STEREO;
+ }
+}
+
+static int catpt_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_template *template;
+ struct catpt_stream_runtime *stream;
+ struct resource *res;
+ int ret;
+
+ template = catpt_get_stream_template(substream);
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, cdev->dev, PAGE_SIZE,
+ &stream->pgtbl);
+ if (ret)
+ goto err_pgtbl;
+
+ res = catpt_request_region(&cdev->dram, template->persistent_size);
+ if (!res) {
+ ret = -EBUSY;
+ goto err_request;
+ }
+
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ stream->template = template;
+ stream->persistent = res;
+ stream->substream = substream;
+ INIT_LIST_HEAD(&stream->node);
+ snd_soc_dai_set_dma_data(dai, substream, stream);
+
+ spin_lock(&cdev->list_lock);
+ list_add_tail(&stream->node, &cdev->stream_list);
+ spin_unlock(&cdev->list_lock);
+
+ return 0;
+
+err_request:
+ snd_dma_free_pages(&stream->pgtbl);
+err_pgtbl:
+ kfree(stream);
+ return ret;
+}
+
+static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ spin_lock(&cdev->list_lock);
+ list_del(&stream->node);
+ spin_unlock(&cdev->list_lock);
+
+ release_resource(stream->persistent);
+ kfree(stream->persistent);
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ snd_dma_free_pages(&stream->pgtbl);
+ kfree(stream);
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int catpt_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ struct catpt_audio_format afmt;
+ struct catpt_ring_info rinfo;
+ struct snd_pcm_runtime *rtm = substream->runtime;
+ struct snd_dma_buffer *dmab;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (stream->allocated)
+ return 0;
+
+ memset(&afmt, 0, sizeof(afmt));
+ afmt.sample_rate = params_rate(params);
+ afmt.bit_depth = params_physical_width(params);
+ afmt.valid_bit_depth = params_width(params);
+ afmt.num_channels = params_channels(params);
+ afmt.channel_config = catpt_get_channel_config(afmt.num_channels);
+ afmt.channel_map = catpt_get_channel_map(afmt.channel_config);
+ afmt.interleaving = CATPT_INTERLEAVING_PER_CHANNEL;
+
+ dmab = snd_pcm_get_dma_buf(substream);
+ catpt_arrange_page_table(substream, &stream->pgtbl);
+
+ memset(&rinfo, 0, sizeof(rinfo));
+ rinfo.page_table_addr = stream->pgtbl.addr;
+ rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE);
+ rinfo.size = rtm->dma_bytes;
+ rinfo.offset = 0;
+ rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0));
+
+ ret = catpt_ipc_alloc_stream(cdev, stream->template->path_id,
+ stream->template->type,
+ &afmt, &rinfo,
+ stream->template->num_entries,
+ stream->template->entries,
+ stream->persistent,
+ cdev->scratch,
+ &stream->info);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ stream->allocated = true;
+ return 0;
+}
+
+static int catpt_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (!stream->allocated)
+ return 0;
+
+ catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
+ catpt_ipc_free_stream(cdev, stream->info.stream_hw_id);
+
+ stream->allocated = false;
+ return 0;
+}
+
+static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol);
+
+static int catpt_dai_apply_usettings(struct snd_soc_dai *dai,
+ struct catpt_stream_runtime *stream)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct snd_soc_component *component = dai->component;
+ struct snd_kcontrol *pos, *kctl = NULL;
+ const char *name;
+ int ret;
+ u32 id = stream->info.stream_hw_id;
+
+ /* only selected streams have individual controls */
+ switch (id) {
+ case CATPT_PIN_ID_OFFLOAD1:
+ name = "Media0 Playback Volume";
+ break;
+ case CATPT_PIN_ID_OFFLOAD2:
+ name = "Media1 Playback Volume";
+ break;
+ case CATPT_PIN_ID_CAPTURE1:
+ name = "Mic Capture Volume";
+ break;
+ case CATPT_PIN_ID_REFERENCE:
+ name = "Loopback Mute";
+ break;
+ default:
+ return 0;
+ };
+
+ list_for_each_entry(pos, &component->card->snd_card->controls, list) {
+ if (pos->private_data == component &&
+ !strncmp(name, pos->id.name, sizeof(pos->id.name))) {
+ kctl = pos;
+ break;
+ }
+ }
+ if (!kctl)
+ return -ENOENT;
+
+ if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK)
+ return catpt_set_dspvol(cdev, id, (long *)kctl->private_value);
+ ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)kctl->private_value);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ return 0;
+}
+
+static int catpt_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (stream->prepared)
+ return 0;
+
+ ret = catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_dai_apply_usettings(dai, stream);
+ if (ret)
+ return ret;
+
+ stream->prepared = true;
+ return 0;
+}
+
+static int catpt_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_uframes_t pos;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* only offload is set_write_pos driven */
+ if (stream->template->type != CATPT_STRM_TYPE_RENDER)
+ goto resume_stream;
+
+ pos = frames_to_bytes(runtime, runtime->start_threshold);
+ /*
+ * Dsp operates on buffer halves, thus max 2x set_write_pos
+ * (entire buffer filled) prior to stream start.
+ */
+ ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
+ pos, false, false);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ resume_stream:
+ catpt_dsp_update_lpclock(cdev);
+ ret = catpt_ipc_resume_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ stream->prepared = false;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
+ catpt_dsp_update_lpclock(cdev);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void catpt_stream_update_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream,
+ struct catpt_notify_position *pos)
+{
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *r = substream->runtime;
+ snd_pcm_uframes_t dsppos, newpos;
+ int ret;
+
+ dsppos = bytes_to_frames(r, pos->stream_position);
+
+ if (!stream->prepared)
+ goto exit;
+ /* only offload is set_write_pos driven */
+ if (stream->template->type != CATPT_STRM_TYPE_RENDER)
+ goto exit;
+
+ if (dsppos >= r->buffer_size / 2)
+ newpos = r->buffer_size / 2;
+ else
+ newpos = 0;
+ /*
+ * Dsp operates on buffer halves, thus on every notify position
+ * (buffer half consumed) update wp to allow stream progression.
+ */
+ ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
+ frames_to_bytes(r, newpos),
+ false, false);
+ if (ret) {
+ dev_err(cdev->dev, "update position for stream %d failed: %d\n",
+ stream->info.stream_hw_id, ret);
+ return;
+ }
+exit:
+ snd_pcm_period_elapsed(substream);
+}
+
+/* 200 ms for 2 32-bit channels at 48kHz (native format) */
+#define CATPT_BUFFER_MAX_SIZE 76800
+#define CATPT_PCM_PERIODS_MAX 4
+#define CATPT_PCM_PERIODS_MIN 2
+
+static const struct snd_pcm_hardware catpt_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = CATPT_BUFFER_MAX_SIZE / CATPT_PCM_PERIODS_MIN,
+ .periods_min = CATPT_PCM_PERIODS_MIN,
+ .periods_max = CATPT_PCM_PERIODS_MAX,
+ .buffer_bytes_max = CATPT_BUFFER_MAX_SIZE,
+};
+
+static int catpt_component_pcm_construct(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtm)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+
+ snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG,
+ cdev->dev,
+ catpt_pcm_hardware.buffer_bytes_max,
+ catpt_pcm_hardware.buffer_bytes_max);
+
+ return 0;
+}
+
+static int catpt_component_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+
+ if (rtm->dai_link->no_pcm)
+ return 0;
+ snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware);
+ return 0;
+}
+
+static snd_pcm_uframes_t
+catpt_component_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+ u32 pos;
+
+ if (rtm->dai_link->no_pcm)
+ return 0;
+
+ stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
+ pos = catpt_stream_read_position(cdev, stream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static const struct snd_soc_dai_ops catpt_fe_dai_ops = {
+ .startup = catpt_dai_startup,
+ .shutdown = catpt_dai_shutdown,
+ .hw_params = catpt_dai_hw_params,
+ .hw_free = catpt_dai_hw_free,
+ .prepare = catpt_dai_prepare,
+ .trigger = catpt_dai_trigger,
+};
+
+static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0);
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_ssp_device_format devfmt;
+ int ret;
+
+ devfmt.iface = dai->driver->id;
+ devfmt.channels = codec_dai->driver->capture.channels_max;
+
+ switch (devfmt.iface) {
+ case CATPT_SSP_IFACE_0:
+ devfmt.mclk = CATPT_MCLK_FREQ_24_MHZ;
+
+ switch (devfmt.channels) {
+ case 4:
+ devfmt.mode = CATPT_SSP_MODE_TDM_PROVIDER;
+ devfmt.clock_divider = 4;
+ break;
+ case 2:
+ default:
+ devfmt.mode = CATPT_SSP_MODE_I2S_PROVIDER;
+ devfmt.clock_divider = 9;
+ break;
+ }
+ break;
+
+ case CATPT_SSP_IFACE_1:
+ devfmt.mclk = CATPT_MCLK_OFF;
+ devfmt.mode = CATPT_SSP_MODE_I2S_CONSUMER;
+ devfmt.clock_divider = 0;
+ break;
+ }
+
+ /* see if this is a new configuration */
+ if (!memcmp(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt)))
+ return 0;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_set_device_format(cdev, &devfmt);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ /* store device format set for given SSP */
+ memcpy(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt));
+ return 0;
+}
+
+static struct snd_soc_dai_driver dai_drivers[] = {
+/* FE DAIs */
+{
+ .name = "System Pin",
+ .id = CATPT_STRM_TYPE_SYSTEM,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "System Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "Analog Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Offload0 Pin",
+ .id = CATPT_STRM_TYPE_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Offload0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Offload1 Pin",
+ .id = CATPT_STRM_TYPE_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Offload1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Loopback Pin",
+ .id = CATPT_STRM_TYPE_LOOPBACK,
+ .ops = &catpt_fe_dai_ops,
+ .capture = {
+ .stream_name = "Loopback Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Bluetooth Pin",
+ .id = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Bluetooth Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Bluetooth Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+/* BE DAIs */
+{
+ .name = "ssp0-port",
+ .id = CATPT_SSP_IFACE_0,
+ .pcm_new = catpt_dai_pcm_new,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp1-port",
+ .id = CATPT_SSP_IFACE_1,
+ .pcm_new = catpt_dai_pcm_new,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+};
+
+#define DSP_VOLUME_MAX S32_MAX /* 0db */
+#define DSP_VOLUME_STEP_MAX 30
+
+static u32 ctlvol_to_dspvol(u32 value)
+{
+ if (value > DSP_VOLUME_STEP_MAX)
+ value = 0;
+ return DSP_VOLUME_MAX >> (DSP_VOLUME_STEP_MAX - value);
+}
+
+static u32 dspvol_to_ctlvol(u32 volume)
+{
+ if (volume > DSP_VOLUME_MAX)
+ return DSP_VOLUME_STEP_MAX;
+ return volume ? __fls(volume) : 0;
+}
+
+static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol)
+{
+ u32 dspvol;
+ int ret, i;
+
+ for (i = 1; i < CATPT_CHANNELS_MAX; i++)
+ if (ctlvol[i] != ctlvol[0])
+ break;
+
+ if (i == CATPT_CHANNELS_MAX) {
+ dspvol = ctlvol_to_dspvol(ctlvol[0]);
+
+ ret = catpt_ipc_set_volume(cdev, stream_id,
+ CATPT_ALL_CHANNELS_MASK, dspvol,
+ 0, CATPT_AUDIO_CURVE_NONE);
+ } else {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = ctlvol_to_dspvol(ctlvol[i]);
+
+ ret = catpt_ipc_set_volume(cdev, stream_id,
+ i, dspvol,
+ 0, CATPT_AUDIO_CURVE_NONE);
+ if (ret)
+ break;
+ }
+ }
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ return 0;
+}
+
+static int catpt_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = CATPT_CHANNELS_MAX;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = DSP_VOLUME_STEP_MAX;
+ return 0;
+}
+
+static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ u32 dspvol;
+ int i;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i);
+ ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+ }
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return 0;
+}
+
+static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ int ret;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id,
+ ucontrol->value.integer.value);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return ret;
+}
+
+static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum catpt_pin_id pin_id)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ long *ctlvol = (long *)kcontrol->private_value;
+ u32 dspvol;
+ int i;
+
+ stream = catpt_stream_find(cdev, pin_id);
+ if (!stream) {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ucontrol->value.integer.value[i] = ctlvol[i];
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = catpt_stream_volume(cdev, stream, i);
+ ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+ }
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return 0;
+}
+
+static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum catpt_pin_id pin_id)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ long *ctlvol = (long *)kcontrol->private_value;
+ int ret, i;
+
+ stream = catpt_stream_find(cdev, pin_id);
+ if (!stream) {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ctlvol[i] = ucontrol->value.integer.value[i];
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id,
+ ucontrol->value.integer.value);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return ret;
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ctlvol[i] = ucontrol->value.integer.value[i];
+ return 0;
+}
+
+static int catpt_offload1_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload1_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload2_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_offload2_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_capture_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_capture_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value;
+ return 0;
+}
+
+static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ bool mute;
+ int ret;
+
+ mute = (bool)ucontrol->value.integer.value[0];
+ stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE);
+ if (!stream) {
+ *(bool *)kcontrol->private_value = mute;
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ *(bool *)kcontrol->private_value = mute;
+ return 0;
+}
+
+static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static int catpt_waves_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static int catpt_waves_param_get(struct snd_kcontrol *kcontrol,
+ unsigned int __user *bytes,
+ unsigned int size)
+{
+ return 0;
+}
+
+static int catpt_waves_param_put(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *bytes,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1);
+
+#define CATPT_VOLUME_CTL(kname, sname) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (kname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = catpt_volume_info, \
+ .get = catpt_##sname##_volume_get, \
+ .put = catpt_##sname##_volume_put, \
+ .tlv.p = catpt_volume_tlv, \
+ .private_value = (unsigned long) \
+ &(long[CATPT_CHANNELS_MAX]) {0} }
+
+static const struct snd_kcontrol_new component_kcontrols[] = {
+/* Master volume (mixer stream) */
+CATPT_VOLUME_CTL("Master Playback Volume", mixer),
+/* Individual volume controls for offload and capture */
+CATPT_VOLUME_CTL("Media0 Playback Volume", offload1),
+CATPT_VOLUME_CTL("Media1 Playback Volume", offload2),
+CATPT_VOLUME_CTL("Mic Capture Volume", capture),
+SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0},
+ catpt_loopback_switch_get, catpt_loopback_switch_put),
+/* Enable or disable WAVES module */
+SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
+ catpt_waves_switch_get, catpt_waves_switch_put),
+/* WAVES module parameter control */
+SND_SOC_BYTES_TLV("Waves Set Param", 128,
+ catpt_waves_param_get, catpt_waves_param_put),
+};
+
+static const struct snd_soc_dapm_widget component_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route component_routes[] = {
+ {"Playback VMixer", NULL, "System Playback"},
+ {"Playback VMixer", NULL, "Offload0 Playback"},
+ {"Playback VMixer", NULL, "Offload1 Playback"},
+
+ {"SSP0 CODEC OUT", NULL, "Playback VMixer"},
+
+ {"Analog Capture", NULL, "SSP0 CODEC IN"},
+ {"Loopback Capture", NULL, "SSP0 CODEC IN"},
+
+ {"SSP1 BT OUT", NULL, "Bluetooth Playback"},
+ {"Bluetooth Capture", NULL, "SSP1 BT IN"},
+};
+
+static const struct snd_soc_component_driver catpt_comp_driver = {
+ .name = "catpt-platform",
+
+ .pcm_construct = catpt_component_pcm_construct,
+ .open = catpt_component_open,
+ .pointer = catpt_component_pointer,
+
+ .controls = component_kcontrols,
+ .num_controls = ARRAY_SIZE(component_kcontrols),
+ .dapm_widgets = component_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(component_widgets),
+ .dapm_routes = component_routes,
+ .num_dapm_routes = ARRAY_SIZE(component_routes),
+};
+
+int catpt_arm_stream_templates(struct catpt_dev *cdev)
+{
+ struct resource *res;
+ u32 scratch_size = 0;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(catpt_topology); i++) {
+ struct catpt_stream_template *template;
+ struct catpt_module_entry *entry;
+ struct catpt_module_type *type;
+
+ template = catpt_topology[i];
+ template->persistent_size = 0;
+
+ for (j = 0; j < template->num_entries; j++) {
+ entry = &template->entries[j];
+ type = &cdev->modules[entry->module_id];
+
+ if (!type->loaded)
+ return -ENOENT;
+
+ entry->entry_point = type->entry_point;
+ template->persistent_size += type->persistent_size;
+ if (type->scratch_size > scratch_size)
+ scratch_size = type->scratch_size;
+ }
+ }
+
+ if (scratch_size) {
+ /* allocate single scratch area for all modules */
+ res = catpt_request_region(&cdev->dram, scratch_size);
+ if (!res)
+ return -EBUSY;
+ cdev->scratch = res;
+ }
+
+ return 0;
+}
+
+int catpt_register_plat_component(struct catpt_dev *cdev)
+{
+ struct snd_soc_component *component;
+ int ret;
+
+ component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL);
+ if (!component)
+ return -ENOMEM;
+
+ ret = snd_soc_component_initialize(component, &catpt_comp_driver,
+ cdev->dev);
+ if (ret)
+ return ret;
+
+ component->name = catpt_comp_driver.name;
+ return snd_soc_add_component(component, dai_drivers,
+ ARRAY_SIZE(dai_drivers));
+}
diff --git a/sound/soc/intel/catpt/registers.h b/sound/soc/intel/catpt/registers.h
new file mode 100644
index 000000000..47280d828
--- /dev/null
+++ b/sound/soc/intel/catpt/registers.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_REGS_H
+#define __SND_SOC_INTEL_CATPT_REGS_H
+
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <uapi/linux/pci_regs.h>
+
+#define CATPT_SHIM_REGS_SIZE 4096
+#define CATPT_DMA_REGS_SIZE 1024
+#define CATPT_DMA_COUNT 2
+#define CATPT_SSP_REGS_SIZE 512
+
+/* DSP Shim registers */
+
+#define CATPT_SHIM_CS1 0x00
+#define CATPT_SHIM_ISC 0x18
+#define CATPT_SHIM_ISD 0x20
+#define CATPT_SHIM_IMC 0x28
+#define CATPT_SHIM_IMD 0x30
+#define CATPT_SHIM_IPCC 0x38
+#define CATPT_SHIM_IPCD 0x40
+#define CATPT_SHIM_CLKCTL 0x78
+#define CATPT_SHIM_CS2 0x80
+#define CATPT_SHIM_LTRC 0xE0
+#define CATPT_SHIM_HMDC 0xE8
+
+#define CATPT_CS_LPCS BIT(31)
+#define CATPT_CS_SFCR(ssp) BIT(27 + (ssp))
+#define CATPT_CS_S1IOCS BIT(23)
+#define CATPT_CS_S0IOCS BIT(21)
+#define CATPT_CS_PCE BIT(15)
+#define CATPT_CS_SDPM(ssp) BIT(11 + (ssp))
+#define CATPT_CS_STALL BIT(10)
+#define CATPT_CS_DCS GENMASK(6, 4)
+/* b100 DSP core & audio fabric high clock */
+#define CATPT_CS_DCS_HIGH (0x4 << 4)
+#define CATPT_CS_SBCS(ssp) BIT(2 + (ssp))
+#define CATPT_CS_RST BIT(1)
+
+#define CATPT_ISC_IPCDB BIT(1)
+#define CATPT_ISC_IPCCD BIT(0)
+#define CATPT_ISD_DCPWM BIT(31)
+#define CATPT_ISD_IPCCB BIT(1)
+#define CATPT_ISD_IPCDD BIT(0)
+
+#define CATPT_IMC_IPCDB BIT(1)
+#define CATPT_IMC_IPCCD BIT(0)
+#define CATPT_IMD_IPCCB BIT(1)
+#define CATPT_IMD_IPCDD BIT(0)
+
+#define CATPT_IPCC_BUSY BIT(31)
+#define CATPT_IPCC_DONE BIT(30)
+#define CATPT_IPCD_BUSY BIT(31)
+#define CATPT_IPCD_DONE BIT(30)
+
+#define CATPT_CLKCTL_CFCIP BIT(31)
+#define CATPT_CLKCTL_SMOS GENMASK(25, 24)
+
+#define CATPT_HMDC_HDDA(e, ch) BIT(8 * (e) + (ch))
+
+/* defaults to reset SHIM registers to after each power cycle */
+#define CATPT_CS_DEFAULT 0x8480040E
+#define CATPT_ISC_DEFAULT 0x0
+#define CATPT_ISD_DEFAULT 0x0
+#define CATPT_IMC_DEFAULT 0x7FFF0003
+#define CATPT_IMD_DEFAULT 0x7FFF0003
+#define CATPT_IPCC_DEFAULT 0x0
+#define CATPT_IPCD_DEFAULT 0x0
+#define CATPT_CLKCTL_DEFAULT 0x7FF
+#define CATPT_CS2_DEFAULT 0x0
+#define CATPT_LTRC_DEFAULT 0x0
+#define CATPT_HMDC_DEFAULT 0x0
+
+/* PCI Configuration registers */
+
+#define CATPT_PCI_PMCAPID 0x80
+#define CATPT_PCI_PMCS (CATPT_PCI_PMCAPID + PCI_PM_CTRL)
+#define CATPT_PCI_VDRTCTL0 0xA0
+#define CATPT_PCI_VDRTCTL2 0xA8
+
+#define CATPT_VDRTCTL2_DTCGE BIT(10)
+#define CATPT_VDRTCTL2_DCLCGE BIT(1)
+#define CATPT_VDRTCTL2_CGEALL 0xF7F
+
+/* LPT PCI Configuration bits */
+
+#define LPT_VDRTCTL0_DSRAMPGE(b) BIT(16 + (b))
+#define LPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 16)
+#define LPT_VDRTCTL0_ISRAMPGE(b) BIT(6 + (b))
+#define LPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(15, 6)
+#define LPT_VDRTCTL0_D3SRAMPGD BIT(2)
+#define LPT_VDRTCTL0_D3PGD BIT(1)
+#define LPT_VDRTCTL0_APLLSE BIT(0)
+
+/* WPT PCI Configuration bits */
+
+#define WPT_VDRTCTL0_DSRAMPGE(b) BIT(12 + (b))
+#define WPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 12)
+#define WPT_VDRTCTL0_ISRAMPGE(b) BIT(2 + (b))
+#define WPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(11, 2)
+#define WPT_VDRTCTL0_D3SRAMPGD BIT(1)
+#define WPT_VDRTCTL0_D3PGD BIT(0)
+
+#define WPT_VDRTCTL2_APLLSE BIT(31)
+
+/* defaults to reset SSP registers to after each power cycle */
+#define CATPT_SSC0_DEFAULT 0x0
+#define CATPT_SSC1_DEFAULT 0x0
+#define CATPT_SSS_DEFAULT 0xF004
+#define CATPT_SSIT_DEFAULT 0x0
+#define CATPT_SSD_DEFAULT 0xC43893A3
+#define CATPT_SSTO_DEFAULT 0x0
+#define CATPT_SSPSP_DEFAULT 0x0
+#define CATPT_SSTSA_DEFAULT 0x0
+#define CATPT_SSRSA_DEFAULT 0x0
+#define CATPT_SSTSS_DEFAULT 0x0
+#define CATPT_SSCR2_DEFAULT 0x0
+#define CATPT_SSPSP2_DEFAULT 0x0
+
+/* Physically the same block, access address differs between host and dsp */
+#define CATPT_DSP_DRAM_OFFSET 0x400000
+#define catpt_to_host_offset(offset) ((offset) & ~(CATPT_DSP_DRAM_OFFSET))
+#define catpt_to_dsp_offset(offset) ((offset) | CATPT_DSP_DRAM_OFFSET)
+
+#define CATPT_MEMBLOCK_SIZE 0x8000
+#define catpt_num_dram(cdev) (hweight_long((cdev)->spec->dram_mask))
+#define catpt_num_iram(cdev) (hweight_long((cdev)->spec->iram_mask))
+#define catpt_dram_size(cdev) (catpt_num_dram(cdev) * CATPT_MEMBLOCK_SIZE)
+#define catpt_iram_size(cdev) (catpt_num_iram(cdev) * CATPT_MEMBLOCK_SIZE)
+
+/* registry I/O helpers */
+
+#define catpt_shim_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_shim_offset)
+#define catpt_dma_addr(cdev, dma) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_dma_offset[dma])
+#define catpt_ssp_addr(cdev, ssp) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_ssp_offset[ssp])
+#define catpt_inbox_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->ipc.config.inbox_offset)
+#define catpt_outbox_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->ipc.config.outbox_offset)
+
+#define catpt_writel_ssp(cdev, ssp, reg, val) \
+ writel(val, catpt_ssp_addr(cdev, ssp) + (reg))
+
+#define catpt_readl_shim(cdev, reg) \
+ readl(catpt_shim_addr(cdev) + CATPT_SHIM_##reg)
+#define catpt_writel_shim(cdev, reg, val) \
+ writel(val, catpt_shim_addr(cdev) + CATPT_SHIM_##reg)
+#define catpt_updatel_shim(cdev, reg, mask, val) \
+ catpt_writel_shim(cdev, reg, \
+ (catpt_readl_shim(cdev, reg) & ~(mask)) | (val))
+
+#define catpt_readl_poll_shim(cdev, reg, val, cond, delay_us, timeout_us) \
+ readl_poll_timeout(catpt_shim_addr(cdev) + CATPT_SHIM_##reg, \
+ val, cond, delay_us, timeout_us)
+
+#define catpt_readl_pci(cdev, reg) \
+ readl(cdev->pci_ba + CATPT_PCI_##reg)
+#define catpt_writel_pci(cdev, reg, val) \
+ writel(val, cdev->pci_ba + CATPT_PCI_##reg)
+#define catpt_updatel_pci(cdev, reg, mask, val) \
+ catpt_writel_pci(cdev, reg, \
+ (catpt_readl_pci(cdev, reg) & ~(mask)) | (val))
+
+#define catpt_readl_poll_pci(cdev, reg, val, cond, delay_us, timeout_us) \
+ readl_poll_timeout((cdev)->pci_ba + CATPT_PCI_##reg, \
+ val, cond, delay_us, timeout_us)
+
+#endif
diff --git a/sound/soc/intel/catpt/sysfs.c b/sound/soc/intel/catpt/sysfs.c
new file mode 100644
index 000000000..9579e233a
--- /dev/null
+++ b/sound/soc/intel/catpt/sysfs.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/pm_runtime.h>
+#include "core.h"
+
+static ssize_t fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ struct catpt_fw_version version;
+ int ret;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_get_fw_version(cdev, &version);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ return sprintf(buf, "%d.%d.%d.%d\n", version.type, version.major,
+ version.minor, version.build);
+}
+static DEVICE_ATTR_RO(fw_version);
+
+static ssize_t fw_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", cdev->ipc.config.fw_info);
+}
+static DEVICE_ATTR_RO(fw_info);
+
+static struct attribute *catpt_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_fw_info.attr,
+ NULL
+};
+
+static const struct attribute_group catpt_attr_group = {
+ .attrs = catpt_attrs,
+};
+
+const struct attribute_group *catpt_attr_groups[] = {
+ &catpt_attr_group,
+ NULL
+};
diff --git a/sound/soc/intel/catpt/trace.h b/sound/soc/intel/catpt/trace.h
new file mode 100644
index 000000000..bb3d627db
--- /dev/null
+++ b/sound/soc/intel/catpt/trace.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM intel_catpt
+
+#if !defined(__SND_SOC_INTEL_CATPT_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define __SND_SOC_INTEL_CATPT_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(catpt_ipc_msg,
+
+ TP_PROTO(u32 header),
+
+ TP_ARGS(header),
+
+ TP_STRUCT__entry(
+ __field(u32, header)
+ ),
+
+ TP_fast_assign(
+ __entry->header = header;
+ ),
+
+ TP_printk("0x%08x", __entry->header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_irq,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_request,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_reply,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_notify,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+TRACE_EVENT_CONDITION(catpt_ipc_payload,
+
+ TP_PROTO(const u8 *data, size_t size),
+
+ TP_ARGS(data, size),
+
+ TP_CONDITION(data && size),
+
+ TP_STRUCT__entry(
+ __dynamic_array(u8, buf, size)
+ ),
+
+ TP_fast_assign(
+ memcpy(__get_dynamic_array(buf), data, size);
+ ),
+
+ TP_printk("%u byte(s)%s",
+ __get_dynamic_array_len(buf),
+ __print_hex_dump("", DUMP_PREFIX_NONE, 16, 4,
+ __get_dynamic_array(buf),
+ __get_dynamic_array_len(buf), false))
+);
+
+#endif /* __SND_SOC_INTEL_CATPT_TRACE_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE trace
+#include <trace/define_trace.h>
diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile
new file mode 100644
index 000000000..64468fe9c
--- /dev/null
+++ b/sound/soc/intel/common/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-soc-sst-dsp-objs := sst-dsp.o
+snd-soc-sst-ipc-objs := sst-ipc.o
+snd-soc-acpi-intel-match-objs := soc-acpi-intel-byt-match.o soc-acpi-intel-cht-match.o \
+ soc-acpi-intel-hsw-bdw-match.o \
+ soc-acpi-intel-skl-match.o soc-acpi-intel-kbl-match.o \
+ soc-acpi-intel-bxt-match.o soc-acpi-intel-glk-match.o \
+ soc-acpi-intel-cnl-match.o soc-acpi-intel-cfl-match.o \
+ soc-acpi-intel-cml-match.o soc-acpi-intel-icl-match.o \
+ soc-acpi-intel-tgl-match.o soc-acpi-intel-ehl-match.o \
+ soc-acpi-intel-jsl-match.o \
+ soc-acpi-intel-hda-match.o
+
+obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o
+obj-$(CONFIG_SND_SOC_ACPI_INTEL_MATCH) += snd-soc-acpi-intel-match.o
diff --git a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c
new file mode 100644
index 000000000..32f77e29c
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-bxt-match.c - tables and support for BXT ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <linux/dmi.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+enum {
+ APL_RVP,
+};
+
+static const struct dmi_system_id apl_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
+ DMI_MATCH(DMI_BOARD_NAME, "Apollolake RVP1A"),
+ },
+ .driver_data = (void *)(APL_RVP),
+ },
+ {}
+};
+
+static struct snd_soc_acpi_mach *apl_quirk(void *arg)
+{
+ struct snd_soc_acpi_mach *mach = arg;
+ const struct dmi_system_id *dmi_id;
+ unsigned long apl_machine_id;
+
+ dmi_id = dmi_first_match(apl_table);
+ if (dmi_id) {
+ apl_machine_id = (unsigned long)dmi_id->driver_data;
+ if (apl_machine_id == APL_RVP)
+ return NULL;
+ }
+
+ return mach;
+}
+
+static struct snd_soc_acpi_codecs bxt_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98357A"}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_bxt_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "bxt_alc298s_i2s",
+ .fw_filename = "intel/dsp_fw_bxtn.bin",
+ .sof_fw_filename = "sof-apl.ri",
+ .sof_tplg_filename = "sof-apl-rt298.tplg",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "bxt_da7219_max98357a",
+ .fw_filename = "intel/dsp_fw_bxtn.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &bxt_codecs,
+ .sof_fw_filename = "sof-apl.ri",
+ .sof_tplg_filename = "sof-apl-da7219.tplg",
+ },
+ {
+ .id = "104C5122",
+ .drv_name = "sof_pcm512x",
+ .sof_fw_filename = "sof-apl.ri",
+ .sof_tplg_filename = "sof-apl-pcm512x.tplg",
+ },
+ {
+ .id = "1AEC8804",
+ .drv_name = "sof-wm8804",
+ .sof_fw_filename = "sof-apl.ri",
+ .sof_tplg_filename = "sof-apl-wm8804.tplg",
+ },
+ {
+ .id = "INT34C3",
+ .drv_name = "bxt_tdf8532",
+ .machine_quirk = apl_quirk,
+ .sof_fw_filename = "sof-apl.ri",
+ .sof_tplg_filename = "sof-apl-tdf8532.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_bxt_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-byt-match.c b/sound/soc/intel/common/soc-acpi-intel-byt-match.c
new file mode 100644
index 000000000..c348607b4
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-byt-match.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-byt-match.c - tables and support for BYT ACPI enumeration.
+ *
+ * Copyright (c) 2017, Intel Corporation.
+ */
+
+#include <linux/dmi.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static unsigned long byt_machine_id;
+
+#define BYT_THINKPAD_10 1
+#define BYT_POV_P1006W 2
+#define BYT_AEGEX_10 3
+
+static int byt_thinkpad10_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_machine_id = BYT_THINKPAD_10;
+ return 1;
+}
+
+static int byt_pov_p1006w_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_machine_id = BYT_POV_P1006W;
+ return 1;
+}
+
+static int byt_aegex10_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_machine_id = BYT_AEGEX_10;
+ return 1;
+}
+
+static const struct dmi_system_id byt_table[] = {
+ {
+ .callback = byt_thinkpad10_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 8"),
+ },
+ },
+ {
+ .callback = byt_thinkpad10_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"),
+ },
+ },
+ {
+ .callback = byt_thinkpad10_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"),
+ },
+ },
+ {
+ .callback = byt_thinkpad10_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"),
+ },
+ },
+ {
+ /* Point of View mobii wintab p1006w (v1.0) */
+ .callback = byt_pov_p1006w_quirk_cb,
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BayTrail"),
+ /* Note 105b is Foxcon's USB/PCI vendor id */
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "105B"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "0E57"),
+ },
+ },
+ {
+ /* Aegex 10 tablet (RU2) */
+ .callback = byt_aegex10_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AEGEX"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "RU2"),
+ },
+ },
+ { }
+};
+
+/* The Thinkapd 10 and Aegex 10 tablets have the same ID problem */
+static struct snd_soc_acpi_mach byt_thinkpad_10 = {
+ .id = "10EC5640",
+ .drv_name = "cht-bsw-rt5672",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5670.tplg",
+};
+
+static struct snd_soc_acpi_mach byt_pov_p1006w = {
+ .id = "10EC5640",
+ .drv_name = "bytcr_rt5651",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcr_rt5651",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5651.tplg",
+};
+
+static struct snd_soc_acpi_mach *byt_quirk(void *arg)
+{
+ struct snd_soc_acpi_mach *mach = arg;
+
+ dmi_check_system(byt_table);
+
+ switch (byt_machine_id) {
+ case BYT_THINKPAD_10:
+ case BYT_AEGEX_10:
+ return &byt_thinkpad_10;
+ case BYT_POV_P1006W:
+ return &byt_pov_p1006w;
+ default:
+ return mach;
+ }
+}
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = {
+ {
+ .id = "10EC5640",
+ .drv_name = "bytcr_rt5640",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcr_rt5640",
+ .machine_quirk = byt_quirk,
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5640.tplg",
+ },
+ {
+ .id = "10EC5642",
+ .drv_name = "bytcr_rt5640",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcr_rt5640",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5640.tplg",
+ },
+ {
+ .id = "INTCCFFD",
+ .drv_name = "bytcr_rt5640",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcr_rt5640",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5640.tplg",
+ },
+ {
+ .id = "10EC5651",
+ .drv_name = "bytcr_rt5651",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcr_rt5651",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5651.tplg",
+ },
+ {
+ .id = "DLGS7212",
+ .drv_name = "bytcht_da7213",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcht_da7213",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-da7213.tplg",
+ },
+ {
+ .id = "DLGS7213",
+ .drv_name = "bytcht_da7213",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcht_da7213",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-da7213.tplg",
+ },
+ {
+ .id = "ESSX8316",
+ .drv_name = "bytcht_es8316",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcht_es8316",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-es8316.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "sof_rt5682",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5682.tplg",
+ },
+ /* some Baytrail platforms rely on RT5645, use CHT machine driver */
+ {
+ .id = "10EC5645",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5645.tplg",
+ },
+ {
+ .id = "10EC5648",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-rt5645.tplg",
+ },
+ /* use CHT driver to Baytrail Chromebooks */
+ {
+ .id = "193C9890",
+ .drv_name = "cht-bsw-max98090",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-max98090.tplg",
+ },
+ {
+ .id = "14F10720",
+ .drv_name = "bytcht_cx2072x",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcht_cx2072x",
+ .sof_fw_filename = "sof-byt.ri",
+ .sof_tplg_filename = "sof-byt-cx2072x.tplg",
+ },
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH)
+ /*
+ * This is always last in the table so that it is selected only when
+ * enabled explicitly and there is no codec-related information in SSDT
+ */
+ {
+ .id = "80860F28",
+ .drv_name = "bytcht_nocodec",
+ .fw_filename = "intel/fw_sst_0f28.bin",
+ .board = "bytcht_nocodec",
+ },
+#endif
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_baytrail_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-cfl-match.c b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c
new file mode 100644
index 000000000..27b4b73d9
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-apci-intel-cfl-match.c - tables and support for CFL ACPI enumeration.
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_machines[] = {
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cfl_machines);
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_sdw_machines[] = {
+ {}
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cfl_sdw_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c
new file mode 100644
index 000000000..2752dc955
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-cht-match.c - tables and support for CHT ACPI enumeration.
+ *
+ * Copyright (c) 2017, Intel Corporation.
+ */
+
+#include <linux/dmi.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static unsigned long cht_machine_id;
+
+#define CHT_SURFACE_MACH 1
+
+static int cht_surface_quirk_cb(const struct dmi_system_id *id)
+{
+ cht_machine_id = CHT_SURFACE_MACH;
+ return 1;
+}
+
+static const struct dmi_system_id cht_table[] = {
+ {
+ .callback = cht_surface_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ },
+ },
+ { }
+};
+
+static struct snd_soc_acpi_mach cht_surface_mach = {
+ .id = "10EC5640",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5645.tplg",
+};
+
+static struct snd_soc_acpi_mach *cht_quirk(void *arg)
+{
+ struct snd_soc_acpi_mach *mach = arg;
+
+ dmi_check_system(cht_table);
+
+ if (cht_machine_id == CHT_SURFACE_MACH)
+ return &cht_surface_mach;
+ else
+ return mach;
+}
+
+/* Cherryview-based platforms: CherryTrail and Braswell */
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = {
+ {
+ .id = "10EC5670",
+ .drv_name = "cht-bsw-rt5672",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5670.tplg",
+ },
+ {
+ .id = "10EC5672",
+ .drv_name = "cht-bsw-rt5672",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5670.tplg",
+ },
+ {
+ .id = "10EC5645",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5645.tplg",
+ },
+ {
+ .id = "10EC5650",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5645.tplg",
+ },
+ {
+ .id = "10EC3270",
+ .drv_name = "cht-bsw-rt5645",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5645.tplg",
+ },
+ {
+ .id = "193C9890",
+ .drv_name = "cht-bsw-max98090",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-max98090.tplg",
+ },
+ {
+ .id = "10508824",
+ .drv_name = "cht-bsw-nau8824",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "cht-bsw",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-nau8824.tplg",
+ },
+ {
+ .id = "DLGS7212",
+ .drv_name = "bytcht_da7213",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcht_da7213",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-da7213.tplg",
+ },
+ {
+ .id = "DLGS7213",
+ .drv_name = "bytcht_da7213",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcht_da7213",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-da7213.tplg",
+ },
+ {
+ .id = "ESSX8316",
+ .drv_name = "bytcht_es8316",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcht_es8316",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-es8316.tplg",
+ },
+ /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */
+ {
+ .id = "10EC5640",
+ .drv_name = "bytcr_rt5640",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcr_rt5640",
+ .machine_quirk = cht_quirk,
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5640.tplg",
+ },
+ {
+ .id = "10EC3276",
+ .drv_name = "bytcr_rt5640",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcr_rt5640",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5640.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "sof_rt5682",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5682.tplg",
+ },
+ /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */
+ {
+ .id = "10EC5651",
+ .drv_name = "bytcr_rt5651",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcr_rt5651",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-rt5651.tplg",
+ },
+ {
+ .id = "14F10720",
+ .drv_name = "bytcht_cx2072x",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcht_cx2072x",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-cx2072x.tplg",
+ },
+ {
+ .id = "104C5122",
+ .drv_name = "sof_pcm512x",
+ .sof_fw_filename = "sof-cht.ri",
+ .sof_tplg_filename = "sof-cht-src-50khz-pcm512x.tplg",
+ },
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH)
+ /*
+ * This is always last in the table so that it is selected only when
+ * enabled explicitly and there is no codec-related information in SSDT
+ */
+ {
+ .id = "808622A8",
+ .drv_name = "bytcht_nocodec",
+ .fw_filename = "intel/fw_sst_22a8.bin",
+ .board = "bytcht_nocodec",
+ },
+#endif
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cherrytrail_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-cml-match.c b/sound/soc/intel/common/soc-acpi-intel-cml-match.c
new file mode 100644
index 000000000..9b85811ff
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-cml-match.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-cml-match.c - tables and support for CML ACPI enumeration.
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static struct snd_soc_acpi_codecs rt1011_spk_codecs = {
+ .num_codecs = 1,
+ .codecs = {"10EC1011"}
+};
+
+static struct snd_soc_acpi_codecs max98357a_spk_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98357A"}
+};
+
+static struct snd_soc_acpi_codecs max98390_spk_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98390"}
+};
+
+/*
+ * The order of the three entries with .id = "10EC5682" matters
+ * here, because DSDT tables expose an ACPI HID for the MAX98357A
+ * speaker amplifier which is not populated on the board.
+ */
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_machines[] = {
+ {
+ .id = "10EC5682",
+ .drv_name = "cml_rt1011_rt5682",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &rt1011_spk_codecs,
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt1011-rt5682.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "sof_rt5682",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &max98357a_spk_codecs,
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt5682-max98357a.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "sof_rt5682",
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt5682.tplg",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "cml_da7219_max98357a",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &max98357a_spk_codecs,
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-da7219-max98357a.tplg",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "cml_da7219_mx98357a",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &max98390_spk_codecs,
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-da7219-max98357a.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_machines);
+
+static const struct snd_soc_acpi_endpoint single_endpoint = {
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+};
+
+static const struct snd_soc_acpi_endpoint spk_l_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 0,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_endpoint spk_r_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 1,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_adr_device rt700_1_adr[] = {
+ {
+ .adr = 0x000110025D070000,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt700"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr cml_rvp[] = {
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt700_1_adr),
+ .adr_d = rt700_1_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_adr_device rt711_0_adr[] = {
+ {
+ .adr = 0x000020025D071100,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_single_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = {
+ {
+ .adr = 0x000220025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1308-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt715_3_adr[] = {
+ {
+ .adr = 0x000320025D071500,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt715"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = {
+ {
+ .adr = 0x000030025D071101,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = {
+ {
+ .adr = 0x000131025D131601, /* unique ID is set for some reason */
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1316-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1316_2_group1_adr[] = {
+ {
+ .adr = 0x000230025D131601,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1316-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt714_3_adr[] = {
+ {
+ .adr = 0x000330025D071401,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt714"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr cml_3_in_1_default[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_group1_adr),
+ .adr_d = rt1308_1_group1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1308_2_group1_adr),
+ .adr_d = rt1308_2_group1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr cml_3_in_1_mono_amp[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_single_adr),
+ .adr_d = rt1308_1_single_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr cml_3_in_1_sdca[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_sdca_0_adr),
+ .adr_d = rt711_sdca_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1316_1_group1_adr),
+ .adr_d = rt1316_1_group1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1316_2_group1_adr),
+ .adr_d = rt1316_2_group1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt714_3_adr),
+ .adr_d = rt714_3_adr,
+ },
+ {}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_sdw_machines[] = {
+ {
+ .link_mask = 0xF, /* 4 active links required */
+ .links = cml_3_in_1_default,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt711-rt1308-rt715.tplg",
+ },
+ {
+ .link_mask = 0xF, /* 4 active links required */
+ .links = cml_3_in_1_sdca,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt711-rt1316-rt714.tplg",
+ },
+ {
+ /*
+ * link_mask should be 0xB, but all links are enabled by BIOS.
+ * This entry will be selected if there is no rt1308 exposed
+ * on link2 since it will fail to match the above entry.
+ */
+ .link_mask = 0xF,
+ .links = cml_3_in_1_mono_amp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt711-rt1308-mono-rt715.tplg",
+ },
+ {
+ .link_mask = 0x2, /* RT700 connected on Link1 */
+ .links = cml_rvp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-cml.ri",
+ .sof_tplg_filename = "sof-cml-rt700.tplg",
+ },
+ {}
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_sdw_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c
new file mode 100644
index 000000000..b80f032a8
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-cnl-match.c - tables and support for CNL ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+static struct skl_machine_pdata cnl_pdata = {
+ .use_tplg_pcm = true,
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = {
+ {
+ .id = "INT34C2",
+ .drv_name = "cnl_rt274",
+ .fw_filename = "intel/dsp_fw_cnl.bin",
+ .pdata = &cnl_pdata,
+ .sof_fw_filename = "sof-cnl.ri",
+ .sof_tplg_filename = "sof-cnl-rt274.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_machines);
+
+static const struct snd_soc_acpi_endpoint single_endpoint = {
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+};
+
+static const struct snd_soc_acpi_adr_device rt5682_2_adr[] = {
+ {
+ .adr = 0x000220025D568200,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt5682"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr up_extreme_rt5682_2[] = {
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt5682_2_adr),
+ .adr_d = rt5682_2_adr,
+ },
+ {}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_sdw_machines[] = {
+ {
+ .link_mask = BIT(2),
+ .links = up_extreme_rt5682_2,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-cnl.ri",
+ .sof_tplg_filename = "sof-cnl-rt5682-sdw2.tplg"
+ },
+ {}
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_sdw_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-ehl-match.c b/sound/soc/intel/common/soc-acpi-intel-ehl-match.c
new file mode 100644
index 000000000..badafc1d5
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-ehl-match.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-apci-intel-ehl-match.c - tables and support for EHL ACPI enumeration.
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_ehl_machines[] = {
+ {
+ .id = "10EC5660",
+ .drv_name = "ehl_rt5660",
+ .sof_fw_filename = "sof-ehl.ri",
+ .sof_tplg_filename = "sof-ehl-rt5660.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ehl_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-glk-match.c b/sound/soc/intel/common/soc-acpi-intel-glk-match.c
new file mode 100644
index 000000000..26cb3b16c
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-glk-match.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-glk-match.c - tables and support for GLK ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static struct snd_soc_acpi_codecs glk_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98357A"}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "glk_alc298s_i2s",
+ .fw_filename = "intel/dsp_fw_glk.bin",
+ .sof_fw_filename = "sof-glk.ri",
+ .sof_tplg_filename = "sof-glk-alc298.tplg",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "glk_da7219_max98357a",
+ .fw_filename = "intel/dsp_fw_glk.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &glk_codecs,
+ .sof_fw_filename = "sof-glk.ri",
+ .sof_tplg_filename = "sof-glk-da7219.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "glk_rt5682_max98357a",
+ .fw_filename = "intel/dsp_fw_glk.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &glk_codecs,
+ .sof_fw_filename = "sof-glk.ri",
+ .sof_tplg_filename = "sof-glk-rt5682.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_glk_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-hda-match.c b/sound/soc/intel/common/soc-acpi-intel-hda-match.c
new file mode 100644
index 000000000..aa9cb522a
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-hda-match.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2018, Intel Corporation.
+
+/*
+ * soc-acpi-intel-hda-match.c - tables and support for HDA+ACPI enumeration.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+static struct skl_machine_pdata hda_pdata = {
+ .use_tplg_pcm = true,
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_hda_machines[] = {
+ {
+ /* .id is not used in this file */
+ .drv_name = "skl_hda_dsp_generic",
+
+ /* .fw_filename is dynamically set in skylake driver */
+
+ /* .sof_fw_filename is dynamically set in sof/intel driver */
+
+ .sof_tplg_filename = "sof-hda-generic.tplg",
+
+ /*
+ * .machine_quirk and .quirk_data are not used here but
+ * can be used if we need a more complicated machine driver
+ * combining HDA+other device (e.g. DMIC).
+ */
+ .pdata = &hda_pdata,
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_hda_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c
new file mode 100644
index 000000000..359585536
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-hsw-bdw-match.c - tables and support for ACPI enumeration.
+ *
+ * Copyright (c) 2017, Intel Corporation.
+ */
+
+#include <linux/dmi.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_haswell_machines[] = {
+ {
+ .id = "INT33CA",
+ .drv_name = "haswell-audio",
+ .fw_filename = "intel/IntcSST1.bin",
+ .sof_fw_filename = "sof-hsw.ri",
+ .sof_tplg_filename = "sof-hsw.tplg",
+ },
+ {}
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_haswell_machines);
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_broadwell_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "broadwell-audio",
+ .fw_filename = "intel/IntcSST2.bin",
+ .sof_fw_filename = "sof-bdw.ri",
+ .sof_tplg_filename = "sof-bdw-rt286.tplg",
+ },
+ {
+ .id = "10EC5650",
+ .drv_name = "bdw-rt5650",
+ .fw_filename = "intel/IntcSST2.bin",
+ .sof_fw_filename = "sof-bdw.ri",
+ .sof_tplg_filename = "sof-bdw-rt5650.tplg",
+ },
+ {
+ .id = "RT5677CE",
+ .drv_name = "bdw-rt5677",
+ .fw_filename = "intel/IntcSST2.bin",
+ .sof_fw_filename = "sof-bdw.ri",
+ .sof_tplg_filename = "sof-bdw-rt5677.tplg",
+ },
+ {
+ .id = "INT33CA",
+ .drv_name = "haswell-audio",
+ .fw_filename = "intel/IntcSST2.bin",
+ .sof_fw_filename = "sof-bdw.ri",
+ .sof_tplg_filename = "sof-bdw-rt5640.tplg",
+ },
+ {}
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_broadwell_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-icl-match.c b/sound/soc/intel/common/soc-acpi-intel-icl-match.c
new file mode 100644
index 000000000..9a529a785
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-icl-match.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-icl-match.c - tables and support for ICL ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+static struct skl_machine_pdata icl_pdata = {
+ .use_tplg_pcm = true,
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_machines[] = {
+ {
+ .id = "INT34C2",
+ .drv_name = "icl_rt274",
+ .fw_filename = "intel/dsp_fw_icl.bin",
+ .pdata = &icl_pdata,
+ .sof_fw_filename = "sof-icl.ri",
+ .sof_tplg_filename = "sof-icl-rt274.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "sof_rt5682",
+ .sof_fw_filename = "sof-icl.ri",
+ .sof_tplg_filename = "sof-icl-rt5682.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_machines);
+
+static const struct snd_soc_acpi_endpoint single_endpoint = {
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+};
+
+static const struct snd_soc_acpi_endpoint spk_l_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 0,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_endpoint spk_r_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 1,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_adr_device rt700_0_adr[] = {
+ {
+ .adr = 0x000010025D070000,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt700"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr icl_rvp[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt700_0_adr),
+ .adr_d = rt700_0_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_adr_device rt711_0_adr[] = {
+ {
+ .adr = 0x000020025D071100,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = {
+ {
+ .adr = 0x000220025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1308-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt715_3_adr[] = {
+ {
+ .adr = 0x000320025D071500,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt715"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr icl_3_in_1_default[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_group1_adr),
+ .adr_d = rt1308_1_group1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1308_2_group1_adr),
+ .adr_d = rt1308_2_group1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr icl_3_in_1_mono_amp[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_adr),
+ .adr_d = rt1308_1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_sdw_machines[] = {
+ {
+ .link_mask = 0xF, /* 4 active links required */
+ .links = icl_3_in_1_default,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-icl.ri",
+ .sof_tplg_filename = "sof-icl-rt711-rt1308-rt715.tplg",
+ },
+ {
+ .link_mask = 0xB, /* 3 active links required */
+ .links = icl_3_in_1_mono_amp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-icl.ri",
+ .sof_tplg_filename = "sof-icl-rt711-rt1308-rt715-mono.tplg",
+ },
+ {
+ .link_mask = 0x1, /* rt700 connected on link0 */
+ .links = icl_rvp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-icl.ri",
+ .sof_tplg_filename = "sof-icl-rt700.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_sdw_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-jsl-match.c b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c
new file mode 100644
index 000000000..34f5fcad5
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-apci-intel-jsl-match.c - tables and support for JSL ACPI enumeration.
+ *
+ * Copyright (c) 2019-2020, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static struct snd_soc_acpi_codecs jsl_7219_98373_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98373"}
+};
+
+static struct snd_soc_acpi_codecs rt1015_spk = {
+ .num_codecs = 1,
+ .codecs = {"10EC1015"}
+};
+
+static struct snd_soc_acpi_codecs mx98360a_spk = {
+ .num_codecs = 1,
+ .codecs = {"MX98360A"}
+};
+
+/*
+ * When adding new entry to the snd_soc_acpi_intel_jsl_machines array,
+ * use .quirk_data member to distinguish different machine driver,
+ * and keep ACPI .id field unchanged for the common codec.
+ */
+struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = {
+ {
+ .id = "DLGS7219",
+ .drv_name = "sof_da7219_max98373",
+ .sof_fw_filename = "sof-jsl.ri",
+ .sof_tplg_filename = "sof-jsl-da7219.tplg",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &jsl_7219_98373_codecs,
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "sof_da7219_max98360a",
+ .sof_fw_filename = "sof-jsl.ri",
+ .sof_tplg_filename = "sof-jsl-da7219-mx98360a.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "jsl_rt5682_rt1015",
+ .sof_fw_filename = "sof-jsl.ri",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &rt1015_spk,
+ .sof_tplg_filename = "sof-jsl-rt5682-rt1015.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "jsl_rt5682_max98360a",
+ .sof_fw_filename = "sof-jsl.ri",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &mx98360a_spk,
+ .sof_tplg_filename = "sof-jsl-rt5682-mx98360a.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_jsl_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c
new file mode 100644
index 000000000..20f2132a9
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-kbl-match.c - tables and support for KBL ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+static struct skl_machine_pdata skl_dmic_data;
+
+static struct snd_soc_acpi_codecs kbl_codecs = {
+ .num_codecs = 1,
+ .codecs = {"10508825"}
+};
+
+static struct snd_soc_acpi_codecs kbl_poppy_codecs = {
+ .num_codecs = 1,
+ .codecs = {"10EC5663"}
+};
+
+static struct snd_soc_acpi_codecs kbl_5663_5514_codecs = {
+ .num_codecs = 2,
+ .codecs = {"10EC5663", "10EC5514"}
+};
+
+static struct snd_soc_acpi_codecs kbl_7219_98357_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98357A"}
+};
+
+static struct snd_soc_acpi_codecs kbl_7219_98927_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98927"}
+};
+
+static struct snd_soc_acpi_codecs kbl_7219_98373_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98373"}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "kbl_alc286s_i2s",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ },
+ {
+ .id = "INT343B",
+ .drv_name = "kbl_n88l25_s4567",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "MX98357A",
+ .drv_name = "kbl_n88l25_m98357a",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "MX98927",
+ .drv_name = "kbl_r5514_5663_max",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_5663_5514_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "MX98927",
+ .drv_name = "kbl_rt5663_m98927",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_poppy_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "10EC5663",
+ .drv_name = "kbl_rt5663",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "kbl_da7219_mx98357a",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_7219_98357_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "kbl_da7219_max98927",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_7219_98927_codecs,
+ .pdata = &skl_dmic_data
+ },
+ {
+ .id = "10EC5660",
+ .drv_name = "kbl_rt5660",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ },
+ {
+ .id = "10EC3277",
+ .drv_name = "kbl_rt5660",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ },
+ {
+ .id = "DLGS7219",
+ .drv_name = "kbl_da7219_mx98373",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &kbl_7219_98373_codecs,
+ .pdata = &skl_dmic_data
+ },
+ {
+ .id = "MX98373",
+ .drv_name = "kbl_max98373",
+ .fw_filename = "intel/dsp_fw_kbl.bin",
+ .pdata = &skl_dmic_data
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_kbl_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-skl-match.c b/sound/soc/intel/common/soc-acpi-intel-skl-match.c
new file mode 100644
index 000000000..26f9ce146
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-skl-match.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-acpi-intel-skl-match.c - tables and support for SKL ACPI enumeration.
+ *
+ * Copyright (c) 2018, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "../skylake/skl.h"
+
+static struct skl_machine_pdata skl_dmic_data;
+
+static struct snd_soc_acpi_codecs skl_codecs = {
+ .num_codecs = 1,
+ .codecs = {"10508825"}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_skl_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "skl_alc286s_i2s",
+ .fw_filename = "intel/dsp_fw_release.bin",
+ },
+ {
+ .id = "INT343B",
+ .drv_name = "skl_n88l25_s4567",
+ .fw_filename = "intel/dsp_fw_release.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &skl_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {
+ .id = "MX98357A",
+ .drv_name = "skl_n88l25_m98357a",
+ .fw_filename = "intel/dsp_fw_release.bin",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &skl_codecs,
+ .pdata = &skl_dmic_data,
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_skl_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c
new file mode 100644
index 000000000..15d862cdc
--- /dev/null
+++ b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * soc-apci-intel-tgl-match.c - tables and support for ICL ACPI enumeration.
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ */
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+static struct snd_soc_acpi_codecs tgl_codecs = {
+ .num_codecs = 1,
+ .codecs = {"MX98357A"}
+};
+
+static const struct snd_soc_acpi_endpoint single_endpoint = {
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+};
+
+static const struct snd_soc_acpi_endpoint spk_l_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 0,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_endpoint spk_r_endpoint = {
+ .num = 0,
+ .aggregated = 1,
+ .group_position = 1,
+ .group_id = 1,
+};
+
+static const struct snd_soc_acpi_adr_device rt711_0_adr[] = {
+ {
+ .adr = 0x000020025D071100,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt711_1_adr[] = {
+ {
+ .adr = 0x000120025D071100,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_dual_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1308-1"
+ },
+ {
+ .adr = 0x000122025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1308-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_single_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_2_single_adr[] = {
+ {
+ .adr = 0x000220025D130800,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_1_group1_adr[] = {
+ {
+ .adr = 0x000120025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1308-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1308_2_group1_adr[] = {
+ {
+ .adr = 0x000220025D130800,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1308-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt715_0_adr[] = {
+ {
+ .adr = 0x000021025D071500,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt715"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt715_3_adr[] = {
+ {
+ .adr = 0x000320025D071500,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt715"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device mx8373_1_adr[] = {
+ {
+ .adr = 0x000123019F837300,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "Right"
+ },
+ {
+ .adr = 0x000127019F837300,
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "Left"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt5682_0_adr[] = {
+ {
+ .adr = 0x000021025D568200,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt5682"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = {
+ {
+ .adr = 0x000030025D071101,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt711"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = {
+ {
+ .adr = 0x000131025D131601, /* unique ID is set for some reason */
+ .num_endpoints = 1,
+ .endpoints = &spk_l_endpoint,
+ .name_prefix = "rt1316-1"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt1316_2_group1_adr[] = {
+ {
+ .adr = 0x000230025D131601,
+ .num_endpoints = 1,
+ .endpoints = &spk_r_endpoint,
+ .name_prefix = "rt1316-2"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device rt714_3_adr[] = {
+ {
+ .adr = 0x000330025D071401,
+ .num_endpoints = 1,
+ .endpoints = &single_endpoint,
+ .name_prefix = "rt714"
+ }
+};
+
+static const struct snd_soc_acpi_link_adr tgl_i2s_rt1308[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_rvp[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_dual_adr),
+ .adr_d = rt1308_1_dual_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_chromebook_base[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt5682_0_adr),
+ .adr_d = rt5682_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(mx8373_1_adr),
+ .adr_d = mx8373_1_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_3_in_1_default[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_group1_adr),
+ .adr_d = rt1308_1_group1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1308_2_group1_adr),
+ .adr_d = rt1308_2_group1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_3_in_1_mono_amp[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_0_adr),
+ .adr_d = rt711_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1308_1_single_adr),
+ .adr_d = rt1308_1_single_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt715_3_adr),
+ .adr_d = rt715_3_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_sdw_rt711_link1_rt1308_link2_rt715_link0[] = {
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt711_1_adr),
+ .adr_d = rt711_1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1308_2_single_adr),
+ .adr_d = rt1308_2_single_adr,
+ },
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt715_0_adr),
+ .adr_d = rt715_0_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(rt711_sdca_0_adr),
+ .adr_d = rt711_sdca_0_adr,
+ },
+ {
+ .mask = BIT(1),
+ .num_adr = ARRAY_SIZE(rt1316_1_group1_adr),
+ .adr_d = rt1316_1_group1_adr,
+ },
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt1316_2_group1_adr),
+ .adr_d = rt1316_2_group1_adr,
+ },
+ {
+ .mask = BIT(3),
+ .num_adr = ARRAY_SIZE(rt714_3_adr),
+ .adr_d = rt714_3_adr,
+ },
+ {}
+};
+
+static struct snd_soc_acpi_codecs tgl_max98373_amp = {
+ .num_codecs = 1,
+ .codecs = {"MX98373"}
+};
+
+struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = {
+ {
+ .id = "10EC1308",
+ .drv_name = "sof_sdw",
+ .link_mask = 0x1, /* RT711 on SoundWire link0 */
+ .links = tgl_i2s_rt1308,
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt711-i2s-rt1308.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "tgl_max98357a_rt5682",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &tgl_codecs,
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-max98357a-rt5682.tplg",
+ },
+ {
+ .id = "10EC5682",
+ .drv_name = "tgl_max98373_rt5682",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &tgl_max98373_amp,
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-max98373-rt5682.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_machines);
+
+/* this table is used when there is no I2S codec present */
+struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[] = {
+ {
+ .link_mask = 0x7,
+ .links = tgl_sdw_rt711_link1_rt1308_link2_rt715_link0,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt715-rt711-rt1308-mono.tplg",
+ },
+ {
+ .link_mask = 0xF, /* 4 active links required */
+ .links = tgl_3_in_1_default,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt711-rt1308-rt715.tplg",
+ },
+ {
+ /*
+ * link_mask should be 0xB, but all links are enabled by BIOS.
+ * This entry will be selected if there is no rt1308 exposed
+ * on link2 since it will fail to match the above entry.
+ */
+ .link_mask = 0xF,
+ .links = tgl_3_in_1_mono_amp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt711-rt1308-mono-rt715.tplg",
+ },
+ {
+ .link_mask = 0xF, /* 4 active links required */
+ .links = tgl_3_in_1_sdca,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt711-rt1316-rt714.tplg",
+ },
+ {
+ .link_mask = 0x3, /* rt711 on link 0 and 2 rt1308s on link 1 */
+ .links = tgl_rvp,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt711-rt1308.tplg",
+ },
+ {
+ .link_mask = 0x3, /* rt5682 on link0 & 2xmax98373 on link 1 */
+ .links = tgl_chromebook_base,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-sdw-max98373-rt5682.tplg",
+ },
+ {
+ .link_mask = 0x1, /* this will only enable rt5682 for now */
+ .links = tgl_chromebook_base,
+ .drv_name = "sof_sdw",
+ .sof_fw_filename = "sof-tgl.ri",
+ .sof_tplg_filename = "sof-tgl-rt5682.tplg",
+ },
+ {},
+};
+EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_sdw_machines);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Common ACPI Match module");
diff --git a/sound/soc/intel/common/soc-intel-quirks.h b/sound/soc/intel/common/soc-intel-quirks.h
new file mode 100644
index 000000000..de4e550c5
--- /dev/null
+++ b/sound/soc/intel/common/soc-intel-quirks.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * soc-intel-quirks.h - prototypes for quirk autodetection
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ */
+
+#ifndef _SND_SOC_INTEL_QUIRKS_H
+#define _SND_SOC_INTEL_QUIRKS_H
+
+#include <linux/platform_data/x86/soc.h>
+
+#if IS_ENABLED(CONFIG_X86)
+
+#include <linux/dmi.h>
+#include <asm/iosf_mbi.h>
+
+static inline bool soc_intel_is_byt_cr(struct platform_device *pdev)
+{
+ /*
+ * List of systems which:
+ * 1. Use a non CR version of the Bay Trail SoC
+ * 2. Contain at least 6 interrupt resources so that the
+ * platform_get_resource(pdev, IORESOURCE_IRQ, 5) check below
+ * succeeds
+ * 3. Despite 1. and 2. still have their IPC IRQ at index 0 rather then 5
+ *
+ * This needs to be here so that it can be shared between the SST and
+ * SOF drivers. We rely on the compiler to optimize this out in files
+ * where soc_intel_is_byt_cr is not used.
+ */
+ static const struct dmi_system_id force_bytcr_table[] = {
+ { /* Lenovo Yoga Tablet 2 series */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "YOGATablet2"),
+ },
+ },
+ {}
+ };
+ struct device *dev = &pdev->dev;
+ int status = 0;
+
+ if (!soc_intel_is_byt())
+ return false;
+
+ if (dmi_check_system(force_bytcr_table))
+ return true;
+
+ if (iosf_mbi_available()) {
+ u32 bios_status;
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, /* 0x04 PUNIT */
+ MBI_REG_READ, /* 0x10 */
+ 0x006, /* BIOS_CONFIG */
+ &bios_status);
+
+ if (status) {
+ dev_err(dev, "could not read PUNIT BIOS_CONFIG\n");
+ } else {
+ /* bits 26:27 mirror PMIC options */
+ bios_status = (bios_status >> 26) & 3;
+
+ if (bios_status == 1 || bios_status == 3) {
+ dev_info(dev, "Detected Baytrail-CR platform\n");
+ return true;
+ }
+
+ dev_info(dev, "BYT-CR not detected\n");
+ }
+ } else {
+ dev_info(dev, "IOSF_MBI not available, no BYT-CR detection\n");
+ }
+
+ if (!platform_get_resource(pdev, IORESOURCE_IRQ, 5)) {
+ /*
+ * Some devices detected as BYT-T have only a single IRQ listed,
+ * causing platform_get_irq with index 5 to return -ENXIO.
+ * The correct IRQ in this case is at index 0, as on BYT-CR.
+ */
+ dev_info(dev, "Falling back to Baytrail-CR platform\n");
+ return true;
+ }
+
+ return false;
+}
+
+#else
+
+static inline bool soc_intel_is_byt_cr(struct platform_device *pdev)
+{
+ return false;
+}
+
+#endif
+
+#endif /* _SND_SOC_INTEL_QUIRKS_H */
diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h
new file mode 100644
index 000000000..de2b44568
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp-priv.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Smart Sound Technology
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SST_DSP_PRIV_H
+#define __SOUND_SOC_SST_DSP_PRIV_H
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+
+#include "../skylake/skl-sst-dsp.h"
+
+/*
+ * DSP Operations exported by platform Audio DSP driver.
+ */
+struct sst_ops {
+ /* Shim IO */
+ void (*write)(void __iomem *addr, u32 offset, u32 value);
+ u32 (*read)(void __iomem *addr, u32 offset);
+
+ /* IRQ handlers */
+ irqreturn_t (*irq_handler)(int irq, void *context);
+
+ /* SST init and free */
+ int (*init)(struct sst_dsp *sst);
+ void (*free)(struct sst_dsp *sst);
+};
+
+/*
+ * Audio DSP memory offsets and addresses.
+ */
+struct sst_addr {
+ u32 sram0_base;
+ u32 sram1_base;
+ u32 w0_stat_sz;
+ u32 w0_up_sz;
+ void __iomem *lpe;
+ void __iomem *shim;
+};
+
+/*
+ * Audio DSP Mailbox configuration.
+ */
+struct sst_mailbox {
+ void __iomem *in_base;
+ void __iomem *out_base;
+ size_t in_size;
+ size_t out_size;
+};
+
+/*
+ * Generic SST Shim Interface.
+ */
+struct sst_dsp {
+
+ /* Shared for all platforms */
+
+ /* runtime */
+ struct sst_dsp_device *sst_dev;
+ spinlock_t spinlock; /* IPC locking */
+ struct mutex mutex; /* DSP FW lock */
+ struct device *dev;
+ void *thread_context;
+ int irq;
+ u32 id;
+
+ /* operations */
+ struct sst_ops *ops;
+
+ /* debug FS */
+ struct dentry *debugfs_root;
+
+ /* base addresses */
+ struct sst_addr addr;
+
+ /* mailbox */
+ struct sst_mailbox mailbox;
+
+ /* SST FW files loaded and their modules */
+ struct list_head module_list;
+
+ /* SKL data */
+
+ const char *fw_name;
+
+ /* To allocate CL dma buffers */
+ struct skl_dsp_loader_ops dsp_ops;
+ struct skl_dsp_fw_ops fw_ops;
+ int sst_state;
+ struct skl_cl_dev cl_dev;
+ u32 intr_status;
+ const struct firmware *fw;
+ struct snd_dma_buffer dmab;
+};
+
+#endif
diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c
new file mode 100644
index 000000000..229d09d61
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Smart Sound Technology (SST) DSP Core Driver
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/delay.h>
+
+#include "sst-dsp.h"
+#include "sst-dsp-priv.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/intel-sst.h>
+
+/* Internal generic low-level SST IO functions - can be overidden */
+void sst_shim32_write(void __iomem *addr, u32 offset, u32 value)
+{
+ writel(value, addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_write);
+
+u32 sst_shim32_read(void __iomem *addr, u32 offset)
+{
+ return readl(addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_read);
+
+void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value)
+{
+ writeq(value, addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_write64);
+
+u64 sst_shim32_read64(void __iomem *addr, u32 offset)
+{
+ return readq(addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_read64);
+
+/* Public API */
+void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ sst->ops->write(sst->addr.shim, offset, value);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_write);
+
+u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset)
+{
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ val = sst->ops->read(sst->addr.shim, offset);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_read);
+
+void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value)
+{
+ sst->ops->write(sst->addr.shim, offset, value);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked);
+
+u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset)
+{
+ return sst->ops->read(sst->addr.shim, offset);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked);
+
+int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value)
+{
+ bool change;
+ unsigned int old, new;
+ u32 ret;
+
+ ret = sst_dsp_shim_read_unlocked(sst, offset);
+
+ old = ret;
+ new = (old & (~mask)) | (value & mask);
+
+ change = (old != new);
+ if (change)
+ sst_dsp_shim_write_unlocked(sst, offset, new);
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked);
+
+/* This is for registers bits with attribute RWC */
+void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value)
+{
+ unsigned int old, new;
+ u32 ret;
+
+ ret = sst_dsp_shim_read_unlocked(sst, offset);
+
+ old = ret;
+ new = (old & (~mask)) | (value & mask);
+
+ sst_dsp_shim_write_unlocked(sst, offset, new);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_forced_unlocked);
+
+int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value)
+{
+ unsigned long flags;
+ bool change;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+ return change;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits);
+
+/* This is for registers bits with attribute RWC */
+void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ sst_dsp_shim_update_bits_forced_unlocked(sst, offset, mask, value);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_forced);
+
+int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask,
+ u32 target, u32 time, char *operation)
+{
+ u32 reg;
+ unsigned long timeout;
+ int k = 0, s = 500;
+
+ /*
+ * split the loop into sleeps of varying resolution. more accurately,
+ * the range of wakeups are:
+ * Phase 1(first 5ms): min sleep 0.5ms; max sleep 1ms.
+ * Phase 2:( 5ms to 10ms) : min sleep 0.5ms; max sleep 10ms
+ * (usleep_range (500, 1000) and usleep_range(5000, 10000) are
+ * both possible in this phase depending on whether k > 10 or not).
+ * Phase 3: (beyond 10 ms) min sleep 5ms; max sleep 10ms.
+ */
+
+ timeout = jiffies + msecs_to_jiffies(time);
+ while ((((reg = sst_dsp_shim_read_unlocked(ctx, offset)) & mask) != target)
+ && time_before(jiffies, timeout)) {
+ k++;
+ if (k > 10)
+ s = 5000;
+
+ usleep_range(s, 2*s);
+ }
+
+ if ((reg & mask) == target) {
+ dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s successful\n",
+ reg, operation);
+
+ return 0;
+ }
+
+ dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s timedout\n",
+ reg, operation);
+ return -ETIME;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_register_poll);
+
+int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size,
+ u32 outbox_offset, size_t outbox_size)
+{
+ sst->mailbox.in_base = sst->addr.lpe + inbox_offset;
+ sst->mailbox.out_base = sst->addr.lpe + outbox_offset;
+ sst->mailbox.in_size = inbox_size;
+ sst->mailbox.out_size = outbox_size;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init);
+
+void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes)
+{
+ u32 i;
+
+ trace_sst_ipc_outbox_write(bytes);
+
+ memcpy_toio(sst->mailbox.out_base, message, bytes);
+
+ for (i = 0; i < bytes; i += 4)
+ trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i));
+}
+EXPORT_SYMBOL_GPL(sst_dsp_outbox_write);
+
+void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes)
+{
+ u32 i;
+
+ trace_sst_ipc_outbox_read(bytes);
+
+ memcpy_fromio(message, sst->mailbox.out_base, bytes);
+
+ for (i = 0; i < bytes; i += 4)
+ trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i));
+}
+EXPORT_SYMBOL_GPL(sst_dsp_outbox_read);
+
+void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes)
+{
+ u32 i;
+
+ trace_sst_ipc_inbox_write(bytes);
+
+ memcpy_toio(sst->mailbox.in_base, message, bytes);
+
+ for (i = 0; i < bytes; i += 4)
+ trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i));
+}
+EXPORT_SYMBOL_GPL(sst_dsp_inbox_write);
+
+void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes)
+{
+ u32 i;
+
+ trace_sst_ipc_inbox_read(bytes);
+
+ memcpy_fromio(message, sst->mailbox.in_base, bytes);
+
+ for (i = 0; i < bytes; i += 4)
+ trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i));
+}
+EXPORT_SYMBOL_GPL(sst_dsp_inbox_read);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_DESCRIPTION("Intel SST Core");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h
new file mode 100644
index 000000000..f111fd3f3
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Smart Sound Technology (SST) Core
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SST_DSP_H
+#define __SOUND_SOC_SST_DSP_H
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+struct sst_dsp;
+
+/*
+ * SST Device.
+ *
+ * This structure is populated by the SST core driver.
+ */
+struct sst_dsp_device {
+ /* Mandatory fields */
+ struct sst_ops *ops;
+ irqreturn_t (*thread)(int irq, void *context);
+ void *thread_context;
+};
+
+/* SHIM Read / Write */
+void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value);
+u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset);
+int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+
+/* SHIM Read / Write Unlocked for callers already holding sst lock */
+void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value);
+u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset);
+int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+
+/* Internal generic low-level SST IO functions - can be overidden */
+void sst_shim32_write(void __iomem *addr, u32 offset, u32 value);
+u32 sst_shim32_read(void __iomem *addr, u32 offset);
+void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value);
+u64 sst_shim32_read64(void __iomem *addr, u32 offset);
+
+/* Mailbox management */
+int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset,
+ size_t inbox_size, u32 outbox_offset, size_t outbox_size);
+void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes);
+void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes);
+void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes);
+void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes);
+int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask,
+ u32 target, u32 time, char *operation);
+
+#endif
diff --git a/sound/soc/intel/common/sst-ipc.c b/sound/soc/intel/common/sst-ipc.c
new file mode 100644
index 000000000..89c10724d
--- /dev/null
+++ b/sound/soc/intel/common/sst-ipc.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel SST generic IPC Support
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <sound/asound.h>
+
+#include "sst-dsp.h"
+#include "sst-dsp-priv.h"
+#include "sst-ipc.h"
+
+/* IPC message timeout (msecs) */
+#define IPC_TIMEOUT_MSECS 300
+
+#define IPC_EMPTY_LIST_SIZE 8
+
+/* locks held by caller */
+static struct ipc_message *msg_get_empty(struct sst_generic_ipc *ipc)
+{
+ struct ipc_message *msg = NULL;
+
+ if (!list_empty(&ipc->empty_list)) {
+ msg = list_first_entry(&ipc->empty_list, struct ipc_message,
+ list);
+ list_del(&msg->list);
+ }
+
+ return msg;
+}
+
+static int tx_wait_done(struct sst_generic_ipc *ipc,
+ struct ipc_message *msg, struct sst_ipc_message *reply)
+{
+ unsigned long flags;
+ int ret;
+
+ /* wait for DSP completion (in all cases atm inc pending) */
+ ret = wait_event_timeout(msg->waitq, msg->complete,
+ msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+ if (ret == 0) {
+ if (ipc->ops.shim_dbg != NULL)
+ ipc->ops.shim_dbg(ipc, "message timeout");
+
+ list_del(&msg->list);
+ ret = -ETIMEDOUT;
+ } else {
+
+ /* copy the data returned from DSP */
+ if (reply) {
+ reply->header = msg->rx.header;
+ if (reply->data)
+ memcpy(reply->data, msg->rx.data, msg->rx.size);
+ }
+ ret = msg->errno;
+ }
+
+ list_add_tail(&msg->list, &ipc->empty_list);
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+ return ret;
+}
+
+static int ipc_tx_message(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request,
+ struct sst_ipc_message *reply, int wait)
+{
+ struct ipc_message *msg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+
+ msg = msg_get_empty(ipc);
+ if (msg == NULL) {
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+ return -EBUSY;
+ }
+
+ msg->tx.header = request.header;
+ msg->tx.size = request.size;
+ msg->rx.header = 0;
+ msg->rx.size = reply ? reply->size : 0;
+ msg->wait = wait;
+ msg->errno = 0;
+ msg->pending = false;
+ msg->complete = false;
+
+ if ((request.size) && (ipc->ops.tx_data_copy != NULL))
+ ipc->ops.tx_data_copy(msg, request.data, request.size);
+
+ list_add_tail(&msg->list, &ipc->tx_list);
+ schedule_work(&ipc->kwork);
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+
+ if (wait)
+ return tx_wait_done(ipc, msg, reply);
+ else
+ return 0;
+}
+
+static int msg_empty_list_init(struct sst_generic_ipc *ipc)
+{
+ int i;
+
+ ipc->msg = kcalloc(IPC_EMPTY_LIST_SIZE, sizeof(struct ipc_message),
+ GFP_KERNEL);
+ if (ipc->msg == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) {
+ ipc->msg[i].tx.data = kzalloc(ipc->tx_data_max_size, GFP_KERNEL);
+ if (ipc->msg[i].tx.data == NULL)
+ goto free_mem;
+
+ ipc->msg[i].rx.data = kzalloc(ipc->rx_data_max_size, GFP_KERNEL);
+ if (ipc->msg[i].rx.data == NULL) {
+ kfree(ipc->msg[i].tx.data);
+ goto free_mem;
+ }
+
+ init_waitqueue_head(&ipc->msg[i].waitq);
+ list_add(&ipc->msg[i].list, &ipc->empty_list);
+ }
+
+ return 0;
+
+free_mem:
+ while (i > 0) {
+ kfree(ipc->msg[i-1].tx.data);
+ kfree(ipc->msg[i-1].rx.data);
+ --i;
+ }
+ kfree(ipc->msg);
+
+ return -ENOMEM;
+}
+
+static void ipc_tx_msgs(struct work_struct *work)
+{
+ struct sst_generic_ipc *ipc =
+ container_of(work, struct sst_generic_ipc, kwork);
+ struct ipc_message *msg;
+
+ spin_lock_irq(&ipc->dsp->spinlock);
+
+ while (!list_empty(&ipc->tx_list) && !ipc->pending) {
+ /* if the DSP is busy, we will TX messages after IRQ.
+ * also postpone if we are in the middle of processing
+ * completion irq
+ */
+ if (ipc->ops.is_dsp_busy && ipc->ops.is_dsp_busy(ipc->dsp)) {
+ dev_dbg(ipc->dev, "ipc_tx_msgs dsp busy\n");
+ break;
+ }
+
+ msg = list_first_entry(&ipc->tx_list, struct ipc_message, list);
+ list_move(&msg->list, &ipc->rx_list);
+
+ if (ipc->ops.tx_msg != NULL)
+ ipc->ops.tx_msg(ipc, msg);
+ }
+
+ spin_unlock_irq(&ipc->dsp->spinlock);
+}
+
+int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request, struct sst_ipc_message *reply)
+{
+ int ret;
+
+ /*
+ * DSP maybe in lower power active state, so
+ * check if the DSP supports DSP lp On method
+ * if so invoke that before sending IPC
+ */
+ if (ipc->ops.check_dsp_lp_on)
+ if (ipc->ops.check_dsp_lp_on(ipc->dsp, true))
+ return -EIO;
+
+ ret = ipc_tx_message(ipc, request, reply, 1);
+
+ if (ipc->ops.check_dsp_lp_on)
+ if (ipc->ops.check_dsp_lp_on(ipc->dsp, false))
+ return -EIO;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_message_wait);
+
+int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request)
+{
+ return ipc_tx_message(ipc, request, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nowait);
+
+int sst_ipc_tx_message_nopm(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request, struct sst_ipc_message *reply)
+{
+ return ipc_tx_message(ipc, request, reply, 1);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nopm);
+
+struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc,
+ u64 header)
+{
+ struct ipc_message *msg;
+ u64 mask;
+
+ if (ipc->ops.reply_msg_match != NULL)
+ header = ipc->ops.reply_msg_match(header, &mask);
+ else
+ mask = (u64)-1;
+
+ if (list_empty(&ipc->rx_list)) {
+ dev_err(ipc->dev, "error: rx list empty but received 0x%llx\n",
+ header);
+ return NULL;
+ }
+
+ list_for_each_entry(msg, &ipc->rx_list, list) {
+ if ((msg->tx.header & mask) == header)
+ return msg;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(sst_ipc_reply_find_msg);
+
+/* locks held by caller */
+void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc,
+ struct ipc_message *msg)
+{
+ msg->complete = true;
+
+ if (!msg->wait)
+ list_add_tail(&msg->list, &ipc->empty_list);
+ else
+ wake_up(&msg->waitq);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_msg_reply_complete);
+
+int sst_ipc_init(struct sst_generic_ipc *ipc)
+{
+ int ret;
+
+ INIT_LIST_HEAD(&ipc->tx_list);
+ INIT_LIST_HEAD(&ipc->rx_list);
+ INIT_LIST_HEAD(&ipc->empty_list);
+ init_waitqueue_head(&ipc->wait_txq);
+
+ ret = msg_empty_list_init(ipc);
+ if (ret < 0)
+ return -ENOMEM;
+
+ INIT_WORK(&ipc->kwork, ipc_tx_msgs);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_ipc_init);
+
+void sst_ipc_fini(struct sst_generic_ipc *ipc)
+{
+ int i;
+
+ cancel_work_sync(&ipc->kwork);
+
+ if (ipc->msg) {
+ for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) {
+ kfree(ipc->msg[i].tx.data);
+ kfree(ipc->msg[i].rx.data);
+ }
+ kfree(ipc->msg);
+ }
+}
+EXPORT_SYMBOL_GPL(sst_ipc_fini);
+
+/* Module information */
+MODULE_AUTHOR("Jin Yao");
+MODULE_DESCRIPTION("Intel SST IPC generic");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/intel/common/sst-ipc.h b/sound/soc/intel/common/sst-ipc.h
new file mode 100644
index 000000000..77d651e88
--- /dev/null
+++ b/sound/soc/intel/common/sst-ipc.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel SST generic IPC Support
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SST_GENERIC_IPC_H
+#define __SST_GENERIC_IPC_H
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+
+struct sst_ipc_message {
+ u64 header;
+ void *data;
+ size_t size;
+};
+
+struct ipc_message {
+ struct list_head list;
+ struct sst_ipc_message tx;
+ struct sst_ipc_message rx;
+
+ wait_queue_head_t waitq;
+ bool pending;
+ bool complete;
+ bool wait;
+ int errno;
+};
+
+struct sst_generic_ipc;
+struct sst_dsp;
+
+struct sst_plat_ipc_ops {
+ void (*tx_msg)(struct sst_generic_ipc *, struct ipc_message *);
+ void (*shim_dbg)(struct sst_generic_ipc *, const char *);
+ void (*tx_data_copy)(struct ipc_message *, char *, size_t);
+ u64 (*reply_msg_match)(u64 header, u64 *mask);
+ bool (*is_dsp_busy)(struct sst_dsp *dsp);
+ int (*check_dsp_lp_on)(struct sst_dsp *dsp, bool state);
+};
+
+/* SST generic IPC data */
+struct sst_generic_ipc {
+ struct device *dev;
+ struct sst_dsp *dsp;
+
+ /* IPC messaging */
+ struct list_head tx_list;
+ struct list_head rx_list;
+ struct list_head empty_list;
+ wait_queue_head_t wait_txq;
+ struct task_struct *tx_thread;
+ struct work_struct kwork;
+ bool pending;
+ struct ipc_message *msg;
+ int tx_data_max_size;
+ int rx_data_max_size;
+
+ struct sst_plat_ipc_ops ops;
+};
+
+int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request, struct sst_ipc_message *reply);
+
+int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request);
+
+int sst_ipc_tx_message_nopm(struct sst_generic_ipc *ipc,
+ struct sst_ipc_message request, struct sst_ipc_message *reply);
+
+struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc,
+ u64 header);
+
+void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc,
+ struct ipc_message *msg);
+
+int sst_ipc_init(struct sst_generic_ipc *ipc);
+void sst_ipc_fini(struct sst_generic_ipc *ipc);
+
+#endif
diff --git a/sound/soc/intel/keembay/Makefile b/sound/soc/intel/keembay/Makefile
new file mode 100644
index 000000000..9084e8c63
--- /dev/null
+++ b/sound/soc/intel/keembay/Makefile
@@ -0,0 +1,4 @@
+snd-soc-kmb_platform-objs := \
+ kmb_platform.o
+
+obj-$(CONFIG_SND_SOC_INTEL_KEEMBAY) += snd-soc-kmb_platform.o
diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c
new file mode 100644
index 000000000..291a68656
--- /dev/null
+++ b/sound/soc/intel/keembay/kmb_platform.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (C) 2020 Intel Corporation.
+//
+// Intel KeemBay Platform driver.
+//
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "kmb_platform.h"
+
+#define PERIODS_MIN 2
+#define PERIODS_MAX 48
+#define PERIOD_BYTES_MIN 4096
+#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN)
+#define TDM_OPERATION 5
+#define I2S_OPERATION 0
+#define DATA_WIDTH_CONFIG_BIT 6
+#define TDM_CHANNEL_CONFIG_BIT 3
+
+static const struct snd_pcm_hardware kmb_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = BUFFER_BYTES_MAX,
+ .period_bytes_min = PERIOD_BYTES_MIN,
+ .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+ .periods_min = PERIODS_MIN,
+ .periods_max = PERIODS_MAX,
+ .fifo_size = 16,
+};
+
+static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s,
+ struct snd_pcm_runtime *runtime,
+ unsigned int tx_ptr, bool *period_elapsed)
+{
+ unsigned int period_pos = tx_ptr % runtime->period_size;
+ void __iomem *i2s_base = kmb_i2s->i2s_base;
+ void *buf = runtime->dma_area;
+ int i;
+
+ /* KMB i2s uses two separate L/R FIFO */
+ for (i = 0; i < kmb_i2s->fifo_th; i++) {
+ if (kmb_i2s->config.data_width == 16) {
+ writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
+ writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
+ } else {
+ writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
+ writel(((u32(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
+ }
+
+ period_pos++;
+
+ if (++tx_ptr >= runtime->buffer_size)
+ tx_ptr = 0;
+ }
+
+ *period_elapsed = period_pos >= runtime->period_size;
+
+ return tx_ptr;
+}
+
+static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s,
+ struct snd_pcm_runtime *runtime,
+ unsigned int rx_ptr, bool *period_elapsed)
+{
+ unsigned int period_pos = rx_ptr % runtime->period_size;
+ void __iomem *i2s_base = kmb_i2s->i2s_base;
+ int chan = kmb_i2s->config.chan_nr;
+ void *buf = runtime->dma_area;
+ int i, j;
+
+ /* KMB i2s uses two separate L/R FIFO */
+ for (i = 0; i < kmb_i2s->fifo_th; i++) {
+ for (j = 0; j < chan / 2; j++) {
+ if (kmb_i2s->config.data_width == 16) {
+ ((u16 *)buf)[rx_ptr * chan + (j * 2)] =
+ readl(i2s_base + LRBR_LTHR(j));
+ ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
+ readl(i2s_base + RRBR_RTHR(j));
+ } else {
+ ((u32 *)buf)[rx_ptr * chan + (j * 2)] =
+ readl(i2s_base + LRBR_LTHR(j));
+ ((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
+ readl(i2s_base + RRBR_RTHR(j));
+ }
+ }
+ period_pos++;
+
+ if (++rx_ptr >= runtime->buffer_size)
+ rx_ptr = 0;
+ }
+
+ *period_elapsed = period_pos >= runtime->period_size;
+
+ return rx_ptr;
+}
+
+static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s,
+ u32 stream)
+{
+ u32 i;
+
+ /* Disable all channels regardless of configuration*/
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < MAX_ISR; i++)
+ writel(0, kmb_i2s->i2s_base + TER(i));
+ } else {
+ for (i = 0; i < MAX_ISR; i++)
+ writel(0, kmb_i2s->i2s_base + RER(i));
+ }
+}
+
+static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream)
+{
+ struct i2s_clk_config_data *config = &kmb_i2s->config;
+ u32 i;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < config->chan_nr / 2; i++)
+ readl(kmb_i2s->i2s_base + TOR(i));
+ } else {
+ for (i = 0; i < config->chan_nr / 2; i++)
+ readl(kmb_i2s->i2s_base + ROR(i));
+ }
+}
+
+static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s,
+ u32 stream, int chan_nr, bool trigger)
+{
+ u32 i, irq;
+ u32 flag;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ flag = TX_INT_FLAG;
+ else
+ flag = RX_INT_FLAG;
+
+ for (i = 0; i < chan_nr / 2; i++) {
+ irq = readl(kmb_i2s->i2s_base + IMR(i));
+
+ if (trigger)
+ irq = irq & ~flag;
+ else
+ irq = irq | flag;
+
+ writel(irq, kmb_i2s->i2s_base + IMR(i));
+ }
+}
+
+static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback)
+{
+ struct snd_pcm_substream *substream;
+ bool period_elapsed;
+ unsigned int new_ptr;
+ unsigned int ptr;
+
+ if (playback)
+ substream = kmb_i2s->tx_substream;
+ else
+ substream = kmb_i2s->rx_substream;
+
+ if (!substream || !snd_pcm_running(substream))
+ return;
+
+ if (playback) {
+ ptr = kmb_i2s->tx_ptr;
+ new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime,
+ ptr, &period_elapsed);
+ cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr);
+ } else {
+ ptr = kmb_i2s->rx_ptr;
+ new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime,
+ ptr, &period_elapsed);
+ cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr);
+ }
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(substream);
+}
+
+static int kmb_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct kmb_i2s_info *kmb_i2s;
+
+ kmb_i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware);
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ runtime->private_data = kmb_i2s;
+
+ return 0;
+}
+
+static int kmb_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct kmb_i2s_info *kmb_i2s = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ kmb_i2s->tx_ptr = 0;
+ kmb_i2s->tx_substream = substream;
+ } else {
+ kmb_i2s->rx_ptr = 0;
+ kmb_i2s->rx_substream = substream;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ kmb_i2s->tx_substream = NULL;
+ else
+ kmb_i2s->rx_substream = NULL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)
+{
+ struct kmb_i2s_info *kmb_i2s = dev_id;
+ struct i2s_clk_config_data *config = &kmb_i2s->config;
+ irqreturn_t ret = IRQ_NONE;
+ u32 tx_enabled = 0;
+ u32 isr[4];
+ int i;
+
+ for (i = 0; i < config->chan_nr / 2; i++)
+ isr[i] = readl(kmb_i2s->i2s_base + ISR(i));
+
+ kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
+ kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
+ /* Only check TX interrupt if TX is active */
+ tx_enabled = readl(kmb_i2s->i2s_base + ITER);
+
+ /*
+ * Data available. Retrieve samples from FIFO
+ */
+
+ /*
+ * 8 channel audio will have isr[0..2] triggered,
+ * reading the specific isr based on the audio configuration,
+ * to avoid reading the buffers too early.
+ */
+ switch (config->chan_nr) {
+ case 2:
+ if (isr[0] & ISR_RXDA)
+ kmb_pcm_operation(kmb_i2s, false);
+ ret = IRQ_HANDLED;
+ break;
+ case 4:
+ if (isr[1] & ISR_RXDA)
+ kmb_pcm_operation(kmb_i2s, false);
+ ret = IRQ_HANDLED;
+ break;
+ case 8:
+ if (isr[3] & ISR_RXDA)
+ kmb_pcm_operation(kmb_i2s, false);
+ ret = IRQ_HANDLED;
+ break;
+ }
+
+ for (i = 0; i < config->chan_nr / 2; i++) {
+ /*
+ * Check if TX fifo is empty. If empty fill FIFO with samples
+ */
+ if ((isr[i] & ISR_TXFE) && tx_enabled) {
+ kmb_pcm_operation(kmb_i2s, true);
+ ret = IRQ_HANDLED;
+ }
+
+ /* Error Handling: TX */
+ if (isr[i] & ISR_TXFO) {
+ dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i);
+ ret = IRQ_HANDLED;
+ }
+ /* Error Handling: RX */
+ if (isr[i] & ISR_RXFO) {
+ dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i);
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ return ret;
+}
+
+static int kmb_platform_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *soc_runtime)
+{
+ size_t size = kmb_pcm_hardware.buffer_bytes_max;
+ /* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */
+ snd_pcm_set_managed_buffer_all(soc_runtime->pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL, size, size);
+ return 0;
+}
+
+static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct kmb_i2s_info *kmb_i2s = runtime->private_data;
+ snd_pcm_uframes_t pos;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ pos = kmb_i2s->tx_ptr;
+ else
+ pos = kmb_i2s->rx_ptr;
+
+ return pos < runtime->buffer_size ? pos : 0;
+}
+
+static const struct snd_soc_component_driver kmb_component = {
+ .name = "kmb",
+ .pcm_construct = kmb_platform_pcm_new,
+ .open = kmb_pcm_open,
+ .trigger = kmb_pcm_trigger,
+ .pointer = kmb_pcm_pointer,
+};
+
+static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,
+ struct snd_pcm_substream *substream)
+{
+ struct i2s_clk_config_data *config = &kmb_i2s->config;
+
+ /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
+ writel(1, kmb_i2s->i2s_base + IER);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ writel(1, kmb_i2s->i2s_base + ITER);
+ else
+ writel(1, kmb_i2s->i2s_base + IRER);
+
+ kmb_i2s_irq_trigger(kmb_i2s, substream->stream, config->chan_nr, true);
+
+ if (kmb_i2s->master)
+ writel(1, kmb_i2s->i2s_base + CER);
+ else
+ writel(0, kmb_i2s->i2s_base + CER);
+}
+
+static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s,
+ struct snd_pcm_substream *substream)
+{
+ /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
+ kmb_i2s_clear_irqs(kmb_i2s, substream->stream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ writel(0, kmb_i2s->i2s_base + ITER);
+ else
+ writel(0, kmb_i2s->i2s_base + IRER);
+
+ kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);
+
+ if (!kmb_i2s->active) {
+ writel(0, kmb_i2s->i2s_base + CER);
+ writel(0, kmb_i2s->i2s_base + IER);
+ }
+}
+
+static void kmb_disable_clk(void *clk)
+{
+ clk_disable_unprepare(clk);
+}
+
+static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ kmb_i2s->master = false;
+ ret = 0;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ writel(MASTER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0);
+
+ ret = clk_prepare_enable(kmb_i2s->clk_i2s);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk,
+ kmb_i2s->clk_i2s);
+ if (ret)
+ return ret;
+
+ kmb_i2s->master = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int kmb_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *cpu_dai)
+{
+ struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* Keep track of i2s activity before turn off
+ * the i2s interface
+ */
+ kmb_i2s->active++;
+ kmb_i2s_start(kmb_i2s, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ kmb_i2s->active--;
+ kmb_i2s_stop(kmb_i2s, substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream)
+{
+ struct i2s_clk_config_data *config = &kmb_i2s->config;
+ u32 ch_reg;
+
+ kmb_i2s_disable_channels(kmb_i2s, stream);
+
+ for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) {
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ writel(kmb_i2s->xfer_resolution,
+ kmb_i2s->i2s_base + TCR(ch_reg));
+
+ writel(kmb_i2s->fifo_th - 1,
+ kmb_i2s->i2s_base + TFCR(ch_reg));
+
+ writel(1, kmb_i2s->i2s_base + TER(ch_reg));
+ } else {
+ writel(kmb_i2s->xfer_resolution,
+ kmb_i2s->i2s_base + RCR(ch_reg));
+
+ writel(kmb_i2s->fifo_th - 1,
+ kmb_i2s->i2s_base + RFCR(ch_reg));
+
+ writel(1, kmb_i2s->i2s_base + RER(ch_reg));
+ }
+ }
+}
+
+static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ struct i2s_clk_config_data *config = &kmb_i2s->config;
+ u32 write_val;
+ int ret;
+
+ switch (params_format(hw_params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ config->data_width = 16;
+ kmb_i2s->ccr = 0x00;
+ kmb_i2s->xfer_resolution = 0x02;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ config->data_width = 32;
+ kmb_i2s->ccr = 0x14;
+ kmb_i2s->xfer_resolution = 0x05;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ config->data_width = 32;
+ kmb_i2s->ccr = 0x10;
+ kmb_i2s->xfer_resolution = 0x05;
+ break;
+ default:
+ dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt");
+ return -EINVAL;
+ }
+
+ config->chan_nr = params_channels(hw_params);
+
+ switch (config->chan_nr) {
+ case 8:
+ case 4:
+ /*
+ * Platform is not capable of providing clocks for
+ * multi channel audio
+ */
+ if (kmb_i2s->master)
+ return -EINVAL;
+
+ write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
+ (config->data_width << DATA_WIDTH_CONFIG_BIT) |
+ TDM_OPERATION;
+
+ writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
+ break;
+ case 2:
+ /*
+ * Platform is only capable of providing clocks need for
+ * 2 channel master mode
+ */
+ if (!(kmb_i2s->master))
+ return -EINVAL;
+
+ write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
+ (config->data_width << DATA_WIDTH_CONFIG_BIT) |
+ MASTER_MODE | I2S_OPERATION;
+
+ writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
+ break;
+ default:
+ dev_dbg(kmb_i2s->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+
+ kmb_i2s_config(kmb_i2s, substream->stream);
+
+ writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR);
+
+ config->sample_rate = params_rate(hw_params);
+
+ if (kmb_i2s->master) {
+ /* Only 2 ch supported in Master mode */
+ u32 bitclk = config->sample_rate * config->data_width * 2;
+
+ ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk);
+ if (ret) {
+ dev_err(kmb_i2s->dev,
+ "Can't set I2S clock rate: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int kmb_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ writel(1, kmb_i2s->i2s_base + TXFFR);
+ else
+ writel(1, kmb_i2s->i2s_base + RXFFR);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops kmb_dai_ops = {
+ .trigger = kmb_dai_trigger,
+ .hw_params = kmb_dai_hw_params,
+ .prepare = kmb_dai_prepare,
+ .set_fmt = kmb_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = {
+ {
+ .name = "intel_kmb_i2s",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = (SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S16_LE),
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = (SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S16_LE),
+ },
+ .ops = &kmb_dai_ops,
+ },
+};
+
+static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = {
+ {
+ .name = "intel_kmb_tdm",
+ .capture = {
+ .channels_min = 4,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = (SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S16_LE),
+ },
+ .ops = &kmb_dai_ops,
+ },
+};
+
+static const struct of_device_id kmb_plat_of_match[] = {
+ { .compatible = "intel,keembay-i2s", .data = &intel_kmb_i2s_dai},
+ { .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai},
+ {}
+};
+
+static int kmb_plat_dai_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_driver *kmb_i2s_dai;
+ const struct of_device_id *match;
+ struct device *dev = &pdev->dev;
+ struct kmb_i2s_info *kmb_i2s;
+ int ret, irq;
+ u32 comp1_reg;
+
+ kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL);
+ if (!kmb_i2s)
+ return -ENOMEM;
+
+ kmb_i2s_dai = devm_kzalloc(dev, sizeof(*kmb_i2s_dai), GFP_KERNEL);
+ if (!kmb_i2s_dai)
+ return -ENOMEM;
+
+ match = of_match_device(kmb_plat_of_match, &pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "Error: No device match found\n");
+ return -ENODEV;
+ }
+ kmb_i2s_dai = (struct snd_soc_dai_driver *) match->data;
+
+ /* Prepare the related clocks */
+ kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk");
+ if (IS_ERR(kmb_i2s->clk_apb)) {
+ dev_err(dev, "Failed to get apb clock\n");
+ return PTR_ERR(kmb_i2s->clk_apb);
+ }
+
+ ret = clk_prepare_enable(kmb_i2s->clk_apb);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb);
+ if (ret) {
+ dev_err(dev, "Failed to add clk_apb reset action\n");
+ return ret;
+ }
+
+ kmb_i2s->clk_i2s = devm_clk_get(dev, "osc");
+ if (IS_ERR(kmb_i2s->clk_i2s)) {
+ dev_err(dev, "Failed to get osc clock\n");
+ return PTR_ERR(kmb_i2s->clk_i2s);
+ }
+
+ kmb_i2s->i2s_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(kmb_i2s->i2s_base))
+ return PTR_ERR(kmb_i2s->i2s_base);
+
+ kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(kmb_i2s->pss_base))
+ return PTR_ERR(kmb_i2s->pss_base);
+
+ kmb_i2s->dev = &pdev->dev;
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq > 0) {
+ ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,
+ pdev->name, kmb_i2s);
+ if (ret < 0) {
+ dev_err(dev, "failed to request irq\n");
+ return ret;
+ }
+ }
+
+ comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1);
+
+ kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;
+
+ ret = devm_snd_soc_register_component(dev, &kmb_component,
+ kmb_i2s_dai, 1);
+ if (ret) {
+ dev_err(dev, "not able to register dai\n");
+ return ret;
+ }
+
+ /* To ensure none of the channels are enabled at boot up */
+ kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
+ kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
+
+ dev_set_drvdata(dev, kmb_i2s);
+
+ return ret;
+}
+
+static struct platform_driver kmb_plat_dai_driver = {
+ .driver = {
+ .name = "kmb-plat-dai",
+ .of_match_table = kmb_plat_of_match,
+ },
+ .probe = kmb_plat_dai_probe,
+};
+
+module_platform_driver(kmb_plat_dai_driver);
+
+MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver");
+MODULE_AUTHOR("Sia Jee Heng <jee.heng.sia@intel.com>");
+MODULE_AUTHOR("Sit, Michael Wei Hong <michael.wei.hong.sit@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:kmb_platform");
diff --git a/sound/soc/intel/keembay/kmb_platform.h b/sound/soc/intel/keembay/kmb_platform.h
new file mode 100644
index 000000000..9756b132c
--- /dev/null
+++ b/sound/soc/intel/keembay/kmb_platform.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel KeemBay Platform driver
+ *
+ * Copyright (C) 2020 Intel Corporation.
+ *
+ */
+
+#ifndef KMB_PLATFORM_H_
+#define KMB_PLATFORM_H_
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/types.h>
+
+/* Register values with reference to KMB databook v1.1 */
+/* common register for all channel */
+#define IER 0x000
+#define IRER 0x004
+#define ITER 0x008
+#define CER 0x00C
+#define CCR 0x010
+#define RXFFR 0x014
+#define TXFFR 0x018
+
+/* Interrupt status register fields */
+#define ISR_TXFO BIT(5)
+#define ISR_TXFE BIT(4)
+#define ISR_RXFO BIT(1)
+#define ISR_RXDA BIT(0)
+
+/* I2S Tx Rx Registers for all channels */
+#define LRBR_LTHR(x) (0x40 * (x) + 0x020)
+#define RRBR_RTHR(x) (0x40 * (x) + 0x024)
+#define RER(x) (0x40 * (x) + 0x028)
+#define TER(x) (0x40 * (x) + 0x02C)
+#define RCR(x) (0x40 * (x) + 0x030)
+#define TCR(x) (0x40 * (x) + 0x034)
+#define ISR(x) (0x40 * (x) + 0x038)
+#define IMR(x) (0x40 * (x) + 0x03C)
+#define ROR(x) (0x40 * (x) + 0x040)
+#define TOR(x) (0x40 * (x) + 0x044)
+#define RFCR(x) (0x40 * (x) + 0x048)
+#define TFCR(x) (0x40 * (x) + 0x04C)
+#define RFF(x) (0x40 * (x) + 0x050)
+#define TFF(x) (0x40 * (x) + 0x054)
+
+/* I2S COMP Registers */
+#define I2S_COMP_PARAM_2 0x01F0
+#define I2S_COMP_PARAM_1 0x01F4
+#define I2S_COMP_VERSION 0x01F8
+#define I2S_COMP_TYPE 0x01FC
+
+/* PSS_GEN_CTRL_I2S_GEN_CFG_0 Registers */
+#define I2S_GEN_CFG_0 0x000
+#define PSS_CPR_RST_EN 0x010
+#define PSS_CPR_RST_SET 0x014
+#define PSS_CPR_CLK_CLR 0x000
+#define PSS_CPR_AUX_RST_EN 0x070
+
+#define MASTER_MODE BIT(13)
+
+/* Interrupt Flag */
+#define TX_INT_FLAG GENMASK(5, 4)
+#define RX_INT_FLAG GENMASK(1, 0)
+/*
+ * Component parameter register fields - define the I2S block's
+ * configuration.
+ */
+#define COMP1_TX_WORDSIZE_3(r) FIELD_GET(GENMASK(27, 25), (r))
+#define COMP1_TX_WORDSIZE_2(r) FIELD_GET(GENMASK(24, 22), (r))
+#define COMP1_TX_WORDSIZE_1(r) FIELD_GET(GENMASK(21, 19), (r))
+#define COMP1_TX_WORDSIZE_0(r) FIELD_GET(GENMASK(18, 16), (r))
+#define COMP1_RX_ENABLED(r) FIELD_GET(BIT(6), (r))
+#define COMP1_TX_ENABLED(r) FIELD_GET(BIT(5), (r))
+#define COMP1_MODE_EN(r) FIELD_GET(BIT(4), (r))
+#define COMP1_APB_DATA_WIDTH(r) FIELD_GET(GENMASK(1, 0), (r))
+#define COMP2_RX_WORDSIZE_3(r) FIELD_GET(GENMASK(12, 10), (r))
+#define COMP2_RX_WORDSIZE_2(r) FIELD_GET(GENMASK(9, 7), (r))
+#define COMP2_RX_WORDSIZE_1(r) FIELD_GET(GENMASK(5, 3), (r))
+#define COMP2_RX_WORDSIZE_0(r) FIELD_GET(GENMASK(2, 0), (r))
+
+/* Add 1 to the below registers to indicate the actual size */
+#define COMP1_TX_CHANNELS(r) (FIELD_GET(GENMASK(10, 9), (r)) + 1)
+#define COMP1_RX_CHANNELS(r) (FIELD_GET(GENMASK(8, 7), (r)) + 1)
+#define COMP1_FIFO_DEPTH(r) (FIELD_GET(GENMASK(3, 2), (r)) + 1)
+
+/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
+#define COMP_MAX_WORDSIZE 8 /* 3 bits register width */
+
+#define MAX_CHANNEL_NUM 8
+#define MIN_CHANNEL_NUM 2
+#define MAX_ISR 4
+
+#define TWO_CHANNEL_SUPPORT 2 /* up to 2.0 */
+#define FOUR_CHANNEL_SUPPORT 4 /* up to 3.1 */
+#define SIX_CHANNEL_SUPPORT 6 /* up to 5.1 */
+#define EIGHT_CHANNEL_SUPPORT 8 /* up to 7.1 */
+
+#define DWC_I2S_PLAY BIT(0)
+#define DWC_I2S_RECORD BIT(1)
+#define DW_I2S_SLAVE BIT(2)
+#define DW_I2S_MASTER BIT(3)
+
+#define I2S_RXDMA 0x01C0
+#define I2S_TXDMA 0x01C8
+
+/*
+ * struct i2s_clk_config_data - represent i2s clk configuration data
+ * @chan_nr: number of channel
+ * @data_width: number of bits per sample (8/16/24/32 bit)
+ * @sample_rate: sampling frequency (8Khz, 16Khz, 48Khz)
+ */
+struct i2s_clk_config_data {
+ int chan_nr;
+ u32 data_width;
+ u32 sample_rate;
+};
+
+struct kmb_i2s_info {
+ void __iomem *i2s_base;
+ void __iomem *pss_base;
+ struct clk *clk_i2s;
+ struct clk *clk_apb;
+ int active;
+ unsigned int capability;
+ unsigned int i2s_reg_comp1;
+ unsigned int i2s_reg_comp2;
+ struct device *dev;
+ u32 ccr;
+ u32 xfer_resolution;
+ u32 fifo_th;
+ bool master;
+
+ struct i2s_clk_config_data config;
+ int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+
+ /* data related to PIO transfers */
+ bool use_pio;
+ struct snd_pcm_substream *tx_substream;
+ struct snd_pcm_substream *rx_substream;
+ unsigned int tx_ptr;
+ unsigned int rx_ptr;
+};
+
+#endif /* KMB_PLATFORM_H_ */
diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile
new file mode 100644
index 000000000..1c4649bcc
--- /dev/null
+++ b/sound/soc/intel/skylake/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-soc-skl-objs := skl.o skl-pcm.o skl-nhlt.o skl-messages.o skl-topology.o \
+ skl-sst-ipc.o skl-sst-dsp.o cnl-sst-dsp.o skl-sst-cldma.o \
+ skl-sst.o bxt-sst.o cnl-sst.o skl-sst-utils.o
+
+ifdef CONFIG_DEBUG_FS
+ snd-soc-skl-objs += skl-debug.o
+endif
+
+obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_COMMON) += snd-soc-skl.o
+
+#Skylake Clock device support
+snd-soc-skl-ssp-clk-objs := skl-ssp-clk.o
+
+obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_SSP_CLK) += snd-soc-skl-ssp-clk.o
diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c
new file mode 100644
index 000000000..fd4fdcb95
--- /dev/null
+++ b/sound/soc/intel/skylake/bxt-sst.c
@@ -0,0 +1,629 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bxt-sst.c - DSP library functions for BXT platform
+ *
+ * Copyright (C) 2015-16 Intel Corp
+ * Author:Rafal Redzimski <rafal.f.redzimski@intel.com>
+ * Jeeja KP <jeeja.kp@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/device.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "skl.h"
+
+#define BXT_BASEFW_TIMEOUT 3000
+#define BXT_ROM_INIT_TIMEOUT 70
+#define BXT_IPC_PURGE_FW 0x01004000
+
+#define BXT_ROM_INIT 0x5
+#define BXT_ADSP_SRAM0_BASE 0x80000
+
+/* Firmware status window */
+#define BXT_ADSP_FW_STATUS BXT_ADSP_SRAM0_BASE
+#define BXT_ADSP_ERROR_CODE (BXT_ADSP_FW_STATUS + 0x4)
+
+#define BXT_ADSP_SRAM1_BASE 0xA0000
+
+#define BXT_INSTANCE_ID 0
+#define BXT_BASE_FW_MODULE_ID 0
+
+#define BXT_ADSP_FW_BIN_HDR_OFFSET 0x2000
+
+/* Delay before scheduling D0i3 entry */
+#define BXT_D0I3_DELAY 5000
+
+static unsigned int bxt_get_errorcode(struct sst_dsp *ctx)
+{
+ return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE);
+}
+
+static int
+bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count)
+{
+ struct snd_dma_buffer dmab;
+ struct skl_dev *skl = ctx->thread_context;
+ struct firmware stripped_fw;
+ int ret = 0, i, dma_id, stream_tag;
+
+ /* library indices start from 1 to N. 0 represents base FW */
+ for (i = 1; i < lib_count; i++) {
+ ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw,
+ BXT_ADSP_FW_BIN_HDR_OFFSET, i);
+ if (ret < 0)
+ goto load_library_failed;
+
+ stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40,
+ stripped_fw.size, &dmab);
+ if (stream_tag <= 0) {
+ dev_err(ctx->dev, "Lib prepare DMA err: %x\n",
+ stream_tag);
+ ret = stream_tag;
+ goto load_library_failed;
+ }
+
+ dma_id = stream_tag - 1;
+ memcpy(dmab.area, stripped_fw.data, stripped_fw.size);
+
+ ctx->dsp_ops.trigger(ctx->dev, true, stream_tag);
+ ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i, true);
+ if (ret < 0)
+ dev_err(ctx->dev, "IPC Load Lib for %s fail: %d\n",
+ linfo[i].name, ret);
+
+ ctx->dsp_ops.trigger(ctx->dev, false, stream_tag);
+ ctx->dsp_ops.cleanup(ctx->dev, &dmab, stream_tag);
+ }
+
+ return ret;
+
+load_library_failed:
+ skl_release_library(linfo, lib_count);
+ return ret;
+}
+
+/*
+ * First boot sequence has some extra steps. Core 0 waits for power
+ * status on core 1, so power up core 1 also momentarily, keep it in
+ * reset/stall and then turn it off
+ */
+static int sst_bxt_prepare_fw(struct sst_dsp *ctx,
+ const void *fwdata, u32 fwsize)
+{
+ int stream_tag, ret;
+
+ stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab);
+ if (stream_tag <= 0) {
+ dev_err(ctx->dev, "Failed to prepare DMA FW loading err: %x\n",
+ stream_tag);
+ return stream_tag;
+ }
+
+ ctx->dsp_ops.stream_tag = stream_tag;
+ memcpy(ctx->dmab.area, fwdata, fwsize);
+
+ /* Step 1: Power up core 0 and core1 */
+ ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK |
+ SKL_DSP_CORE_MASK(1));
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core0/1 power up failed\n");
+ goto base_fw_load_failed;
+ }
+
+ /* Step 2: Purge FW request */
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_HIPCI, SKL_ADSP_REG_HIPCI_BUSY |
+ (BXT_IPC_PURGE_FW | ((stream_tag - 1) << 9)));
+
+ /* Step 3: Unset core0 reset state & unstall/run core0 */
+ ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret);
+ ret = -EIO;
+ goto base_fw_load_failed;
+ }
+
+ /* Step 4: Wait for DONE Bit */
+ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_HIPCIE,
+ SKL_ADSP_REG_HIPCIE_DONE,
+ SKL_ADSP_REG_HIPCIE_DONE,
+ BXT_INIT_TIMEOUT, "HIPCIE Done");
+ if (ret < 0) {
+ dev_err(ctx->dev, "Timeout for Purge Request%d\n", ret);
+ goto base_fw_load_failed;
+ }
+
+ /* Step 5: power down core1 */
+ ret = skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1));
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core1 power down failed\n");
+ goto base_fw_load_failed;
+ }
+
+ /* Step 6: Enable Interrupt */
+ skl_ipc_int_enable(ctx);
+ skl_ipc_op_int_enable(ctx);
+
+ /* Step 7: Wait for ROM init */
+ ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK,
+ SKL_FW_INIT, BXT_ROM_INIT_TIMEOUT, "ROM Load");
+ if (ret < 0) {
+ dev_err(ctx->dev, "Timeout for ROM init, ret:%d\n", ret);
+ goto base_fw_load_failed;
+ }
+
+ return ret;
+
+base_fw_load_failed:
+ ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag);
+ skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1));
+ skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ return ret;
+}
+
+static int sst_transfer_fw_host_dma(struct sst_dsp *ctx)
+{
+ int ret;
+
+ ctx->dsp_ops.trigger(ctx->dev, true, ctx->dsp_ops.stream_tag);
+ ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK,
+ BXT_ROM_INIT, BXT_BASEFW_TIMEOUT, "Firmware boot");
+
+ ctx->dsp_ops.trigger(ctx->dev, false, ctx->dsp_ops.stream_tag);
+ ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, ctx->dsp_ops.stream_tag);
+
+ return ret;
+}
+
+static int bxt_load_base_firmware(struct sst_dsp *ctx)
+{
+ struct firmware stripped_fw;
+ struct skl_dev *skl = ctx->thread_context;
+ int ret, i;
+
+ if (ctx->fw == NULL) {
+ ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Request firmware failed %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* prase uuids on first boot */
+ if (skl->is_first_boot) {
+ ret = snd_skl_parse_uuids(ctx, ctx->fw, BXT_ADSP_FW_BIN_HDR_OFFSET, 0);
+ if (ret < 0)
+ goto sst_load_base_firmware_failed;
+ }
+
+ stripped_fw.data = ctx->fw->data;
+ stripped_fw.size = ctx->fw->size;
+ skl_dsp_strip_extended_manifest(&stripped_fw);
+
+
+ for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) {
+ ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size);
+ if (ret == 0)
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n",
+ sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE),
+ sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS));
+
+ dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret);
+ goto sst_load_base_firmware_failed;
+ }
+
+ ret = sst_transfer_fw_host_dma(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Transfer firmware failed %d\n", ret);
+ dev_info(ctx->dev, "Error code=0x%x: FW status=0x%x\n",
+ sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE),
+ sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS));
+
+ skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ } else {
+ dev_dbg(ctx->dev, "Firmware download successful\n");
+ ret = wait_event_timeout(skl->boot_wait, skl->boot_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+ if (ret == 0) {
+ dev_err(ctx->dev, "DSP boot fail, FW Ready timeout\n");
+ skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ ret = -EIO;
+ } else {
+ ret = 0;
+ skl->fw_loaded = true;
+ }
+ }
+
+ return ret;
+
+sst_load_base_firmware_failed:
+ release_firmware(ctx->fw);
+ ctx->fw = NULL;
+ return ret;
+}
+
+/*
+ * Decide the D0i3 state that can be targeted based on the usecase
+ * ref counts and DSP state
+ *
+ * Decision Matrix: (X= dont care; state = target state)
+ *
+ * DSP state != SKL_DSP_RUNNING ; state = no d0i3
+ *
+ * DSP state == SKL_DSP_RUNNING , the following matrix applies
+ * non_d0i3 >0; streaming =X; non_streaming =X; state = no d0i3
+ * non_d0i3 =X; streaming =0; non_streaming =0; state = no d0i3
+ * non_d0i3 =0; streaming >0; non_streaming =X; state = streaming d0i3
+ * non_d0i3 =0; streaming =0; non_streaming =X; state = non-streaming d0i3
+ */
+static int bxt_d0i3_target_state(struct sst_dsp *ctx)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ struct skl_d0i3_data *d0i3 = &skl->d0i3;
+
+ if (skl->cores.state[SKL_DSP_CORE0_ID] != SKL_DSP_RUNNING)
+ return SKL_DSP_D0I3_NONE;
+
+ if (d0i3->non_d0i3)
+ return SKL_DSP_D0I3_NONE;
+ else if (d0i3->streaming)
+ return SKL_DSP_D0I3_STREAMING;
+ else if (d0i3->non_streaming)
+ return SKL_DSP_D0I3_NON_STREAMING;
+ else
+ return SKL_DSP_D0I3_NONE;
+}
+
+static void bxt_set_dsp_D0i3(struct work_struct *work)
+{
+ int ret;
+ struct skl_ipc_d0ix_msg msg;
+ struct skl_dev *skl = container_of(work,
+ struct skl_dev, d0i3.work.work);
+ struct sst_dsp *ctx = skl->dsp;
+ struct skl_d0i3_data *d0i3 = &skl->d0i3;
+ int target_state;
+
+ dev_dbg(ctx->dev, "In %s:\n", __func__);
+
+ /* D0i3 entry allowed only if core 0 alone is running */
+ if (skl_dsp_get_enabled_cores(ctx) != SKL_DSP_CORE0_MASK) {
+ dev_warn(ctx->dev,
+ "D0i3 allowed when only core0 running:Exit\n");
+ return;
+ }
+
+ target_state = bxt_d0i3_target_state(ctx);
+ if (target_state == SKL_DSP_D0I3_NONE)
+ return;
+
+ msg.instance_id = 0;
+ msg.module_id = 0;
+ msg.wake = 1;
+ msg.streaming = 0;
+ if (target_state == SKL_DSP_D0I3_STREAMING)
+ msg.streaming = 1;
+
+ ret = skl_ipc_set_d0ix(&skl->ipc, &msg);
+
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to set DSP to D0i3 state\n");
+ return;
+ }
+
+ /* Set Vendor specific register D0I3C.I3 to enable D0i3*/
+ if (skl->update_d0i3c)
+ skl->update_d0i3c(skl->dev, true);
+
+ d0i3->state = target_state;
+ skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING_D0I3;
+}
+
+static int bxt_schedule_dsp_D0i3(struct sst_dsp *ctx)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ struct skl_d0i3_data *d0i3 = &skl->d0i3;
+
+ /* Schedule D0i3 only if the usecase ref counts are appropriate */
+ if (bxt_d0i3_target_state(ctx) != SKL_DSP_D0I3_NONE) {
+
+ dev_dbg(ctx->dev, "%s: Schedule D0i3\n", __func__);
+
+ schedule_delayed_work(&d0i3->work,
+ msecs_to_jiffies(BXT_D0I3_DELAY));
+ }
+
+ return 0;
+}
+
+static int bxt_set_dsp_D0i0(struct sst_dsp *ctx)
+{
+ int ret;
+ struct skl_ipc_d0ix_msg msg;
+ struct skl_dev *skl = ctx->thread_context;
+
+ dev_dbg(ctx->dev, "In %s:\n", __func__);
+
+ /* First Cancel any pending attempt to put DSP to D0i3 */
+ cancel_delayed_work_sync(&skl->d0i3.work);
+
+ /* If DSP is currently in D0i3, bring it to D0i0 */
+ if (skl->cores.state[SKL_DSP_CORE0_ID] != SKL_DSP_RUNNING_D0I3)
+ return 0;
+
+ dev_dbg(ctx->dev, "Set DSP to D0i0\n");
+
+ msg.instance_id = 0;
+ msg.module_id = 0;
+ msg.streaming = 0;
+ msg.wake = 0;
+
+ if (skl->d0i3.state == SKL_DSP_D0I3_STREAMING)
+ msg.streaming = 1;
+
+ /* Clear Vendor specific register D0I3C.I3 to disable D0i3*/
+ if (skl->update_d0i3c)
+ skl->update_d0i3c(skl->dev, false);
+
+ ret = skl_ipc_set_d0ix(&skl->ipc, &msg);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to set DSP to D0i0\n");
+ return ret;
+ }
+
+ skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING;
+ skl->d0i3.state = SKL_DSP_D0I3_NONE;
+
+ return 0;
+}
+
+static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ int ret;
+ struct skl_ipc_dxstate_info dx;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+
+ if (skl->fw_loaded == false) {
+ skl->boot_complete = false;
+ ret = bxt_load_base_firmware(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "reload fw failed: %d\n", ret);
+ return ret;
+ }
+
+ if (skl->lib_count > 1) {
+ ret = bxt_load_library(ctx, skl->lib_info,
+ skl->lib_count);
+ if (ret < 0) {
+ dev_err(ctx->dev, "reload libs failed: %d\n", ret);
+ return ret;
+ }
+ }
+ skl->cores.state[core_id] = SKL_DSP_RUNNING;
+ return ret;
+ }
+
+ /* If core 0 is being turned on, turn on core 1 as well */
+ if (core_id == SKL_DSP_CORE0_ID)
+ ret = skl_dsp_core_power_up(ctx, core_mask |
+ SKL_DSP_CORE_MASK(1));
+ else
+ ret = skl_dsp_core_power_up(ctx, core_mask);
+
+ if (ret < 0)
+ goto err;
+
+ if (core_id == SKL_DSP_CORE0_ID) {
+
+ /*
+ * Enable interrupt after SPA is set and before
+ * DSP is unstalled
+ */
+ skl_ipc_int_enable(ctx);
+ skl_ipc_op_int_enable(ctx);
+ skl->boot_complete = false;
+ }
+
+ ret = skl_dsp_start_core(ctx, core_mask);
+ if (ret < 0)
+ goto err;
+
+ if (core_id == SKL_DSP_CORE0_ID) {
+ ret = wait_event_timeout(skl->boot_wait,
+ skl->boot_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+
+ /* If core 1 was turned on for booting core 0, turn it off */
+ skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1));
+ if (ret == 0) {
+ dev_err(ctx->dev, "%s: DSP boot timeout\n", __func__);
+ dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n",
+ sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE),
+ sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS));
+ dev_err(ctx->dev, "Failed to set core0 to D0 state\n");
+ ret = -EIO;
+ goto err;
+ }
+ }
+
+ /* Tell FW if additional core in now On */
+
+ if (core_id != SKL_DSP_CORE0_ID) {
+ dx.core_mask = core_mask;
+ dx.dx_mask = core_mask;
+
+ ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID,
+ BXT_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "IPC set_dx for core %d fail: %d\n",
+ core_id, ret);
+ goto err;
+ }
+ }
+
+ skl->cores.state[core_id] = SKL_DSP_RUNNING;
+ return 0;
+err:
+ if (core_id == SKL_DSP_CORE0_ID)
+ core_mask |= SKL_DSP_CORE_MASK(1);
+ skl_dsp_disable_core(ctx, core_mask);
+
+ return ret;
+}
+
+static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id)
+{
+ int ret;
+ struct skl_ipc_dxstate_info dx;
+ struct skl_dev *skl = ctx->thread_context;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+
+ dx.core_mask = core_mask;
+ dx.dx_mask = SKL_IPC_D3_MASK;
+
+ dev_dbg(ctx->dev, "core mask=%x dx_mask=%x\n",
+ dx.core_mask, dx.dx_mask);
+
+ ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID,
+ BXT_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0) {
+ dev_err(ctx->dev,
+ "Failed to set DSP to D3:core id = %d;Continue reset\n",
+ core_id);
+ /*
+ * In case of D3 failure, re-download the firmware, so set
+ * fw_loaded to false.
+ */
+ skl->fw_loaded = false;
+ }
+
+ if (core_id == SKL_DSP_CORE0_ID) {
+ /* disable Interrupt */
+ skl_ipc_op_int_disable(ctx);
+ skl_ipc_int_disable(ctx);
+ }
+ ret = skl_dsp_disable_core(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to disable core %d\n", ret);
+ return ret;
+ }
+ skl->cores.state[core_id] = SKL_DSP_RESET;
+ return 0;
+}
+
+static const struct skl_dsp_fw_ops bxt_fw_ops = {
+ .set_state_D0 = bxt_set_dsp_D0,
+ .set_state_D3 = bxt_set_dsp_D3,
+ .set_state_D0i3 = bxt_schedule_dsp_D0i3,
+ .set_state_D0i0 = bxt_set_dsp_D0i0,
+ .load_fw = bxt_load_base_firmware,
+ .get_fw_errcode = bxt_get_errorcode,
+ .load_library = bxt_load_library,
+};
+
+static struct sst_ops skl_ops = {
+ .irq_handler = skl_dsp_sst_interrupt,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .free = skl_dsp_free,
+};
+
+static struct sst_dsp_device skl_dev = {
+ .thread = skl_dsp_irq_thread_handler,
+ .ops = &skl_ops,
+};
+
+int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp)
+{
+ struct skl_dev *skl;
+ struct sst_dsp *sst;
+ int ret;
+
+ ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: no device\n", __func__);
+ return ret;
+ }
+
+ skl = *dsp;
+ sst = skl->dsp;
+ sst->fw_ops = bxt_fw_ops;
+ sst->addr.lpe = mmio_base;
+ sst->addr.shim = mmio_base;
+ sst->addr.sram0_base = BXT_ADSP_SRAM0_BASE;
+ sst->addr.sram1_base = BXT_ADSP_SRAM1_BASE;
+ sst->addr.w0_stat_sz = SKL_ADSP_W0_STAT_SZ;
+ sst->addr.w0_up_sz = SKL_ADSP_W0_UP_SZ;
+
+ sst_dsp_mailbox_init(sst, (BXT_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ),
+ SKL_ADSP_W0_UP_SZ, BXT_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ);
+
+ ret = skl_ipc_init(dev, skl);
+ if (ret) {
+ skl_dsp_free(sst);
+ return ret;
+ }
+
+ /* set the D0i3 check */
+ skl->ipc.ops.check_dsp_lp_on = skl_ipc_check_D0i0;
+
+ skl->boot_complete = false;
+ init_waitqueue_head(&skl->boot_wait);
+ INIT_DELAYED_WORK(&skl->d0i3.work, bxt_set_dsp_D0i3);
+ skl->d0i3.state = SKL_DSP_D0I3_NONE;
+
+ return skl_dsp_acquire_irq(sst);
+}
+EXPORT_SYMBOL_GPL(bxt_sst_dsp_init);
+
+int bxt_sst_init_fw(struct device *dev, struct skl_dev *skl)
+{
+ int ret;
+ struct sst_dsp *sst = skl->dsp;
+
+ ret = sst->fw_ops.load_fw(sst);
+ if (ret < 0) {
+ dev_err(dev, "Load base fw failed: %x\n", ret);
+ return ret;
+ }
+
+ skl_dsp_init_core_state(sst);
+
+ if (skl->lib_count > 1) {
+ ret = sst->fw_ops.load_library(sst, skl->lib_info,
+ skl->lib_count);
+ if (ret < 0) {
+ dev_err(dev, "Load Library failed : %x\n", ret);
+ return ret;
+ }
+ }
+ skl->is_first_boot = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(bxt_sst_init_fw);
+
+void bxt_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl)
+{
+
+ skl_release_library(skl->lib_info, skl->lib_count);
+ if (skl->dsp->fw)
+ release_firmware(skl->dsp->fw);
+ skl_freeup_uuid_list(skl);
+ skl_ipc_free(&skl->ipc);
+ skl->dsp->ops->free(skl->dsp);
+}
+EXPORT_SYMBOL_GPL(bxt_sst_dsp_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Broxton IPC driver");
diff --git a/sound/soc/intel/skylake/cnl-sst-dsp.c b/sound/soc/intel/skylake/cnl-sst-dsp.c
new file mode 100644
index 000000000..3ef1b194a
--- /dev/null
+++ b/sound/soc/intel/skylake/cnl-sst-dsp.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cnl-sst-dsp.c - CNL SST library generic function
+ *
+ * Copyright (C) 2016-17, Intel Corporation.
+ * Author: Guneshwor Singh <guneshwor.o.singh@intel.com>
+ *
+ * Modified from:
+ * SKL SST library generic function
+ * Copyright (C) 2014-15, Intel Corporation.
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/device.h>
+#include "../common/sst-dsp.h"
+#include "../common/sst-ipc.h"
+#include "../common/sst-dsp-priv.h"
+#include "cnl-sst-dsp.h"
+
+/* various timeout values */
+#define CNL_DSP_PU_TO 50
+#define CNL_DSP_PD_TO 50
+#define CNL_DSP_RESET_TO 50
+
+static int
+cnl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ CNL_ADSP_REG_ADSPCS, CNL_ADSPCS_CRST(core_mask),
+ CNL_ADSPCS_CRST(core_mask));
+
+ /* poll with timeout to check if operation successful */
+ return sst_dsp_register_poll(ctx,
+ CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CRST(core_mask),
+ CNL_ADSPCS_CRST(core_mask),
+ CNL_DSP_RESET_TO,
+ "Set reset");
+}
+
+static int
+cnl_dsp_core_unset_reset_state(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CRST(core_mask), 0);
+
+ /* poll with timeout to check if operation successful */
+ return sst_dsp_register_poll(ctx,
+ CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CRST(core_mask),
+ 0,
+ CNL_DSP_RESET_TO,
+ "Unset reset");
+}
+
+static bool is_cnl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int val;
+ bool is_enable;
+
+ val = sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPCS);
+
+ is_enable = (val & CNL_ADSPCS_CPA(core_mask)) &&
+ (val & CNL_ADSPCS_SPA(core_mask)) &&
+ !(val & CNL_ADSPCS_CRST(core_mask)) &&
+ !(val & CNL_ADSPCS_CSTALL(core_mask));
+
+ dev_dbg(ctx->dev, "DSP core(s) enabled? %d: core_mask %#x\n",
+ is_enable, core_mask);
+
+ return is_enable;
+}
+
+static int cnl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* stall core */
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CSTALL(core_mask),
+ CNL_ADSPCS_CSTALL(core_mask));
+
+ /* set reset state */
+ return cnl_dsp_core_set_reset_state(ctx, core_mask);
+}
+
+static int cnl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* unset reset state */
+ ret = cnl_dsp_core_unset_reset_state(ctx, core_mask);
+ if (ret < 0)
+ return ret;
+
+ /* run core */
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CSTALL(core_mask), 0);
+
+ if (!is_cnl_dsp_core_enable(ctx, core_mask)) {
+ cnl_dsp_reset_core(ctx, core_mask);
+ dev_err(ctx->dev, "DSP core mask %#x enable failed\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int cnl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_SPA(core_mask),
+ CNL_ADSPCS_SPA(core_mask));
+
+ /* poll with timeout to check if operation successful */
+ return sst_dsp_register_poll(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CPA(core_mask),
+ CNL_ADSPCS_CPA(core_mask),
+ CNL_DSP_PU_TO,
+ "Power up");
+}
+
+static int cnl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_SPA(core_mask), 0);
+
+ /* poll with timeout to check if operation successful */
+ return sst_dsp_register_poll(ctx,
+ CNL_ADSP_REG_ADSPCS,
+ CNL_ADSPCS_CPA(core_mask),
+ 0,
+ CNL_DSP_PD_TO,
+ "Power down");
+}
+
+int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* power up */
+ ret = cnl_dsp_core_power_up(ctx, core_mask);
+ if (ret < 0) {
+ dev_dbg(ctx->dev, "DSP core mask %#x power up failed",
+ core_mask);
+ return ret;
+ }
+
+ return cnl_dsp_start_core(ctx, core_mask);
+}
+
+int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ ret = cnl_dsp_reset_core(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "DSP core mask %#x reset failed\n",
+ core_mask);
+ return ret;
+ }
+
+ /* power down core*/
+ ret = cnl_dsp_core_power_down(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "DSP core mask %#x power down failed\n",
+ core_mask);
+ return ret;
+ }
+
+ if (is_cnl_dsp_core_enable(ctx, core_mask)) {
+ dev_err(ctx->dev, "DSP core mask %#x disable failed\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+irqreturn_t cnl_dsp_sst_interrupt(int irq, void *dev_id)
+{
+ struct sst_dsp *ctx = dev_id;
+ u32 val;
+ irqreturn_t ret = IRQ_NONE;
+
+ spin_lock(&ctx->spinlock);
+
+ val = sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPIS);
+ ctx->intr_status = val;
+
+ if (val == 0xffffffff) {
+ spin_unlock(&ctx->spinlock);
+ return IRQ_NONE;
+ }
+
+ if (val & CNL_ADSPIS_IPC) {
+ cnl_ipc_int_disable(ctx);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ spin_unlock(&ctx->spinlock);
+
+ return ret;
+}
+
+void cnl_dsp_free(struct sst_dsp *dsp)
+{
+ cnl_ipc_int_disable(dsp);
+
+ free_irq(dsp->irq, dsp);
+ cnl_ipc_op_int_disable(dsp);
+ cnl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK);
+}
+EXPORT_SYMBOL_GPL(cnl_dsp_free);
+
+void cnl_ipc_int_enable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_ADSPIC,
+ CNL_ADSPIC_IPC, CNL_ADSPIC_IPC);
+}
+
+void cnl_ipc_int_disable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPIC,
+ CNL_ADSPIC_IPC, 0);
+}
+
+void cnl_ipc_op_int_enable(struct sst_dsp *ctx)
+{
+ /* enable IPC DONE interrupt */
+ sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_DONE,
+ CNL_ADSP_REG_HIPCCTL_DONE);
+
+ /* enable IPC BUSY interrupt */
+ sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_BUSY,
+ CNL_ADSP_REG_HIPCCTL_BUSY);
+}
+
+void cnl_ipc_op_int_disable(struct sst_dsp *ctx)
+{
+ /* disable IPC DONE interrupt */
+ sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_DONE, 0);
+
+ /* disable IPC BUSY interrupt */
+ sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_BUSY, 0);
+}
+
+bool cnl_ipc_int_status(struct sst_dsp *ctx)
+{
+ return sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPIS) &
+ CNL_ADSPIS_IPC;
+}
+
+void cnl_ipc_free(struct sst_generic_ipc *ipc)
+{
+ cnl_ipc_op_int_disable(ipc->dsp);
+ sst_ipc_fini(ipc);
+}
diff --git a/sound/soc/intel/skylake/cnl-sst-dsp.h b/sound/soc/intel/skylake/cnl-sst-dsp.h
new file mode 100644
index 000000000..d3cf4bd1a
--- /dev/null
+++ b/sound/soc/intel/skylake/cnl-sst-dsp.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Cannonlake SST DSP Support
+ *
+ * Copyright (C) 2016-17, Intel Corporation.
+ */
+
+#ifndef __CNL_SST_DSP_H__
+#define __CNL_SST_DSP_H__
+
+struct sst_dsp;
+struct sst_dsp_device;
+struct sst_generic_ipc;
+
+/* Intel HD Audio General DSP Registers */
+#define CNL_ADSP_GEN_BASE 0x0
+#define CNL_ADSP_REG_ADSPCS (CNL_ADSP_GEN_BASE + 0x04)
+#define CNL_ADSP_REG_ADSPIC (CNL_ADSP_GEN_BASE + 0x08)
+#define CNL_ADSP_REG_ADSPIS (CNL_ADSP_GEN_BASE + 0x0c)
+
+/* Intel HD Audio Inter-Processor Communication Registers */
+#define CNL_ADSP_IPC_BASE 0xc0
+#define CNL_ADSP_REG_HIPCTDR (CNL_ADSP_IPC_BASE + 0x00)
+#define CNL_ADSP_REG_HIPCTDA (CNL_ADSP_IPC_BASE + 0x04)
+#define CNL_ADSP_REG_HIPCTDD (CNL_ADSP_IPC_BASE + 0x08)
+#define CNL_ADSP_REG_HIPCIDR (CNL_ADSP_IPC_BASE + 0x10)
+#define CNL_ADSP_REG_HIPCIDA (CNL_ADSP_IPC_BASE + 0x14)
+#define CNL_ADSP_REG_HIPCIDD (CNL_ADSP_IPC_BASE + 0x18)
+#define CNL_ADSP_REG_HIPCCTL (CNL_ADSP_IPC_BASE + 0x28)
+
+/* HIPCTDR */
+#define CNL_ADSP_REG_HIPCTDR_BUSY BIT(31)
+
+/* HIPCTDA */
+#define CNL_ADSP_REG_HIPCTDA_DONE BIT(31)
+
+/* HIPCIDR */
+#define CNL_ADSP_REG_HIPCIDR_BUSY BIT(31)
+
+/* HIPCIDA */
+#define CNL_ADSP_REG_HIPCIDA_DONE BIT(31)
+
+/* CNL HIPCCTL */
+#define CNL_ADSP_REG_HIPCCTL_DONE BIT(1)
+#define CNL_ADSP_REG_HIPCCTL_BUSY BIT(0)
+
+/* CNL HIPCT */
+#define CNL_ADSP_REG_HIPCT_BUSY BIT(31)
+
+/* Intel HD Audio SRAM Window 1 */
+#define CNL_ADSP_SRAM1_BASE 0xa0000
+
+#define CNL_ADSP_MMIO_LEN 0x10000
+
+#define CNL_ADSP_W0_STAT_SZ 0x1000
+
+#define CNL_ADSP_W0_UP_SZ 0x1000
+
+#define CNL_ADSP_W1_SZ 0x1000
+
+#define CNL_FW_STS_MASK 0xf
+
+#define CNL_ADSPIC_IPC 0x1
+#define CNL_ADSPIS_IPC 0x1
+
+#define CNL_DSP_CORES 4
+#define CNL_DSP_CORES_MASK ((1 << CNL_DSP_CORES) - 1)
+
+/* core reset - asserted high */
+#define CNL_ADSPCS_CRST_SHIFT 0
+#define CNL_ADSPCS_CRST(x) (x << CNL_ADSPCS_CRST_SHIFT)
+
+/* core run/stall - when set to 1 core is stalled */
+#define CNL_ADSPCS_CSTALL_SHIFT 8
+#define CNL_ADSPCS_CSTALL(x) (x << CNL_ADSPCS_CSTALL_SHIFT)
+
+/* set power active - when set to 1 turn core on */
+#define CNL_ADSPCS_SPA_SHIFT 16
+#define CNL_ADSPCS_SPA(x) (x << CNL_ADSPCS_SPA_SHIFT)
+
+/* current power active - power status of cores, set by hardware */
+#define CNL_ADSPCS_CPA_SHIFT 24
+#define CNL_ADSPCS_CPA(x) (x << CNL_ADSPCS_CPA_SHIFT)
+
+int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask);
+int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask);
+irqreturn_t cnl_dsp_sst_interrupt(int irq, void *dev_id);
+void cnl_dsp_free(struct sst_dsp *dsp);
+
+void cnl_ipc_int_enable(struct sst_dsp *ctx);
+void cnl_ipc_int_disable(struct sst_dsp *ctx);
+void cnl_ipc_op_int_enable(struct sst_dsp *ctx);
+void cnl_ipc_op_int_disable(struct sst_dsp *ctx);
+bool cnl_ipc_int_status(struct sst_dsp *ctx);
+void cnl_ipc_free(struct sst_generic_ipc *ipc);
+
+int cnl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp);
+int cnl_sst_init_fw(struct device *dev, struct skl_dev *skl);
+void cnl_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl);
+
+#endif /*__CNL_SST_DSP_H__*/
diff --git a/sound/soc/intel/skylake/cnl-sst.c b/sound/soc/intel/skylake/cnl-sst.c
new file mode 100644
index 000000000..1275c149a
--- /dev/null
+++ b/sound/soc/intel/skylake/cnl-sst.c
@@ -0,0 +1,508 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cnl-sst.c - DSP library functions for CNL platform
+ *
+ * Copyright (C) 2016-17, Intel Corporation.
+ *
+ * Author: Guneshwor Singh <guneshwor.o.singh@intel.com>
+ *
+ * Modified from:
+ * HDA DSP library functions for SKL platform
+ * Copyright (C) 2014-15, Intel Corporation.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/device.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-ipc.h"
+#include "cnl-sst-dsp.h"
+#include "skl.h"
+
+#define CNL_FW_ROM_INIT 0x1
+#define CNL_FW_INIT 0x5
+#define CNL_IPC_PURGE 0x01004000
+#define CNL_INIT_TIMEOUT 300
+#define CNL_BASEFW_TIMEOUT 3000
+
+#define CNL_ADSP_SRAM0_BASE 0x80000
+
+/* Firmware status window */
+#define CNL_ADSP_FW_STATUS CNL_ADSP_SRAM0_BASE
+#define CNL_ADSP_ERROR_CODE (CNL_ADSP_FW_STATUS + 0x4)
+
+#define CNL_INSTANCE_ID 0
+#define CNL_BASE_FW_MODULE_ID 0
+#define CNL_ADSP_FW_HDR_OFFSET 0x2000
+#define CNL_ROM_CTRL_DMA_ID 0x9
+
+static int cnl_prepare_fw(struct sst_dsp *ctx, const void *fwdata, u32 fwsize)
+{
+
+ int ret, stream_tag;
+
+ stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab);
+ if (stream_tag <= 0) {
+ dev_err(ctx->dev, "dma prepare failed: 0%#x\n", stream_tag);
+ return stream_tag;
+ }
+
+ ctx->dsp_ops.stream_tag = stream_tag;
+ memcpy(ctx->dmab.area, fwdata, fwsize);
+
+ ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core0 power up failed\n");
+ ret = -EIO;
+ goto base_fw_load_failed;
+ }
+
+ /* purge FW request */
+ sst_dsp_shim_write(ctx, CNL_ADSP_REG_HIPCIDR,
+ CNL_ADSP_REG_HIPCIDR_BUSY | (CNL_IPC_PURGE |
+ ((stream_tag - 1) << CNL_ROM_CTRL_DMA_ID)));
+
+ ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret);
+ ret = -EIO;
+ goto base_fw_load_failed;
+ }
+
+ ret = sst_dsp_register_poll(ctx, CNL_ADSP_REG_HIPCIDA,
+ CNL_ADSP_REG_HIPCIDA_DONE,
+ CNL_ADSP_REG_HIPCIDA_DONE,
+ BXT_INIT_TIMEOUT, "HIPCIDA Done");
+ if (ret < 0) {
+ dev_err(ctx->dev, "timeout for purge request: %d\n", ret);
+ goto base_fw_load_failed;
+ }
+
+ /* enable interrupt */
+ cnl_ipc_int_enable(ctx);
+ cnl_ipc_op_int_enable(ctx);
+
+ ret = sst_dsp_register_poll(ctx, CNL_ADSP_FW_STATUS, CNL_FW_STS_MASK,
+ CNL_FW_ROM_INIT, CNL_INIT_TIMEOUT,
+ "rom load");
+ if (ret < 0) {
+ dev_err(ctx->dev, "rom init timeout, ret: %d\n", ret);
+ goto base_fw_load_failed;
+ }
+
+ return 0;
+
+base_fw_load_failed:
+ ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag);
+ cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+
+ return ret;
+}
+
+static int sst_transfer_fw_host_dma(struct sst_dsp *ctx)
+{
+ int ret;
+
+ ctx->dsp_ops.trigger(ctx->dev, true, ctx->dsp_ops.stream_tag);
+ ret = sst_dsp_register_poll(ctx, CNL_ADSP_FW_STATUS, CNL_FW_STS_MASK,
+ CNL_FW_INIT, CNL_BASEFW_TIMEOUT,
+ "firmware boot");
+
+ ctx->dsp_ops.trigger(ctx->dev, false, ctx->dsp_ops.stream_tag);
+ ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, ctx->dsp_ops.stream_tag);
+
+ return ret;
+}
+
+static int cnl_load_base_firmware(struct sst_dsp *ctx)
+{
+ struct firmware stripped_fw;
+ struct skl_dev *cnl = ctx->thread_context;
+ int ret, i;
+
+ if (!ctx->fw) {
+ ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "request firmware failed: %d\n", ret);
+ goto cnl_load_base_firmware_failed;
+ }
+ }
+
+ /* parse uuids if first boot */
+ if (cnl->is_first_boot) {
+ ret = snd_skl_parse_uuids(ctx, ctx->fw,
+ CNL_ADSP_FW_HDR_OFFSET, 0);
+ if (ret < 0)
+ goto cnl_load_base_firmware_failed;
+ }
+
+ stripped_fw.data = ctx->fw->data;
+ stripped_fw.size = ctx->fw->size;
+ skl_dsp_strip_extended_manifest(&stripped_fw);
+
+ for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) {
+ ret = cnl_prepare_fw(ctx, stripped_fw.data, stripped_fw.size);
+ if (!ret)
+ break;
+ dev_dbg(ctx->dev, "prepare firmware failed: %d\n", ret);
+ }
+
+ if (ret < 0)
+ goto cnl_load_base_firmware_failed;
+
+ ret = sst_transfer_fw_host_dma(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "transfer firmware failed: %d\n", ret);
+ cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ goto cnl_load_base_firmware_failed;
+ }
+
+ ret = wait_event_timeout(cnl->boot_wait, cnl->boot_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+ if (ret == 0) {
+ dev_err(ctx->dev, "FW ready timed-out\n");
+ cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ ret = -EIO;
+ goto cnl_load_base_firmware_failed;
+ }
+
+ cnl->fw_loaded = true;
+
+ return 0;
+
+cnl_load_base_firmware_failed:
+ dev_err(ctx->dev, "firmware load failed: %d\n", ret);
+ release_firmware(ctx->fw);
+ ctx->fw = NULL;
+
+ return ret;
+}
+
+static int cnl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id)
+{
+ struct skl_dev *cnl = ctx->thread_context;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+ struct skl_ipc_dxstate_info dx;
+ int ret;
+
+ if (!cnl->fw_loaded) {
+ cnl->boot_complete = false;
+ ret = cnl_load_base_firmware(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "fw reload failed: %d\n", ret);
+ return ret;
+ }
+
+ cnl->cores.state[core_id] = SKL_DSP_RUNNING;
+ return ret;
+ }
+
+ ret = cnl_dsp_enable_core(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "enable dsp core %d failed: %d\n",
+ core_id, ret);
+ goto err;
+ }
+
+ if (core_id == SKL_DSP_CORE0_ID) {
+ /* enable interrupt */
+ cnl_ipc_int_enable(ctx);
+ cnl_ipc_op_int_enable(ctx);
+ cnl->boot_complete = false;
+
+ ret = wait_event_timeout(cnl->boot_wait, cnl->boot_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+ if (ret == 0) {
+ dev_err(ctx->dev,
+ "dsp boot timeout, status=%#x error=%#x\n",
+ sst_dsp_shim_read(ctx, CNL_ADSP_FW_STATUS),
+ sst_dsp_shim_read(ctx, CNL_ADSP_ERROR_CODE));
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+ } else {
+ dx.core_mask = core_mask;
+ dx.dx_mask = core_mask;
+
+ ret = skl_ipc_set_dx(&cnl->ipc, CNL_INSTANCE_ID,
+ CNL_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "set_dx failed, core: %d ret: %d\n",
+ core_id, ret);
+ goto err;
+ }
+ }
+ cnl->cores.state[core_id] = SKL_DSP_RUNNING;
+
+ return 0;
+err:
+ cnl_dsp_disable_core(ctx, core_mask);
+
+ return ret;
+}
+
+static int cnl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id)
+{
+ struct skl_dev *cnl = ctx->thread_context;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+ struct skl_ipc_dxstate_info dx;
+ int ret;
+
+ dx.core_mask = core_mask;
+ dx.dx_mask = SKL_IPC_D3_MASK;
+
+ ret = skl_ipc_set_dx(&cnl->ipc, CNL_INSTANCE_ID,
+ CNL_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0) {
+ dev_err(ctx->dev,
+ "dsp core %d to d3 failed; continue reset\n",
+ core_id);
+ cnl->fw_loaded = false;
+ }
+
+ /* disable interrupts if core 0 */
+ if (core_id == SKL_DSP_CORE0_ID) {
+ skl_ipc_op_int_disable(ctx);
+ skl_ipc_int_disable(ctx);
+ }
+
+ ret = cnl_dsp_disable_core(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "disable dsp core %d failed: %d\n",
+ core_id, ret);
+ return ret;
+ }
+
+ cnl->cores.state[core_id] = SKL_DSP_RESET;
+
+ return ret;
+}
+
+static unsigned int cnl_get_errno(struct sst_dsp *ctx)
+{
+ return sst_dsp_shim_read(ctx, CNL_ADSP_ERROR_CODE);
+}
+
+static const struct skl_dsp_fw_ops cnl_fw_ops = {
+ .set_state_D0 = cnl_set_dsp_D0,
+ .set_state_D3 = cnl_set_dsp_D3,
+ .load_fw = cnl_load_base_firmware,
+ .get_fw_errcode = cnl_get_errno,
+};
+
+static struct sst_ops cnl_ops = {
+ .irq_handler = cnl_dsp_sst_interrupt,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .free = cnl_dsp_free,
+};
+
+#define CNL_IPC_GLB_NOTIFY_RSP_SHIFT 29
+#define CNL_IPC_GLB_NOTIFY_RSP_MASK 0x1
+#define CNL_IPC_GLB_NOTIFY_RSP_TYPE(x) (((x) >> CNL_IPC_GLB_NOTIFY_RSP_SHIFT) \
+ & CNL_IPC_GLB_NOTIFY_RSP_MASK)
+
+static irqreturn_t cnl_dsp_irq_thread_handler(int irq, void *context)
+{
+ struct sst_dsp *dsp = context;
+ struct skl_dev *cnl = dsp->thread_context;
+ struct sst_generic_ipc *ipc = &cnl->ipc;
+ struct skl_ipc_header header = {0};
+ u32 hipcida, hipctdr, hipctdd;
+ int ipc_irq = 0;
+
+ /* here we handle ipc interrupts only */
+ if (!(dsp->intr_status & CNL_ADSPIS_IPC))
+ return IRQ_NONE;
+
+ hipcida = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCIDA);
+ hipctdr = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCTDR);
+ hipctdd = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCTDD);
+
+ /* reply message from dsp */
+ if (hipcida & CNL_ADSP_REG_HIPCIDA_DONE) {
+ sst_dsp_shim_update_bits(dsp, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_DONE, 0);
+
+ /* clear done bit - tell dsp operation is complete */
+ sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCIDA,
+ CNL_ADSP_REG_HIPCIDA_DONE, CNL_ADSP_REG_HIPCIDA_DONE);
+
+ ipc_irq = 1;
+
+ /* unmask done interrupt */
+ sst_dsp_shim_update_bits(dsp, CNL_ADSP_REG_HIPCCTL,
+ CNL_ADSP_REG_HIPCCTL_DONE, CNL_ADSP_REG_HIPCCTL_DONE);
+ }
+
+ /* new message from dsp */
+ if (hipctdr & CNL_ADSP_REG_HIPCTDR_BUSY) {
+ header.primary = hipctdr;
+ header.extension = hipctdd;
+ dev_dbg(dsp->dev, "IPC irq: Firmware respond primary:%x",
+ header.primary);
+ dev_dbg(dsp->dev, "IPC irq: Firmware respond extension:%x",
+ header.extension);
+
+ if (CNL_IPC_GLB_NOTIFY_RSP_TYPE(header.primary)) {
+ /* Handle Immediate reply from DSP Core */
+ skl_ipc_process_reply(ipc, header);
+ } else {
+ dev_dbg(dsp->dev, "IPC irq: Notification from firmware\n");
+ skl_ipc_process_notification(ipc, header);
+ }
+ /* clear busy interrupt */
+ sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCTDR,
+ CNL_ADSP_REG_HIPCTDR_BUSY, CNL_ADSP_REG_HIPCTDR_BUSY);
+
+ /* set done bit to ack dsp */
+ sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCTDA,
+ CNL_ADSP_REG_HIPCTDA_DONE, CNL_ADSP_REG_HIPCTDA_DONE);
+ ipc_irq = 1;
+ }
+
+ if (ipc_irq == 0)
+ return IRQ_NONE;
+
+ cnl_ipc_int_enable(dsp);
+
+ /* continue to send any remaining messages */
+ schedule_work(&ipc->kwork);
+
+ return IRQ_HANDLED;
+}
+
+static struct sst_dsp_device cnl_dev = {
+ .thread = cnl_dsp_irq_thread_handler,
+ .ops = &cnl_ops,
+};
+
+static void cnl_ipc_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg)
+{
+ struct skl_ipc_header *header = (struct skl_ipc_header *)(&msg->tx.header);
+
+ if (msg->tx.size)
+ sst_dsp_outbox_write(ipc->dsp, msg->tx.data, msg->tx.size);
+ sst_dsp_shim_write_unlocked(ipc->dsp, CNL_ADSP_REG_HIPCIDD,
+ header->extension);
+ sst_dsp_shim_write_unlocked(ipc->dsp, CNL_ADSP_REG_HIPCIDR,
+ header->primary | CNL_ADSP_REG_HIPCIDR_BUSY);
+}
+
+static bool cnl_ipc_is_dsp_busy(struct sst_dsp *dsp)
+{
+ u32 hipcidr;
+
+ hipcidr = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCIDR);
+
+ return (hipcidr & CNL_ADSP_REG_HIPCIDR_BUSY);
+}
+
+static int cnl_ipc_init(struct device *dev, struct skl_dev *cnl)
+{
+ struct sst_generic_ipc *ipc;
+ int err;
+
+ ipc = &cnl->ipc;
+ ipc->dsp = cnl->dsp;
+ ipc->dev = dev;
+
+ ipc->tx_data_max_size = CNL_ADSP_W1_SZ;
+ ipc->rx_data_max_size = CNL_ADSP_W0_UP_SZ;
+
+ err = sst_ipc_init(ipc);
+ if (err)
+ return err;
+
+ /*
+ * overriding tx_msg and is_dsp_busy since
+ * ipc registers are different for cnl
+ */
+ ipc->ops.tx_msg = cnl_ipc_tx_msg;
+ ipc->ops.tx_data_copy = skl_ipc_tx_data_copy;
+ ipc->ops.is_dsp_busy = cnl_ipc_is_dsp_busy;
+
+ return 0;
+}
+
+int cnl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp)
+{
+ struct skl_dev *cnl;
+ struct sst_dsp *sst;
+ int ret;
+
+ ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &cnl_dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: no device\n", __func__);
+ return ret;
+ }
+
+ cnl = *dsp;
+ sst = cnl->dsp;
+ sst->fw_ops = cnl_fw_ops;
+ sst->addr.lpe = mmio_base;
+ sst->addr.shim = mmio_base;
+ sst->addr.sram0_base = CNL_ADSP_SRAM0_BASE;
+ sst->addr.sram1_base = CNL_ADSP_SRAM1_BASE;
+ sst->addr.w0_stat_sz = CNL_ADSP_W0_STAT_SZ;
+ sst->addr.w0_up_sz = CNL_ADSP_W0_UP_SZ;
+
+ sst_dsp_mailbox_init(sst, (CNL_ADSP_SRAM0_BASE + CNL_ADSP_W0_STAT_SZ),
+ CNL_ADSP_W0_UP_SZ, CNL_ADSP_SRAM1_BASE,
+ CNL_ADSP_W1_SZ);
+
+ ret = cnl_ipc_init(dev, cnl);
+ if (ret) {
+ skl_dsp_free(sst);
+ return ret;
+ }
+
+ cnl->boot_complete = false;
+ init_waitqueue_head(&cnl->boot_wait);
+
+ return skl_dsp_acquire_irq(sst);
+}
+EXPORT_SYMBOL_GPL(cnl_sst_dsp_init);
+
+int cnl_sst_init_fw(struct device *dev, struct skl_dev *skl)
+{
+ int ret;
+ struct sst_dsp *sst = skl->dsp;
+
+ ret = skl->dsp->fw_ops.load_fw(sst);
+ if (ret < 0) {
+ dev_err(dev, "load base fw failed: %d", ret);
+ return ret;
+ }
+
+ skl_dsp_init_core_state(sst);
+
+ skl->is_first_boot = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cnl_sst_init_fw);
+
+void cnl_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl)
+{
+ if (skl->dsp->fw)
+ release_firmware(skl->dsp->fw);
+
+ skl_freeup_uuid_list(skl);
+ cnl_ipc_free(&skl->ipc);
+
+ skl->dsp->ops->free(skl->dsp);
+}
+EXPORT_SYMBOL_GPL(cnl_sst_dsp_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Cannonlake IPC driver");
diff --git a/sound/soc/intel/skylake/skl-debug.c b/sound/soc/intel/skylake/skl-debug.c
new file mode 100644
index 000000000..a15aa2ffa
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-debug.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-debug.c - Debugfs for skl driver
+ *
+ * Copyright (C) 2016-17 Intel Corp
+ */
+
+#include <linux/pci.h>
+#include <linux/debugfs.h>
+#include <uapi/sound/skl-tplg-interface.h>
+#include "skl.h"
+#include "skl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+#include "skl-topology.h"
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+
+#define MOD_BUF PAGE_SIZE
+#define FW_REG_BUF PAGE_SIZE
+#define FW_REG_SIZE 0x60
+
+struct skl_debug {
+ struct skl_dev *skl;
+ struct device *dev;
+
+ struct dentry *fs;
+ struct dentry *modules;
+ u8 fw_read_buff[FW_REG_BUF];
+};
+
+static ssize_t skl_print_pins(struct skl_module_pin *m_pin, char *buf,
+ int max_pin, ssize_t size, bool direction)
+{
+ int i;
+ ssize_t ret = 0;
+
+ for (i = 0; i < max_pin; i++) {
+ ret += scnprintf(buf + size, MOD_BUF - size,
+ "%s %d\n\tModule %d\n\tInstance %d\n\t"
+ "In-used %s\n\tType %s\n"
+ "\tState %d\n\tIndex %d\n",
+ direction ? "Input Pin:" : "Output Pin:",
+ i, m_pin[i].id.module_id,
+ m_pin[i].id.instance_id,
+ m_pin[i].in_use ? "Used" : "Unused",
+ m_pin[i].is_dynamic ? "Dynamic" : "Static",
+ m_pin[i].pin_state, i);
+ size += ret;
+ }
+ return ret;
+}
+
+static ssize_t skl_print_fmt(struct skl_module_fmt *fmt, char *buf,
+ ssize_t size, bool direction)
+{
+ return scnprintf(buf + size, MOD_BUF - size,
+ "%s\n\tCh %d\n\tFreq %d\n\tBit depth %d\n\t"
+ "Valid bit depth %d\n\tCh config %#x\n\tInterleaving %d\n\t"
+ "Sample Type %d\n\tCh Map %#x\n",
+ direction ? "Input Format:" : "Output Format:",
+ fmt->channels, fmt->s_freq, fmt->bit_depth,
+ fmt->valid_bit_depth, fmt->ch_cfg,
+ fmt->interleaving_style, fmt->sample_type,
+ fmt->ch_map);
+}
+
+static ssize_t module_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct skl_module_cfg *mconfig = file->private_data;
+ struct skl_module *module = mconfig->module;
+ struct skl_module_res *res = &module->resources[mconfig->res_idx];
+ char *buf;
+ ssize_t ret;
+
+ buf = kzalloc(MOD_BUF, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = scnprintf(buf, MOD_BUF, "Module:\n\tUUID %pUL\n\tModule id %d\n"
+ "\tInstance id %d\n\tPvt_id %d\n", mconfig->guid,
+ mconfig->id.module_id, mconfig->id.instance_id,
+ mconfig->id.pvt_id);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Resources:\n\tCPC %#x\n\tIBS %#x\n\tOBS %#x\t\n",
+ res->cpc, res->ibs, res->obs);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Module data:\n\tCore %d\n\tIn queue %d\n\t"
+ "Out queue %d\n\tType %s\n",
+ mconfig->core_id, mconfig->max_in_queue,
+ mconfig->max_out_queue,
+ mconfig->is_loadable ? "loadable" : "inbuilt");
+
+ ret += skl_print_fmt(mconfig->in_fmt, buf, ret, true);
+ ret += skl_print_fmt(mconfig->out_fmt, buf, ret, false);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Fixup:\n\tParams %#x\n\tConverter %#x\n",
+ mconfig->params_fixup, mconfig->converter);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Module Gateway:\n\tType %#x\n\tVbus %#x\n\tHW conn %#x\n\tSlot %#x\n",
+ mconfig->dev_type, mconfig->vbus_id,
+ mconfig->hw_conn_type, mconfig->time_slot);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Pipeline:\n\tID %d\n\tPriority %d\n\tConn Type %d\n\t"
+ "Pages %#x\n", mconfig->pipe->ppl_id,
+ mconfig->pipe->pipe_priority, mconfig->pipe->conn_type,
+ mconfig->pipe->memory_pages);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "\tParams:\n\t\tHost DMA %d\n\t\tLink DMA %d\n",
+ mconfig->pipe->p_params->host_dma_id,
+ mconfig->pipe->p_params->link_dma_id);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "\tPCM params:\n\t\tCh %d\n\t\tFreq %d\n\t\tFormat %d\n",
+ mconfig->pipe->p_params->ch,
+ mconfig->pipe->p_params->s_freq,
+ mconfig->pipe->p_params->s_fmt);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "\tLink %#x\n\tStream %#x\n",
+ mconfig->pipe->p_params->linktype,
+ mconfig->pipe->p_params->stream);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "\tState %d\n\tPassthru %s\n",
+ mconfig->pipe->state,
+ mconfig->pipe->passthru ? "true" : "false");
+
+ ret += skl_print_pins(mconfig->m_in_pin, buf,
+ mconfig->max_in_queue, ret, true);
+ ret += skl_print_pins(mconfig->m_out_pin, buf,
+ mconfig->max_out_queue, ret, false);
+
+ ret += scnprintf(buf + ret, MOD_BUF - ret,
+ "Other:\n\tDomain %d\n\tHomogeneous Input %s\n\t"
+ "Homogeneous Output %s\n\tIn Queue Mask %d\n\t"
+ "Out Queue Mask %d\n\tDMA ID %d\n\tMem Pages %d\n\t"
+ "Module Type %d\n\tModule State %d\n",
+ mconfig->domain,
+ mconfig->homogenous_inputs ? "true" : "false",
+ mconfig->homogenous_outputs ? "true" : "false",
+ mconfig->in_queue_mask, mconfig->out_queue_mask,
+ mconfig->dma_id, mconfig->mem_pages, mconfig->m_state,
+ mconfig->m_type);
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations mcfg_fops = {
+ .open = simple_open,
+ .read = module_read,
+ .llseek = default_llseek,
+};
+
+
+void skl_debug_init_module(struct skl_debug *d,
+ struct snd_soc_dapm_widget *w,
+ struct skl_module_cfg *mconfig)
+{
+ debugfs_create_file(w->name, 0444, d->modules, mconfig,
+ &mcfg_fops);
+}
+
+static ssize_t fw_softreg_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct skl_debug *d = file->private_data;
+ struct sst_dsp *sst = d->skl->dsp;
+ size_t w0_stat_sz = sst->addr.w0_stat_sz;
+ void __iomem *in_base = sst->mailbox.in_base;
+ void __iomem *fw_reg_addr;
+ unsigned int offset;
+ char *tmp;
+ ssize_t ret = 0;
+
+ tmp = kzalloc(FW_REG_BUF, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ fw_reg_addr = in_base - w0_stat_sz;
+ memset(d->fw_read_buff, 0, FW_REG_BUF);
+
+ if (w0_stat_sz > 0)
+ __ioread32_copy(d->fw_read_buff, fw_reg_addr, w0_stat_sz >> 2);
+
+ for (offset = 0; offset < FW_REG_SIZE; offset += 16) {
+ ret += scnprintf(tmp + ret, FW_REG_BUF - ret, "%#.4x: ", offset);
+ hex_dump_to_buffer(d->fw_read_buff + offset, 16, 16, 4,
+ tmp + ret, FW_REG_BUF - ret, 0);
+ ret += strlen(tmp + ret);
+
+ /* print newline for each offset */
+ if (FW_REG_BUF - ret > 0)
+ tmp[ret++] = '\n';
+ }
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, tmp, ret);
+ kfree(tmp);
+
+ return ret;
+}
+
+static const struct file_operations soft_regs_ctrl_fops = {
+ .open = simple_open,
+ .read = fw_softreg_read,
+ .llseek = default_llseek,
+};
+
+struct skl_debug *skl_debugfs_init(struct skl_dev *skl)
+{
+ struct skl_debug *d;
+
+ d = devm_kzalloc(&skl->pci->dev, sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return NULL;
+
+ /* create the debugfs dir with platform component's debugfs as parent */
+ d->fs = debugfs_create_dir("dsp", skl->component->debugfs_root);
+
+ d->skl = skl;
+ d->dev = &skl->pci->dev;
+
+ /* now create the module dir */
+ d->modules = debugfs_create_dir("modules", d->fs);
+
+ debugfs_create_file("fw_soft_regs_rd", 0444, d->fs, d,
+ &soft_regs_ctrl_fops);
+
+ return d;
+}
+
+void skl_debugfs_exit(struct skl_dev *skl)
+{
+ struct skl_debug *d = skl->debugfs;
+
+ debugfs_remove_recursive(d->fs);
+
+ d = NULL;
+}
diff --git a/sound/soc/intel/skylake/skl-i2s.h b/sound/soc/intel/skylake/skl-i2s.h
new file mode 100644
index 000000000..dfce91e11
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-i2s.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * skl-i2s.h - i2s blob mapping
+ *
+ * Copyright (C) 2017 Intel Corp
+ * Author: Subhransu S. Prusty < subhransu.s.prusty@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SOUND_SOC_SKL_I2S_H
+#define __SOUND_SOC_SKL_I2S_H
+
+#define SKL_I2S_MAX_TIME_SLOTS 8
+#define SKL_MCLK_DIV_CLK_SRC_MASK GENMASK(17, 16)
+
+#define SKL_MNDSS_DIV_CLK_SRC_MASK GENMASK(21, 20)
+#define SKL_SHIFT(x) (ffs(x) - 1)
+#define SKL_MCLK_DIV_RATIO_MASK GENMASK(11, 0)
+
+#define is_legacy_blob(x) (x.signature != 0xEE)
+#define ext_to_legacy_blob(i2s_config_blob_ext) \
+ ((struct skl_i2s_config_blob_legacy *) i2s_config_blob_ext)
+
+#define get_clk_src(mclk, mask) \
+ ((mclk.mdivctrl & mask) >> SKL_SHIFT(mask))
+struct skl_i2s_config {
+ u32 ssc0;
+ u32 ssc1;
+ u32 sscto;
+ u32 sspsp;
+ u32 sstsa;
+ u32 ssrsa;
+ u32 ssc2;
+ u32 sspsp2;
+ u32 ssc3;
+ u32 ssioc;
+} __packed;
+
+struct skl_i2s_config_mclk {
+ u32 mdivctrl;
+ u32 mdivr;
+};
+
+struct skl_i2s_config_mclk_ext {
+ u32 mdivctrl;
+ u32 mdivr_count;
+ u32 mdivr[];
+} __packed;
+
+struct skl_i2s_config_blob_signature {
+ u32 minor_ver : 8;
+ u32 major_ver : 8;
+ u32 resvdz : 8;
+ u32 signature : 8;
+} __packed;
+
+struct skl_i2s_config_blob_header {
+ struct skl_i2s_config_blob_signature sig;
+ u32 size;
+};
+
+/**
+ * struct skl_i2s_config_blob_legacy - Structure defines I2S Gateway
+ * configuration legacy blob
+ *
+ * @gtw_attr: Gateway attribute for the I2S Gateway
+ * @tdm_ts_group: TDM slot mapping against channels in the Gateway.
+ * @i2s_cfg: I2S HW registers
+ * @mclk: MCLK clock source and divider values
+ */
+struct skl_i2s_config_blob_legacy {
+ u32 gtw_attr;
+ u32 tdm_ts_group[SKL_I2S_MAX_TIME_SLOTS];
+ struct skl_i2s_config i2s_cfg;
+ struct skl_i2s_config_mclk mclk;
+};
+
+struct skl_i2s_config_blob_ext {
+ u32 gtw_attr;
+ struct skl_i2s_config_blob_header hdr;
+ u32 tdm_ts_group[SKL_I2S_MAX_TIME_SLOTS];
+ struct skl_i2s_config i2s_cfg;
+ struct skl_i2s_config_mclk_ext mclk;
+} __packed;
+#endif /* __SOUND_SOC_SKL_I2S_H */
diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c
new file mode 100644
index 000000000..79c6cf2c1
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-messages.c
@@ -0,0 +1,1387 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-message.c - HDA DSP interface for FW registration, Pipe and Module
+ * configurations
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author:Rafal Redzimski <rafal.f.redzimski@intel.com>
+ * Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <uapi/sound/skl-tplg-interface.h>
+#include "skl-sst-dsp.h"
+#include "cnl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+#include "skl.h"
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "skl-topology.h"
+
+static int skl_alloc_dma_buf(struct device *dev,
+ struct snd_dma_buffer *dmab, size_t size)
+{
+ return snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, dmab);
+}
+
+static int skl_free_dma_buf(struct device *dev, struct snd_dma_buffer *dmab)
+{
+ snd_dma_free_pages(dmab);
+ return 0;
+}
+
+#define SKL_ASTATE_PARAM_ID 4
+
+void skl_dsp_set_astate_cfg(struct skl_dev *skl, u32 cnt, void *data)
+{
+ struct skl_ipc_large_config_msg msg = {0};
+
+ msg.large_param_id = SKL_ASTATE_PARAM_ID;
+ msg.param_data_size = (cnt * sizeof(struct skl_astate_param) +
+ sizeof(cnt));
+
+ skl_ipc_set_large_config(&skl->ipc, &msg, data);
+}
+
+static int skl_dsp_setup_spib(struct device *dev, unsigned int size,
+ int stream_tag, int enable)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct hdac_stream *stream = snd_hdac_get_stream(bus,
+ SNDRV_PCM_STREAM_PLAYBACK, stream_tag);
+ struct hdac_ext_stream *estream;
+
+ if (!stream)
+ return -EINVAL;
+
+ estream = stream_to_hdac_ext_stream(stream);
+ /* enable/disable SPIB for this hdac stream */
+ snd_hdac_ext_stream_spbcap_enable(bus, enable, stream->index);
+
+ /* set the spib value */
+ snd_hdac_ext_stream_set_spib(bus, estream, size);
+
+ return 0;
+}
+
+static int skl_dsp_prepare(struct device *dev, unsigned int format,
+ unsigned int size, struct snd_dma_buffer *dmab)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct hdac_ext_stream *estream;
+ struct hdac_stream *stream;
+ struct snd_pcm_substream substream;
+ int ret;
+
+ if (!bus)
+ return -ENODEV;
+
+ memset(&substream, 0, sizeof(substream));
+ substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+
+ estream = snd_hdac_ext_stream_assign(bus, &substream,
+ HDAC_EXT_STREAM_TYPE_HOST);
+ if (!estream)
+ return -ENODEV;
+
+ stream = hdac_stream(estream);
+
+ /* assign decouple host dma channel */
+ ret = snd_hdac_dsp_prepare(stream, format, size, dmab);
+ if (ret < 0)
+ return ret;
+
+ skl_dsp_setup_spib(dev, size, stream->stream_tag, true);
+
+ return stream->stream_tag;
+}
+
+static int skl_dsp_trigger(struct device *dev, bool start, int stream_tag)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct hdac_stream *stream;
+
+ if (!bus)
+ return -ENODEV;
+
+ stream = snd_hdac_get_stream(bus,
+ SNDRV_PCM_STREAM_PLAYBACK, stream_tag);
+ if (!stream)
+ return -EINVAL;
+
+ snd_hdac_dsp_trigger(stream, start);
+
+ return 0;
+}
+
+static int skl_dsp_cleanup(struct device *dev,
+ struct snd_dma_buffer *dmab, int stream_tag)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct hdac_stream *stream;
+ struct hdac_ext_stream *estream;
+
+ if (!bus)
+ return -ENODEV;
+
+ stream = snd_hdac_get_stream(bus,
+ SNDRV_PCM_STREAM_PLAYBACK, stream_tag);
+ if (!stream)
+ return -EINVAL;
+
+ estream = stream_to_hdac_ext_stream(stream);
+ skl_dsp_setup_spib(dev, 0, stream_tag, false);
+ snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST);
+
+ snd_hdac_dsp_cleanup(stream, dmab);
+
+ return 0;
+}
+
+static struct skl_dsp_loader_ops skl_get_loader_ops(void)
+{
+ struct skl_dsp_loader_ops loader_ops;
+
+ memset(&loader_ops, 0, sizeof(struct skl_dsp_loader_ops));
+
+ loader_ops.alloc_dma_buf = skl_alloc_dma_buf;
+ loader_ops.free_dma_buf = skl_free_dma_buf;
+
+ return loader_ops;
+};
+
+static struct skl_dsp_loader_ops bxt_get_loader_ops(void)
+{
+ struct skl_dsp_loader_ops loader_ops;
+
+ memset(&loader_ops, 0, sizeof(loader_ops));
+
+ loader_ops.alloc_dma_buf = skl_alloc_dma_buf;
+ loader_ops.free_dma_buf = skl_free_dma_buf;
+ loader_ops.prepare = skl_dsp_prepare;
+ loader_ops.trigger = skl_dsp_trigger;
+ loader_ops.cleanup = skl_dsp_cleanup;
+
+ return loader_ops;
+};
+
+static const struct skl_dsp_ops dsp_ops[] = {
+ {
+ .id = 0x9d70,
+ .num_cores = 2,
+ .loader_ops = skl_get_loader_ops,
+ .init = skl_sst_dsp_init,
+ .init_fw = skl_sst_init_fw,
+ .cleanup = skl_sst_dsp_cleanup
+ },
+ {
+ .id = 0x9d71,
+ .num_cores = 2,
+ .loader_ops = skl_get_loader_ops,
+ .init = skl_sst_dsp_init,
+ .init_fw = skl_sst_init_fw,
+ .cleanup = skl_sst_dsp_cleanup
+ },
+ {
+ .id = 0x5a98,
+ .num_cores = 2,
+ .loader_ops = bxt_get_loader_ops,
+ .init = bxt_sst_dsp_init,
+ .init_fw = bxt_sst_init_fw,
+ .cleanup = bxt_sst_dsp_cleanup
+ },
+ {
+ .id = 0x3198,
+ .num_cores = 2,
+ .loader_ops = bxt_get_loader_ops,
+ .init = bxt_sst_dsp_init,
+ .init_fw = bxt_sst_init_fw,
+ .cleanup = bxt_sst_dsp_cleanup
+ },
+ {
+ .id = 0x9dc8,
+ .num_cores = 4,
+ .loader_ops = bxt_get_loader_ops,
+ .init = cnl_sst_dsp_init,
+ .init_fw = cnl_sst_init_fw,
+ .cleanup = cnl_sst_dsp_cleanup
+ },
+ {
+ .id = 0xa348,
+ .num_cores = 4,
+ .loader_ops = bxt_get_loader_ops,
+ .init = cnl_sst_dsp_init,
+ .init_fw = cnl_sst_init_fw,
+ .cleanup = cnl_sst_dsp_cleanup
+ },
+ {
+ .id = 0x02c8,
+ .num_cores = 4,
+ .loader_ops = bxt_get_loader_ops,
+ .init = cnl_sst_dsp_init,
+ .init_fw = cnl_sst_init_fw,
+ .cleanup = cnl_sst_dsp_cleanup
+ },
+ {
+ .id = 0x06c8,
+ .num_cores = 4,
+ .loader_ops = bxt_get_loader_ops,
+ .init = cnl_sst_dsp_init,
+ .init_fw = cnl_sst_init_fw,
+ .cleanup = cnl_sst_dsp_cleanup
+ },
+};
+
+const struct skl_dsp_ops *skl_get_dsp_ops(int pci_id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dsp_ops); i++) {
+ if (dsp_ops[i].id == pci_id)
+ return &dsp_ops[i];
+ }
+
+ return NULL;
+}
+
+int skl_init_dsp(struct skl_dev *skl)
+{
+ void __iomem *mmio_base;
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct skl_dsp_loader_ops loader_ops;
+ int irq = bus->irq;
+ const struct skl_dsp_ops *ops;
+ struct skl_dsp_cores *cores;
+ int ret;
+
+ /* enable ppcap interrupt */
+ snd_hdac_ext_bus_ppcap_enable(bus, true);
+ snd_hdac_ext_bus_ppcap_int_enable(bus, true);
+
+ /* read the BAR of the ADSP MMIO */
+ mmio_base = pci_ioremap_bar(skl->pci, 4);
+ if (mmio_base == NULL) {
+ dev_err(bus->dev, "ioremap error\n");
+ return -ENXIO;
+ }
+
+ ops = skl_get_dsp_ops(skl->pci->device);
+ if (!ops) {
+ ret = -EIO;
+ goto unmap_mmio;
+ }
+
+ loader_ops = ops->loader_ops();
+ ret = ops->init(bus->dev, mmio_base, irq,
+ skl->fw_name, loader_ops,
+ &skl);
+
+ if (ret < 0)
+ goto unmap_mmio;
+
+ skl->dsp_ops = ops;
+ cores = &skl->cores;
+ cores->count = ops->num_cores;
+
+ cores->state = kcalloc(cores->count, sizeof(*cores->state), GFP_KERNEL);
+ if (!cores->state) {
+ ret = -ENOMEM;
+ goto unmap_mmio;
+ }
+
+ cores->usage_count = kcalloc(cores->count, sizeof(*cores->usage_count),
+ GFP_KERNEL);
+ if (!cores->usage_count) {
+ ret = -ENOMEM;
+ goto free_core_state;
+ }
+
+ dev_dbg(bus->dev, "dsp registration status=%d\n", ret);
+
+ return 0;
+
+free_core_state:
+ kfree(cores->state);
+
+unmap_mmio:
+ iounmap(mmio_base);
+
+ return ret;
+}
+
+int skl_free_dsp(struct skl_dev *skl)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+
+ /* disable ppcap interrupt */
+ snd_hdac_ext_bus_ppcap_int_enable(bus, false);
+
+ skl->dsp_ops->cleanup(bus->dev, skl);
+
+ kfree(skl->cores.state);
+ kfree(skl->cores.usage_count);
+
+ if (skl->dsp->addr.lpe)
+ iounmap(skl->dsp->addr.lpe);
+
+ return 0;
+}
+
+/*
+ * In the case of "suspend_active" i.e, the Audio IP being active
+ * during system suspend, immediately excecute any pending D0i3 work
+ * before suspending. This is needed for the IP to work in low power
+ * mode during system suspend. In the case of normal suspend, cancel
+ * any pending D0i3 work.
+ */
+int skl_suspend_late_dsp(struct skl_dev *skl)
+{
+ struct delayed_work *dwork;
+
+ if (!skl)
+ return 0;
+
+ dwork = &skl->d0i3.work;
+
+ if (dwork->work.func) {
+ if (skl->supend_active)
+ flush_delayed_work(dwork);
+ else
+ cancel_delayed_work_sync(dwork);
+ }
+
+ return 0;
+}
+
+int skl_suspend_dsp(struct skl_dev *skl)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+ int ret;
+
+ /* if ppcap is not supported return 0 */
+ if (!bus->ppcap)
+ return 0;
+
+ ret = skl_dsp_sleep(skl->dsp);
+ if (ret < 0)
+ return ret;
+
+ /* disable ppcap interrupt */
+ snd_hdac_ext_bus_ppcap_int_enable(bus, false);
+ snd_hdac_ext_bus_ppcap_enable(bus, false);
+
+ return 0;
+}
+
+int skl_resume_dsp(struct skl_dev *skl)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+ int ret;
+
+ /* if ppcap is not supported return 0 */
+ if (!bus->ppcap)
+ return 0;
+
+ /* enable ppcap interrupt */
+ snd_hdac_ext_bus_ppcap_enable(bus, true);
+ snd_hdac_ext_bus_ppcap_int_enable(bus, true);
+
+ /* check if DSP 1st boot is done */
+ if (skl->is_first_boot)
+ return 0;
+
+ /*
+ * Disable dynamic clock and power gating during firmware
+ * and library download
+ */
+ skl->enable_miscbdcge(skl->dev, false);
+ skl->clock_power_gating(skl->dev, false);
+
+ ret = skl_dsp_wake(skl->dsp);
+ skl->enable_miscbdcge(skl->dev, true);
+ skl->clock_power_gating(skl->dev, true);
+ if (ret < 0)
+ return ret;
+
+ if (skl->cfg.astate_cfg != NULL) {
+ skl_dsp_set_astate_cfg(skl, skl->cfg.astate_cfg->count,
+ skl->cfg.astate_cfg);
+ }
+ return ret;
+}
+
+enum skl_bitdepth skl_get_bit_depth(int params)
+{
+ switch (params) {
+ case 8:
+ return SKL_DEPTH_8BIT;
+
+ case 16:
+ return SKL_DEPTH_16BIT;
+
+ case 24:
+ return SKL_DEPTH_24BIT;
+
+ case 32:
+ return SKL_DEPTH_32BIT;
+
+ default:
+ return SKL_DEPTH_INVALID;
+
+ }
+}
+
+/*
+ * Each module in DSP expects a base module configuration, which consists of
+ * PCM format information, which we calculate in driver and resource values
+ * which are read from widget information passed through topology binary
+ * This is send when we create a module with INIT_INSTANCE IPC msg
+ */
+static void skl_set_base_module_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_base_cfg *base_cfg)
+{
+ struct skl_module *module = mconfig->module;
+ struct skl_module_res *res = &module->resources[mconfig->res_idx];
+ struct skl_module_iface *fmt = &module->formats[mconfig->fmt_idx];
+ struct skl_module_fmt *format = &fmt->inputs[0].fmt;
+
+ base_cfg->audio_fmt.number_of_channels = format->channels;
+
+ base_cfg->audio_fmt.s_freq = format->s_freq;
+ base_cfg->audio_fmt.bit_depth = format->bit_depth;
+ base_cfg->audio_fmt.valid_bit_depth = format->valid_bit_depth;
+ base_cfg->audio_fmt.ch_cfg = format->ch_cfg;
+ base_cfg->audio_fmt.sample_type = format->sample_type;
+
+ dev_dbg(skl->dev, "bit_depth=%x valid_bd=%x ch_config=%x\n",
+ format->bit_depth, format->valid_bit_depth,
+ format->ch_cfg);
+
+ base_cfg->audio_fmt.channel_map = format->ch_map;
+
+ base_cfg->audio_fmt.interleaving = format->interleaving_style;
+
+ base_cfg->cpc = res->cpc;
+ base_cfg->ibs = res->ibs;
+ base_cfg->obs = res->obs;
+ base_cfg->is_pages = res->is_pages;
+}
+
+/*
+ * Copies copier capabilities into copier module and updates copier module
+ * config size.
+ */
+static void skl_copy_copier_caps(struct skl_module_cfg *mconfig,
+ struct skl_cpr_cfg *cpr_mconfig)
+{
+ if (mconfig->formats_config.caps_size == 0)
+ return;
+
+ memcpy(cpr_mconfig->gtw_cfg.config_data,
+ mconfig->formats_config.caps,
+ mconfig->formats_config.caps_size);
+
+ cpr_mconfig->gtw_cfg.config_length =
+ (mconfig->formats_config.caps_size) / 4;
+}
+
+#define SKL_NON_GATEWAY_CPR_NODE_ID 0xFFFFFFFF
+/*
+ * Calculate the gatewat settings required for copier module, type of
+ * gateway and index of gateway to use
+ */
+static u32 skl_get_node_id(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig)
+{
+ union skl_connector_node_id node_id = {0};
+ union skl_ssp_dma_node ssp_node = {0};
+ struct skl_pipe_params *params = mconfig->pipe->p_params;
+
+ switch (mconfig->dev_type) {
+ case SKL_DEVICE_BT:
+ node_id.node.dma_type =
+ (SKL_CONN_SOURCE == mconfig->hw_conn_type) ?
+ SKL_DMA_I2S_LINK_OUTPUT_CLASS :
+ SKL_DMA_I2S_LINK_INPUT_CLASS;
+ node_id.node.vindex = params->host_dma_id +
+ (mconfig->vbus_id << 3);
+ break;
+
+ case SKL_DEVICE_I2S:
+ node_id.node.dma_type =
+ (SKL_CONN_SOURCE == mconfig->hw_conn_type) ?
+ SKL_DMA_I2S_LINK_OUTPUT_CLASS :
+ SKL_DMA_I2S_LINK_INPUT_CLASS;
+ ssp_node.dma_node.time_slot_index = mconfig->time_slot;
+ ssp_node.dma_node.i2s_instance = mconfig->vbus_id;
+ node_id.node.vindex = ssp_node.val;
+ break;
+
+ case SKL_DEVICE_DMIC:
+ node_id.node.dma_type = SKL_DMA_DMIC_LINK_INPUT_CLASS;
+ node_id.node.vindex = mconfig->vbus_id +
+ (mconfig->time_slot);
+ break;
+
+ case SKL_DEVICE_HDALINK:
+ node_id.node.dma_type =
+ (SKL_CONN_SOURCE == mconfig->hw_conn_type) ?
+ SKL_DMA_HDA_LINK_OUTPUT_CLASS :
+ SKL_DMA_HDA_LINK_INPUT_CLASS;
+ node_id.node.vindex = params->link_dma_id;
+ break;
+
+ case SKL_DEVICE_HDAHOST:
+ node_id.node.dma_type =
+ (SKL_CONN_SOURCE == mconfig->hw_conn_type) ?
+ SKL_DMA_HDA_HOST_OUTPUT_CLASS :
+ SKL_DMA_HDA_HOST_INPUT_CLASS;
+ node_id.node.vindex = params->host_dma_id;
+ break;
+
+ default:
+ node_id.val = 0xFFFFFFFF;
+ break;
+ }
+
+ return node_id.val;
+}
+
+static void skl_setup_cpr_gateway_cfg(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_cpr_cfg *cpr_mconfig)
+{
+ u32 dma_io_buf;
+ struct skl_module_res *res;
+ int res_idx = mconfig->res_idx;
+
+ cpr_mconfig->gtw_cfg.node_id = skl_get_node_id(skl, mconfig);
+
+ if (cpr_mconfig->gtw_cfg.node_id == SKL_NON_GATEWAY_CPR_NODE_ID) {
+ cpr_mconfig->cpr_feature_mask = 0;
+ return;
+ }
+
+ if (skl->nr_modules) {
+ res = &mconfig->module->resources[mconfig->res_idx];
+ cpr_mconfig->gtw_cfg.dma_buffer_size = res->dma_buffer_size;
+ goto skip_buf_size_calc;
+ } else {
+ res = &mconfig->module->resources[res_idx];
+ }
+
+ switch (mconfig->hw_conn_type) {
+ case SKL_CONN_SOURCE:
+ if (mconfig->dev_type == SKL_DEVICE_HDAHOST)
+ dma_io_buf = res->ibs;
+ else
+ dma_io_buf = res->obs;
+ break;
+
+ case SKL_CONN_SINK:
+ if (mconfig->dev_type == SKL_DEVICE_HDAHOST)
+ dma_io_buf = res->obs;
+ else
+ dma_io_buf = res->ibs;
+ break;
+
+ default:
+ dev_warn(skl->dev, "wrong connection type: %d\n",
+ mconfig->hw_conn_type);
+ return;
+ }
+
+ cpr_mconfig->gtw_cfg.dma_buffer_size =
+ mconfig->dma_buffer_size * dma_io_buf;
+
+ /* fallback to 2ms default value */
+ if (!cpr_mconfig->gtw_cfg.dma_buffer_size) {
+ if (mconfig->hw_conn_type == SKL_CONN_SOURCE)
+ cpr_mconfig->gtw_cfg.dma_buffer_size = 2 * res->obs;
+ else
+ cpr_mconfig->gtw_cfg.dma_buffer_size = 2 * res->ibs;
+ }
+
+skip_buf_size_calc:
+ cpr_mconfig->cpr_feature_mask = 0;
+ cpr_mconfig->gtw_cfg.config_length = 0;
+
+ skl_copy_copier_caps(mconfig, cpr_mconfig);
+}
+
+#define DMA_CONTROL_ID 5
+#define DMA_I2S_BLOB_SIZE 21
+
+int skl_dsp_set_dma_control(struct skl_dev *skl, u32 *caps,
+ u32 caps_size, u32 node_id)
+{
+ struct skl_dma_control *dma_ctrl;
+ struct skl_ipc_large_config_msg msg = {0};
+ int err = 0;
+
+
+ /*
+ * if blob size zero, then return
+ */
+ if (caps_size == 0)
+ return 0;
+
+ msg.large_param_id = DMA_CONTROL_ID;
+ msg.param_data_size = sizeof(struct skl_dma_control) + caps_size;
+
+ dma_ctrl = kzalloc(msg.param_data_size, GFP_KERNEL);
+ if (dma_ctrl == NULL)
+ return -ENOMEM;
+
+ dma_ctrl->node_id = node_id;
+
+ /*
+ * NHLT blob may contain additional configs along with i2s blob.
+ * firmware expects only the i2s blob size as the config_length.
+ * So fix to i2s blob size.
+ * size in dwords.
+ */
+ dma_ctrl->config_length = DMA_I2S_BLOB_SIZE;
+
+ memcpy(dma_ctrl->config_data, caps, caps_size);
+
+ err = skl_ipc_set_large_config(&skl->ipc, &msg, (u32 *)dma_ctrl);
+
+ kfree(dma_ctrl);
+ return err;
+}
+EXPORT_SYMBOL_GPL(skl_dsp_set_dma_control);
+
+static void skl_setup_out_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_audio_data_format *out_fmt)
+{
+ struct skl_module *module = mconfig->module;
+ struct skl_module_iface *fmt = &module->formats[mconfig->fmt_idx];
+ struct skl_module_fmt *format = &fmt->outputs[0].fmt;
+
+ out_fmt->number_of_channels = (u8)format->channels;
+ out_fmt->s_freq = format->s_freq;
+ out_fmt->bit_depth = format->bit_depth;
+ out_fmt->valid_bit_depth = format->valid_bit_depth;
+ out_fmt->ch_cfg = format->ch_cfg;
+
+ out_fmt->channel_map = format->ch_map;
+ out_fmt->interleaving = format->interleaving_style;
+ out_fmt->sample_type = format->sample_type;
+
+ dev_dbg(skl->dev, "copier out format chan=%d fre=%d bitdepth=%d\n",
+ out_fmt->number_of_channels, format->s_freq, format->bit_depth);
+}
+
+/*
+ * DSP needs SRC module for frequency conversion, SRC takes base module
+ * configuration and the target frequency as extra parameter passed as src
+ * config
+ */
+static void skl_set_src_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_src_module_cfg *src_mconfig)
+{
+ struct skl_module *module = mconfig->module;
+ struct skl_module_iface *iface = &module->formats[mconfig->fmt_idx];
+ struct skl_module_fmt *fmt = &iface->outputs[0].fmt;
+
+ skl_set_base_module_format(skl, mconfig,
+ (struct skl_base_cfg *)src_mconfig);
+
+ src_mconfig->src_cfg = fmt->s_freq;
+}
+
+/*
+ * DSP needs updown module to do channel conversion. updown module take base
+ * module configuration and channel configuration
+ * It also take coefficients and now we have defaults applied here
+ */
+static void skl_set_updown_mixer_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_up_down_mixer_cfg *mixer_mconfig)
+{
+ struct skl_module *module = mconfig->module;
+ struct skl_module_iface *iface = &module->formats[mconfig->fmt_idx];
+ struct skl_module_fmt *fmt = &iface->outputs[0].fmt;
+
+ skl_set_base_module_format(skl, mconfig,
+ (struct skl_base_cfg *)mixer_mconfig);
+ mixer_mconfig->out_ch_cfg = fmt->ch_cfg;
+ mixer_mconfig->ch_map = fmt->ch_map;
+}
+
+/*
+ * 'copier' is DSP internal module which copies data from Host DMA (HDA host
+ * dma) or link (hda link, SSP, PDM)
+ * Here we calculate the copier module parameters, like PCM format, output
+ * format, gateway settings
+ * copier_module_config is sent as input buffer with INIT_INSTANCE IPC msg
+ */
+static void skl_set_copier_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_cpr_cfg *cpr_mconfig)
+{
+ struct skl_audio_data_format *out_fmt = &cpr_mconfig->out_fmt;
+ struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)cpr_mconfig;
+
+ skl_set_base_module_format(skl, mconfig, base_cfg);
+
+ skl_setup_out_format(skl, mconfig, out_fmt);
+ skl_setup_cpr_gateway_cfg(skl, mconfig, cpr_mconfig);
+}
+
+/*
+ * Algo module are DSP pre processing modules. Algo module take base module
+ * configuration and params
+ */
+
+static void skl_set_algo_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_algo_cfg *algo_mcfg)
+{
+ struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)algo_mcfg;
+
+ skl_set_base_module_format(skl, mconfig, base_cfg);
+
+ if (mconfig->formats_config.caps_size == 0)
+ return;
+
+ memcpy(algo_mcfg->params,
+ mconfig->formats_config.caps,
+ mconfig->formats_config.caps_size);
+
+}
+
+/*
+ * Mic select module allows selecting one or many input channels, thus
+ * acting as a demux.
+ *
+ * Mic select module take base module configuration and out-format
+ * configuration
+ */
+static void skl_set_base_outfmt_format(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig,
+ struct skl_base_outfmt_cfg *base_outfmt_mcfg)
+{
+ struct skl_audio_data_format *out_fmt = &base_outfmt_mcfg->out_fmt;
+ struct skl_base_cfg *base_cfg =
+ (struct skl_base_cfg *)base_outfmt_mcfg;
+
+ skl_set_base_module_format(skl, mconfig, base_cfg);
+ skl_setup_out_format(skl, mconfig, out_fmt);
+}
+
+static u16 skl_get_module_param_size(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig)
+{
+ u16 param_size;
+
+ switch (mconfig->m_type) {
+ case SKL_MODULE_TYPE_COPIER:
+ param_size = sizeof(struct skl_cpr_cfg);
+ param_size += mconfig->formats_config.caps_size;
+ return param_size;
+
+ case SKL_MODULE_TYPE_SRCINT:
+ return sizeof(struct skl_src_module_cfg);
+
+ case SKL_MODULE_TYPE_UPDWMIX:
+ return sizeof(struct skl_up_down_mixer_cfg);
+
+ case SKL_MODULE_TYPE_ALGO:
+ param_size = sizeof(struct skl_base_cfg);
+ param_size += mconfig->formats_config.caps_size;
+ return param_size;
+
+ case SKL_MODULE_TYPE_BASE_OUTFMT:
+ case SKL_MODULE_TYPE_MIC_SELECT:
+ return sizeof(struct skl_base_outfmt_cfg);
+
+ case SKL_MODULE_TYPE_MIXER:
+ case SKL_MODULE_TYPE_KPB:
+ return sizeof(struct skl_base_cfg);
+
+ default:
+ /*
+ * return only base cfg when no specific module type is
+ * specified
+ */
+ return sizeof(struct skl_base_cfg);
+ }
+
+ return 0;
+}
+
+/*
+ * DSP firmware supports various modules like copier, SRC, updown etc.
+ * These modules required various parameters to be calculated and sent for
+ * the module initialization to DSP. By default a generic module needs only
+ * base module format configuration
+ */
+
+static int skl_set_module_format(struct skl_dev *skl,
+ struct skl_module_cfg *module_config,
+ u16 *module_config_size,
+ void **param_data)
+{
+ u16 param_size;
+
+ param_size = skl_get_module_param_size(skl, module_config);
+
+ *param_data = kzalloc(param_size, GFP_KERNEL);
+ if (NULL == *param_data)
+ return -ENOMEM;
+
+ *module_config_size = param_size;
+
+ switch (module_config->m_type) {
+ case SKL_MODULE_TYPE_COPIER:
+ skl_set_copier_format(skl, module_config, *param_data);
+ break;
+
+ case SKL_MODULE_TYPE_SRCINT:
+ skl_set_src_format(skl, module_config, *param_data);
+ break;
+
+ case SKL_MODULE_TYPE_UPDWMIX:
+ skl_set_updown_mixer_format(skl, module_config, *param_data);
+ break;
+
+ case SKL_MODULE_TYPE_ALGO:
+ skl_set_algo_format(skl, module_config, *param_data);
+ break;
+
+ case SKL_MODULE_TYPE_BASE_OUTFMT:
+ case SKL_MODULE_TYPE_MIC_SELECT:
+ skl_set_base_outfmt_format(skl, module_config, *param_data);
+ break;
+
+ case SKL_MODULE_TYPE_MIXER:
+ case SKL_MODULE_TYPE_KPB:
+ skl_set_base_module_format(skl, module_config, *param_data);
+ break;
+
+ default:
+ skl_set_base_module_format(skl, module_config, *param_data);
+ break;
+
+ }
+
+ dev_dbg(skl->dev, "Module type=%d id=%d config size: %d bytes\n",
+ module_config->m_type, module_config->id.module_id,
+ param_size);
+ print_hex_dump_debug("Module params:", DUMP_PREFIX_OFFSET, 8, 4,
+ *param_data, param_size, false);
+ return 0;
+}
+
+static int skl_get_queue_index(struct skl_module_pin *mpin,
+ struct skl_module_inst_id id, int max)
+{
+ int i;
+
+ for (i = 0; i < max; i++) {
+ if (mpin[i].id.module_id == id.module_id &&
+ mpin[i].id.instance_id == id.instance_id)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Allocates queue for each module.
+ * if dynamic, the pin_index is allocated 0 to max_pin.
+ * In static, the pin_index is fixed based on module_id and instance id
+ */
+static int skl_alloc_queue(struct skl_module_pin *mpin,
+ struct skl_module_cfg *tgt_cfg, int max)
+{
+ int i;
+ struct skl_module_inst_id id = tgt_cfg->id;
+ /*
+ * if pin in dynamic, find first free pin
+ * otherwise find match module and instance id pin as topology will
+ * ensure a unique pin is assigned to this so no need to
+ * allocate/free
+ */
+ for (i = 0; i < max; i++) {
+ if (mpin[i].is_dynamic) {
+ if (!mpin[i].in_use &&
+ mpin[i].pin_state == SKL_PIN_UNBIND) {
+
+ mpin[i].in_use = true;
+ mpin[i].id.module_id = id.module_id;
+ mpin[i].id.instance_id = id.instance_id;
+ mpin[i].id.pvt_id = id.pvt_id;
+ mpin[i].tgt_mcfg = tgt_cfg;
+ return i;
+ }
+ } else {
+ if (mpin[i].id.module_id == id.module_id &&
+ mpin[i].id.instance_id == id.instance_id &&
+ mpin[i].pin_state == SKL_PIN_UNBIND) {
+
+ mpin[i].tgt_mcfg = tgt_cfg;
+ return i;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void skl_free_queue(struct skl_module_pin *mpin, int q_index)
+{
+ if (mpin[q_index].is_dynamic) {
+ mpin[q_index].in_use = false;
+ mpin[q_index].id.module_id = 0;
+ mpin[q_index].id.instance_id = 0;
+ mpin[q_index].id.pvt_id = 0;
+ }
+ mpin[q_index].pin_state = SKL_PIN_UNBIND;
+ mpin[q_index].tgt_mcfg = NULL;
+}
+
+/* Module state will be set to unint, if all the out pin state is UNBIND */
+
+static void skl_clear_module_state(struct skl_module_pin *mpin, int max,
+ struct skl_module_cfg *mcfg)
+{
+ int i;
+ bool found = false;
+
+ for (i = 0; i < max; i++) {
+ if (mpin[i].pin_state == SKL_PIN_UNBIND)
+ continue;
+ found = true;
+ break;
+ }
+
+ if (!found)
+ mcfg->m_state = SKL_MODULE_INIT_DONE;
+ return;
+}
+
+/*
+ * A module needs to be instanataited in DSP. A mdoule is present in a
+ * collection of module referred as a PIPE.
+ * We first calculate the module format, based on module type and then
+ * invoke the DSP by sending IPC INIT_INSTANCE using ipc helper
+ */
+int skl_init_module(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig)
+{
+ u16 module_config_size = 0;
+ void *param_data = NULL;
+ int ret;
+ struct skl_ipc_init_instance_msg msg;
+
+ dev_dbg(skl->dev, "%s: module_id = %d instance=%d\n", __func__,
+ mconfig->id.module_id, mconfig->id.pvt_id);
+
+ if (mconfig->pipe->state != SKL_PIPE_CREATED) {
+ dev_err(skl->dev, "Pipe not created state= %d pipe_id= %d\n",
+ mconfig->pipe->state, mconfig->pipe->ppl_id);
+ return -EIO;
+ }
+
+ ret = skl_set_module_format(skl, mconfig,
+ &module_config_size, &param_data);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to set module format ret=%d\n", ret);
+ return ret;
+ }
+
+ msg.module_id = mconfig->id.module_id;
+ msg.instance_id = mconfig->id.pvt_id;
+ msg.ppl_instance_id = mconfig->pipe->ppl_id;
+ msg.param_data_size = module_config_size;
+ msg.core_id = mconfig->core_id;
+ msg.domain = mconfig->domain;
+
+ ret = skl_ipc_init_instance(&skl->ipc, &msg, param_data);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to init instance ret=%d\n", ret);
+ kfree(param_data);
+ return ret;
+ }
+ mconfig->m_state = SKL_MODULE_INIT_DONE;
+ kfree(param_data);
+ return ret;
+}
+
+static void skl_dump_bind_info(struct skl_dev *skl, struct skl_module_cfg
+ *src_module, struct skl_module_cfg *dst_module)
+{
+ dev_dbg(skl->dev, "%s: src module_id = %d src_instance=%d\n",
+ __func__, src_module->id.module_id, src_module->id.pvt_id);
+ dev_dbg(skl->dev, "%s: dst_module=%d dst_instance=%d\n", __func__,
+ dst_module->id.module_id, dst_module->id.pvt_id);
+
+ dev_dbg(skl->dev, "src_module state = %d dst module state = %d\n",
+ src_module->m_state, dst_module->m_state);
+}
+
+/*
+ * On module freeup, we need to unbind the module with modules
+ * it is already bind.
+ * Find the pin allocated and unbind then using bind_unbind IPC
+ */
+int skl_unbind_modules(struct skl_dev *skl,
+ struct skl_module_cfg *src_mcfg,
+ struct skl_module_cfg *dst_mcfg)
+{
+ int ret;
+ struct skl_ipc_bind_unbind_msg msg;
+ struct skl_module_inst_id src_id = src_mcfg->id;
+ struct skl_module_inst_id dst_id = dst_mcfg->id;
+ int in_max = dst_mcfg->module->max_input_pins;
+ int out_max = src_mcfg->module->max_output_pins;
+ int src_index, dst_index, src_pin_state, dst_pin_state;
+
+ skl_dump_bind_info(skl, src_mcfg, dst_mcfg);
+
+ /* get src queue index */
+ src_index = skl_get_queue_index(src_mcfg->m_out_pin, dst_id, out_max);
+ if (src_index < 0)
+ return 0;
+
+ msg.src_queue = src_index;
+
+ /* get dst queue index */
+ dst_index = skl_get_queue_index(dst_mcfg->m_in_pin, src_id, in_max);
+ if (dst_index < 0)
+ return 0;
+
+ msg.dst_queue = dst_index;
+
+ src_pin_state = src_mcfg->m_out_pin[src_index].pin_state;
+ dst_pin_state = dst_mcfg->m_in_pin[dst_index].pin_state;
+
+ if (src_pin_state != SKL_PIN_BIND_DONE ||
+ dst_pin_state != SKL_PIN_BIND_DONE)
+ return 0;
+
+ msg.module_id = src_mcfg->id.module_id;
+ msg.instance_id = src_mcfg->id.pvt_id;
+ msg.dst_module_id = dst_mcfg->id.module_id;
+ msg.dst_instance_id = dst_mcfg->id.pvt_id;
+ msg.bind = false;
+
+ ret = skl_ipc_bind_unbind(&skl->ipc, &msg);
+ if (!ret) {
+ /* free queue only if unbind is success */
+ skl_free_queue(src_mcfg->m_out_pin, src_index);
+ skl_free_queue(dst_mcfg->m_in_pin, dst_index);
+
+ /*
+ * check only if src module bind state, bind is
+ * always from src -> sink
+ */
+ skl_clear_module_state(src_mcfg->m_out_pin, out_max, src_mcfg);
+ }
+
+ return ret;
+}
+
+static void fill_pin_params(struct skl_audio_data_format *pin_fmt,
+ struct skl_module_fmt *format)
+{
+ pin_fmt->number_of_channels = format->channels;
+ pin_fmt->s_freq = format->s_freq;
+ pin_fmt->bit_depth = format->bit_depth;
+ pin_fmt->valid_bit_depth = format->valid_bit_depth;
+ pin_fmt->ch_cfg = format->ch_cfg;
+ pin_fmt->sample_type = format->sample_type;
+ pin_fmt->channel_map = format->ch_map;
+ pin_fmt->interleaving = format->interleaving_style;
+}
+
+#define CPR_SINK_FMT_PARAM_ID 2
+
+/*
+ * Once a module is instantiated it need to be 'bind' with other modules in
+ * the pipeline. For binding we need to find the module pins which are bind
+ * together
+ * This function finds the pins and then sends bund_unbind IPC message to
+ * DSP using IPC helper
+ */
+int skl_bind_modules(struct skl_dev *skl,
+ struct skl_module_cfg *src_mcfg,
+ struct skl_module_cfg *dst_mcfg)
+{
+ int ret = 0;
+ struct skl_ipc_bind_unbind_msg msg;
+ int in_max = dst_mcfg->module->max_input_pins;
+ int out_max = src_mcfg->module->max_output_pins;
+ int src_index, dst_index;
+ struct skl_module_fmt *format;
+ struct skl_cpr_pin_fmt pin_fmt;
+ struct skl_module *module;
+ struct skl_module_iface *fmt;
+
+ skl_dump_bind_info(skl, src_mcfg, dst_mcfg);
+
+ if (src_mcfg->m_state < SKL_MODULE_INIT_DONE ||
+ dst_mcfg->m_state < SKL_MODULE_INIT_DONE)
+ return 0;
+
+ src_index = skl_alloc_queue(src_mcfg->m_out_pin, dst_mcfg, out_max);
+ if (src_index < 0)
+ return -EINVAL;
+
+ msg.src_queue = src_index;
+ dst_index = skl_alloc_queue(dst_mcfg->m_in_pin, src_mcfg, in_max);
+ if (dst_index < 0) {
+ skl_free_queue(src_mcfg->m_out_pin, src_index);
+ return -EINVAL;
+ }
+
+ /*
+ * Copier module requires the separate large_config_set_ipc to
+ * configure the pins other than 0
+ */
+ if (src_mcfg->m_type == SKL_MODULE_TYPE_COPIER && src_index > 0) {
+ pin_fmt.sink_id = src_index;
+ module = src_mcfg->module;
+ fmt = &module->formats[src_mcfg->fmt_idx];
+
+ /* Input fmt is same as that of src module input cfg */
+ format = &fmt->inputs[0].fmt;
+ fill_pin_params(&(pin_fmt.src_fmt), format);
+
+ format = &fmt->outputs[src_index].fmt;
+ fill_pin_params(&(pin_fmt.dst_fmt), format);
+ ret = skl_set_module_params(skl, (void *)&pin_fmt,
+ sizeof(struct skl_cpr_pin_fmt),
+ CPR_SINK_FMT_PARAM_ID, src_mcfg);
+
+ if (ret < 0)
+ goto out;
+ }
+
+ msg.dst_queue = dst_index;
+
+ dev_dbg(skl->dev, "src queue = %d dst queue =%d\n",
+ msg.src_queue, msg.dst_queue);
+
+ msg.module_id = src_mcfg->id.module_id;
+ msg.instance_id = src_mcfg->id.pvt_id;
+ msg.dst_module_id = dst_mcfg->id.module_id;
+ msg.dst_instance_id = dst_mcfg->id.pvt_id;
+ msg.bind = true;
+
+ ret = skl_ipc_bind_unbind(&skl->ipc, &msg);
+
+ if (!ret) {
+ src_mcfg->m_state = SKL_MODULE_BIND_DONE;
+ src_mcfg->m_out_pin[src_index].pin_state = SKL_PIN_BIND_DONE;
+ dst_mcfg->m_in_pin[dst_index].pin_state = SKL_PIN_BIND_DONE;
+ return ret;
+ }
+out:
+ /* error case , if IPC fails, clear the queue index */
+ skl_free_queue(src_mcfg->m_out_pin, src_index);
+ skl_free_queue(dst_mcfg->m_in_pin, dst_index);
+
+ return ret;
+}
+
+static int skl_set_pipe_state(struct skl_dev *skl, struct skl_pipe *pipe,
+ enum skl_ipc_pipeline_state state)
+{
+ dev_dbg(skl->dev, "%s: pipe_state = %d\n", __func__, state);
+
+ return skl_ipc_set_pipeline_state(&skl->ipc, pipe->ppl_id, state);
+}
+
+/*
+ * A pipeline is a collection of modules. Before a module in instantiated a
+ * pipeline needs to be created for it.
+ * This function creates pipeline, by sending create pipeline IPC messages
+ * to FW
+ */
+int skl_create_pipeline(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ int ret;
+
+ dev_dbg(skl->dev, "%s: pipe_id = %d\n", __func__, pipe->ppl_id);
+
+ ret = skl_ipc_create_pipeline(&skl->ipc, pipe->memory_pages,
+ pipe->pipe_priority, pipe->ppl_id,
+ pipe->lp_mode);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to create pipeline\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_CREATED;
+
+ return 0;
+}
+
+/*
+ * A pipeline needs to be deleted on cleanup. If a pipeline is running,
+ * then pause it first. Before actual deletion, pipeline should enter
+ * reset state. Finish the procedure by sending delete pipeline IPC.
+ * DSP will stop the DMA engines and release resources
+ */
+int skl_delete_pipe(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ int ret;
+
+ dev_dbg(skl->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id);
+
+ /* If pipe was not created in FW, do not try to delete it */
+ if (pipe->state < SKL_PIPE_CREATED)
+ return 0;
+
+ /* If pipe is started, do stop the pipe in FW. */
+ if (pipe->state >= SKL_PIPE_STARTED) {
+ ret = skl_set_pipe_state(skl, pipe, PPL_PAUSED);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to stop pipeline\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_PAUSED;
+ }
+
+ /* reset pipe state before deletion */
+ ret = skl_set_pipe_state(skl, pipe, PPL_RESET);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to reset pipe ret=%d\n", ret);
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_RESET;
+
+ ret = skl_ipc_delete_pipeline(&skl->ipc, pipe->ppl_id);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to delete pipeline\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_INVALID;
+
+ return ret;
+}
+
+/*
+ * A pipeline is also a scheduling entity in DSP which can be run, stopped
+ * For processing data the pipe need to be run by sending IPC set pipe state
+ * to DSP
+ */
+int skl_run_pipe(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ int ret;
+
+ dev_dbg(skl->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id);
+
+ /* If pipe was not created in FW, do not try to pause or delete */
+ if (pipe->state < SKL_PIPE_CREATED)
+ return 0;
+
+ /* Pipe has to be paused before it is started */
+ ret = skl_set_pipe_state(skl, pipe, PPL_PAUSED);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to pause pipe\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_PAUSED;
+
+ ret = skl_set_pipe_state(skl, pipe, PPL_RUNNING);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to start pipe\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_STARTED;
+
+ return 0;
+}
+
+/*
+ * Stop the pipeline by sending set pipe state IPC
+ * DSP doesnt implement stop so we always send pause message
+ */
+int skl_stop_pipe(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ int ret;
+
+ dev_dbg(skl->dev, "In %s pipe=%d\n", __func__, pipe->ppl_id);
+
+ /* If pipe was not created in FW, do not try to pause or delete */
+ if (pipe->state < SKL_PIPE_PAUSED)
+ return 0;
+
+ ret = skl_set_pipe_state(skl, pipe, PPL_PAUSED);
+ if (ret < 0) {
+ dev_dbg(skl->dev, "Failed to stop pipe\n");
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_PAUSED;
+
+ return 0;
+}
+
+/*
+ * Reset the pipeline by sending set pipe state IPC this will reset the DMA
+ * from the DSP side
+ */
+int skl_reset_pipe(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ int ret;
+
+ /* If pipe was not created in FW, do not try to pause or delete */
+ if (pipe->state < SKL_PIPE_PAUSED)
+ return 0;
+
+ ret = skl_set_pipe_state(skl, pipe, PPL_RESET);
+ if (ret < 0) {
+ dev_dbg(skl->dev, "Failed to reset pipe ret=%d\n", ret);
+ return ret;
+ }
+
+ pipe->state = SKL_PIPE_RESET;
+
+ return 0;
+}
+
+/* Algo parameter set helper function */
+int skl_set_module_params(struct skl_dev *skl, u32 *params, int size,
+ u32 param_id, struct skl_module_cfg *mcfg)
+{
+ struct skl_ipc_large_config_msg msg;
+
+ msg.module_id = mcfg->id.module_id;
+ msg.instance_id = mcfg->id.pvt_id;
+ msg.param_data_size = size;
+ msg.large_param_id = param_id;
+
+ return skl_ipc_set_large_config(&skl->ipc, &msg, params);
+}
+
+int skl_get_module_params(struct skl_dev *skl, u32 *params, int size,
+ u32 param_id, struct skl_module_cfg *mcfg)
+{
+ struct skl_ipc_large_config_msg msg;
+ size_t bytes = size;
+
+ msg.module_id = mcfg->id.module_id;
+ msg.instance_id = mcfg->id.pvt_id;
+ msg.param_data_size = size;
+ msg.large_param_id = param_id;
+
+ return skl_ipc_get_large_config(&skl->ipc, &msg, &params, &bytes);
+}
diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c
new file mode 100644
index 000000000..3b3868df9
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-nhlt.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-nhlt.c - Intel SKL Platform NHLT parsing
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author: Sanjiv Kumar <sanjiv.kumar@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/pci.h>
+#include <sound/intel-nhlt.h>
+#include "skl.h"
+#include "skl-i2s.h"
+
+static struct nhlt_specific_cfg *skl_get_specific_cfg(
+ struct device *dev, struct nhlt_fmt *fmt,
+ u8 no_ch, u32 rate, u16 bps, u8 linktype)
+{
+ struct nhlt_specific_cfg *sp_config;
+ struct wav_fmt *wfmt;
+ struct nhlt_fmt_cfg *fmt_config = fmt->fmt_config;
+ int i;
+
+ dev_dbg(dev, "Format count =%d\n", fmt->fmt_count);
+
+ for (i = 0; i < fmt->fmt_count; i++) {
+ wfmt = &fmt_config->fmt_ext.fmt;
+ dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", wfmt->channels,
+ wfmt->bits_per_sample, wfmt->samples_per_sec);
+ if (wfmt->channels == no_ch && wfmt->bits_per_sample == bps) {
+ /*
+ * if link type is dmic ignore rate check as the blob is
+ * generic for all rates
+ */
+ sp_config = &fmt_config->config;
+ if (linktype == NHLT_LINK_DMIC)
+ return sp_config;
+
+ if (wfmt->samples_per_sec == rate)
+ return sp_config;
+ }
+
+ fmt_config = (struct nhlt_fmt_cfg *)(fmt_config->config.caps +
+ fmt_config->config.size);
+ }
+
+ return NULL;
+}
+
+static void dump_config(struct device *dev, u32 instance_id, u8 linktype,
+ u8 s_fmt, u8 num_channels, u32 s_rate, u8 dirn, u16 bps)
+{
+ dev_dbg(dev, "Input configuration\n");
+ dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", num_channels, s_fmt, s_rate);
+ dev_dbg(dev, "vbus_id=%d link_type=%d\n", instance_id, linktype);
+ dev_dbg(dev, "bits_per_sample=%d\n", bps);
+}
+
+static bool skl_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt,
+ u32 instance_id, u8 link_type, u8 dirn, u8 dev_type)
+{
+ dev_dbg(dev, "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 == instance_id) &&
+ (epnt->linktype == link_type) &&
+ (epnt->direction == dirn)) {
+ /* do not check dev_type for DMIC link type */
+ if (epnt->linktype == NHLT_LINK_DMIC)
+ return true;
+
+ if (epnt->device_type == dev_type)
+ return true;
+ }
+
+ return false;
+}
+
+struct nhlt_specific_cfg
+*skl_get_ep_blob(struct skl_dev *skl, u32 instance, u8 link_type,
+ u8 s_fmt, u8 num_ch, u32 s_rate,
+ u8 dirn, u8 dev_type)
+{
+ struct nhlt_fmt *fmt;
+ struct nhlt_endpoint *epnt;
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct device *dev = bus->dev;
+ struct nhlt_specific_cfg *sp_config;
+ struct nhlt_acpi_table *nhlt = skl->nhlt;
+ u16 bps = (s_fmt == 16) ? 16 : 32;
+ u8 j;
+
+ dump_config(dev, instance, link_type, s_fmt, num_ch, s_rate, dirn, bps);
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+
+ dev_dbg(dev, "endpoint count =%d\n", nhlt->endpoint_count);
+
+ for (j = 0; j < nhlt->endpoint_count; j++) {
+ if (skl_check_ep_match(dev, epnt, instance, link_type,
+ dirn, dev_type)) {
+ fmt = (struct nhlt_fmt *)(epnt->config.caps +
+ epnt->config.size);
+ sp_config = skl_get_specific_cfg(dev, fmt, num_ch,
+ s_rate, bps, link_type);
+ if (sp_config)
+ return sp_config;
+ }
+
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+
+ return NULL;
+}
+
+static void skl_nhlt_trim_space(char *trim)
+{
+ char *s = trim;
+ int cnt;
+ int i;
+
+ cnt = 0;
+ for (i = 0; s[i]; i++) {
+ if (!isspace(s[i]))
+ s[cnt++] = s[i];
+ }
+
+ s[cnt] = '\0';
+}
+
+int skl_nhlt_update_topology_bin(struct skl_dev *skl)
+{
+ struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt;
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct device *dev = bus->dev;
+
+ dev_dbg(dev, "oem_id %.6s, oem_table_id %.8s oem_revision %d\n",
+ nhlt->header.oem_id, nhlt->header.oem_table_id,
+ nhlt->header.oem_revision);
+
+ snprintf(skl->tplg_name, sizeof(skl->tplg_name), "%x-%.6s-%.8s-%d%s",
+ skl->pci_id, nhlt->header.oem_id, nhlt->header.oem_table_id,
+ nhlt->header.oem_revision, "-tplg.bin");
+
+ skl_nhlt_trim_space(skl->tplg_name);
+
+ return 0;
+}
+
+static ssize_t skl_nhlt_platform_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt;
+ char platform_id[32];
+
+ sprintf(platform_id, "%x-%.6s-%.8s-%d", skl->pci_id,
+ nhlt->header.oem_id, nhlt->header.oem_table_id,
+ nhlt->header.oem_revision);
+
+ skl_nhlt_trim_space(platform_id);
+ return sprintf(buf, "%s\n", platform_id);
+}
+
+static DEVICE_ATTR(platform_id, 0444, skl_nhlt_platform_id_show, NULL);
+
+int skl_nhlt_create_sysfs(struct skl_dev *skl)
+{
+ struct device *dev = &skl->pci->dev;
+
+ if (sysfs_create_file(&dev->kobj, &dev_attr_platform_id.attr))
+ dev_warn(dev, "Error creating sysfs entry\n");
+
+ return 0;
+}
+
+void skl_nhlt_remove_sysfs(struct skl_dev *skl)
+{
+ struct device *dev = &skl->pci->dev;
+
+ if (skl->nhlt)
+ sysfs_remove_file(&dev->kobj, &dev_attr_platform_id.attr);
+}
+
+/*
+ * Queries NHLT for all the fmt configuration for a particular endpoint and
+ * stores all possible rates supported in a rate table for the corresponding
+ * sclk/sclkfs.
+ */
+static void skl_get_ssp_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks,
+ struct nhlt_fmt *fmt, u8 id)
+{
+ struct skl_i2s_config_blob_ext *i2s_config_ext;
+ struct skl_i2s_config_blob_legacy *i2s_config;
+ struct skl_clk_parent_src *parent;
+ struct skl_ssp_clk *sclk, *sclkfs;
+ struct nhlt_fmt_cfg *fmt_cfg;
+ struct wav_fmt_ext *wav_fmt;
+ unsigned long rate;
+ int rate_index = 0;
+ u16 channels, bps;
+ u8 clk_src;
+ int i, j;
+ u32 fs;
+
+ sclk = &ssp_clks[SKL_SCLK_OFS];
+ sclkfs = &ssp_clks[SKL_SCLKFS_OFS];
+
+ if (fmt->fmt_count == 0)
+ return;
+
+ fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config;
+ for (i = 0; i < fmt->fmt_count; i++) {
+ struct nhlt_fmt_cfg *saved_fmt_cfg = fmt_cfg;
+ bool present = false;
+
+ wav_fmt = &saved_fmt_cfg->fmt_ext;
+
+ channels = wav_fmt->fmt.channels;
+ bps = wav_fmt->fmt.bits_per_sample;
+ fs = wav_fmt->fmt.samples_per_sec;
+
+ /*
+ * In case of TDM configuration on a ssp, there can
+ * be more than one blob in which channel masks are
+ * different for each usecase for a specific rate and bps.
+ * But the sclk rate will be generated for the total
+ * number of channels used for that endpoint.
+ *
+ * So for the given fs and bps, choose blob which has
+ * the superset of all channels for that endpoint and
+ * derive the rate.
+ */
+ for (j = i; j < fmt->fmt_count; j++) {
+ struct nhlt_fmt_cfg *tmp_fmt_cfg = fmt_cfg;
+
+ wav_fmt = &tmp_fmt_cfg->fmt_ext;
+ if ((fs == wav_fmt->fmt.samples_per_sec) &&
+ (bps == wav_fmt->fmt.bits_per_sample)) {
+ channels = max_t(u16, channels,
+ wav_fmt->fmt.channels);
+ saved_fmt_cfg = tmp_fmt_cfg;
+ }
+ /* Move to the next nhlt_fmt_cfg */
+ tmp_fmt_cfg = (struct nhlt_fmt_cfg *)(tmp_fmt_cfg->config.caps +
+ tmp_fmt_cfg->config.size);
+ }
+
+ rate = channels * bps * fs;
+
+ /* check if the rate is added already to the given SSP's sclk */
+ for (j = 0; (j < SKL_MAX_CLK_RATES) &&
+ (sclk[id].rate_cfg[j].rate != 0); j++) {
+ if (sclk[id].rate_cfg[j].rate == rate) {
+ present = true;
+ break;
+ }
+ }
+
+ /* Fill rate and parent for sclk/sclkfs */
+ if (!present) {
+ struct nhlt_fmt_cfg *first_fmt_cfg;
+
+ first_fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config;
+ i2s_config_ext = (struct skl_i2s_config_blob_ext *)
+ first_fmt_cfg->config.caps;
+
+ /* MCLK Divider Source Select */
+ if (is_legacy_blob(i2s_config_ext->hdr.sig)) {
+ i2s_config = ext_to_legacy_blob(i2s_config_ext);
+ clk_src = get_clk_src(i2s_config->mclk,
+ SKL_MNDSS_DIV_CLK_SRC_MASK);
+ } else {
+ clk_src = get_clk_src(i2s_config_ext->mclk,
+ SKL_MNDSS_DIV_CLK_SRC_MASK);
+ }
+
+ parent = skl_get_parent_clk(clk_src);
+
+ /* Move to the next nhlt_fmt_cfg */
+ fmt_cfg = (struct nhlt_fmt_cfg *)(fmt_cfg->config.caps +
+ fmt_cfg->config.size);
+ /*
+ * Do not copy the config data if there is no parent
+ * clock available for this clock source select
+ */
+ if (!parent)
+ continue;
+
+ sclk[id].rate_cfg[rate_index].rate = rate;
+ sclk[id].rate_cfg[rate_index].config = saved_fmt_cfg;
+ sclkfs[id].rate_cfg[rate_index].rate = rate;
+ sclkfs[id].rate_cfg[rate_index].config = saved_fmt_cfg;
+ sclk[id].parent_name = parent->name;
+ sclkfs[id].parent_name = parent->name;
+
+ rate_index++;
+ }
+ }
+}
+
+static void skl_get_mclk(struct skl_dev *skl, struct skl_ssp_clk *mclk,
+ struct nhlt_fmt *fmt, u8 id)
+{
+ struct skl_i2s_config_blob_ext *i2s_config_ext;
+ struct skl_i2s_config_blob_legacy *i2s_config;
+ struct nhlt_fmt_cfg *fmt_cfg;
+ struct skl_clk_parent_src *parent;
+ u32 clkdiv, div_ratio;
+ u8 clk_src;
+
+ fmt_cfg = (struct nhlt_fmt_cfg *)fmt->fmt_config;
+ i2s_config_ext = (struct skl_i2s_config_blob_ext *)fmt_cfg->config.caps;
+
+ /* MCLK Divider Source Select and divider */
+ if (is_legacy_blob(i2s_config_ext->hdr.sig)) {
+ i2s_config = ext_to_legacy_blob(i2s_config_ext);
+ clk_src = get_clk_src(i2s_config->mclk,
+ SKL_MCLK_DIV_CLK_SRC_MASK);
+ clkdiv = i2s_config->mclk.mdivr &
+ SKL_MCLK_DIV_RATIO_MASK;
+ } else {
+ clk_src = get_clk_src(i2s_config_ext->mclk,
+ SKL_MCLK_DIV_CLK_SRC_MASK);
+ clkdiv = i2s_config_ext->mclk.mdivr[0] &
+ SKL_MCLK_DIV_RATIO_MASK;
+ }
+
+ /* bypass divider */
+ div_ratio = 1;
+
+ if (clkdiv != SKL_MCLK_DIV_RATIO_MASK)
+ /* Divider is 2 + clkdiv */
+ div_ratio = clkdiv + 2;
+
+ /* Calculate MCLK rate from source using div value */
+ parent = skl_get_parent_clk(clk_src);
+ if (!parent)
+ return;
+
+ mclk[id].rate_cfg[0].rate = parent->rate/div_ratio;
+ mclk[id].rate_cfg[0].config = fmt_cfg;
+ mclk[id].parent_name = parent->name;
+}
+
+void skl_get_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks)
+{
+ struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt;
+ struct nhlt_endpoint *epnt;
+ struct nhlt_fmt *fmt;
+ int i;
+ u8 id;
+
+ epnt = (struct nhlt_endpoint *)nhlt->desc;
+ for (i = 0; i < nhlt->endpoint_count; i++) {
+ if (epnt->linktype == NHLT_LINK_SSP) {
+ id = epnt->virtual_bus_id;
+
+ fmt = (struct nhlt_fmt *)(epnt->config.caps
+ + epnt->config.size);
+
+ skl_get_ssp_clks(skl, ssp_clks, fmt, id);
+ skl_get_mclk(skl, ssp_clks, fmt, id);
+ }
+ epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
+ }
+}
diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c
new file mode 100644
index 000000000..935c871ab
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-pcm.c
@@ -0,0 +1,1515 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-pcm.c -ASoC HDA Platform driver file implementing PCM functionality
+ *
+ * Copyright (C) 2014-2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "skl.h"
+#include "skl-topology.h"
+#include "skl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+
+#define HDA_MONO 1
+#define HDA_STEREO 2
+#define HDA_QUAD 4
+#define HDA_MAX 8
+
+static const struct snd_pcm_hardware azx_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
+ SNDRV_PCM_INFO_HAS_LINK_ATIME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_8000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = AZX_MAX_BUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = AZX_MAX_BUF_SIZE / 2,
+ .periods_min = 2,
+ .periods_max = AZX_MAX_FRAG,
+ .fifo_size = 0,
+};
+
+static inline
+struct hdac_ext_stream *get_hdac_ext_stream(struct snd_pcm_substream *substream)
+{
+ return substream->runtime->private_data;
+}
+
+static struct hdac_bus *get_bus_ctx(struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+ struct hdac_stream *hstream = hdac_stream(stream);
+ struct hdac_bus *bus = hstream->bus;
+ return bus;
+}
+
+static int skl_substream_alloc_pages(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream,
+ size_t size)
+{
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+
+ hdac_stream(stream)->bufsize = 0;
+ hdac_stream(stream)->period_bytes = 0;
+ hdac_stream(stream)->format_val = 0;
+
+ return 0;
+}
+
+static void skl_set_pcm_constrains(struct hdac_bus *bus,
+ struct snd_pcm_runtime *runtime)
+{
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ /* avoid wrap-around with wall-clock */
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+ 20, 178000000);
+}
+
+static enum hdac_ext_stream_type skl_get_host_stream_type(struct hdac_bus *bus)
+{
+ if (bus->ppcap)
+ return HDAC_EXT_STREAM_TYPE_HOST;
+ else
+ return HDAC_EXT_STREAM_TYPE_COUPLED;
+}
+
+/*
+ * check if the stream opened is marked as ignore_suspend by machine, if so
+ * then enable suspend_active refcount
+ *
+ * The count supend_active does not need lock as it is used in open/close
+ * and suspend context
+ */
+static void skl_set_suspend_active(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai, bool enable)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct snd_soc_dapm_widget *w;
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ w = snd_soc_dai_get_widget(dai, substream->stream);
+
+ if (w->ignore_suspend && enable)
+ skl->supend_active++;
+ else if (w->ignore_suspend && !enable)
+ skl->supend_active--;
+}
+
+int skl_pcm_host_dma_prepare(struct device *dev, struct skl_pipe_params *params)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct skl_dev *skl = bus_to_skl(bus);
+ unsigned int format_val;
+ struct hdac_stream *hstream;
+ struct hdac_ext_stream *stream;
+ int err;
+
+ hstream = snd_hdac_get_stream(bus, params->stream,
+ params->host_dma_id + 1);
+ if (!hstream)
+ return -EINVAL;
+
+ stream = stream_to_hdac_ext_stream(hstream);
+ snd_hdac_ext_stream_decouple(bus, stream, true);
+
+ format_val = snd_hdac_calc_stream_format(params->s_freq,
+ params->ch, params->format, params->host_bps, 0);
+
+ dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n",
+ format_val, params->s_freq, params->ch, params->format);
+
+ snd_hdac_stream_reset(hdac_stream(stream));
+ err = snd_hdac_stream_set_params(hdac_stream(stream), format_val);
+ if (err < 0)
+ return err;
+
+ /*
+ * The recommended SDxFMT programming sequence for BXT
+ * platforms is to couple the stream before writing the format
+ */
+ if (IS_BXT(skl->pci)) {
+ snd_hdac_ext_stream_decouple(bus, stream, false);
+ err = snd_hdac_stream_setup(hdac_stream(stream));
+ snd_hdac_ext_stream_decouple(bus, stream, true);
+ } else {
+ err = snd_hdac_stream_setup(hdac_stream(stream));
+ }
+
+ if (err < 0)
+ return err;
+
+ hdac_stream(stream)->prepared = 1;
+
+ return 0;
+}
+
+int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ unsigned int format_val;
+ struct hdac_stream *hstream;
+ struct hdac_ext_stream *stream;
+ struct hdac_ext_link *link;
+ unsigned char stream_tag;
+
+ hstream = snd_hdac_get_stream(bus, params->stream,
+ params->link_dma_id + 1);
+ if (!hstream)
+ return -EINVAL;
+
+ stream = stream_to_hdac_ext_stream(hstream);
+ snd_hdac_ext_stream_decouple(bus, stream, true);
+ format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch,
+ params->format, params->link_bps, 0);
+
+ dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n",
+ format_val, params->s_freq, params->ch, params->format);
+
+ snd_hdac_ext_link_stream_reset(stream);
+
+ snd_hdac_ext_link_stream_setup(stream, format_val);
+
+ stream_tag = hstream->stream_tag;
+ if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ list_for_each_entry(link, &bus->hlink_list, list) {
+ if (link->index == params->link_index)
+ snd_hdac_ext_link_set_stream_id(link,
+ stream_tag);
+ }
+ }
+
+ stream->link_prepared = 1;
+
+ return 0;
+}
+
+static int skl_pcm_open(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct hdac_ext_stream *stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct skl_dma_params *dma_params;
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ struct skl_module_cfg *mconfig;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+
+ stream = snd_hdac_ext_stream_assign(bus, substream,
+ skl_get_host_stream_type(bus));
+ if (stream == NULL)
+ return -EBUSY;
+
+ skl_set_pcm_constrains(bus, runtime);
+
+ /*
+ * disable WALLCLOCK timestamps for capture streams
+ * until we figure out how to handle digital inputs
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME;
+ }
+
+ runtime->private_data = stream;
+
+ dma_params = kzalloc(sizeof(*dma_params), GFP_KERNEL);
+ if (!dma_params)
+ return -ENOMEM;
+
+ dma_params->stream_tag = hdac_stream(stream)->stream_tag;
+ snd_soc_dai_set_dma_data(dai, substream, dma_params);
+
+ dev_dbg(dai->dev, "stream tag set in dma params=%d\n",
+ dma_params->stream_tag);
+ skl_set_suspend_active(substream, dai, true);
+ snd_pcm_set_sync(substream);
+
+ mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
+ if (!mconfig) {
+ kfree(dma_params);
+ return -EINVAL;
+ }
+
+ skl_tplg_d0i3_get(skl, mconfig->d0i3_caps);
+
+ return 0;
+}
+
+static int skl_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ struct skl_module_cfg *mconfig;
+ int ret;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+
+ mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
+
+ /*
+ * In case of XRUN recovery or in the case when the application
+ * calls prepare another time, reset the FW pipe to clean state
+ */
+ if (mconfig &&
+ (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ mconfig->pipe->state == SKL_PIPE_CREATED ||
+ mconfig->pipe->state == SKL_PIPE_PAUSED)) {
+
+ ret = skl_reset_pipe(skl, mconfig->pipe);
+
+ if (ret < 0)
+ return ret;
+
+ ret = skl_pcm_host_dma_prepare(dai->dev,
+ mconfig->pipe->p_params);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int skl_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct skl_pipe_params p_params = {0};
+ struct skl_module_cfg *m_cfg;
+ int ret, dma_id;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+ ret = skl_substream_alloc_pages(bus, substream,
+ params_buffer_bytes(params));
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dai->dev, "format_val, rate=%d, ch=%d, format=%d\n",
+ runtime->rate, runtime->channels, runtime->format);
+
+ dma_id = hdac_stream(stream)->stream_tag - 1;
+ dev_dbg(dai->dev, "dma_id=%d\n", dma_id);
+
+ p_params.s_fmt = snd_pcm_format_width(params_format(params));
+ p_params.ch = params_channels(params);
+ p_params.s_freq = params_rate(params);
+ p_params.host_dma_id = dma_id;
+ p_params.stream = substream->stream;
+ p_params.format = params_format(params);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ p_params.host_bps = dai->driver->playback.sig_bits;
+ else
+ p_params.host_bps = dai->driver->capture.sig_bits;
+
+
+ m_cfg = skl_tplg_fe_get_cpr_module(dai, p_params.stream);
+ if (m_cfg)
+ skl_tplg_update_pipe_params(dai->dev, m_cfg, &p_params);
+
+ return 0;
+}
+
+static void skl_pcm_close(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct skl_dma_params *dma_params = NULL;
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_module_cfg *mconfig;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+
+ snd_hdac_ext_stream_release(stream, skl_get_host_stream_type(bus));
+
+ dma_params = snd_soc_dai_get_dma_data(dai, substream);
+ /*
+ * now we should set this to NULL as we are freeing by the
+ * dma_params
+ */
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+ skl_set_suspend_active(substream, dai, false);
+
+ /*
+ * check if close is for "Reference Pin" and set back the
+ * CGCTL.MISCBDCGE if disabled by driver
+ */
+ if (!strncmp(dai->name, "Reference Pin", 13) &&
+ skl->miscbdcg_disabled) {
+ skl->enable_miscbdcge(dai->dev, true);
+ skl->miscbdcg_disabled = false;
+ }
+
+ mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
+ if (mconfig)
+ skl_tplg_d0i3_put(skl, mconfig->d0i3_caps);
+
+ kfree(dma_params);
+}
+
+static int skl_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ struct skl_module_cfg *mconfig;
+ int ret;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+
+ mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
+
+ if (mconfig) {
+ ret = skl_reset_pipe(skl, mconfig->pipe);
+ if (ret < 0)
+ dev_err(dai->dev, "%s:Reset failed ret =%d",
+ __func__, ret);
+ }
+
+ snd_hdac_stream_cleanup(hdac_stream(stream));
+ hdac_stream(stream)->prepared = 0;
+
+ return 0;
+}
+
+static int skl_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct skl_pipe_params p_params = {0};
+
+ p_params.s_fmt = snd_pcm_format_width(params_format(params));
+ p_params.ch = params_channels(params);
+ p_params.s_freq = params_rate(params);
+ p_params.stream = substream->stream;
+
+ return skl_tplg_be_update_params(dai, &p_params);
+}
+
+static int skl_decoupled_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct hdac_bus *bus = get_bus_ctx(substream);
+ struct hdac_ext_stream *stream;
+ int start;
+ unsigned long cookie;
+ struct hdac_stream *hstr;
+
+ stream = get_hdac_ext_stream(substream);
+ hstr = hdac_stream(stream);
+
+ if (!hstr->prepared)
+ return -EPIPE;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ start = 1;
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ start = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&bus->reg_lock, cookie);
+
+ if (start) {
+ snd_hdac_stream_start(hdac_stream(stream), true);
+ snd_hdac_stream_timecounter_init(hstr, 0);
+ } else {
+ snd_hdac_stream_stop(hdac_stream(stream));
+ }
+
+ spin_unlock_irqrestore(&bus->reg_lock, cookie);
+
+ return 0;
+}
+
+static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ struct skl_module_cfg *mconfig;
+ struct hdac_bus *bus = get_bus_ctx(substream);
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+ struct snd_soc_dapm_widget *w;
+ int ret;
+
+ mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream);
+ if (!mconfig)
+ return -EIO;
+
+ w = snd_soc_dai_get_widget(dai, substream->stream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (!w->ignore_suspend) {
+ /*
+ * enable DMA Resume enable bit for the stream, set the
+ * dpib & lpib position to resume before starting the
+ * DMA
+ */
+ snd_hdac_ext_stream_drsm_enable(bus, true,
+ hdac_stream(stream)->index);
+ snd_hdac_ext_stream_set_dpibr(bus, stream,
+ stream->lpib);
+ snd_hdac_ext_stream_set_lpib(stream, stream->lpib);
+ }
+ fallthrough;
+
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*
+ * Start HOST DMA and Start FE Pipe.This is to make sure that
+ * there are no underrun/overrun in the case when the FE
+ * pipeline is started but there is a delay in starting the
+ * DMA channel on the host.
+ */
+ ret = skl_decoupled_trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ return skl_run_pipe(skl, mconfig->pipe);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ /*
+ * Stop FE Pipe first and stop DMA. This is to make sure that
+ * there are no underrun/overrun in the case if there is a delay
+ * between the two operations.
+ */
+ ret = skl_stop_pipe(skl, mconfig->pipe);
+ if (ret < 0)
+ return ret;
+
+ ret = skl_decoupled_trigger(substream, cmd);
+ if ((cmd == SNDRV_PCM_TRIGGER_SUSPEND) && !w->ignore_suspend) {
+ /* save the dpib and lpib positions */
+ stream->dpib = readl(bus->remap_addr +
+ AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hdac_stream(stream)->index));
+
+ stream->lpib = snd_hdac_stream_get_pos_lpib(
+ hdac_stream(stream));
+ snd_hdac_ext_stream_decouple(bus, stream, false);
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int skl_link_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct hdac_ext_stream *link_dev;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct skl_pipe_params p_params = {0};
+ struct hdac_ext_link *link;
+ int stream_tag;
+
+ link_dev = snd_hdac_ext_stream_assign(bus, substream,
+ HDAC_EXT_STREAM_TYPE_LINK);
+ if (!link_dev)
+ return -EBUSY;
+
+ snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev);
+
+ link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name);
+ if (!link)
+ return -EINVAL;
+
+ stream_tag = hdac_stream(link_dev)->stream_tag;
+
+ /* set the hdac_stream in the codec dai */
+ snd_soc_dai_set_stream(codec_dai, hdac_stream(link_dev), substream->stream);
+
+ p_params.s_fmt = snd_pcm_format_width(params_format(params));
+ p_params.ch = params_channels(params);
+ p_params.s_freq = params_rate(params);
+ p_params.stream = substream->stream;
+ p_params.link_dma_id = stream_tag - 1;
+ p_params.link_index = link->index;
+ p_params.format = params_format(params);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ p_params.link_bps = codec_dai->driver->playback.sig_bits;
+ else
+ p_params.link_bps = codec_dai->driver->capture.sig_bits;
+
+ return skl_tplg_be_update_params(dai, &p_params);
+}
+
+static int skl_link_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ struct skl_module_cfg *mconfig = NULL;
+
+ /* In case of XRUN recovery, reset the FW pipe to clean state */
+ mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream);
+ if (mconfig && !mconfig->pipe->passthru &&
+ (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN))
+ skl_reset_pipe(skl, mconfig->pipe);
+
+ return 0;
+}
+
+static int skl_link_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *link_dev =
+ snd_soc_dai_get_dma_data(dai, substream);
+ struct hdac_bus *bus = get_bus_ctx(substream);
+ struct hdac_ext_stream *stream = get_hdac_ext_stream(substream);
+
+ dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_hdac_ext_link_stream_start(link_dev);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_hdac_ext_link_stream_clear(link_dev);
+ if (cmd == SNDRV_PCM_TRIGGER_SUSPEND)
+ snd_hdac_ext_stream_decouple(bus, stream, false);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int skl_link_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct hdac_ext_stream *link_dev =
+ snd_soc_dai_get_dma_data(dai, substream);
+ struct hdac_ext_link *link;
+ unsigned char stream_tag;
+
+ dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);
+
+ link_dev->link_prepared = 0;
+
+ link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name);
+ if (!link)
+ return -EINVAL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ stream_tag = hdac_stream(link_dev)->stream_tag;
+ snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+ }
+
+ snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK);
+ return 0;
+}
+
+static const struct snd_soc_dai_ops skl_pcm_dai_ops = {
+ .startup = skl_pcm_open,
+ .shutdown = skl_pcm_close,
+ .prepare = skl_pcm_prepare,
+ .hw_params = skl_pcm_hw_params,
+ .hw_free = skl_pcm_hw_free,
+ .trigger = skl_pcm_trigger,
+};
+
+static const struct snd_soc_dai_ops skl_dmic_dai_ops = {
+ .hw_params = skl_be_hw_params,
+};
+
+static const struct snd_soc_dai_ops skl_be_ssp_dai_ops = {
+ .hw_params = skl_be_hw_params,
+};
+
+static const struct snd_soc_dai_ops skl_link_dai_ops = {
+ .prepare = skl_link_pcm_prepare,
+ .hw_params = skl_link_hw_params,
+ .hw_free = skl_link_hw_free,
+ .trigger = skl_link_pcm_trigger,
+};
+
+static struct snd_soc_dai_driver skl_fe_dai[] = {
+{
+ .name = "System Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "System Playback",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ .sig_bits = 32,
+ },
+ .capture = {
+ .stream_name = "System Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "System Pin2",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "Headset Playback",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+},
+{
+ .name = "Echoref Pin",
+ .ops = &skl_pcm_dai_ops,
+ .capture = {
+ .stream_name = "Echoreference Capture",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+ },
+},
+{
+ .name = "Reference Pin",
+ .ops = &skl_pcm_dai_ops,
+ .capture = {
+ .stream_name = "Reference Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_QUAD,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "Deepbuffer Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "Deepbuffer Playback",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "LowLatency Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "Low Latency Playback",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "DMIC Pin",
+ .ops = &skl_pcm_dai_ops,
+ .capture = {
+ .stream_name = "DMIC Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_QUAD,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "HDMI1 Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "HDMI1 Playback",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "HDMI2 Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "HDMI2 Playback",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .sig_bits = 32,
+ },
+},
+{
+ .name = "HDMI3 Pin",
+ .ops = &skl_pcm_dai_ops,
+ .playback = {
+ .stream_name = "HDMI3 Playback",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .sig_bits = 32,
+ },
+},
+};
+
+/* BE CPU Dais */
+static struct snd_soc_dai_driver skl_platform_dai[] = {
+{
+ .name = "SSP0 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp0 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp0 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "SSP1 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp1 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp1 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "SSP2 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp2 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp2 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "SSP3 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp3 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp3 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "SSP4 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp4 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp4 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "SSP5 Pin",
+ .ops = &skl_be_ssp_dai_ops,
+ .playback = {
+ .stream_name = "ssp5 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ssp5 Rx",
+ .channels_min = HDA_STEREO,
+ .channels_max = HDA_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "iDisp1 Pin",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "iDisp1 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "iDisp2 Pin",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "iDisp2 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "iDisp3 Pin",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "iDisp3 Tx",
+ .channels_min = HDA_STEREO,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "DMIC01 Pin",
+ .ops = &skl_dmic_dai_ops,
+ .capture = {
+ .stream_name = "DMIC01 Rx",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_QUAD,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "DMIC16k Pin",
+ .ops = &skl_dmic_dai_ops,
+ .capture = {
+ .stream_name = "DMIC16k Rx",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_QUAD,
+ .rates = SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+{
+ .name = "Analog CPU DAI",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "Analog CPU Playback",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "Analog CPU Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+},
+{
+ .name = "Alt Analog CPU DAI",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "Alt Analog CPU Playback",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "Alt Analog CPU Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+},
+{
+ .name = "Digital CPU DAI",
+ .ops = &skl_link_dai_ops,
+ .playback = {
+ .stream_name = "Digital CPU Playback",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "Digital CPU Capture",
+ .channels_min = HDA_MONO,
+ .channels_max = HDA_MAX,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+},
+};
+
+int skl_dai_load(struct snd_soc_component *cmp, int index,
+ struct snd_soc_dai_driver *dai_drv,
+ struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai)
+{
+ dai_drv->ops = &skl_pcm_dai_ops;
+
+ return 0;
+}
+
+static int skl_platform_soc_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "In %s:%s\n", __func__,
+ dai_link->cpus->dai_name);
+
+ snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw);
+
+ return 0;
+}
+
+static int skl_coupled_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct hdac_bus *bus = get_bus_ctx(substream);
+ struct hdac_ext_stream *stream;
+ struct snd_pcm_substream *s;
+ bool start;
+ int sbits = 0;
+ unsigned long cookie;
+ struct hdac_stream *hstr;
+
+ stream = get_hdac_ext_stream(substream);
+ hstr = hdac_stream(stream);
+
+ dev_dbg(bus->dev, "In %s cmd=%d\n", __func__, cmd);
+
+ if (!hstr->prepared)
+ return -EPIPE;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ start = true;
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ start = false;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ stream = get_hdac_ext_stream(s);
+ sbits |= 1 << hdac_stream(stream)->index;
+ snd_pcm_trigger_done(s, substream);
+ }
+
+ spin_lock_irqsave(&bus->reg_lock, cookie);
+
+ /* first, set SYNC bits of corresponding streams */
+ snd_hdac_stream_sync_trigger(hstr, true, sbits, AZX_REG_SSYNC);
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ stream = get_hdac_ext_stream(s);
+ if (start)
+ snd_hdac_stream_start(hdac_stream(stream), true);
+ else
+ snd_hdac_stream_stop(hdac_stream(stream));
+ }
+ spin_unlock_irqrestore(&bus->reg_lock, cookie);
+
+ snd_hdac_stream_sync(hstr, start, sbits);
+
+ spin_lock_irqsave(&bus->reg_lock, cookie);
+
+ /* reset SYNC bits */
+ snd_hdac_stream_sync_trigger(hstr, false, sbits, AZX_REG_SSYNC);
+ if (start)
+ snd_hdac_stream_timecounter_init(hstr, sbits);
+ spin_unlock_irqrestore(&bus->reg_lock, cookie);
+
+ return 0;
+}
+
+static int skl_platform_soc_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct hdac_bus *bus = get_bus_ctx(substream);
+
+ if (!bus->ppcap)
+ return skl_coupled_trigger(substream, cmd);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t skl_platform_soc_pointer(
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream);
+ struct hdac_bus *bus = get_bus_ctx(substream);
+ unsigned int pos;
+
+ /*
+ * Use DPIB for Playback stream as the periodic DMA Position-in-
+ * Buffer Writes may be scheduled at the same time or later than
+ * the MSI and does not guarantee to reflect the Position of the
+ * last buffer that was transferred. Whereas DPIB register in
+ * HAD space reflects the actual data that is transferred.
+ * Use the position buffer for capture, as DPIB write gets
+ * completed earlier than the actual data written to the DDR.
+ *
+ * For capture stream following workaround is required to fix the
+ * incorrect position reporting.
+ *
+ * 1. Wait for 20us before reading the DMA position in buffer once
+ * the interrupt is generated for stream completion as update happens
+ * on the HDA frame boundary i.e. 20.833uSec.
+ * 2. Read DPIB register to flush the DMA position value. This dummy
+ * read is required to flush DMA position value.
+ * 3. Read the DMA Position-in-Buffer. This value now will be equal to
+ * or greater than period boundary.
+ */
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pos = readl(bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hdac_stream(hstream)->index));
+ } else {
+ udelay(20);
+ readl(bus->remap_addr +
+ AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hdac_stream(hstream)->index));
+ pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream));
+ }
+
+ if (pos >= hdac_stream(hstream)->bufsize)
+ pos = 0;
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static int skl_platform_soc_mmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+ return snd_pcm_lib_default_mmap(substream, area);
+}
+
+static u64 skl_adjust_codec_delay(struct snd_pcm_substream *substream,
+ u64 nsec)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ u64 codec_frames, codec_nsecs;
+
+ if (!codec_dai->driver->ops->delay)
+ return nsec;
+
+ codec_frames = codec_dai->driver->ops->delay(substream, codec_dai);
+ codec_nsecs = div_u64(codec_frames * 1000000000LL,
+ substream->runtime->rate);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return nsec + codec_nsecs;
+
+ return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0;
+}
+
+static int skl_platform_soc_get_time_info(
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct timespec64 *system_ts, struct timespec64 *audio_ts,
+ struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
+ struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
+{
+ struct hdac_ext_stream *sstream = get_hdac_ext_stream(substream);
+ struct hdac_stream *hstr = hdac_stream(sstream);
+ u64 nsec;
+
+ if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) &&
+ (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) {
+
+ snd_pcm_gettime(substream->runtime, system_ts);
+
+ nsec = timecounter_read(&hstr->tc);
+ nsec = div_u64(nsec, 3); /* can be optimized */
+ if (audio_tstamp_config->report_delay)
+ nsec = skl_adjust_codec_delay(substream, nsec);
+
+ *audio_ts = ns_to_timespec64(nsec);
+
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
+ audio_tstamp_report->accuracy_report = 1; /* rest of struct is valid */
+ audio_tstamp_report->accuracy = 42; /* 24MHzWallClk == 42ns resolution */
+
+ } else {
+ audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
+ }
+
+ return 0;
+}
+
+#define MAX_PREALLOC_SIZE (32 * 1024 * 1024)
+
+static int skl_platform_soc_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
+ struct hdac_bus *bus = dev_get_drvdata(dai->dev);
+ struct snd_pcm *pcm = rtd->pcm;
+ unsigned int size;
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ if (dai->driver->playback.channels_min ||
+ dai->driver->capture.channels_min) {
+ /* buffer pre-allocation */
+ size = CONFIG_SND_HDA_PREALLOC_SIZE * 1024;
+ if (size > MAX_PREALLOC_SIZE)
+ size = MAX_PREALLOC_SIZE;
+ snd_pcm_set_managed_buffer_all(pcm,
+ SNDRV_DMA_TYPE_DEV_SG,
+ &skl->pci->dev,
+ size, MAX_PREALLOC_SIZE);
+ }
+
+ return 0;
+}
+
+static int skl_get_module_info(struct skl_dev *skl,
+ struct skl_module_cfg *mconfig)
+{
+ struct skl_module_inst_id *pin_id;
+ guid_t *uuid_mod, *uuid_tplg;
+ struct skl_module *skl_module;
+ struct uuid_module *module;
+ int i, ret = -EIO;
+
+ uuid_mod = (guid_t *)mconfig->guid;
+
+ if (list_empty(&skl->uuid_list)) {
+ dev_err(skl->dev, "Module list is empty\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < skl->nr_modules; i++) {
+ skl_module = skl->modules[i];
+ uuid_tplg = &skl_module->uuid;
+ if (guid_equal(uuid_mod, uuid_tplg)) {
+ mconfig->module = skl_module;
+ ret = 0;
+ break;
+ }
+ }
+
+ if (skl->nr_modules && ret)
+ return ret;
+
+ ret = -EIO;
+ list_for_each_entry(module, &skl->uuid_list, list) {
+ if (guid_equal(uuid_mod, &module->uuid)) {
+ mconfig->id.module_id = module->id;
+ mconfig->module->loadable = module->is_loadable;
+ ret = 0;
+ }
+
+ for (i = 0; i < MAX_IN_QUEUE; i++) {
+ pin_id = &mconfig->m_in_pin[i].id;
+ if (guid_equal(&pin_id->mod_uuid, &module->uuid))
+ pin_id->module_id = module->id;
+ }
+
+ for (i = 0; i < MAX_OUT_QUEUE; i++) {
+ pin_id = &mconfig->m_out_pin[i].id;
+ if (guid_equal(&pin_id->mod_uuid, &module->uuid))
+ pin_id->module_id = module->id;
+ }
+ }
+
+ return ret;
+}
+
+static int skl_populate_modules(struct skl_dev *skl)
+{
+ struct skl_pipeline *p;
+ struct skl_pipe_module *m;
+ struct snd_soc_dapm_widget *w;
+ struct skl_module_cfg *mconfig;
+ int ret = 0;
+
+ list_for_each_entry(p, &skl->ppl_list, node) {
+ list_for_each_entry(m, &p->pipe->w_list, node) {
+ w = m->w;
+ mconfig = w->priv;
+
+ ret = skl_get_module_info(skl, mconfig);
+ if (ret < 0) {
+ dev_err(skl->dev,
+ "query module info failed\n");
+ return ret;
+ }
+
+ skl_tplg_add_moduleid_in_bind_params(skl, w);
+ }
+ }
+
+ return ret;
+}
+
+static int skl_platform_soc_probe(struct snd_soc_component *component)
+{
+ struct hdac_bus *bus = dev_get_drvdata(component->dev);
+ struct skl_dev *skl = bus_to_skl(bus);
+ const struct skl_dsp_ops *ops;
+ int ret;
+
+ pm_runtime_get_sync(component->dev);
+ if (bus->ppcap) {
+ skl->component = component;
+
+ /* init debugfs */
+ skl->debugfs = skl_debugfs_init(skl);
+
+ ret = skl_tplg_init(component, bus);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to init topology!\n");
+ return ret;
+ }
+
+ /* load the firmwares, since all is set */
+ ops = skl_get_dsp_ops(skl->pci->device);
+ if (!ops)
+ return -EIO;
+
+ /*
+ * Disable dynamic clock and power gating during firmware
+ * and library download
+ */
+ skl->enable_miscbdcge(component->dev, false);
+ skl->clock_power_gating(component->dev, false);
+
+ ret = ops->init_fw(component->dev, skl);
+ skl->enable_miscbdcge(component->dev, true);
+ skl->clock_power_gating(component->dev, true);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to boot first fw: %d\n", ret);
+ return ret;
+ }
+ skl_populate_modules(skl);
+ skl->update_d0i3c = skl_update_d0i3c;
+
+ if (skl->cfg.astate_cfg != NULL) {
+ skl_dsp_set_astate_cfg(skl,
+ skl->cfg.astate_cfg->count,
+ skl->cfg.astate_cfg);
+ }
+ }
+ pm_runtime_mark_last_busy(component->dev);
+ pm_runtime_put_autosuspend(component->dev);
+
+ return 0;
+}
+
+static void skl_platform_soc_remove(struct snd_soc_component *component)
+{
+ struct hdac_bus *bus = dev_get_drvdata(component->dev);
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ skl_tplg_exit(component, bus);
+
+ skl_debugfs_exit(skl);
+}
+
+static const struct snd_soc_component_driver skl_component = {
+ .name = "pcm",
+ .probe = skl_platform_soc_probe,
+ .remove = skl_platform_soc_remove,
+ .open = skl_platform_soc_open,
+ .trigger = skl_platform_soc_trigger,
+ .pointer = skl_platform_soc_pointer,
+ .get_time_info = skl_platform_soc_get_time_info,
+ .mmap = skl_platform_soc_mmap,
+ .pcm_construct = skl_platform_soc_new,
+ .module_get_upon_open = 1, /* increment refcount when a pcm is opened */
+};
+
+int skl_platform_register(struct device *dev)
+{
+ int ret;
+ struct snd_soc_dai_driver *dais;
+ int num_dais = ARRAY_SIZE(skl_platform_dai);
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ skl->dais = kmemdup(skl_platform_dai, sizeof(skl_platform_dai),
+ GFP_KERNEL);
+ if (!skl->dais) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (!skl->use_tplg_pcm) {
+ dais = krealloc(skl->dais, sizeof(skl_fe_dai) +
+ sizeof(skl_platform_dai), GFP_KERNEL);
+ if (!dais) {
+ kfree(skl->dais);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ skl->dais = dais;
+ memcpy(&skl->dais[ARRAY_SIZE(skl_platform_dai)], skl_fe_dai,
+ sizeof(skl_fe_dai));
+ num_dais += ARRAY_SIZE(skl_fe_dai);
+ }
+
+ ret = devm_snd_soc_register_component(dev, &skl_component,
+ skl->dais, num_dais);
+ if (ret) {
+ kfree(skl->dais);
+ dev_err(dev, "soc component registration failed %d\n", ret);
+ }
+err:
+ return ret;
+}
+
+int skl_platform_unregister(struct device *dev)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_module_deferred_bind *modules, *tmp;
+
+ list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) {
+ list_del(&modules->node);
+ kfree(modules);
+ }
+
+ kfree(skl->dais);
+
+ return 0;
+}
diff --git a/sound/soc/intel/skylake/skl-ssp-clk.c b/sound/soc/intel/skylake/skl-ssp-clk.c
new file mode 100644
index 000000000..a3a73c26f
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-ssp-clk.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-17 Intel Corporation
+
+/*
+ * skl-ssp-clk.c - ASoC skylake ssp clock driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <sound/intel-nhlt.h>
+#include "skl.h"
+#include "skl-ssp-clk.h"
+#include "skl-topology.h"
+
+#define to_skl_clk(_hw) container_of(_hw, struct skl_clk, hw)
+
+struct skl_clk_parent {
+ struct clk_hw *hw;
+ struct clk_lookup *lookup;
+};
+
+struct skl_clk {
+ struct clk_hw hw;
+ struct clk_lookup *lookup;
+ unsigned long rate;
+ struct skl_clk_pdata *pdata;
+ u32 id;
+};
+
+struct skl_clk_data {
+ struct skl_clk_parent parent[SKL_MAX_CLK_SRC];
+ struct skl_clk *clk[SKL_MAX_CLK_CNT];
+ u8 avail_clk_cnt;
+};
+
+static int skl_get_clk_type(u32 index)
+{
+ switch (index) {
+ case 0 ... (SKL_SCLK_OFS - 1):
+ return SKL_MCLK;
+
+ case SKL_SCLK_OFS ... (SKL_SCLKFS_OFS - 1):
+ return SKL_SCLK;
+
+ case SKL_SCLKFS_OFS ... (SKL_MAX_CLK_CNT - 1):
+ return SKL_SCLK_FS;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int skl_get_vbus_id(u32 index, u8 clk_type)
+{
+ switch (clk_type) {
+ case SKL_MCLK:
+ return index;
+
+ case SKL_SCLK:
+ return index - SKL_SCLK_OFS;
+
+ case SKL_SCLK_FS:
+ return index - SKL_SCLKFS_OFS;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void skl_fill_clk_ipc(struct skl_clk_rate_cfg_table *rcfg, u8 clk_type)
+{
+ struct nhlt_fmt_cfg *fmt_cfg;
+ union skl_clk_ctrl_ipc *ipc;
+ struct wav_fmt *wfmt;
+
+ if (!rcfg)
+ return;
+
+ ipc = &rcfg->dma_ctl_ipc;
+ if (clk_type == SKL_SCLK_FS) {
+ fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config;
+ wfmt = &fmt_cfg->fmt_ext.fmt;
+
+ /* Remove TLV Header size */
+ ipc->sclk_fs.hdr.size = sizeof(struct skl_dmactrl_sclkfs_cfg) -
+ sizeof(struct skl_tlv_hdr);
+ ipc->sclk_fs.sampling_frequency = wfmt->samples_per_sec;
+ ipc->sclk_fs.bit_depth = wfmt->bits_per_sample;
+ ipc->sclk_fs.valid_bit_depth =
+ fmt_cfg->fmt_ext.sample.valid_bits_per_sample;
+ ipc->sclk_fs.number_of_channels = wfmt->channels;
+ } else {
+ ipc->mclk.hdr.type = DMA_CLK_CONTROLS;
+ /* Remove TLV Header size */
+ ipc->mclk.hdr.size = sizeof(struct skl_dmactrl_mclk_cfg) -
+ sizeof(struct skl_tlv_hdr);
+ }
+}
+
+/* Sends dma control IPC to turn the clock ON/OFF */
+static int skl_send_clk_dma_control(struct skl_dev *skl,
+ struct skl_clk_rate_cfg_table *rcfg,
+ u32 vbus_id, u8 clk_type,
+ bool enable)
+{
+ struct nhlt_specific_cfg *sp_cfg;
+ u32 i2s_config_size, node_id = 0;
+ struct nhlt_fmt_cfg *fmt_cfg;
+ union skl_clk_ctrl_ipc *ipc;
+ void *i2s_config = NULL;
+ u8 *data, size;
+ int ret;
+
+ if (!rcfg)
+ return -EIO;
+
+ ipc = &rcfg->dma_ctl_ipc;
+ fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config;
+ sp_cfg = &fmt_cfg->config;
+
+ if (clk_type == SKL_SCLK_FS) {
+ ipc->sclk_fs.hdr.type =
+ enable ? DMA_TRANSMITION_START : DMA_TRANSMITION_STOP;
+ data = (u8 *)&ipc->sclk_fs;
+ size = sizeof(struct skl_dmactrl_sclkfs_cfg);
+ } else {
+ /* 1 to enable mclk, 0 to enable sclk */
+ if (clk_type == SKL_SCLK)
+ ipc->mclk.mclk = 0;
+ else
+ ipc->mclk.mclk = 1;
+
+ ipc->mclk.keep_running = enable;
+ ipc->mclk.warm_up_over = enable;
+ ipc->mclk.clk_stop_over = !enable;
+ data = (u8 *)&ipc->mclk;
+ size = sizeof(struct skl_dmactrl_mclk_cfg);
+ }
+
+ i2s_config_size = sp_cfg->size + size;
+ i2s_config = kzalloc(i2s_config_size, GFP_KERNEL);
+ if (!i2s_config)
+ return -ENOMEM;
+
+ /* copy blob */
+ memcpy(i2s_config, sp_cfg->caps, sp_cfg->size);
+
+ /* copy additional dma controls information */
+ memcpy(i2s_config + sp_cfg->size, data, size);
+
+ node_id = ((SKL_DMA_I2S_LINK_INPUT_CLASS << 8) | (vbus_id << 4));
+ ret = skl_dsp_set_dma_control(skl, (u32 *)i2s_config,
+ i2s_config_size, node_id);
+ kfree(i2s_config);
+
+ return ret;
+}
+
+static struct skl_clk_rate_cfg_table *skl_get_rate_cfg(
+ struct skl_clk_rate_cfg_table *rcfg,
+ unsigned long rate)
+{
+ int i;
+
+ for (i = 0; (i < SKL_MAX_CLK_RATES) && rcfg[i].rate; i++) {
+ if (rcfg[i].rate == rate)
+ return &rcfg[i];
+ }
+
+ return NULL;
+}
+
+static int skl_clk_change_status(struct skl_clk *clkdev,
+ bool enable)
+{
+ struct skl_clk_rate_cfg_table *rcfg;
+ int vbus_id, clk_type;
+
+ clk_type = skl_get_clk_type(clkdev->id);
+ if (clk_type < 0)
+ return clk_type;
+
+ vbus_id = skl_get_vbus_id(clkdev->id, clk_type);
+ if (vbus_id < 0)
+ return vbus_id;
+
+ rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg,
+ clkdev->rate);
+ if (!rcfg)
+ return -EINVAL;
+
+ return skl_send_clk_dma_control(clkdev->pdata->pvt_data, rcfg,
+ vbus_id, clk_type, enable);
+}
+
+static int skl_clk_prepare(struct clk_hw *hw)
+{
+ struct skl_clk *clkdev = to_skl_clk(hw);
+
+ return skl_clk_change_status(clkdev, true);
+}
+
+static void skl_clk_unprepare(struct clk_hw *hw)
+{
+ struct skl_clk *clkdev = to_skl_clk(hw);
+
+ skl_clk_change_status(clkdev, false);
+}
+
+static int skl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct skl_clk *clkdev = to_skl_clk(hw);
+ struct skl_clk_rate_cfg_table *rcfg;
+ int clk_type;
+
+ if (!rate)
+ return -EINVAL;
+
+ rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg,
+ rate);
+ if (!rcfg)
+ return -EINVAL;
+
+ clk_type = skl_get_clk_type(clkdev->id);
+ if (clk_type < 0)
+ return clk_type;
+
+ skl_fill_clk_ipc(rcfg, clk_type);
+ clkdev->rate = rate;
+
+ return 0;
+}
+
+static unsigned long skl_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct skl_clk *clkdev = to_skl_clk(hw);
+
+ if (clkdev->rate)
+ return clkdev->rate;
+
+ return 0;
+}
+
+/* Not supported by clk driver. Implemented to satisfy clk fw */
+static long skl_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return rate;
+}
+
+/*
+ * prepare/unprepare are used instead of enable/disable as IPC will be sent
+ * in non-atomic context.
+ */
+static const struct clk_ops skl_clk_ops = {
+ .prepare = skl_clk_prepare,
+ .unprepare = skl_clk_unprepare,
+ .set_rate = skl_clk_set_rate,
+ .round_rate = skl_clk_round_rate,
+ .recalc_rate = skl_clk_recalc_rate,
+};
+
+static void unregister_parent_src_clk(struct skl_clk_parent *pclk,
+ unsigned int id)
+{
+ while (id--) {
+ clkdev_drop(pclk[id].lookup);
+ clk_hw_unregister_fixed_rate(pclk[id].hw);
+ }
+}
+
+static void unregister_src_clk(struct skl_clk_data *dclk)
+{
+ while (dclk->avail_clk_cnt--)
+ clkdev_drop(dclk->clk[dclk->avail_clk_cnt]->lookup);
+}
+
+static int skl_register_parent_clks(struct device *dev,
+ struct skl_clk_parent *parent,
+ struct skl_clk_parent_src *pclk)
+{
+ int i, ret;
+
+ for (i = 0; i < SKL_MAX_CLK_SRC; i++) {
+
+ /* Register Parent clock */
+ parent[i].hw = clk_hw_register_fixed_rate(dev, pclk[i].name,
+ pclk[i].parent_name, 0, pclk[i].rate);
+ if (IS_ERR(parent[i].hw)) {
+ ret = PTR_ERR(parent[i].hw);
+ goto err;
+ }
+
+ parent[i].lookup = clkdev_hw_create(parent[i].hw, pclk[i].name,
+ NULL);
+ if (!parent[i].lookup) {
+ clk_hw_unregister_fixed_rate(parent[i].hw);
+ ret = -ENOMEM;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ unregister_parent_src_clk(parent, i);
+ return ret;
+}
+
+/* Assign fmt_config to clk_data */
+static struct skl_clk *register_skl_clk(struct device *dev,
+ struct skl_ssp_clk *clk,
+ struct skl_clk_pdata *clk_pdata, int id)
+{
+ struct clk_init_data init;
+ struct skl_clk *clkdev;
+ int ret;
+
+ clkdev = devm_kzalloc(dev, sizeof(*clkdev), GFP_KERNEL);
+ if (!clkdev)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = clk->name;
+ init.ops = &skl_clk_ops;
+ init.flags = CLK_SET_RATE_GATE;
+ init.parent_names = &clk->parent_name;
+ init.num_parents = 1;
+ clkdev->hw.init = &init;
+ clkdev->pdata = clk_pdata;
+
+ clkdev->id = id;
+ ret = devm_clk_hw_register(dev, &clkdev->hw);
+ if (ret) {
+ clkdev = ERR_PTR(ret);
+ return clkdev;
+ }
+
+ clkdev->lookup = clkdev_hw_create(&clkdev->hw, init.name, NULL);
+ if (!clkdev->lookup)
+ clkdev = ERR_PTR(-ENOMEM);
+
+ return clkdev;
+}
+
+static int skl_clk_dev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *parent_dev = dev->parent;
+ struct skl_clk_parent_src *parent_clks;
+ struct skl_clk_pdata *clk_pdata;
+ struct skl_clk_data *data;
+ struct skl_ssp_clk *clks;
+ int ret, i;
+
+ clk_pdata = dev_get_platdata(&pdev->dev);
+ parent_clks = clk_pdata->parent_clks;
+ clks = clk_pdata->ssp_clks;
+ if (!parent_clks || !clks)
+ return -EIO;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* Register Parent clock */
+ ret = skl_register_parent_clks(parent_dev, data->parent, parent_clks);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < clk_pdata->num_clks; i++) {
+ /*
+ * Only register valid clocks
+ * i.e. for which nhlt entry is present.
+ */
+ if (clks[i].rate_cfg[0].rate == 0)
+ continue;
+
+ data->clk[data->avail_clk_cnt] = register_skl_clk(dev,
+ &clks[i], clk_pdata, i);
+
+ if (IS_ERR(data->clk[data->avail_clk_cnt])) {
+ ret = PTR_ERR(data->clk[data->avail_clk_cnt]);
+ goto err_unreg_skl_clk;
+ }
+
+ data->avail_clk_cnt++;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+
+err_unreg_skl_clk:
+ unregister_src_clk(data);
+ unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC);
+
+ return ret;
+}
+
+static int skl_clk_dev_remove(struct platform_device *pdev)
+{
+ struct skl_clk_data *data;
+
+ data = platform_get_drvdata(pdev);
+ unregister_src_clk(data);
+ unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC);
+
+ return 0;
+}
+
+static struct platform_driver skl_clk_driver = {
+ .driver = {
+ .name = "skl-ssp-clk",
+ },
+ .probe = skl_clk_dev_probe,
+ .remove = skl_clk_dev_remove,
+};
+
+module_platform_driver(skl_clk_driver);
+
+MODULE_DESCRIPTION("Skylake clock driver");
+MODULE_AUTHOR("Jaikrishna Nemallapudi <jaikrishnax.nemallapudi@intel.com>");
+MODULE_AUTHOR("Subhransu S. Prusty <subhransu.s.prusty@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl-ssp-clk");
diff --git a/sound/soc/intel/skylake/skl-ssp-clk.h b/sound/soc/intel/skylake/skl-ssp-clk.h
new file mode 100644
index 000000000..b7852c7f2
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-ssp-clk.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * skl-ssp-clk.h - Skylake ssp clock information and ipc structure
+ *
+ * Copyright (C) 2017 Intel Corp
+ * Author: Jaikrishna Nemallapudi <jaikrishnax.nemallapudi@intel.com>
+ * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef SOUND_SOC_SKL_SSP_CLK_H
+#define SOUND_SOC_SKL_SSP_CLK_H
+
+#define SKL_MAX_SSP 6
+/* xtal/cardinal/pll, parent of ssp clocks and mclk */
+#define SKL_MAX_CLK_SRC 3
+#define SKL_MAX_SSP_CLK_TYPES 3 /* mclk, sclk, sclkfs */
+
+#define SKL_MAX_CLK_CNT (SKL_MAX_SSP * SKL_MAX_SSP_CLK_TYPES)
+
+/* Max number of configurations supported for each clock */
+#define SKL_MAX_CLK_RATES 10
+
+#define SKL_SCLK_OFS SKL_MAX_SSP
+#define SKL_SCLKFS_OFS (SKL_SCLK_OFS + SKL_MAX_SSP)
+
+enum skl_clk_type {
+ SKL_MCLK,
+ SKL_SCLK,
+ SKL_SCLK_FS,
+};
+
+enum skl_clk_src_type {
+ SKL_XTAL,
+ SKL_CARDINAL,
+ SKL_PLL,
+};
+
+struct skl_clk_parent_src {
+ u8 clk_id;
+ const char *name;
+ unsigned long rate;
+ const char *parent_name;
+};
+
+struct skl_tlv_hdr {
+ u32 type;
+ u32 size;
+};
+
+struct skl_dmactrl_mclk_cfg {
+ struct skl_tlv_hdr hdr;
+ /* DMA Clk TLV params */
+ u32 clk_warm_up:16;
+ u32 mclk:1;
+ u32 warm_up_over:1;
+ u32 rsvd0:14;
+ u32 clk_stop_delay:16;
+ u32 keep_running:1;
+ u32 clk_stop_over:1;
+ u32 rsvd1:14;
+};
+
+struct skl_dmactrl_sclkfs_cfg {
+ struct skl_tlv_hdr hdr;
+ /* DMA SClk&FS TLV params */
+ u32 sampling_frequency;
+ u32 bit_depth;
+ u32 channel_map;
+ u32 channel_config;
+ u32 interleaving_style;
+ u32 number_of_channels : 8;
+ u32 valid_bit_depth : 8;
+ u32 sample_type : 8;
+ u32 reserved : 8;
+};
+
+union skl_clk_ctrl_ipc {
+ struct skl_dmactrl_mclk_cfg mclk;
+ struct skl_dmactrl_sclkfs_cfg sclk_fs;
+};
+
+struct skl_clk_rate_cfg_table {
+ unsigned long rate;
+ union skl_clk_ctrl_ipc dma_ctl_ipc;
+ void *config;
+};
+
+/*
+ * rate for mclk will be in rates[0]. For sclk and sclkfs, rates[] store
+ * all possible clocks ssp can generate for that platform.
+ */
+struct skl_ssp_clk {
+ const char *name;
+ const char *parent_name;
+ struct skl_clk_rate_cfg_table rate_cfg[SKL_MAX_CLK_RATES];
+};
+
+struct skl_clk_pdata {
+ struct skl_clk_parent_src *parent_clks;
+ int num_clks;
+ struct skl_ssp_clk *ssp_clks;
+ void *pvt_data;
+};
+
+#endif /* SOUND_SOC_SKL_SSP_CLK_H */
diff --git a/sound/soc/intel/skylake/skl-sst-cldma.c b/sound/soc/intel/skylake/skl-sst-cldma.c
new file mode 100644
index 000000000..b91f7a652
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-cldma.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-sst-cldma.c - Code Loader DMA handler
+ *
+ * Copyright (C) 2015, Intel Corporation.
+ * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+
+static void skl_cldma_int_enable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPIC,
+ SKL_ADSPIC_CL_DMA, SKL_ADSPIC_CL_DMA);
+}
+
+void skl_cldma_int_disable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ SKL_ADSP_REG_ADSPIC, SKL_ADSPIC_CL_DMA, 0);
+}
+
+static void skl_cldma_stream_run(struct sst_dsp *ctx, bool enable)
+{
+ unsigned char val;
+ int timeout;
+
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_RUN_MASK, CL_SD_CTL_RUN(enable));
+
+ udelay(3);
+ timeout = 300;
+ do {
+ /* waiting for hardware to report that the stream Run bit set */
+ val = sst_dsp_shim_read(ctx, SKL_ADSP_REG_CL_SD_CTL) &
+ CL_SD_CTL_RUN_MASK;
+ if (enable && val)
+ break;
+ else if (!enable && !val)
+ break;
+ udelay(3);
+ } while (--timeout);
+
+ if (timeout == 0)
+ dev_err(ctx->dev, "Failed to set Run bit=%d enable=%d\n", val, enable);
+}
+
+static void skl_cldma_stream_clear(struct sst_dsp *ctx)
+{
+ /* make sure Run bit is cleared before setting stream register */
+ skl_cldma_stream_run(ctx, 0);
+
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(0));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(0));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(0));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(0));
+
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL, CL_SD_BDLPLBA(0));
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU, 0);
+
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, 0);
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, 0);
+}
+
+/* Code loader helper APIs */
+static void skl_cldma_setup_bdle(struct sst_dsp *ctx,
+ struct snd_dma_buffer *dmab_data,
+ __le32 **bdlp, int size, int with_ioc)
+{
+ __le32 *bdl = *bdlp;
+
+ ctx->cl_dev.frags = 0;
+ while (size > 0) {
+ phys_addr_t addr = virt_to_phys(dmab_data->area +
+ (ctx->cl_dev.frags * ctx->cl_dev.bufsize));
+
+ bdl[0] = cpu_to_le32(lower_32_bits(addr));
+ bdl[1] = cpu_to_le32(upper_32_bits(addr));
+
+ bdl[2] = cpu_to_le32(ctx->cl_dev.bufsize);
+
+ size -= ctx->cl_dev.bufsize;
+ bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01);
+
+ bdl += 4;
+ ctx->cl_dev.frags++;
+ }
+}
+
+/*
+ * Setup controller
+ * Configure the registers to update the dma buffer address and
+ * enable interrupts.
+ * Note: Using the channel 1 for transfer
+ */
+static void skl_cldma_setup_controller(struct sst_dsp *ctx,
+ struct snd_dma_buffer *dmab_bdl, unsigned int max_size,
+ u32 count)
+{
+ skl_cldma_stream_clear(ctx);
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL,
+ CL_SD_BDLPLBA(dmab_bdl->addr));
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU,
+ CL_SD_BDLPUBA(dmab_bdl->addr));
+
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, max_size);
+ sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, count - 1);
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(1));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(1));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(1));
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL,
+ CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(FW_CL_STREAM_NUMBER));
+}
+
+static void skl_cldma_setup_spb(struct sst_dsp *ctx,
+ unsigned int size, bool enable)
+{
+ if (enable)
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
+ CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
+ CL_SPBFIFO_SPBFCCTL_SPIBE(1));
+
+ sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_CL_SPBFIFO_SPIB, size);
+}
+
+static void skl_cldma_cleanup_spb(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
+ CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
+ CL_SPBFIFO_SPBFCCTL_SPIBE(0));
+
+ sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_CL_SPBFIFO_SPIB, 0);
+}
+
+static void skl_cldma_cleanup(struct sst_dsp *ctx)
+{
+ skl_cldma_cleanup_spb(ctx);
+ skl_cldma_stream_clear(ctx);
+
+ ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_data);
+ ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_bdl);
+}
+
+int skl_cldma_wait_interruptible(struct sst_dsp *ctx)
+{
+ int ret = 0;
+
+ if (!wait_event_timeout(ctx->cl_dev.wait_queue,
+ ctx->cl_dev.wait_condition,
+ msecs_to_jiffies(SKL_WAIT_TIMEOUT))) {
+ dev_err(ctx->dev, "%s: Wait timeout\n", __func__);
+ ret = -EIO;
+ goto cleanup;
+ }
+
+ dev_dbg(ctx->dev, "%s: Event wake\n", __func__);
+ if (ctx->cl_dev.wake_status != SKL_CL_DMA_BUF_COMPLETE) {
+ dev_err(ctx->dev, "%s: DMA Error\n", __func__);
+ ret = -EIO;
+ }
+
+cleanup:
+ ctx->cl_dev.wake_status = SKL_CL_DMA_STATUS_NONE;
+ return ret;
+}
+
+static void skl_cldma_stop(struct sst_dsp *ctx)
+{
+ skl_cldma_stream_run(ctx, false);
+}
+
+static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size,
+ const void *curr_pos, bool intr_enable, bool trigger)
+{
+ dev_dbg(ctx->dev, "Size: %x, intr_enable: %d\n", size, intr_enable);
+ dev_dbg(ctx->dev, "buf_pos_index:%d, trigger:%d\n",
+ ctx->cl_dev.dma_buffer_offset, trigger);
+ dev_dbg(ctx->dev, "spib position: %d\n", ctx->cl_dev.curr_spib_pos);
+
+ /*
+ * Check if the size exceeds buffer boundary. If it exceeds
+ * max_buffer size, then copy till buffer size and then copy
+ * remaining buffer from the start of ring buffer.
+ */
+ if (ctx->cl_dev.dma_buffer_offset + size > ctx->cl_dev.bufsize) {
+ unsigned int size_b = ctx->cl_dev.bufsize -
+ ctx->cl_dev.dma_buffer_offset;
+ memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset,
+ curr_pos, size_b);
+ size -= size_b;
+ curr_pos += size_b;
+ ctx->cl_dev.dma_buffer_offset = 0;
+ }
+
+ memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset,
+ curr_pos, size);
+
+ if (ctx->cl_dev.curr_spib_pos == ctx->cl_dev.bufsize)
+ ctx->cl_dev.dma_buffer_offset = 0;
+ else
+ ctx->cl_dev.dma_buffer_offset = ctx->cl_dev.curr_spib_pos;
+
+ ctx->cl_dev.wait_condition = false;
+
+ if (intr_enable)
+ skl_cldma_int_enable(ctx);
+
+ ctx->cl_dev.ops.cl_setup_spb(ctx, ctx->cl_dev.curr_spib_pos, trigger);
+ if (trigger)
+ ctx->cl_dev.ops.cl_trigger(ctx, true);
+}
+
+/*
+ * The CL dma doesn't have any way to update the transfer status until a BDL
+ * buffer is fully transferred
+ *
+ * So Copying is divided in two parts.
+ * 1. Interrupt on buffer done where the size to be transferred is more than
+ * ring buffer size.
+ * 2. Polling on fw register to identify if data left to transferred doesn't
+ * fill the ring buffer. Caller takes care of polling the required status
+ * register to identify the transfer status.
+ * 3. if wait flag is set, waits for DBL interrupt to copy the next chunk till
+ * bytes_left is 0.
+ * if wait flag is not set, doesn't wait for BDL interrupt. after ccopying
+ * the first chunk return the no of bytes_left to be copied.
+ */
+static int
+skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin,
+ u32 total_size, bool wait)
+{
+ int ret;
+ bool start = true;
+ unsigned int excess_bytes;
+ u32 size;
+ unsigned int bytes_left = total_size;
+ const void *curr_pos = bin;
+
+ if (total_size <= 0)
+ return -EINVAL;
+
+ dev_dbg(ctx->dev, "%s: Total binary size: %u\n", __func__, bytes_left);
+
+ while (bytes_left) {
+ if (bytes_left > ctx->cl_dev.bufsize) {
+
+ /*
+ * dma transfers only till the write pointer as
+ * updated in spib
+ */
+ if (ctx->cl_dev.curr_spib_pos == 0)
+ ctx->cl_dev.curr_spib_pos = ctx->cl_dev.bufsize;
+
+ size = ctx->cl_dev.bufsize;
+ skl_cldma_fill_buffer(ctx, size, curr_pos, true, start);
+
+ if (wait) {
+ start = false;
+ ret = skl_cldma_wait_interruptible(ctx);
+ if (ret < 0) {
+ skl_cldma_stop(ctx);
+ return ret;
+ }
+ }
+ } else {
+ skl_cldma_int_disable(ctx);
+
+ if ((ctx->cl_dev.curr_spib_pos + bytes_left)
+ <= ctx->cl_dev.bufsize) {
+ ctx->cl_dev.curr_spib_pos += bytes_left;
+ } else {
+ excess_bytes = bytes_left -
+ (ctx->cl_dev.bufsize -
+ ctx->cl_dev.curr_spib_pos);
+ ctx->cl_dev.curr_spib_pos = excess_bytes;
+ }
+
+ size = bytes_left;
+ skl_cldma_fill_buffer(ctx, size,
+ curr_pos, false, start);
+ }
+ bytes_left -= size;
+ curr_pos = curr_pos + size;
+ if (!wait)
+ return bytes_left;
+ }
+
+ return bytes_left;
+}
+
+void skl_cldma_process_intr(struct sst_dsp *ctx)
+{
+ u8 cl_dma_intr_status;
+
+ cl_dma_intr_status =
+ sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_CL_SD_STS);
+
+ if (!(cl_dma_intr_status & SKL_CL_DMA_SD_INT_COMPLETE))
+ ctx->cl_dev.wake_status = SKL_CL_DMA_ERR;
+ else
+ ctx->cl_dev.wake_status = SKL_CL_DMA_BUF_COMPLETE;
+
+ ctx->cl_dev.wait_condition = true;
+ wake_up(&ctx->cl_dev.wait_queue);
+}
+
+int skl_cldma_prepare(struct sst_dsp *ctx)
+{
+ int ret;
+ __le32 *bdl;
+
+ ctx->cl_dev.bufsize = SKL_MAX_BUFFER_SIZE;
+
+ /* Allocate cl ops */
+ ctx->cl_dev.ops.cl_setup_bdle = skl_cldma_setup_bdle;
+ ctx->cl_dev.ops.cl_setup_controller = skl_cldma_setup_controller;
+ ctx->cl_dev.ops.cl_setup_spb = skl_cldma_setup_spb;
+ ctx->cl_dev.ops.cl_cleanup_spb = skl_cldma_cleanup_spb;
+ ctx->cl_dev.ops.cl_trigger = skl_cldma_stream_run;
+ ctx->cl_dev.ops.cl_cleanup_controller = skl_cldma_cleanup;
+ ctx->cl_dev.ops.cl_copy_to_dmabuf = skl_cldma_copy_to_buf;
+ ctx->cl_dev.ops.cl_stop_dma = skl_cldma_stop;
+
+ /* Allocate buffer*/
+ ret = ctx->dsp_ops.alloc_dma_buf(ctx->dev,
+ &ctx->cl_dev.dmab_data, ctx->cl_dev.bufsize);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Alloc buffer for base fw failed: %x\n", ret);
+ return ret;
+ }
+ /* Setup Code loader BDL */
+ ret = ctx->dsp_ops.alloc_dma_buf(ctx->dev,
+ &ctx->cl_dev.dmab_bdl, PAGE_SIZE);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Alloc buffer for blde failed: %x\n", ret);
+ ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_data);
+ return ret;
+ }
+ bdl = (__le32 *)ctx->cl_dev.dmab_bdl.area;
+
+ /* Allocate BDLs */
+ ctx->cl_dev.ops.cl_setup_bdle(ctx, &ctx->cl_dev.dmab_data,
+ &bdl, ctx->cl_dev.bufsize, 1);
+ ctx->cl_dev.ops.cl_setup_controller(ctx, &ctx->cl_dev.dmab_bdl,
+ ctx->cl_dev.bufsize, ctx->cl_dev.frags);
+
+ ctx->cl_dev.curr_spib_pos = 0;
+ ctx->cl_dev.dma_buffer_offset = 0;
+ init_waitqueue_head(&ctx->cl_dev.wait_queue);
+
+ return ret;
+}
diff --git a/sound/soc/intel/skylake/skl-sst-cldma.h b/sound/soc/intel/skylake/skl-sst-cldma.h
new file mode 100644
index 000000000..d5e285a69
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-cldma.h
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Code Loader DMA support
+ *
+ * Copyright (C) 2015, Intel Corporation.
+ */
+
+#ifndef SKL_SST_CLDMA_H_
+#define SKL_SST_CLDMA_H_
+
+#define FW_CL_STREAM_NUMBER 0x1
+
+#define DMA_ADDRESS_128_BITS_ALIGNMENT 7
+#define BDL_ALIGN(x) (x >> DMA_ADDRESS_128_BITS_ALIGNMENT)
+
+#define SKL_ADSPIC_CL_DMA 0x2
+#define SKL_ADSPIS_CL_DMA 0x2
+#define SKL_CL_DMA_SD_INT_DESC_ERR 0x10 /* Descriptor error interrupt */
+#define SKL_CL_DMA_SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
+#define SKL_CL_DMA_SD_INT_COMPLETE 0x04 /* Buffer completion interrupt */
+
+/* Intel HD Audio Code Loader DMA Registers */
+
+#define HDA_ADSP_LOADER_BASE 0x80
+
+/* Stream Registers */
+#define SKL_ADSP_REG_CL_SD_CTL (HDA_ADSP_LOADER_BASE + 0x00)
+#define SKL_ADSP_REG_CL_SD_STS (HDA_ADSP_LOADER_BASE + 0x03)
+#define SKL_ADSP_REG_CL_SD_LPIB (HDA_ADSP_LOADER_BASE + 0x04)
+#define SKL_ADSP_REG_CL_SD_CBL (HDA_ADSP_LOADER_BASE + 0x08)
+#define SKL_ADSP_REG_CL_SD_LVI (HDA_ADSP_LOADER_BASE + 0x0c)
+#define SKL_ADSP_REG_CL_SD_FIFOW (HDA_ADSP_LOADER_BASE + 0x0e)
+#define SKL_ADSP_REG_CL_SD_FIFOSIZE (HDA_ADSP_LOADER_BASE + 0x10)
+#define SKL_ADSP_REG_CL_SD_FORMAT (HDA_ADSP_LOADER_BASE + 0x12)
+#define SKL_ADSP_REG_CL_SD_FIFOL (HDA_ADSP_LOADER_BASE + 0x14)
+#define SKL_ADSP_REG_CL_SD_BDLPL (HDA_ADSP_LOADER_BASE + 0x18)
+#define SKL_ADSP_REG_CL_SD_BDLPU (HDA_ADSP_LOADER_BASE + 0x1c)
+
+/* CL: Software Position Based FIFO Capability Registers */
+#define SKL_ADSP_REG_CL_SPBFIFO (HDA_ADSP_LOADER_BASE + 0x20)
+#define SKL_ADSP_REG_CL_SPBFIFO_SPBFCH (SKL_ADSP_REG_CL_SPBFIFO + 0x0)
+#define SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL (SKL_ADSP_REG_CL_SPBFIFO + 0x4)
+#define SKL_ADSP_REG_CL_SPBFIFO_SPIB (SKL_ADSP_REG_CL_SPBFIFO + 0x8)
+#define SKL_ADSP_REG_CL_SPBFIFO_MAXFIFOS (SKL_ADSP_REG_CL_SPBFIFO + 0xc)
+
+/* CL: Stream Descriptor x Control */
+
+/* Stream Reset */
+#define CL_SD_CTL_SRST_SHIFT 0
+#define CL_SD_CTL_SRST_MASK (1 << CL_SD_CTL_SRST_SHIFT)
+#define CL_SD_CTL_SRST(x) \
+ ((x << CL_SD_CTL_SRST_SHIFT) & CL_SD_CTL_SRST_MASK)
+
+/* Stream Run */
+#define CL_SD_CTL_RUN_SHIFT 1
+#define CL_SD_CTL_RUN_MASK (1 << CL_SD_CTL_RUN_SHIFT)
+#define CL_SD_CTL_RUN(x) \
+ ((x << CL_SD_CTL_RUN_SHIFT) & CL_SD_CTL_RUN_MASK)
+
+/* Interrupt On Completion Enable */
+#define CL_SD_CTL_IOCE_SHIFT 2
+#define CL_SD_CTL_IOCE_MASK (1 << CL_SD_CTL_IOCE_SHIFT)
+#define CL_SD_CTL_IOCE(x) \
+ ((x << CL_SD_CTL_IOCE_SHIFT) & CL_SD_CTL_IOCE_MASK)
+
+/* FIFO Error Interrupt Enable */
+#define CL_SD_CTL_FEIE_SHIFT 3
+#define CL_SD_CTL_FEIE_MASK (1 << CL_SD_CTL_FEIE_SHIFT)
+#define CL_SD_CTL_FEIE(x) \
+ ((x << CL_SD_CTL_FEIE_SHIFT) & CL_SD_CTL_FEIE_MASK)
+
+/* Descriptor Error Interrupt Enable */
+#define CL_SD_CTL_DEIE_SHIFT 4
+#define CL_SD_CTL_DEIE_MASK (1 << CL_SD_CTL_DEIE_SHIFT)
+#define CL_SD_CTL_DEIE(x) \
+ ((x << CL_SD_CTL_DEIE_SHIFT) & CL_SD_CTL_DEIE_MASK)
+
+/* FIFO Limit Change */
+#define CL_SD_CTL_FIFOLC_SHIFT 5
+#define CL_SD_CTL_FIFOLC_MASK (1 << CL_SD_CTL_FIFOLC_SHIFT)
+#define CL_SD_CTL_FIFOLC(x) \
+ ((x << CL_SD_CTL_FIFOLC_SHIFT) & CL_SD_CTL_FIFOLC_MASK)
+
+/* Stripe Control */
+#define CL_SD_CTL_STRIPE_SHIFT 16
+#define CL_SD_CTL_STRIPE_MASK (0x3 << CL_SD_CTL_STRIPE_SHIFT)
+#define CL_SD_CTL_STRIPE(x) \
+ ((x << CL_SD_CTL_STRIPE_SHIFT) & CL_SD_CTL_STRIPE_MASK)
+
+/* Traffic Priority */
+#define CL_SD_CTL_TP_SHIFT 18
+#define CL_SD_CTL_TP_MASK (1 << CL_SD_CTL_TP_SHIFT)
+#define CL_SD_CTL_TP(x) \
+ ((x << CL_SD_CTL_TP_SHIFT) & CL_SD_CTL_TP_MASK)
+
+/* Bidirectional Direction Control */
+#define CL_SD_CTL_DIR_SHIFT 19
+#define CL_SD_CTL_DIR_MASK (1 << CL_SD_CTL_DIR_SHIFT)
+#define CL_SD_CTL_DIR(x) \
+ ((x << CL_SD_CTL_DIR_SHIFT) & CL_SD_CTL_DIR_MASK)
+
+/* Stream Number */
+#define CL_SD_CTL_STRM_SHIFT 20
+#define CL_SD_CTL_STRM_MASK (0xf << CL_SD_CTL_STRM_SHIFT)
+#define CL_SD_CTL_STRM(x) \
+ ((x << CL_SD_CTL_STRM_SHIFT) & CL_SD_CTL_STRM_MASK)
+
+/* CL: Stream Descriptor x Status */
+
+/* Buffer Completion Interrupt Status */
+#define CL_SD_STS_BCIS(x) CL_SD_CTL_IOCE(x)
+
+/* FIFO Error */
+#define CL_SD_STS_FIFOE(x) CL_SD_CTL_FEIE(x)
+
+/* Descriptor Error */
+#define CL_SD_STS_DESE(x) CL_SD_CTL_DEIE(x)
+
+/* FIFO Ready */
+#define CL_SD_STS_FIFORDY(x) CL_SD_CTL_FIFOLC(x)
+
+
+/* CL: Stream Descriptor x Last Valid Index */
+#define CL_SD_LVI_SHIFT 0
+#define CL_SD_LVI_MASK (0xff << CL_SD_LVI_SHIFT)
+#define CL_SD_LVI(x) ((x << CL_SD_LVI_SHIFT) & CL_SD_LVI_MASK)
+
+/* CL: Stream Descriptor x FIFO Eviction Watermark */
+#define CL_SD_FIFOW_SHIFT 0
+#define CL_SD_FIFOW_MASK (0x7 << CL_SD_FIFOW_SHIFT)
+#define CL_SD_FIFOW(x) \
+ ((x << CL_SD_FIFOW_SHIFT) & CL_SD_FIFOW_MASK)
+
+/* CL: Stream Descriptor x Buffer Descriptor List Pointer Lower Base Address */
+
+/* Protect Bits */
+#define CL_SD_BDLPLBA_PROT_SHIFT 0
+#define CL_SD_BDLPLBA_PROT_MASK (1 << CL_SD_BDLPLBA_PROT_SHIFT)
+#define CL_SD_BDLPLBA_PROT(x) \
+ ((x << CL_SD_BDLPLBA_PROT_SHIFT) & CL_SD_BDLPLBA_PROT_MASK)
+
+/* Buffer Descriptor List Lower Base Address */
+#define CL_SD_BDLPLBA_SHIFT 7
+#define CL_SD_BDLPLBA_MASK (0x1ffffff << CL_SD_BDLPLBA_SHIFT)
+#define CL_SD_BDLPLBA(x) \
+ ((BDL_ALIGN(lower_32_bits(x)) << CL_SD_BDLPLBA_SHIFT) & CL_SD_BDLPLBA_MASK)
+
+/* Buffer Descriptor List Upper Base Address */
+#define CL_SD_BDLPUBA_SHIFT 0
+#define CL_SD_BDLPUBA_MASK (0xffffffff << CL_SD_BDLPUBA_SHIFT)
+#define CL_SD_BDLPUBA(x) \
+ ((upper_32_bits(x) << CL_SD_BDLPUBA_SHIFT) & CL_SD_BDLPUBA_MASK)
+
+/*
+ * Code Loader - Software Position Based FIFO
+ * Capability Registers x Software Position Based FIFO Header
+ */
+
+/* Next Capability Pointer */
+#define CL_SPBFIFO_SPBFCH_PTR_SHIFT 0
+#define CL_SPBFIFO_SPBFCH_PTR_MASK (0xff << CL_SPBFIFO_SPBFCH_PTR_SHIFT)
+#define CL_SPBFIFO_SPBFCH_PTR(x) \
+ ((x << CL_SPBFIFO_SPBFCH_PTR_SHIFT) & CL_SPBFIFO_SPBFCH_PTR_MASK)
+
+/* Capability Identifier */
+#define CL_SPBFIFO_SPBFCH_ID_SHIFT 16
+#define CL_SPBFIFO_SPBFCH_ID_MASK (0xfff << CL_SPBFIFO_SPBFCH_ID_SHIFT)
+#define CL_SPBFIFO_SPBFCH_ID(x) \
+ ((x << CL_SPBFIFO_SPBFCH_ID_SHIFT) & CL_SPBFIFO_SPBFCH_ID_MASK)
+
+/* Capability Version */
+#define CL_SPBFIFO_SPBFCH_VER_SHIFT 28
+#define CL_SPBFIFO_SPBFCH_VER_MASK (0xf << CL_SPBFIFO_SPBFCH_VER_SHIFT)
+#define CL_SPBFIFO_SPBFCH_VER(x) \
+ ((x << CL_SPBFIFO_SPBFCH_VER_SHIFT) & CL_SPBFIFO_SPBFCH_VER_MASK)
+
+/* Software Position in Buffer Enable */
+#define CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT 0
+#define CL_SPBFIFO_SPBFCCTL_SPIBE_MASK (1 << CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT)
+#define CL_SPBFIFO_SPBFCCTL_SPIBE(x) \
+ ((x << CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) & CL_SPBFIFO_SPBFCCTL_SPIBE_MASK)
+
+/* SST IPC SKL defines */
+#define SKL_WAIT_TIMEOUT 500 /* 500 msec */
+#define SKL_MAX_BUFFER_SIZE (32 * PAGE_SIZE)
+
+enum skl_cl_dma_wake_states {
+ SKL_CL_DMA_STATUS_NONE = 0,
+ SKL_CL_DMA_BUF_COMPLETE,
+ SKL_CL_DMA_ERR, /* TODO: Expand the error states */
+};
+
+struct sst_dsp;
+
+struct skl_cl_dev_ops {
+ void (*cl_setup_bdle)(struct sst_dsp *ctx,
+ struct snd_dma_buffer *dmab_data,
+ __le32 **bdlp, int size, int with_ioc);
+ void (*cl_setup_controller)(struct sst_dsp *ctx,
+ struct snd_dma_buffer *dmab_bdl,
+ unsigned int max_size, u32 page_count);
+ void (*cl_setup_spb)(struct sst_dsp *ctx,
+ unsigned int size, bool enable);
+ void (*cl_cleanup_spb)(struct sst_dsp *ctx);
+ void (*cl_trigger)(struct sst_dsp *ctx, bool enable);
+ void (*cl_cleanup_controller)(struct sst_dsp *ctx);
+ int (*cl_copy_to_dmabuf)(struct sst_dsp *ctx,
+ const void *bin, u32 size, bool wait);
+ void (*cl_stop_dma)(struct sst_dsp *ctx);
+};
+
+/**
+ * skl_cl_dev - holds information for code loader dma transfer
+ *
+ * @dmab_data: buffer pointer
+ * @dmab_bdl: buffer descriptor list
+ * @bufsize: ring buffer size
+ * @frags: Last valid buffer descriptor index in the BDL
+ * @curr_spib_pos: Current position in ring buffer
+ * @dma_buffer_offset: dma buffer offset
+ * @ops: operations supported on CL dma
+ * @wait_queue: wait queue to wake for wake event
+ * @wake_status: DMA wake status
+ * @wait_condition: condition to wait on wait queue
+ * @cl_dma_lock: for synchronized access to cldma
+ */
+struct skl_cl_dev {
+ struct snd_dma_buffer dmab_data;
+ struct snd_dma_buffer dmab_bdl;
+
+ unsigned int bufsize;
+ unsigned int frags;
+
+ unsigned int curr_spib_pos;
+ unsigned int dma_buffer_offset;
+ struct skl_cl_dev_ops ops;
+
+ wait_queue_head_t wait_queue;
+ int wake_status;
+ bool wait_condition;
+};
+
+#endif /* SKL_SST_CLDMA_H_ */
diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c
new file mode 100644
index 000000000..4ae3eae0d
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-dsp.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-sst-dsp.c - SKL SST library generic function
+ *
+ * Copyright (C) 2014-15, Intel Corporation.
+ * Author:Rafal Redzimski <rafal.f.redzimski@intel.com>
+ * Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <sound/pcm.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-ipc.h"
+#include "../common/sst-dsp-priv.h"
+#include "skl.h"
+
+/* various timeout values */
+#define SKL_DSP_PU_TO 50
+#define SKL_DSP_PD_TO 50
+#define SKL_DSP_RESET_TO 50
+
+void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state)
+{
+ mutex_lock(&ctx->mutex);
+ ctx->sst_state = state;
+ mutex_unlock(&ctx->mutex);
+}
+
+/*
+ * Initialize core power state and usage count. To be called after
+ * successful first boot. Hence core 0 will be running and other cores
+ * will be reset
+ */
+void skl_dsp_init_core_state(struct sst_dsp *ctx)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ int i;
+
+ skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING;
+ skl->cores.usage_count[SKL_DSP_CORE0_ID] = 1;
+
+ for (i = SKL_DSP_CORE0_ID + 1; i < skl->cores.count; i++) {
+ skl->cores.state[i] = SKL_DSP_RESET;
+ skl->cores.usage_count[i] = 0;
+ }
+}
+
+/* Get the mask for all enabled cores */
+unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ unsigned int core_mask, en_cores_mask;
+ u32 val;
+
+ core_mask = SKL_DSP_CORES_MASK(skl->cores.count);
+
+ val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS);
+
+ /* Cores having CPA bit set */
+ en_cores_mask = (val & SKL_ADSPCS_CPA_MASK(core_mask)) >>
+ SKL_ADSPCS_CPA_SHIFT;
+
+ /* And cores having CRST bit cleared */
+ en_cores_mask &= (~val & SKL_ADSPCS_CRST_MASK(core_mask)) >>
+ SKL_ADSPCS_CRST_SHIFT;
+
+ /* And cores having CSTALL bit cleared */
+ en_cores_mask &= (~val & SKL_ADSPCS_CSTALL_MASK(core_mask)) >>
+ SKL_ADSPCS_CSTALL_SHIFT;
+ en_cores_mask &= core_mask;
+
+ dev_dbg(ctx->dev, "DSP enabled cores mask = %x\n", en_cores_mask);
+
+ return en_cores_mask;
+}
+
+static int
+skl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx,
+ SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK(core_mask),
+ SKL_ADSPCS_CRST_MASK(core_mask));
+
+ /* poll with timeout to check if operation successful */
+ ret = sst_dsp_register_poll(ctx,
+ SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CRST_MASK(core_mask),
+ SKL_ADSPCS_CRST_MASK(core_mask),
+ SKL_DSP_RESET_TO,
+ "Set reset");
+ if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) &
+ SKL_ADSPCS_CRST_MASK(core_mask)) !=
+ SKL_ADSPCS_CRST_MASK(core_mask)) {
+ dev_err(ctx->dev, "Set reset state failed: core_mask %x\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+int skl_dsp_core_unset_reset_state(
+ struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ dev_dbg(ctx->dev, "In %s\n", __func__);
+
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CRST_MASK(core_mask), 0);
+
+ /* poll with timeout to check if operation successful */
+ ret = sst_dsp_register_poll(ctx,
+ SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CRST_MASK(core_mask),
+ 0,
+ SKL_DSP_RESET_TO,
+ "Unset reset");
+
+ if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) &
+ SKL_ADSPCS_CRST_MASK(core_mask)) != 0) {
+ dev_err(ctx->dev, "Unset reset state failed: core_mask %x\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static bool
+is_skl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int val;
+ bool is_enable;
+
+ val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS);
+
+ is_enable = ((val & SKL_ADSPCS_CPA_MASK(core_mask)) &&
+ (val & SKL_ADSPCS_SPA_MASK(core_mask)) &&
+ !(val & SKL_ADSPCS_CRST_MASK(core_mask)) &&
+ !(val & SKL_ADSPCS_CSTALL_MASK(core_mask)));
+
+ dev_dbg(ctx->dev, "DSP core(s) enabled? %d : core_mask %x\n",
+ is_enable, core_mask);
+
+ return is_enable;
+}
+
+static int skl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* stall core */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CSTALL_MASK(core_mask),
+ SKL_ADSPCS_CSTALL_MASK(core_mask));
+
+ /* set reset state */
+ return skl_dsp_core_set_reset_state(ctx, core_mask);
+}
+
+int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* unset reset state */
+ ret = skl_dsp_core_unset_reset_state(ctx, core_mask);
+ if (ret < 0)
+ return ret;
+
+ /* run core */
+ dev_dbg(ctx->dev, "unstall/run core: core_mask = %x\n", core_mask);
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CSTALL_MASK(core_mask), 0);
+
+ if (!is_skl_dsp_core_enable(ctx, core_mask)) {
+ skl_dsp_reset_core(ctx, core_mask);
+ dev_err(ctx->dev, "DSP start core failed: core_mask %x\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_SPA_MASK(core_mask),
+ SKL_ADSPCS_SPA_MASK(core_mask));
+
+ /* poll with timeout to check if operation successful */
+ ret = sst_dsp_register_poll(ctx,
+ SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CPA_MASK(core_mask),
+ SKL_ADSPCS_CPA_MASK(core_mask),
+ SKL_DSP_PU_TO,
+ "Power up");
+
+ if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) &
+ SKL_ADSPCS_CPA_MASK(core_mask)) !=
+ SKL_ADSPCS_CPA_MASK(core_mask)) {
+ dev_err(ctx->dev, "DSP core power up failed: core_mask %x\n",
+ core_mask);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ /* update bits */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_SPA_MASK(core_mask), 0);
+
+ /* poll with timeout to check if operation successful */
+ return sst_dsp_register_poll(ctx,
+ SKL_ADSP_REG_ADSPCS,
+ SKL_ADSPCS_CPA_MASK(core_mask),
+ 0,
+ SKL_DSP_PD_TO,
+ "Power down");
+}
+
+int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ /* power up */
+ ret = skl_dsp_core_power_up(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core power up failed: core_mask %x\n",
+ core_mask);
+ return ret;
+ }
+
+ return skl_dsp_start_core(ctx, core_mask);
+}
+
+int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask)
+{
+ int ret;
+
+ ret = skl_dsp_reset_core(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core reset failed: core_mask %x\n",
+ core_mask);
+ return ret;
+ }
+
+ /* power down core*/
+ ret = skl_dsp_core_power_down(ctx, core_mask);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core power down fail mask %x: %d\n",
+ core_mask, ret);
+ return ret;
+ }
+
+ if (is_skl_dsp_core_enable(ctx, core_mask)) {
+ dev_err(ctx->dev, "dsp core disable fail mask %x: %d\n",
+ core_mask, ret);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+int skl_dsp_boot(struct sst_dsp *ctx)
+{
+ int ret;
+
+ if (is_skl_dsp_core_enable(ctx, SKL_DSP_CORE0_MASK)) {
+ ret = skl_dsp_reset_core(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core0 reset fail: %d\n", ret);
+ return ret;
+ }
+
+ ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core0 start fail: %d\n", ret);
+ return ret;
+ }
+ } else {
+ ret = skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "dsp core0 disable fail: %d\n", ret);
+ return ret;
+ }
+ ret = skl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK);
+ }
+
+ return ret;
+}
+
+irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id)
+{
+ struct sst_dsp *ctx = dev_id;
+ u32 val;
+ irqreturn_t result = IRQ_NONE;
+
+ spin_lock(&ctx->spinlock);
+
+ val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPIS);
+ ctx->intr_status = val;
+
+ if (val == 0xffffffff) {
+ spin_unlock(&ctx->spinlock);
+ return IRQ_NONE;
+ }
+
+ if (val & SKL_ADSPIS_IPC) {
+ skl_ipc_int_disable(ctx);
+ result = IRQ_WAKE_THREAD;
+ }
+
+ if (val & SKL_ADSPIS_CL_DMA) {
+ skl_cldma_int_disable(ctx);
+ result = IRQ_WAKE_THREAD;
+ }
+
+ spin_unlock(&ctx->spinlock);
+
+ return result;
+}
+/*
+ * skl_dsp_get_core/skl_dsp_put_core will be called inside DAPM context
+ * within the dapm mutex. Hence no separate lock is used.
+ */
+int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ int ret = 0;
+
+ if (core_id >= skl->cores.count) {
+ dev_err(ctx->dev, "invalid core id: %d\n", core_id);
+ return -EINVAL;
+ }
+
+ skl->cores.usage_count[core_id]++;
+
+ if (skl->cores.state[core_id] == SKL_DSP_RESET) {
+ ret = ctx->fw_ops.set_state_D0(ctx, core_id);
+ if (ret < 0) {
+ dev_err(ctx->dev, "unable to get core%d\n", core_id);
+ goto out;
+ }
+ }
+
+out:
+ dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n",
+ core_id, skl->cores.state[core_id],
+ skl->cores.usage_count[core_id]);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_dsp_get_core);
+
+int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ int ret = 0;
+
+ if (core_id >= skl->cores.count) {
+ dev_err(ctx->dev, "invalid core id: %d\n", core_id);
+ return -EINVAL;
+ }
+
+ if ((--skl->cores.usage_count[core_id] == 0) &&
+ (skl->cores.state[core_id] != SKL_DSP_RESET)) {
+ ret = ctx->fw_ops.set_state_D3(ctx, core_id);
+ if (ret < 0) {
+ dev_err(ctx->dev, "unable to put core %d: %d\n",
+ core_id, ret);
+ skl->cores.usage_count[core_id]++;
+ }
+ }
+
+ dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n",
+ core_id, skl->cores.state[core_id],
+ skl->cores.usage_count[core_id]);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_dsp_put_core);
+
+int skl_dsp_wake(struct sst_dsp *ctx)
+{
+ return skl_dsp_get_core(ctx, SKL_DSP_CORE0_ID);
+}
+EXPORT_SYMBOL_GPL(skl_dsp_wake);
+
+int skl_dsp_sleep(struct sst_dsp *ctx)
+{
+ return skl_dsp_put_core(ctx, SKL_DSP_CORE0_ID);
+}
+EXPORT_SYMBOL_GPL(skl_dsp_sleep);
+
+struct sst_dsp *skl_dsp_ctx_init(struct device *dev,
+ struct sst_dsp_device *sst_dev, int irq)
+{
+ int ret;
+ struct sst_dsp *sst;
+
+ sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL);
+ if (sst == NULL)
+ return NULL;
+
+ spin_lock_init(&sst->spinlock);
+ mutex_init(&sst->mutex);
+ sst->dev = dev;
+ sst->sst_dev = sst_dev;
+ sst->irq = irq;
+ sst->ops = sst_dev->ops;
+ sst->thread_context = sst_dev->thread_context;
+
+ /* Initialise SST Audio DSP */
+ if (sst->ops->init) {
+ ret = sst->ops->init(sst);
+ if (ret < 0)
+ return NULL;
+ }
+
+ return sst;
+}
+
+int skl_dsp_acquire_irq(struct sst_dsp *sst)
+{
+ struct sst_dsp_device *sst_dev = sst->sst_dev;
+ int ret;
+
+ /* Register the ISR */
+ ret = request_threaded_irq(sst->irq, sst->ops->irq_handler,
+ sst_dev->thread, IRQF_SHARED, "AudioDSP", sst);
+ if (ret)
+ dev_err(sst->dev, "unable to grab threaded IRQ %d, disabling device\n",
+ sst->irq);
+
+ return ret;
+}
+
+void skl_dsp_free(struct sst_dsp *dsp)
+{
+ skl_ipc_int_disable(dsp);
+
+ free_irq(dsp->irq, dsp);
+ skl_ipc_op_int_disable(dsp);
+ skl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK);
+}
+EXPORT_SYMBOL_GPL(skl_dsp_free);
+
+bool is_skl_dsp_running(struct sst_dsp *ctx)
+{
+ return (ctx->sst_state == SKL_DSP_RUNNING);
+}
+EXPORT_SYMBOL_GPL(is_skl_dsp_running);
diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h
new file mode 100644
index 000000000..1df9ef422
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-dsp.h
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Skylake SST DSP Support
+ *
+ * Copyright (C) 2014-15, Intel Corporation.
+ */
+
+#ifndef __SKL_SST_DSP_H__
+#define __SKL_SST_DSP_H__
+
+#include <linux/interrupt.h>
+#include <linux/uuid.h>
+#include <linux/firmware.h>
+#include <sound/memalloc.h>
+#include "skl-sst-cldma.h"
+
+struct sst_dsp;
+struct sst_dsp_device;
+struct skl_lib_info;
+struct skl_dev;
+
+/* Intel HD Audio General DSP Registers */
+#define SKL_ADSP_GEN_BASE 0x0
+#define SKL_ADSP_REG_ADSPCS (SKL_ADSP_GEN_BASE + 0x04)
+#define SKL_ADSP_REG_ADSPIC (SKL_ADSP_GEN_BASE + 0x08)
+#define SKL_ADSP_REG_ADSPIS (SKL_ADSP_GEN_BASE + 0x0C)
+#define SKL_ADSP_REG_ADSPIC2 (SKL_ADSP_GEN_BASE + 0x10)
+#define SKL_ADSP_REG_ADSPIS2 (SKL_ADSP_GEN_BASE + 0x14)
+
+/* Intel HD Audio Inter-Processor Communication Registers */
+#define SKL_ADSP_IPC_BASE 0x40
+#define SKL_ADSP_REG_HIPCT (SKL_ADSP_IPC_BASE + 0x00)
+#define SKL_ADSP_REG_HIPCTE (SKL_ADSP_IPC_BASE + 0x04)
+#define SKL_ADSP_REG_HIPCI (SKL_ADSP_IPC_BASE + 0x08)
+#define SKL_ADSP_REG_HIPCIE (SKL_ADSP_IPC_BASE + 0x0C)
+#define SKL_ADSP_REG_HIPCCTL (SKL_ADSP_IPC_BASE + 0x10)
+
+/* HIPCI */
+#define SKL_ADSP_REG_HIPCI_BUSY BIT(31)
+
+/* HIPCIE */
+#define SKL_ADSP_REG_HIPCIE_DONE BIT(30)
+
+/* HIPCCTL */
+#define SKL_ADSP_REG_HIPCCTL_DONE BIT(1)
+#define SKL_ADSP_REG_HIPCCTL_BUSY BIT(0)
+
+/* HIPCT */
+#define SKL_ADSP_REG_HIPCT_BUSY BIT(31)
+
+/* FW base IDs */
+#define SKL_INSTANCE_ID 0
+#define SKL_BASE_FW_MODULE_ID 0
+
+/* Intel HD Audio SRAM Window 1 */
+#define SKL_ADSP_SRAM1_BASE 0xA000
+
+#define SKL_ADSP_MMIO_LEN 0x10000
+
+#define SKL_ADSP_W0_STAT_SZ 0x1000
+
+#define SKL_ADSP_W0_UP_SZ 0x1000
+
+#define SKL_ADSP_W1_SZ 0x1000
+
+#define SKL_FW_STS_MASK 0xf
+
+#define SKL_FW_INIT 0x1
+#define SKL_FW_RFW_START 0xf
+#define BXT_FW_ROM_INIT_RETRY 3
+#define BXT_INIT_TIMEOUT 300
+
+#define SKL_ADSPIC_IPC 1
+#define SKL_ADSPIS_IPC 1
+
+/* Core ID of core0 */
+#define SKL_DSP_CORE0_ID 0
+
+/* Mask for a given core index, c = 0.. number of supported cores - 1 */
+#define SKL_DSP_CORE_MASK(c) BIT(c)
+
+/*
+ * Core 0 mask = SKL_DSP_CORE_MASK(0); Defined separately
+ * since Core0 is primary core and it is used often
+ */
+#define SKL_DSP_CORE0_MASK BIT(0)
+
+/*
+ * Mask for a given number of cores
+ * nc = number of supported cores
+ */
+#define SKL_DSP_CORES_MASK(nc) GENMASK((nc - 1), 0)
+
+/* ADSPCS - Audio DSP Control & Status */
+
+/*
+ * Core Reset - asserted high
+ * CRST Mask for a given core mask pattern, cm
+ */
+#define SKL_ADSPCS_CRST_SHIFT 0
+#define SKL_ADSPCS_CRST_MASK(cm) ((cm) << SKL_ADSPCS_CRST_SHIFT)
+
+/*
+ * Core run/stall - when set to '1' core is stalled
+ * CSTALL Mask for a given core mask pattern, cm
+ */
+#define SKL_ADSPCS_CSTALL_SHIFT 8
+#define SKL_ADSPCS_CSTALL_MASK(cm) ((cm) << SKL_ADSPCS_CSTALL_SHIFT)
+
+/*
+ * Set Power Active - when set to '1' turn cores on
+ * SPA Mask for a given core mask pattern, cm
+ */
+#define SKL_ADSPCS_SPA_SHIFT 16
+#define SKL_ADSPCS_SPA_MASK(cm) ((cm) << SKL_ADSPCS_SPA_SHIFT)
+
+/*
+ * Current Power Active - power status of cores, set by hardware
+ * CPA Mask for a given core mask pattern, cm
+ */
+#define SKL_ADSPCS_CPA_SHIFT 24
+#define SKL_ADSPCS_CPA_MASK(cm) ((cm) << SKL_ADSPCS_CPA_SHIFT)
+
+/* DSP Core state */
+enum skl_dsp_states {
+ SKL_DSP_RUNNING = 1,
+ /* Running in D0i3 state; can be in streaming or non-streaming D0i3 */
+ SKL_DSP_RUNNING_D0I3, /* Running in D0i3 state*/
+ SKL_DSP_RESET,
+};
+
+/* D0i3 substates */
+enum skl_dsp_d0i3_states {
+ SKL_DSP_D0I3_NONE = -1, /* No D0i3 */
+ SKL_DSP_D0I3_NON_STREAMING = 0,
+ SKL_DSP_D0I3_STREAMING = 1,
+};
+
+struct skl_dsp_fw_ops {
+ int (*load_fw)(struct sst_dsp *ctx);
+ /* FW module parser/loader */
+ int (*load_library)(struct sst_dsp *ctx,
+ struct skl_lib_info *linfo, int lib_count);
+ int (*parse_fw)(struct sst_dsp *ctx);
+ int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id);
+ int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id);
+ int (*set_state_D0i3)(struct sst_dsp *ctx);
+ int (*set_state_D0i0)(struct sst_dsp *ctx);
+ unsigned int (*get_fw_errcode)(struct sst_dsp *ctx);
+ int (*load_mod)(struct sst_dsp *ctx, u16 mod_id, u8 *mod_name);
+ int (*unload_mod)(struct sst_dsp *ctx, u16 mod_id);
+
+};
+
+struct skl_dsp_loader_ops {
+ int stream_tag;
+
+ int (*alloc_dma_buf)(struct device *dev,
+ struct snd_dma_buffer *dmab, size_t size);
+ int (*free_dma_buf)(struct device *dev,
+ struct snd_dma_buffer *dmab);
+ int (*prepare)(struct device *dev, unsigned int format,
+ unsigned int byte_size,
+ struct snd_dma_buffer *bufp);
+ int (*trigger)(struct device *dev, bool start, int stream_tag);
+
+ int (*cleanup)(struct device *dev, struct snd_dma_buffer *dmab,
+ int stream_tag);
+};
+
+#define MAX_INSTANCE_BUFF 2
+
+struct uuid_module {
+ guid_t uuid;
+ int id;
+ int is_loadable;
+ int max_instance;
+ u64 pvt_id[MAX_INSTANCE_BUFF];
+ int *instance_id;
+
+ struct list_head list;
+};
+
+struct skl_load_module_info {
+ u16 mod_id;
+ const struct firmware *fw;
+};
+
+struct skl_module_table {
+ struct skl_load_module_info *mod_info;
+ unsigned int usage_cnt;
+ struct list_head list;
+};
+
+void skl_cldma_process_intr(struct sst_dsp *ctx);
+void skl_cldma_int_disable(struct sst_dsp *ctx);
+int skl_cldma_prepare(struct sst_dsp *ctx);
+int skl_cldma_wait_interruptible(struct sst_dsp *ctx);
+
+void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state);
+struct sst_dsp *skl_dsp_ctx_init(struct device *dev,
+ struct sst_dsp_device *sst_dev, int irq);
+int skl_dsp_acquire_irq(struct sst_dsp *sst);
+bool is_skl_dsp_running(struct sst_dsp *ctx);
+
+unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx);
+void skl_dsp_init_core_state(struct sst_dsp *ctx);
+int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask);
+int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask);
+int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask);
+int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask);
+int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx,
+ unsigned int core_mask);
+int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask);
+
+irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id);
+int skl_dsp_wake(struct sst_dsp *ctx);
+int skl_dsp_sleep(struct sst_dsp *ctx);
+void skl_dsp_free(struct sst_dsp *dsp);
+
+int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id);
+int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id);
+
+int skl_dsp_boot(struct sst_dsp *ctx);
+int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp);
+int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp);
+int skl_sst_init_fw(struct device *dev, struct skl_dev *skl);
+int bxt_sst_init_fw(struct device *dev, struct skl_dev *skl);
+void skl_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl);
+void bxt_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl);
+
+int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw,
+ unsigned int offset, int index);
+int skl_get_pvt_id(struct skl_dev *skl, guid_t *uuid_mod, int instance_id);
+int skl_put_pvt_id(struct skl_dev *skl, guid_t *uuid_mod, int *pvt_id);
+int skl_get_pvt_instance_id_map(struct skl_dev *skl,
+ int module_id, int instance_id);
+void skl_freeup_uuid_list(struct skl_dev *skl);
+
+int skl_dsp_strip_extended_manifest(struct firmware *fw);
+
+void skl_dsp_set_astate_cfg(struct skl_dev *skl, u32 cnt, void *data);
+
+int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name,
+ struct skl_dsp_loader_ops dsp_ops, struct skl_dev **dsp,
+ struct sst_dsp_device *skl_dev);
+int skl_prepare_lib_load(struct skl_dev *skl, struct skl_lib_info *linfo,
+ struct firmware *stripped_fw,
+ unsigned int hdr_offset, int index);
+void skl_release_library(struct skl_lib_info *linfo, int lib_count);
+
+#endif /*__SKL_SST_DSP_H__*/
diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c
new file mode 100644
index 000000000..fd9624ad5
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-ipc.c
@@ -0,0 +1,1071 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-sst-ipc.c - Intel skl IPC Support
+ *
+ * Copyright (C) 2014-15, Intel Corporation.
+ */
+#include <linux/device.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "skl.h"
+#include "skl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+#include "sound/hdaudio_ext.h"
+
+
+#define IPC_IXC_STATUS_BITS 24
+
+/* Global Message - Generic */
+#define IPC_GLB_TYPE_SHIFT 24
+#define IPC_GLB_TYPE_MASK (0xf << IPC_GLB_TYPE_SHIFT)
+#define IPC_GLB_TYPE(x) ((x) << IPC_GLB_TYPE_SHIFT)
+
+/* Global Message - Reply */
+#define IPC_GLB_REPLY_STATUS_SHIFT 24
+#define IPC_GLB_REPLY_STATUS_MASK ((0x1 << IPC_GLB_REPLY_STATUS_SHIFT) - 1)
+#define IPC_GLB_REPLY_STATUS(x) ((x) << IPC_GLB_REPLY_STATUS_SHIFT)
+
+#define IPC_GLB_REPLY_TYPE_SHIFT 29
+#define IPC_GLB_REPLY_TYPE_MASK 0x1F
+#define IPC_GLB_REPLY_TYPE(x) (((x) >> IPC_GLB_REPLY_TYPE_SHIFT) \
+ & IPC_GLB_RPLY_TYPE_MASK)
+
+#define IPC_TIMEOUT_MSECS 3000
+
+#define IPC_EMPTY_LIST_SIZE 8
+
+#define IPC_MSG_TARGET_SHIFT 30
+#define IPC_MSG_TARGET_MASK 0x1
+#define IPC_MSG_TARGET(x) (((x) & IPC_MSG_TARGET_MASK) \
+ << IPC_MSG_TARGET_SHIFT)
+
+#define IPC_MSG_DIR_SHIFT 29
+#define IPC_MSG_DIR_MASK 0x1
+#define IPC_MSG_DIR(x) (((x) & IPC_MSG_DIR_MASK) \
+ << IPC_MSG_DIR_SHIFT)
+/* Global Notification Message */
+#define IPC_GLB_NOTIFY_TYPE_SHIFT 16
+#define IPC_GLB_NOTIFY_TYPE_MASK 0xFF
+#define IPC_GLB_NOTIFY_TYPE(x) (((x) >> IPC_GLB_NOTIFY_TYPE_SHIFT) \
+ & IPC_GLB_NOTIFY_TYPE_MASK)
+
+#define IPC_GLB_NOTIFY_MSG_TYPE_SHIFT 24
+#define IPC_GLB_NOTIFY_MSG_TYPE_MASK 0x1F
+#define IPC_GLB_NOTIFY_MSG_TYPE(x) (((x) >> IPC_GLB_NOTIFY_MSG_TYPE_SHIFT) \
+ & IPC_GLB_NOTIFY_MSG_TYPE_MASK)
+
+#define IPC_GLB_NOTIFY_RSP_SHIFT 29
+#define IPC_GLB_NOTIFY_RSP_MASK 0x1
+#define IPC_GLB_NOTIFY_RSP_TYPE(x) (((x) >> IPC_GLB_NOTIFY_RSP_SHIFT) \
+ & IPC_GLB_NOTIFY_RSP_MASK)
+
+/* Pipeline operations */
+
+/* Create pipeline message */
+#define IPC_PPL_MEM_SIZE_SHIFT 0
+#define IPC_PPL_MEM_SIZE_MASK 0x7FF
+#define IPC_PPL_MEM_SIZE(x) (((x) & IPC_PPL_MEM_SIZE_MASK) \
+ << IPC_PPL_MEM_SIZE_SHIFT)
+
+#define IPC_PPL_TYPE_SHIFT 11
+#define IPC_PPL_TYPE_MASK 0x1F
+#define IPC_PPL_TYPE(x) (((x) & IPC_PPL_TYPE_MASK) \
+ << IPC_PPL_TYPE_SHIFT)
+
+#define IPC_INSTANCE_ID_SHIFT 16
+#define IPC_INSTANCE_ID_MASK 0xFF
+#define IPC_INSTANCE_ID(x) (((x) & IPC_INSTANCE_ID_MASK) \
+ << IPC_INSTANCE_ID_SHIFT)
+
+#define IPC_PPL_LP_MODE_SHIFT 0
+#define IPC_PPL_LP_MODE_MASK 0x1
+#define IPC_PPL_LP_MODE(x) (((x) & IPC_PPL_LP_MODE_MASK) \
+ << IPC_PPL_LP_MODE_SHIFT)
+
+/* Set pipeline state message */
+#define IPC_PPL_STATE_SHIFT 0
+#define IPC_PPL_STATE_MASK 0x1F
+#define IPC_PPL_STATE(x) (((x) & IPC_PPL_STATE_MASK) \
+ << IPC_PPL_STATE_SHIFT)
+
+/* Module operations primary register */
+#define IPC_MOD_ID_SHIFT 0
+#define IPC_MOD_ID_MASK 0xFFFF
+#define IPC_MOD_ID(x) (((x) & IPC_MOD_ID_MASK) \
+ << IPC_MOD_ID_SHIFT)
+
+#define IPC_MOD_INSTANCE_ID_SHIFT 16
+#define IPC_MOD_INSTANCE_ID_MASK 0xFF
+#define IPC_MOD_INSTANCE_ID(x) (((x) & IPC_MOD_INSTANCE_ID_MASK) \
+ << IPC_MOD_INSTANCE_ID_SHIFT)
+
+/* Init instance message extension register */
+#define IPC_PARAM_BLOCK_SIZE_SHIFT 0
+#define IPC_PARAM_BLOCK_SIZE_MASK 0xFFFF
+#define IPC_PARAM_BLOCK_SIZE(x) (((x) & IPC_PARAM_BLOCK_SIZE_MASK) \
+ << IPC_PARAM_BLOCK_SIZE_SHIFT)
+
+#define IPC_PPL_INSTANCE_ID_SHIFT 16
+#define IPC_PPL_INSTANCE_ID_MASK 0xFF
+#define IPC_PPL_INSTANCE_ID(x) (((x) & IPC_PPL_INSTANCE_ID_MASK) \
+ << IPC_PPL_INSTANCE_ID_SHIFT)
+
+#define IPC_CORE_ID_SHIFT 24
+#define IPC_CORE_ID_MASK 0x1F
+#define IPC_CORE_ID(x) (((x) & IPC_CORE_ID_MASK) \
+ << IPC_CORE_ID_SHIFT)
+
+#define IPC_DOMAIN_SHIFT 28
+#define IPC_DOMAIN_MASK 0x1
+#define IPC_DOMAIN(x) (((x) & IPC_DOMAIN_MASK) \
+ << IPC_DOMAIN_SHIFT)
+
+/* Bind/Unbind message extension register */
+#define IPC_DST_MOD_ID_SHIFT 0
+#define IPC_DST_MOD_ID(x) (((x) & IPC_MOD_ID_MASK) \
+ << IPC_DST_MOD_ID_SHIFT)
+
+#define IPC_DST_MOD_INSTANCE_ID_SHIFT 16
+#define IPC_DST_MOD_INSTANCE_ID(x) (((x) & IPC_MOD_INSTANCE_ID_MASK) \
+ << IPC_DST_MOD_INSTANCE_ID_SHIFT)
+
+#define IPC_DST_QUEUE_SHIFT 24
+#define IPC_DST_QUEUE_MASK 0x7
+#define IPC_DST_QUEUE(x) (((x) & IPC_DST_QUEUE_MASK) \
+ << IPC_DST_QUEUE_SHIFT)
+
+#define IPC_SRC_QUEUE_SHIFT 27
+#define IPC_SRC_QUEUE_MASK 0x7
+#define IPC_SRC_QUEUE(x) (((x) & IPC_SRC_QUEUE_MASK) \
+ << IPC_SRC_QUEUE_SHIFT)
+/* Load Module count */
+#define IPC_LOAD_MODULE_SHIFT 0
+#define IPC_LOAD_MODULE_MASK 0xFF
+#define IPC_LOAD_MODULE_CNT(x) (((x) & IPC_LOAD_MODULE_MASK) \
+ << IPC_LOAD_MODULE_SHIFT)
+
+/* Save pipeline messgae extension register */
+#define IPC_DMA_ID_SHIFT 0
+#define IPC_DMA_ID_MASK 0x1F
+#define IPC_DMA_ID(x) (((x) & IPC_DMA_ID_MASK) \
+ << IPC_DMA_ID_SHIFT)
+/* Large Config message extension register */
+#define IPC_DATA_OFFSET_SZ_SHIFT 0
+#define IPC_DATA_OFFSET_SZ_MASK 0xFFFFF
+#define IPC_DATA_OFFSET_SZ(x) (((x) & IPC_DATA_OFFSET_SZ_MASK) \
+ << IPC_DATA_OFFSET_SZ_SHIFT)
+#define IPC_DATA_OFFSET_SZ_CLEAR ~(IPC_DATA_OFFSET_SZ_MASK \
+ << IPC_DATA_OFFSET_SZ_SHIFT)
+
+#define IPC_LARGE_PARAM_ID_SHIFT 20
+#define IPC_LARGE_PARAM_ID_MASK 0xFF
+#define IPC_LARGE_PARAM_ID(x) (((x) & IPC_LARGE_PARAM_ID_MASK) \
+ << IPC_LARGE_PARAM_ID_SHIFT)
+
+#define IPC_FINAL_BLOCK_SHIFT 28
+#define IPC_FINAL_BLOCK_MASK 0x1
+#define IPC_FINAL_BLOCK(x) (((x) & IPC_FINAL_BLOCK_MASK) \
+ << IPC_FINAL_BLOCK_SHIFT)
+
+#define IPC_INITIAL_BLOCK_SHIFT 29
+#define IPC_INITIAL_BLOCK_MASK 0x1
+#define IPC_INITIAL_BLOCK(x) (((x) & IPC_INITIAL_BLOCK_MASK) \
+ << IPC_INITIAL_BLOCK_SHIFT)
+#define IPC_INITIAL_BLOCK_CLEAR ~(IPC_INITIAL_BLOCK_MASK \
+ << IPC_INITIAL_BLOCK_SHIFT)
+/* Set D0ix IPC extension register */
+#define IPC_D0IX_WAKE_SHIFT 0
+#define IPC_D0IX_WAKE_MASK 0x1
+#define IPC_D0IX_WAKE(x) (((x) & IPC_D0IX_WAKE_MASK) \
+ << IPC_D0IX_WAKE_SHIFT)
+
+#define IPC_D0IX_STREAMING_SHIFT 1
+#define IPC_D0IX_STREAMING_MASK 0x1
+#define IPC_D0IX_STREAMING(x) (((x) & IPC_D0IX_STREAMING_MASK) \
+ << IPC_D0IX_STREAMING_SHIFT)
+
+
+enum skl_ipc_msg_target {
+ IPC_FW_GEN_MSG = 0,
+ IPC_MOD_MSG = 1
+};
+
+enum skl_ipc_msg_direction {
+ IPC_MSG_REQUEST = 0,
+ IPC_MSG_REPLY = 1
+};
+
+/* Global Message Types */
+enum skl_ipc_glb_type {
+ IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */
+ IPC_GLB_LOAD_MULTIPLE_MODS = 15,
+ IPC_GLB_UNLOAD_MULTIPLE_MODS = 16,
+ IPC_GLB_CREATE_PPL = 17,
+ IPC_GLB_DELETE_PPL = 18,
+ IPC_GLB_SET_PPL_STATE = 19,
+ IPC_GLB_GET_PPL_STATE = 20,
+ IPC_GLB_GET_PPL_CONTEXT_SIZE = 21,
+ IPC_GLB_SAVE_PPL = 22,
+ IPC_GLB_RESTORE_PPL = 23,
+ IPC_GLB_LOAD_LIBRARY = 24,
+ IPC_GLB_NOTIFY = 26,
+ IPC_GLB_MAX_IPC_MSG_NUMBER = 31 /* Maximum message number */
+};
+
+enum skl_ipc_glb_reply {
+ IPC_GLB_REPLY_SUCCESS = 0,
+
+ IPC_GLB_REPLY_UNKNOWN_MSG_TYPE = 1,
+ IPC_GLB_REPLY_ERROR_INVALID_PARAM = 2,
+
+ IPC_GLB_REPLY_BUSY = 3,
+ IPC_GLB_REPLY_PENDING = 4,
+ IPC_GLB_REPLY_FAILURE = 5,
+ IPC_GLB_REPLY_INVALID_REQUEST = 6,
+
+ IPC_GLB_REPLY_OUT_OF_MEMORY = 7,
+ IPC_GLB_REPLY_OUT_OF_MIPS = 8,
+
+ IPC_GLB_REPLY_INVALID_RESOURCE_ID = 9,
+ IPC_GLB_REPLY_INVALID_RESOURCE_STATE = 10,
+
+ IPC_GLB_REPLY_MOD_MGMT_ERROR = 100,
+ IPC_GLB_REPLY_MOD_LOAD_CL_FAILED = 101,
+ IPC_GLB_REPLY_MOD_LOAD_INVALID_HASH = 102,
+
+ IPC_GLB_REPLY_MOD_UNLOAD_INST_EXIST = 103,
+ IPC_GLB_REPLY_MOD_NOT_INITIALIZED = 104,
+
+ IPC_GLB_REPLY_INVALID_CONFIG_PARAM_ID = 120,
+ IPC_GLB_REPLY_INVALID_CONFIG_DATA_LEN = 121,
+ IPC_GLB_REPLY_GATEWAY_NOT_INITIALIZED = 140,
+ IPC_GLB_REPLY_GATEWAY_NOT_EXIST = 141,
+ IPC_GLB_REPLY_SCLK_ALREADY_RUNNING = 150,
+ IPC_GLB_REPLY_MCLK_ALREADY_RUNNING = 151,
+
+ IPC_GLB_REPLY_PPL_NOT_INITIALIZED = 160,
+ IPC_GLB_REPLY_PPL_NOT_EXIST = 161,
+ IPC_GLB_REPLY_PPL_SAVE_FAILED = 162,
+ IPC_GLB_REPLY_PPL_RESTORE_FAILED = 163,
+
+ IPC_MAX_STATUS = ((1<<IPC_IXC_STATUS_BITS)-1)
+};
+
+enum skl_ipc_notification_type {
+ IPC_GLB_NOTIFY_GLITCH = 0,
+ IPC_GLB_NOTIFY_OVERRUN = 1,
+ IPC_GLB_NOTIFY_UNDERRUN = 2,
+ IPC_GLB_NOTIFY_END_STREAM = 3,
+ IPC_GLB_NOTIFY_PHRASE_DETECTED = 4,
+ IPC_GLB_NOTIFY_RESOURCE_EVENT = 5,
+ IPC_GLB_NOTIFY_LOG_BUFFER_STATUS = 6,
+ IPC_GLB_NOTIFY_TIMESTAMP_CAPTURED = 7,
+ IPC_GLB_NOTIFY_FW_READY = 8
+};
+
+/* Module Message Types */
+enum skl_ipc_module_msg {
+ IPC_MOD_INIT_INSTANCE = 0,
+ IPC_MOD_CONFIG_GET = 1,
+ IPC_MOD_CONFIG_SET = 2,
+ IPC_MOD_LARGE_CONFIG_GET = 3,
+ IPC_MOD_LARGE_CONFIG_SET = 4,
+ IPC_MOD_BIND = 5,
+ IPC_MOD_UNBIND = 6,
+ IPC_MOD_SET_DX = 7,
+ IPC_MOD_SET_D0IX = 8
+};
+
+void skl_ipc_tx_data_copy(struct ipc_message *msg, char *tx_data,
+ size_t tx_size)
+{
+ if (tx_size)
+ memcpy(msg->tx.data, tx_data, tx_size);
+}
+
+static bool skl_ipc_is_dsp_busy(struct sst_dsp *dsp)
+{
+ u32 hipci;
+
+ hipci = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCI);
+ return (hipci & SKL_ADSP_REG_HIPCI_BUSY);
+}
+
+/* Lock to be held by caller */
+static void skl_ipc_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg)
+{
+ struct skl_ipc_header *header = (struct skl_ipc_header *)(&msg->tx.header);
+
+ if (msg->tx.size)
+ sst_dsp_outbox_write(ipc->dsp, msg->tx.data, msg->tx.size);
+ sst_dsp_shim_write_unlocked(ipc->dsp, SKL_ADSP_REG_HIPCIE,
+ header->extension);
+ sst_dsp_shim_write_unlocked(ipc->dsp, SKL_ADSP_REG_HIPCI,
+ header->primary | SKL_ADSP_REG_HIPCI_BUSY);
+}
+
+int skl_ipc_check_D0i0(struct sst_dsp *dsp, bool state)
+{
+ int ret;
+
+ /* check D0i3 support */
+ if (!dsp->fw_ops.set_state_D0i0)
+ return 0;
+
+ /* Attempt D0i0 or D0i3 based on state */
+ if (state)
+ ret = dsp->fw_ops.set_state_D0i0(dsp);
+ else
+ ret = dsp->fw_ops.set_state_D0i3(dsp);
+
+ return ret;
+}
+
+static struct ipc_message *skl_ipc_reply_get_msg(struct sst_generic_ipc *ipc,
+ u64 ipc_header)
+{
+ struct ipc_message *msg = NULL;
+ struct skl_ipc_header *header = (struct skl_ipc_header *)(&ipc_header);
+
+ if (list_empty(&ipc->rx_list)) {
+ dev_err(ipc->dev, "ipc: rx list is empty but received 0x%x\n",
+ header->primary);
+ goto out;
+ }
+
+ msg = list_first_entry(&ipc->rx_list, struct ipc_message, list);
+
+ list_del(&msg->list);
+out:
+ return msg;
+
+}
+
+int skl_ipc_process_notification(struct sst_generic_ipc *ipc,
+ struct skl_ipc_header header)
+{
+ struct skl_dev *skl = container_of(ipc, struct skl_dev, ipc);
+
+ if (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) {
+ switch (IPC_GLB_NOTIFY_TYPE(header.primary)) {
+
+ case IPC_GLB_NOTIFY_UNDERRUN:
+ dev_err(ipc->dev, "FW Underrun %x\n", header.primary);
+ break;
+
+ case IPC_GLB_NOTIFY_RESOURCE_EVENT:
+ dev_err(ipc->dev, "MCPS Budget Violation: %x\n",
+ header.primary);
+ break;
+
+ case IPC_GLB_NOTIFY_FW_READY:
+ skl->boot_complete = true;
+ wake_up(&skl->boot_wait);
+ break;
+
+ case IPC_GLB_NOTIFY_PHRASE_DETECTED:
+ dev_dbg(ipc->dev, "***** Phrase Detected **********\n");
+
+ /*
+ * Per HW recomendation, After phrase detection,
+ * clear the CGCTL.MISCBDCGE.
+ *
+ * This will be set back on stream closure
+ */
+ skl->enable_miscbdcge(ipc->dev, false);
+ skl->miscbdcg_disabled = true;
+ break;
+
+ default:
+ dev_err(ipc->dev, "ipc: Unhandled error msg=%x\n",
+ header.primary);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+struct skl_ipc_err_map {
+ const char *msg;
+ enum skl_ipc_glb_reply reply;
+ int err;
+};
+
+static struct skl_ipc_err_map skl_err_map[] = {
+ {"DSP out of memory", IPC_GLB_REPLY_OUT_OF_MEMORY, -ENOMEM},
+ {"DSP busy", IPC_GLB_REPLY_BUSY, -EBUSY},
+ {"SCLK already running", IPC_GLB_REPLY_SCLK_ALREADY_RUNNING,
+ IPC_GLB_REPLY_SCLK_ALREADY_RUNNING},
+ {"MCLK already running", IPC_GLB_REPLY_MCLK_ALREADY_RUNNING,
+ IPC_GLB_REPLY_MCLK_ALREADY_RUNNING},
+};
+
+static int skl_ipc_set_reply_error_code(struct sst_generic_ipc *ipc, u32 reply)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(skl_err_map); i++) {
+ if (skl_err_map[i].reply == reply)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(skl_err_map)) {
+ dev_err(ipc->dev, "ipc FW reply: %d FW Error Code: %u\n",
+ reply,
+ ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp));
+ return -EINVAL;
+ }
+
+ if (skl_err_map[i].err < 0)
+ dev_err(ipc->dev, "ipc FW reply: %s FW Error Code: %u\n",
+ skl_err_map[i].msg,
+ ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp));
+ else
+ dev_info(ipc->dev, "ipc FW reply: %s FW Error Code: %u\n",
+ skl_err_map[i].msg,
+ ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp));
+
+ return skl_err_map[i].err;
+}
+
+void skl_ipc_process_reply(struct sst_generic_ipc *ipc,
+ struct skl_ipc_header header)
+{
+ struct ipc_message *msg;
+ u32 reply = header.primary & IPC_GLB_REPLY_STATUS_MASK;
+ u64 *ipc_header = (u64 *)(&header);
+ struct skl_dev *skl = container_of(ipc, struct skl_dev, ipc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+ msg = skl_ipc_reply_get_msg(ipc, *ipc_header);
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+ if (msg == NULL) {
+ dev_dbg(ipc->dev, "ipc: rx list is empty\n");
+ return;
+ }
+
+ msg->rx.header = *ipc_header;
+ /* first process the header */
+ if (reply == IPC_GLB_REPLY_SUCCESS) {
+ dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary);
+ /* copy the rx data from the mailbox */
+ sst_dsp_inbox_read(ipc->dsp, msg->rx.data, msg->rx.size);
+ switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) {
+ case IPC_GLB_LOAD_MULTIPLE_MODS:
+ case IPC_GLB_LOAD_LIBRARY:
+ skl->mod_load_complete = true;
+ skl->mod_load_status = true;
+ wake_up(&skl->mod_load_wait);
+ break;
+
+ default:
+ break;
+
+ }
+ } else {
+ msg->errno = skl_ipc_set_reply_error_code(ipc, reply);
+ switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) {
+ case IPC_GLB_LOAD_MULTIPLE_MODS:
+ case IPC_GLB_LOAD_LIBRARY:
+ skl->mod_load_complete = true;
+ skl->mod_load_status = false;
+ wake_up(&skl->mod_load_wait);
+ break;
+
+ default:
+ break;
+
+ }
+ }
+
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+ sst_ipc_tx_msg_reply_complete(ipc, msg);
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+}
+
+irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context)
+{
+ struct sst_dsp *dsp = context;
+ struct skl_dev *skl = dsp->thread_context;
+ struct sst_generic_ipc *ipc = &skl->ipc;
+ struct skl_ipc_header header = {0};
+ u32 hipcie, hipct, hipcte;
+ int ipc_irq = 0;
+
+ if (dsp->intr_status & SKL_ADSPIS_CL_DMA)
+ skl_cldma_process_intr(dsp);
+
+ /* Here we handle IPC interrupts only */
+ if (!(dsp->intr_status & SKL_ADSPIS_IPC))
+ return IRQ_NONE;
+
+ hipcie = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCIE);
+ hipct = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCT);
+ hipcte = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCTE);
+
+ /* reply message from DSP */
+ if (hipcie & SKL_ADSP_REG_HIPCIE_DONE) {
+ sst_dsp_shim_update_bits(dsp, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_DONE, 0);
+
+ /* clear DONE bit - tell DSP we have completed the operation */
+ sst_dsp_shim_update_bits_forced(dsp, SKL_ADSP_REG_HIPCIE,
+ SKL_ADSP_REG_HIPCIE_DONE, SKL_ADSP_REG_HIPCIE_DONE);
+
+ ipc_irq = 1;
+
+ /* unmask Done interrupt */
+ sst_dsp_shim_update_bits(dsp, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_DONE, SKL_ADSP_REG_HIPCCTL_DONE);
+ }
+
+ /* New message from DSP */
+ if (hipct & SKL_ADSP_REG_HIPCT_BUSY) {
+ header.primary = hipct;
+ header.extension = hipcte;
+ dev_dbg(dsp->dev, "IPC irq: Firmware respond primary:%x\n",
+ header.primary);
+ dev_dbg(dsp->dev, "IPC irq: Firmware respond extension:%x\n",
+ header.extension);
+
+ if (IPC_GLB_NOTIFY_RSP_TYPE(header.primary)) {
+ /* Handle Immediate reply from DSP Core */
+ skl_ipc_process_reply(ipc, header);
+ } else {
+ dev_dbg(dsp->dev, "IPC irq: Notification from firmware\n");
+ skl_ipc_process_notification(ipc, header);
+ }
+ /* clear busy interrupt */
+ sst_dsp_shim_update_bits_forced(dsp, SKL_ADSP_REG_HIPCT,
+ SKL_ADSP_REG_HIPCT_BUSY, SKL_ADSP_REG_HIPCT_BUSY);
+ ipc_irq = 1;
+ }
+
+ if (ipc_irq == 0)
+ return IRQ_NONE;
+
+ skl_ipc_int_enable(dsp);
+
+ /* continue to send any remaining messages... */
+ schedule_work(&ipc->kwork);
+
+ return IRQ_HANDLED;
+}
+
+void skl_ipc_int_enable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_ADSPIC,
+ SKL_ADSPIC_IPC, SKL_ADSPIC_IPC);
+}
+
+void skl_ipc_int_disable(struct sst_dsp *ctx)
+{
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPIC,
+ SKL_ADSPIC_IPC, 0);
+}
+
+void skl_ipc_op_int_enable(struct sst_dsp *ctx)
+{
+ /* enable IPC DONE interrupt */
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_DONE, SKL_ADSP_REG_HIPCCTL_DONE);
+
+ /* Enable IPC BUSY interrupt */
+ sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_BUSY, SKL_ADSP_REG_HIPCCTL_BUSY);
+}
+
+void skl_ipc_op_int_disable(struct sst_dsp *ctx)
+{
+ /* disable IPC DONE interrupt */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_DONE, 0);
+
+ /* Disable IPC BUSY interrupt */
+ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_BUSY, 0);
+
+}
+
+bool skl_ipc_int_status(struct sst_dsp *ctx)
+{
+ return sst_dsp_shim_read_unlocked(ctx,
+ SKL_ADSP_REG_ADSPIS) & SKL_ADSPIS_IPC;
+}
+
+int skl_ipc_init(struct device *dev, struct skl_dev *skl)
+{
+ struct sst_generic_ipc *ipc;
+ int err;
+
+ ipc = &skl->ipc;
+ ipc->dsp = skl->dsp;
+ ipc->dev = dev;
+
+ ipc->tx_data_max_size = SKL_ADSP_W1_SZ;
+ ipc->rx_data_max_size = SKL_ADSP_W0_UP_SZ;
+
+ err = sst_ipc_init(ipc);
+ if (err)
+ return err;
+
+ ipc->ops.tx_msg = skl_ipc_tx_msg;
+ ipc->ops.tx_data_copy = skl_ipc_tx_data_copy;
+ ipc->ops.is_dsp_busy = skl_ipc_is_dsp_busy;
+
+ return 0;
+}
+
+void skl_ipc_free(struct sst_generic_ipc *ipc)
+{
+ /* Disable IPC DONE interrupt */
+ sst_dsp_shim_update_bits(ipc->dsp, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_DONE, 0);
+
+ /* Disable IPC BUSY interrupt */
+ sst_dsp_shim_update_bits(ipc->dsp, SKL_ADSP_REG_HIPCCTL,
+ SKL_ADSP_REG_HIPCCTL_BUSY, 0);
+
+ sst_ipc_fini(ipc);
+}
+
+int skl_ipc_create_pipeline(struct sst_generic_ipc *ipc,
+ u16 ppl_mem_size, u8 ppl_type, u8 instance_id, u8 lp_mode)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_CREATE_PPL);
+ header.primary |= IPC_INSTANCE_ID(instance_id);
+ header.primary |= IPC_PPL_TYPE(ppl_type);
+ header.primary |= IPC_PPL_MEM_SIZE(ppl_mem_size);
+
+ header.extension = IPC_PPL_LP_MODE(lp_mode);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: create pipeline fail, err: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_create_pipeline);
+
+int skl_ipc_delete_pipeline(struct sst_generic_ipc *ipc, u8 instance_id)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_DELETE_PPL);
+ header.primary |= IPC_INSTANCE_ID(instance_id);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: delete pipeline failed, err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_delete_pipeline);
+
+int skl_ipc_set_pipeline_state(struct sst_generic_ipc *ipc,
+ u8 instance_id, enum skl_ipc_pipeline_state state)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_SET_PPL_STATE);
+ header.primary |= IPC_INSTANCE_ID(instance_id);
+ header.primary |= IPC_PPL_STATE(state);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: set pipeline state failed, err: %d\n", ret);
+ return ret;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_set_pipeline_state);
+
+int
+skl_ipc_save_pipeline(struct sst_generic_ipc *ipc, u8 instance_id, int dma_id)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_SAVE_PPL);
+ header.primary |= IPC_INSTANCE_ID(instance_id);
+
+ header.extension = IPC_DMA_ID(dma_id);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: save pipeline failed, err: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_save_pipeline);
+
+int skl_ipc_restore_pipeline(struct sst_generic_ipc *ipc, u8 instance_id)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_RESTORE_PPL);
+ header.primary |= IPC_INSTANCE_ID(instance_id);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: restore pipeline failed, err: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_restore_pipeline);
+
+int skl_ipc_set_dx(struct sst_generic_ipc *ipc, u8 instance_id,
+ u16 module_id, struct skl_ipc_dxstate_info *dx)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request;
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_MOD_SET_DX);
+ header.primary |= IPC_MOD_INSTANCE_ID(instance_id);
+ header.primary |= IPC_MOD_ID(module_id);
+
+ request.header = *(u64 *)(&header);
+ request.data = dx;
+ request.size = sizeof(*dx);
+
+ dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__,
+ header.primary, header.extension);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: set dx failed, err %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_set_dx);
+
+int skl_ipc_init_instance(struct sst_generic_ipc *ipc,
+ struct skl_ipc_init_instance_msg *msg, void *param_data)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request;
+ int ret;
+ u32 *buffer = (u32 *)param_data;
+ /* param_block_size must be in dwords */
+ u16 param_block_size = msg->param_data_size / sizeof(u32);
+
+ print_hex_dump_debug("Param data:", DUMP_PREFIX_NONE,
+ 16, 4, buffer, param_block_size, false);
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_MOD_INIT_INSTANCE);
+ header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
+ header.primary |= IPC_MOD_ID(msg->module_id);
+
+ header.extension = IPC_CORE_ID(msg->core_id);
+ header.extension |= IPC_PPL_INSTANCE_ID(msg->ppl_instance_id);
+ header.extension |= IPC_PARAM_BLOCK_SIZE(param_block_size);
+ header.extension |= IPC_DOMAIN(msg->domain);
+
+ request.header = *(u64 *)(&header);
+ request.data = param_data;
+ request.size = msg->param_data_size;
+
+ dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__,
+ header.primary, header.extension);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: init instance failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_init_instance);
+
+int skl_ipc_bind_unbind(struct sst_generic_ipc *ipc,
+ struct skl_ipc_bind_unbind_msg *msg)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ u8 bind_unbind = msg->bind ? IPC_MOD_BIND : IPC_MOD_UNBIND;
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(bind_unbind);
+ header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
+ header.primary |= IPC_MOD_ID(msg->module_id);
+
+ header.extension = IPC_DST_MOD_ID(msg->dst_module_id);
+ header.extension |= IPC_DST_MOD_INSTANCE_ID(msg->dst_instance_id);
+ header.extension |= IPC_DST_QUEUE(msg->dst_queue);
+ header.extension |= IPC_SRC_QUEUE(msg->src_queue);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s hdr=%x ext=%x\n", __func__, header.primary,
+ header.extension);
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev, "ipc: bind/unbind failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_bind_unbind);
+
+/*
+ * In order to load a module we need to send IPC to initiate that. DMA will
+ * performed to load the module memory. The FW supports multiple module load
+ * at single shot, so we can send IPC with N modules represented by
+ * module_cnt
+ */
+int skl_ipc_load_modules(struct sst_generic_ipc *ipc,
+ u8 module_cnt, void *data)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request;
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_MULTIPLE_MODS);
+ header.primary |= IPC_LOAD_MODULE_CNT(module_cnt);
+
+ request.header = *(u64 *)(&header);
+ request.data = data;
+ request.size = sizeof(u16) * module_cnt;
+
+ ret = sst_ipc_tx_message_nowait(ipc, request);
+ if (ret < 0)
+ dev_err(ipc->dev, "ipc: load modules failed :%d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_load_modules);
+
+int skl_ipc_unload_modules(struct sst_generic_ipc *ipc, u8 module_cnt,
+ void *data)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request;
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_UNLOAD_MULTIPLE_MODS);
+ header.primary |= IPC_LOAD_MODULE_CNT(module_cnt);
+
+ request.header = *(u64 *)(&header);
+ request.data = data;
+ request.size = sizeof(u16) * module_cnt;
+
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0)
+ dev_err(ipc->dev, "ipc: unload modules failed :%d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_unload_modules);
+
+int skl_ipc_set_large_config(struct sst_generic_ipc *ipc,
+ struct skl_ipc_large_config_msg *msg, u32 *param)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request;
+ int ret = 0;
+ size_t sz_remaining, tx_size, data_offset;
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_MOD_LARGE_CONFIG_SET);
+ header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
+ header.primary |= IPC_MOD_ID(msg->module_id);
+
+ header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size);
+ header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id);
+ header.extension |= IPC_FINAL_BLOCK(0);
+ header.extension |= IPC_INITIAL_BLOCK(1);
+
+ sz_remaining = msg->param_data_size;
+ data_offset = 0;
+ while (sz_remaining != 0) {
+ tx_size = sz_remaining > SKL_ADSP_W1_SZ
+ ? SKL_ADSP_W1_SZ : sz_remaining;
+ if (tx_size == sz_remaining)
+ header.extension |= IPC_FINAL_BLOCK(1);
+
+ dev_dbg(ipc->dev, "In %s primary=%#x ext=%#x\n", __func__,
+ header.primary, header.extension);
+ dev_dbg(ipc->dev, "transmitting offset: %#x, size: %#x\n",
+ (unsigned)data_offset, (unsigned)tx_size);
+
+ request.header = *(u64 *)(&header);
+ request.data = ((char *)param) + data_offset;
+ request.size = tx_size;
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ if (ret < 0) {
+ dev_err(ipc->dev,
+ "ipc: set large config fail, err: %d\n", ret);
+ return ret;
+ }
+ sz_remaining -= tx_size;
+ data_offset = msg->param_data_size - sz_remaining;
+
+ /* clear the fields */
+ header.extension &= IPC_INITIAL_BLOCK_CLEAR;
+ header.extension &= IPC_DATA_OFFSET_SZ_CLEAR;
+ /* fill the fields */
+ header.extension |= IPC_INITIAL_BLOCK(0);
+ header.extension |= IPC_DATA_OFFSET_SZ(data_offset);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_set_large_config);
+
+int skl_ipc_get_large_config(struct sst_generic_ipc *ipc,
+ struct skl_ipc_large_config_msg *msg,
+ u32 **payload, size_t *bytes)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request, reply = {0};
+ unsigned int *buf;
+ int ret;
+
+ reply.data = kzalloc(SKL_ADSP_W1_SZ, GFP_KERNEL);
+ if (!reply.data)
+ return -ENOMEM;
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_MOD_LARGE_CONFIG_GET);
+ header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
+ header.primary |= IPC_MOD_ID(msg->module_id);
+
+ header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size);
+ header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id);
+ header.extension |= IPC_FINAL_BLOCK(1);
+ header.extension |= IPC_INITIAL_BLOCK(1);
+
+ request.header = *(u64 *)&header;
+ request.data = *payload;
+ request.size = *bytes;
+ reply.size = SKL_ADSP_W1_SZ;
+
+ ret = sst_ipc_tx_message_wait(ipc, request, &reply);
+ if (ret < 0)
+ dev_err(ipc->dev, "ipc: get large config fail, err: %d\n", ret);
+
+ reply.size = (reply.header >> 32) & IPC_DATA_OFFSET_SZ_MASK;
+ buf = krealloc(reply.data, reply.size, GFP_KERNEL);
+ if (!buf) {
+ kfree(reply.data);
+ return -ENOMEM;
+ }
+ *payload = buf;
+ *bytes = reply.size;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_get_large_config);
+
+int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc,
+ u8 dma_id, u8 table_id, bool wait)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret = 0;
+
+ header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_LIBRARY);
+ header.primary |= IPC_MOD_INSTANCE_ID(table_id);
+ header.primary |= IPC_MOD_ID(dma_id);
+ request.header = *(u64 *)(&header);
+
+ if (wait)
+ ret = sst_ipc_tx_message_wait(ipc, request, NULL);
+ else
+ ret = sst_ipc_tx_message_nowait(ipc, request);
+
+ if (ret < 0)
+ dev_err(ipc->dev, "ipc: load lib failed\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_sst_ipc_load_library);
+
+int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, struct skl_ipc_d0ix_msg *msg)
+{
+ struct skl_ipc_header header = {0};
+ struct sst_ipc_message request = {0};
+ int ret;
+
+ header.primary = IPC_MSG_TARGET(IPC_MOD_MSG);
+ header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST);
+ header.primary |= IPC_GLB_TYPE(IPC_MOD_SET_D0IX);
+ header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id);
+ header.primary |= IPC_MOD_ID(msg->module_id);
+
+ header.extension = IPC_D0IX_WAKE(msg->wake);
+ header.extension |= IPC_D0IX_STREAMING(msg->streaming);
+ request.header = *(u64 *)(&header);
+
+ dev_dbg(ipc->dev, "In %s primary=%x ext=%x\n", __func__,
+ header.primary, header.extension);
+
+ /*
+ * Use the nopm IPC here as we dont want it checking for D0iX
+ */
+ ret = sst_ipc_tx_message_nopm(ipc, request, NULL);
+ if (ret < 0)
+ dev_err(ipc->dev, "ipc: set d0ix failed, err %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(skl_ipc_set_d0ix);
diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h
new file mode 100644
index 000000000..aaaab3b3e
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-ipc.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel SKL IPC Support
+ *
+ * Copyright (C) 2014-15, Intel Corporation.
+ */
+
+#ifndef __SKL_IPC_H
+#define __SKL_IPC_H
+
+#include <linux/irqreturn.h>
+#include "../common/sst-ipc.h"
+#include "skl-sst-dsp.h"
+
+struct sst_dsp;
+struct sst_generic_ipc;
+
+enum skl_ipc_pipeline_state {
+ PPL_INVALID_STATE = 0,
+ PPL_UNINITIALIZED = 1,
+ PPL_RESET = 2,
+ PPL_PAUSED = 3,
+ PPL_RUNNING = 4,
+ PPL_ERROR_STOP = 5,
+ PPL_SAVED = 6,
+ PPL_RESTORED = 7
+};
+
+struct skl_ipc_dxstate_info {
+ u32 core_mask;
+ u32 dx_mask;
+};
+
+struct skl_ipc_header {
+ u32 primary;
+ u32 extension;
+};
+
+struct skl_dsp_cores {
+ unsigned int count;
+ enum skl_dsp_states *state;
+ int *usage_count;
+};
+
+/**
+ * skl_d0i3_data: skl D0i3 counters data struct
+ *
+ * @streaming: Count of usecases that can attempt streaming D0i3
+ * @non_streaming: Count of usecases that can attempt non-streaming D0i3
+ * @non_d0i3: Count of usecases that cannot attempt D0i3
+ * @state: current state
+ * @work: D0i3 worker thread
+ */
+struct skl_d0i3_data {
+ int streaming;
+ int non_streaming;
+ int non_d0i3;
+ enum skl_dsp_d0i3_states state;
+ struct delayed_work work;
+};
+
+#define SKL_LIB_NAME_LENGTH 128
+#define SKL_MAX_LIB 16
+
+struct skl_lib_info {
+ char name[SKL_LIB_NAME_LENGTH];
+ const struct firmware *fw;
+};
+
+struct skl_ipc_init_instance_msg {
+ u32 module_id;
+ u32 instance_id;
+ u16 param_data_size;
+ u8 ppl_instance_id;
+ u8 core_id;
+ u8 domain;
+};
+
+struct skl_ipc_bind_unbind_msg {
+ u32 module_id;
+ u32 instance_id;
+ u32 dst_module_id;
+ u32 dst_instance_id;
+ u8 src_queue;
+ u8 dst_queue;
+ bool bind;
+};
+
+struct skl_ipc_large_config_msg {
+ u32 module_id;
+ u32 instance_id;
+ u32 large_param_id;
+ u32 param_data_size;
+};
+
+struct skl_ipc_d0ix_msg {
+ u32 module_id;
+ u32 instance_id;
+ u8 streaming;
+ u8 wake;
+};
+
+#define SKL_IPC_BOOT_MSECS 3000
+
+#define SKL_IPC_D3_MASK 0
+#define SKL_IPC_D0_MASK 3
+
+irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context);
+
+int skl_ipc_create_pipeline(struct sst_generic_ipc *ipc,
+ u16 ppl_mem_size, u8 ppl_type, u8 instance_id, u8 lp_mode);
+
+int skl_ipc_delete_pipeline(struct sst_generic_ipc *ipc, u8 instance_id);
+
+int skl_ipc_set_pipeline_state(struct sst_generic_ipc *ipc,
+ u8 instance_id, enum skl_ipc_pipeline_state state);
+
+int skl_ipc_save_pipeline(struct sst_generic_ipc *ipc,
+ u8 instance_id, int dma_id);
+
+int skl_ipc_restore_pipeline(struct sst_generic_ipc *ipc, u8 instance_id);
+
+int skl_ipc_init_instance(struct sst_generic_ipc *ipc,
+ struct skl_ipc_init_instance_msg *msg, void *param_data);
+
+int skl_ipc_bind_unbind(struct sst_generic_ipc *ipc,
+ struct skl_ipc_bind_unbind_msg *msg);
+
+int skl_ipc_load_modules(struct sst_generic_ipc *ipc,
+ u8 module_cnt, void *data);
+
+int skl_ipc_unload_modules(struct sst_generic_ipc *ipc,
+ u8 module_cnt, void *data);
+
+int skl_ipc_set_dx(struct sst_generic_ipc *ipc,
+ u8 instance_id, u16 module_id, struct skl_ipc_dxstate_info *dx);
+
+int skl_ipc_set_large_config(struct sst_generic_ipc *ipc,
+ struct skl_ipc_large_config_msg *msg, u32 *param);
+
+int skl_ipc_get_large_config(struct sst_generic_ipc *ipc,
+ struct skl_ipc_large_config_msg *msg,
+ u32 **payload, size_t *bytes);
+
+int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc,
+ u8 dma_id, u8 table_id, bool wait);
+
+int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc,
+ struct skl_ipc_d0ix_msg *msg);
+
+int skl_ipc_check_D0i0(struct sst_dsp *dsp, bool state);
+
+void skl_ipc_int_enable(struct sst_dsp *ctx);
+void skl_ipc_op_int_enable(struct sst_dsp *ctx);
+void skl_ipc_op_int_disable(struct sst_dsp *ctx);
+void skl_ipc_int_disable(struct sst_dsp *ctx);
+
+bool skl_ipc_int_status(struct sst_dsp *ctx);
+void skl_ipc_free(struct sst_generic_ipc *ipc);
+int skl_ipc_init(struct device *dev, struct skl_dev *skl);
+void skl_clear_module_cnt(struct sst_dsp *ctx);
+
+void skl_ipc_process_reply(struct sst_generic_ipc *ipc,
+ struct skl_ipc_header header);
+int skl_ipc_process_notification(struct sst_generic_ipc *ipc,
+ struct skl_ipc_header header);
+void skl_ipc_tx_data_copy(struct ipc_message *msg, char *tx_data,
+ size_t tx_size);
+#endif /* __SKL_IPC_H */
diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c
new file mode 100644
index 000000000..b776c58dc
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst-utils.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-sst-utils.c - SKL sst utils functions
+ *
+ * Copyright (C) 2016 Intel Corp
+ */
+
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/uuid.h>
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "skl.h"
+
+#define DEFAULT_HASH_SHA256_LEN 32
+
+/* FW Extended Manifest Header id = $AE1 */
+#define SKL_EXT_MANIFEST_HEADER_MAGIC 0x31454124
+
+union seg_flags {
+ u32 ul;
+ struct {
+ u32 contents : 1;
+ u32 alloc : 1;
+ u32 load : 1;
+ u32 read_only : 1;
+ u32 code : 1;
+ u32 data : 1;
+ u32 _rsvd0 : 2;
+ u32 type : 4;
+ u32 _rsvd1 : 4;
+ u32 length : 16;
+ } r;
+} __packed;
+
+struct segment_desc {
+ union seg_flags flags;
+ u32 v_base_addr;
+ u32 file_offset;
+};
+
+struct module_type {
+ u32 load_type : 4;
+ u32 auto_start : 1;
+ u32 domain_ll : 1;
+ u32 domain_dp : 1;
+ u32 rsvd : 25;
+} __packed;
+
+struct adsp_module_entry {
+ u32 struct_id;
+ u8 name[8];
+ u8 uuid[16];
+ struct module_type type;
+ u8 hash1[DEFAULT_HASH_SHA256_LEN];
+ u32 entry_point;
+ u16 cfg_offset;
+ u16 cfg_count;
+ u32 affinity_mask;
+ u16 instance_max_count;
+ u16 instance_bss_size;
+ struct segment_desc segments[3];
+} __packed;
+
+struct adsp_fw_hdr {
+ u32 id;
+ u32 len;
+ u8 name[8];
+ u32 preload_page_count;
+ u32 fw_image_flags;
+ u32 feature_mask;
+ u16 major;
+ u16 minor;
+ u16 hotfix;
+ u16 build;
+ u32 num_modules;
+ u32 hw_buf_base;
+ u32 hw_buf_length;
+ u32 load_offset;
+} __packed;
+
+struct skl_ext_manifest_hdr {
+ u32 id;
+ u32 len;
+ u16 version_major;
+ u16 version_minor;
+ u32 entries;
+};
+
+static int skl_get_pvtid_map(struct uuid_module *module, int instance_id)
+{
+ int pvt_id;
+
+ for (pvt_id = 0; pvt_id < module->max_instance; pvt_id++) {
+ if (module->instance_id[pvt_id] == instance_id)
+ return pvt_id;
+ }
+ return -EINVAL;
+}
+
+int skl_get_pvt_instance_id_map(struct skl_dev *skl,
+ int module_id, int instance_id)
+{
+ struct uuid_module *module;
+
+ list_for_each_entry(module, &skl->uuid_list, list) {
+ if (module->id == module_id)
+ return skl_get_pvtid_map(module, instance_id);
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(skl_get_pvt_instance_id_map);
+
+static inline int skl_getid_32(struct uuid_module *module, u64 *val,
+ int word1_mask, int word2_mask)
+{
+ int index, max_inst, pvt_id;
+ u32 mask_val;
+
+ max_inst = module->max_instance;
+ mask_val = (u32)(*val >> word1_mask);
+
+ if (mask_val != 0xffffffff) {
+ index = ffz(mask_val);
+ pvt_id = index + word1_mask + word2_mask;
+ if (pvt_id <= (max_inst - 1)) {
+ *val |= 1ULL << (index + word1_mask);
+ return pvt_id;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static inline int skl_pvtid_128(struct uuid_module *module)
+{
+ int j, i, word1_mask, word2_mask = 0, pvt_id;
+
+ for (j = 0; j < MAX_INSTANCE_BUFF; j++) {
+ word1_mask = 0;
+
+ for (i = 0; i < 2; i++) {
+ pvt_id = skl_getid_32(module, &module->pvt_id[j],
+ word1_mask, word2_mask);
+ if (pvt_id >= 0)
+ return pvt_id;
+
+ word1_mask += 32;
+ if ((word1_mask + word2_mask) >= module->max_instance)
+ return -EINVAL;
+ }
+
+ word2_mask += 64;
+ if (word2_mask >= module->max_instance)
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * skl_get_pvt_id: generate a private id for use as module id
+ *
+ * @skl: driver context
+ * @uuid_mod: module's uuid
+ * @instance_id: module's instance id
+ *
+ * This generates a 128 bit private unique id for a module TYPE so that
+ * module instance is unique
+ */
+int skl_get_pvt_id(struct skl_dev *skl, guid_t *uuid_mod, int instance_id)
+{
+ struct uuid_module *module;
+ int pvt_id;
+
+ list_for_each_entry(module, &skl->uuid_list, list) {
+ if (guid_equal(uuid_mod, &module->uuid)) {
+
+ pvt_id = skl_pvtid_128(module);
+ if (pvt_id >= 0) {
+ module->instance_id[pvt_id] = instance_id;
+
+ return pvt_id;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(skl_get_pvt_id);
+
+/**
+ * skl_put_pvt_id: free up the private id allocated
+ *
+ * @skl: driver context
+ * @uuid_mod: module's uuid
+ * @pvt_id: module pvt id
+ *
+ * This frees a 128 bit private unique id previously generated
+ */
+int skl_put_pvt_id(struct skl_dev *skl, guid_t *uuid_mod, int *pvt_id)
+{
+ int i;
+ struct uuid_module *module;
+
+ list_for_each_entry(module, &skl->uuid_list, list) {
+ if (guid_equal(uuid_mod, &module->uuid)) {
+
+ if (*pvt_id != 0)
+ i = (*pvt_id) / 64;
+ else
+ i = 0;
+
+ module->pvt_id[i] &= ~(1 << (*pvt_id));
+ *pvt_id = -1;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(skl_put_pvt_id);
+
+/*
+ * Parse the firmware binary to get the UUID, module id
+ * and loadable flags
+ */
+int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw,
+ unsigned int offset, int index)
+{
+ struct adsp_fw_hdr *adsp_hdr;
+ struct adsp_module_entry *mod_entry;
+ int i, num_entry, size;
+ const char *buf;
+ struct skl_dev *skl = ctx->thread_context;
+ struct uuid_module *module;
+ struct firmware stripped_fw;
+ unsigned int safe_file;
+ int ret;
+
+ /* Get the FW pointer to derive ADSP header */
+ stripped_fw.data = fw->data;
+ stripped_fw.size = fw->size;
+
+ skl_dsp_strip_extended_manifest(&stripped_fw);
+
+ buf = stripped_fw.data;
+
+ /* check if we have enough space in file to move to header */
+ safe_file = sizeof(*adsp_hdr) + offset;
+ if (stripped_fw.size <= safe_file) {
+ dev_err(ctx->dev, "Small fw file size, No space for hdr\n");
+ return -EINVAL;
+ }
+
+ adsp_hdr = (struct adsp_fw_hdr *)(buf + offset);
+
+ /* check 1st module entry is in file */
+ safe_file += adsp_hdr->len + sizeof(*mod_entry);
+ if (stripped_fw.size <= safe_file) {
+ dev_err(ctx->dev, "Small fw file size, No module entry\n");
+ return -EINVAL;
+ }
+
+ mod_entry = (struct adsp_module_entry *)(buf + offset + adsp_hdr->len);
+
+ num_entry = adsp_hdr->num_modules;
+
+ /* check all entries are in file */
+ safe_file += num_entry * sizeof(*mod_entry);
+ if (stripped_fw.size <= safe_file) {
+ dev_err(ctx->dev, "Small fw file size, No modules\n");
+ return -EINVAL;
+ }
+
+
+ /*
+ * Read the UUID(GUID) from FW Manifest.
+ *
+ * The 16 byte UUID format is: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
+ * Populate the UUID table to store module_id and loadable flags
+ * for the module.
+ */
+
+ for (i = 0; i < num_entry; i++, mod_entry++) {
+ module = kzalloc(sizeof(*module), GFP_KERNEL);
+ if (!module) {
+ ret = -ENOMEM;
+ goto free_uuid_list;
+ }
+
+ import_guid(&module->uuid, mod_entry->uuid);
+
+ module->id = (i | (index << 12));
+ module->is_loadable = mod_entry->type.load_type;
+ module->max_instance = mod_entry->instance_max_count;
+ size = sizeof(int) * mod_entry->instance_max_count;
+ module->instance_id = devm_kzalloc(ctx->dev, size, GFP_KERNEL);
+ if (!module->instance_id) {
+ ret = -ENOMEM;
+ kfree(module);
+ goto free_uuid_list;
+ }
+
+ list_add_tail(&module->list, &skl->uuid_list);
+
+ dev_dbg(ctx->dev,
+ "Adding uuid :%pUL mod id: %d Loadable: %d\n",
+ &module->uuid, module->id, module->is_loadable);
+ }
+
+ return 0;
+
+free_uuid_list:
+ skl_freeup_uuid_list(skl);
+ return ret;
+}
+
+void skl_freeup_uuid_list(struct skl_dev *skl)
+{
+ struct uuid_module *uuid, *_uuid;
+
+ list_for_each_entry_safe(uuid, _uuid, &skl->uuid_list, list) {
+ list_del(&uuid->list);
+ kfree(uuid);
+ }
+}
+
+/*
+ * some firmware binary contains some extended manifest. This needs
+ * to be stripped in that case before we load and use that image.
+ *
+ * Get the module id for the module by checking
+ * the table for the UUID for the module
+ */
+int skl_dsp_strip_extended_manifest(struct firmware *fw)
+{
+ struct skl_ext_manifest_hdr *hdr;
+
+ /* check if fw file is greater than header we are looking */
+ if (fw->size < sizeof(hdr)) {
+ pr_err("%s: Firmware file small, no hdr\n", __func__);
+ return -EINVAL;
+ }
+
+ hdr = (struct skl_ext_manifest_hdr *)fw->data;
+
+ if (hdr->id == SKL_EXT_MANIFEST_HEADER_MAGIC) {
+ fw->size -= hdr->len;
+ fw->data += hdr->len;
+ }
+
+ return 0;
+}
+
+int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name,
+ struct skl_dsp_loader_ops dsp_ops, struct skl_dev **dsp,
+ struct sst_dsp_device *skl_dev)
+{
+ struct skl_dev *skl = *dsp;
+ struct sst_dsp *sst;
+
+ skl->dev = dev;
+ skl_dev->thread_context = skl;
+ INIT_LIST_HEAD(&skl->uuid_list);
+ skl->dsp = skl_dsp_ctx_init(dev, skl_dev, irq);
+ if (!skl->dsp) {
+ dev_err(skl->dev, "%s: no device\n", __func__);
+ return -ENODEV;
+ }
+
+ sst = skl->dsp;
+ sst->fw_name = fw_name;
+ sst->dsp_ops = dsp_ops;
+ init_waitqueue_head(&skl->mod_load_wait);
+ INIT_LIST_HEAD(&sst->module_list);
+
+ skl->is_first_boot = true;
+
+ return 0;
+}
+
+int skl_prepare_lib_load(struct skl_dev *skl, struct skl_lib_info *linfo,
+ struct firmware *stripped_fw,
+ unsigned int hdr_offset, int index)
+{
+ int ret;
+ struct sst_dsp *dsp = skl->dsp;
+
+ if (linfo->fw == NULL) {
+ ret = request_firmware(&linfo->fw, linfo->name,
+ skl->dev);
+ if (ret < 0) {
+ dev_err(skl->dev, "Request lib %s failed:%d\n",
+ linfo->name, ret);
+ return ret;
+ }
+ }
+
+ if (skl->is_first_boot) {
+ ret = snd_skl_parse_uuids(dsp, linfo->fw, hdr_offset, index);
+ if (ret < 0)
+ return ret;
+ }
+
+ stripped_fw->data = linfo->fw->data;
+ stripped_fw->size = linfo->fw->size;
+ skl_dsp_strip_extended_manifest(stripped_fw);
+
+ return 0;
+}
+
+void skl_release_library(struct skl_lib_info *linfo, int lib_count)
+{
+ int i;
+
+ /* library indices start from 1 to N. 0 represents base FW */
+ for (i = 1; i < lib_count; i++) {
+ if (linfo[i].fw) {
+ release_firmware(linfo[i].fw);
+ linfo[i].fw = NULL;
+ }
+ }
+}
diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c
new file mode 100644
index 000000000..39d027ac1
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-sst.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-sst.c - HDA DSP library functions for SKL platform
+ *
+ * Copyright (C) 2014-15, Intel Corporation.
+ * Author:Rafal Redzimski <rafal.f.redzimski@intel.com>
+ * Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/uuid.h>
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-ipc.h"
+#include "skl.h"
+
+#define SKL_BASEFW_TIMEOUT 300
+#define SKL_INIT_TIMEOUT 1000
+
+/* Intel HD Audio SRAM Window 0*/
+#define SKL_ADSP_SRAM0_BASE 0x8000
+
+/* Firmware status window */
+#define SKL_ADSP_FW_STATUS SKL_ADSP_SRAM0_BASE
+#define SKL_ADSP_ERROR_CODE (SKL_ADSP_FW_STATUS + 0x4)
+
+#define SKL_NUM_MODULES 1
+
+static bool skl_check_fw_status(struct sst_dsp *ctx, u32 status)
+{
+ u32 cur_sts;
+
+ cur_sts = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS) & SKL_FW_STS_MASK;
+
+ return (cur_sts == status);
+}
+
+static int skl_transfer_firmware(struct sst_dsp *ctx,
+ const void *basefw, u32 base_fw_size)
+{
+ int ret = 0;
+
+ ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size,
+ true);
+ if (ret < 0)
+ return ret;
+
+ ret = sst_dsp_register_poll(ctx,
+ SKL_ADSP_FW_STATUS,
+ SKL_FW_STS_MASK,
+ SKL_FW_RFW_START,
+ SKL_BASEFW_TIMEOUT,
+ "Firmware boot");
+
+ ctx->cl_dev.ops.cl_stop_dma(ctx);
+
+ return ret;
+}
+
+#define SKL_ADSP_FW_BIN_HDR_OFFSET 0x284
+
+static int skl_load_base_firmware(struct sst_dsp *ctx)
+{
+ int ret = 0, i;
+ struct skl_dev *skl = ctx->thread_context;
+ struct firmware stripped_fw;
+ u32 reg;
+
+ skl->boot_complete = false;
+ init_waitqueue_head(&skl->boot_wait);
+
+ if (ctx->fw == NULL) {
+ ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Request firmware failed %d\n", ret);
+ return -EIO;
+ }
+ }
+
+ /* prase uuids on first boot */
+ if (skl->is_first_boot) {
+ ret = snd_skl_parse_uuids(ctx, ctx->fw, SKL_ADSP_FW_BIN_HDR_OFFSET, 0);
+ if (ret < 0) {
+ dev_err(ctx->dev, "UUID parsing err: %d\n", ret);
+ release_firmware(ctx->fw);
+ skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ return ret;
+ }
+ }
+
+ /* check for extended manifest */
+ stripped_fw.data = ctx->fw->data;
+ stripped_fw.size = ctx->fw->size;
+
+ skl_dsp_strip_extended_manifest(&stripped_fw);
+
+ ret = skl_dsp_boot(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Boot dsp core failed ret: %d\n", ret);
+ goto skl_load_base_firmware_failed;
+ }
+
+ ret = skl_cldma_prepare(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "CL dma prepare failed : %d\n", ret);
+ goto skl_load_base_firmware_failed;
+ }
+
+ /* enable Interrupt */
+ skl_ipc_int_enable(ctx);
+ skl_ipc_op_int_enable(ctx);
+
+ /* check ROM Status */
+ for (i = SKL_INIT_TIMEOUT; i > 0; --i) {
+ if (skl_check_fw_status(ctx, SKL_FW_INIT)) {
+ dev_dbg(ctx->dev,
+ "ROM loaded, we can continue with FW loading\n");
+ break;
+ }
+ mdelay(1);
+ }
+ if (!i) {
+ reg = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS);
+ dev_err(ctx->dev,
+ "Timeout waiting for ROM init done, reg:0x%x\n", reg);
+ ret = -EIO;
+ goto transfer_firmware_failed;
+ }
+
+ ret = skl_transfer_firmware(ctx, stripped_fw.data, stripped_fw.size);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Transfer firmware failed%d\n", ret);
+ goto transfer_firmware_failed;
+ } else {
+ ret = wait_event_timeout(skl->boot_wait, skl->boot_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+ if (ret == 0) {
+ dev_err(ctx->dev, "DSP boot failed, FW Ready timed-out\n");
+ ret = -EIO;
+ goto transfer_firmware_failed;
+ }
+
+ dev_dbg(ctx->dev, "Download firmware successful%d\n", ret);
+ skl->fw_loaded = true;
+ }
+ return 0;
+transfer_firmware_failed:
+ ctx->cl_dev.ops.cl_cleanup_controller(ctx);
+skl_load_base_firmware_failed:
+ skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK);
+ release_firmware(ctx->fw);
+ ctx->fw = NULL;
+ return ret;
+}
+
+static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id)
+{
+ int ret;
+ struct skl_ipc_dxstate_info dx;
+ struct skl_dev *skl = ctx->thread_context;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+
+ /* If core0 is being turned on, we need to load the FW */
+ if (core_id == SKL_DSP_CORE0_ID) {
+ ret = skl_load_base_firmware(ctx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "unable to load firmware\n");
+ return ret;
+ }
+
+ /* load libs as they are also lost on D3 */
+ if (skl->lib_count > 1) {
+ ret = ctx->fw_ops.load_library(ctx, skl->lib_info,
+ skl->lib_count);
+ if (ret < 0) {
+ dev_err(ctx->dev, "reload libs failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ }
+ }
+
+ /*
+ * If any core other than core 0 is being moved to D0, enable the
+ * core and send the set dx IPC for the core.
+ */
+ if (core_id != SKL_DSP_CORE0_ID) {
+ ret = skl_dsp_enable_core(ctx, core_mask);
+ if (ret < 0)
+ return ret;
+
+ dx.core_mask = core_mask;
+ dx.dx_mask = core_mask;
+
+ ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID,
+ SKL_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to set dsp to D0:core id= %d\n",
+ core_id);
+ skl_dsp_disable_core(ctx, core_mask);
+ }
+ }
+
+ skl->cores.state[core_id] = SKL_DSP_RUNNING;
+
+ return 0;
+}
+
+static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id)
+{
+ int ret;
+ struct skl_ipc_dxstate_info dx;
+ struct skl_dev *skl = ctx->thread_context;
+ unsigned int core_mask = SKL_DSP_CORE_MASK(core_id);
+
+ dx.core_mask = core_mask;
+ dx.dx_mask = SKL_IPC_D3_MASK;
+
+ ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx);
+ if (ret < 0)
+ dev_err(ctx->dev, "set Dx core %d fail: %d\n", core_id, ret);
+
+ if (core_id == SKL_DSP_CORE0_ID) {
+ /* disable Interrupt */
+ ctx->cl_dev.ops.cl_cleanup_controller(ctx);
+ skl_cldma_int_disable(ctx);
+ skl_ipc_op_int_disable(ctx);
+ skl_ipc_int_disable(ctx);
+ }
+
+ ret = skl_dsp_disable_core(ctx, core_mask);
+ if (ret < 0)
+ return ret;
+
+ skl->cores.state[core_id] = SKL_DSP_RESET;
+ return ret;
+}
+
+static unsigned int skl_get_errorcode(struct sst_dsp *ctx)
+{
+ return sst_dsp_shim_read(ctx, SKL_ADSP_ERROR_CODE);
+}
+
+/*
+ * since get/set_module are called from DAPM context,
+ * we don't need lock for usage count
+ */
+static int skl_get_module(struct sst_dsp *ctx, u16 mod_id)
+{
+ struct skl_module_table *module;
+
+ list_for_each_entry(module, &ctx->module_list, list) {
+ if (module->mod_info->mod_id == mod_id)
+ return ++module->usage_cnt;
+ }
+
+ return -EINVAL;
+}
+
+static int skl_put_module(struct sst_dsp *ctx, u16 mod_id)
+{
+ struct skl_module_table *module;
+
+ list_for_each_entry(module, &ctx->module_list, list) {
+ if (module->mod_info->mod_id == mod_id)
+ return --module->usage_cnt;
+ }
+
+ return -EINVAL;
+}
+
+static struct skl_module_table *skl_fill_module_table(struct sst_dsp *ctx,
+ char *mod_name, int mod_id)
+{
+ const struct firmware *fw;
+ struct skl_module_table *skl_module;
+ unsigned int size;
+ int ret;
+
+ ret = request_firmware(&fw, mod_name, ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Request Module %s failed :%d\n",
+ mod_name, ret);
+ return NULL;
+ }
+
+ skl_module = devm_kzalloc(ctx->dev, sizeof(*skl_module), GFP_KERNEL);
+ if (skl_module == NULL) {
+ release_firmware(fw);
+ return NULL;
+ }
+
+ size = sizeof(*skl_module->mod_info);
+ skl_module->mod_info = devm_kzalloc(ctx->dev, size, GFP_KERNEL);
+ if (skl_module->mod_info == NULL) {
+ release_firmware(fw);
+ return NULL;
+ }
+
+ skl_module->mod_info->mod_id = mod_id;
+ skl_module->mod_info->fw = fw;
+ list_add(&skl_module->list, &ctx->module_list);
+
+ return skl_module;
+}
+
+/* get a module from it's unique ID */
+static struct skl_module_table *skl_module_get_from_id(
+ struct sst_dsp *ctx, u16 mod_id)
+{
+ struct skl_module_table *module;
+
+ if (list_empty(&ctx->module_list)) {
+ dev_err(ctx->dev, "Module list is empty\n");
+ return NULL;
+ }
+
+ list_for_each_entry(module, &ctx->module_list, list) {
+ if (module->mod_info->mod_id == mod_id)
+ return module;
+ }
+
+ return NULL;
+}
+
+static int skl_transfer_module(struct sst_dsp *ctx, const void *data,
+ u32 size, u16 mod_id, u8 table_id, bool is_module)
+{
+ int ret, bytes_left, curr_pos;
+ struct skl_dev *skl = ctx->thread_context;
+ skl->mod_load_complete = false;
+
+ bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, data, size, false);
+ if (bytes_left < 0)
+ return bytes_left;
+
+ /* check is_module flag to load module or library */
+ if (is_module)
+ ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id);
+ else
+ ret = skl_sst_ipc_load_library(&skl->ipc, 0, table_id, false);
+
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to Load %s with err %d\n",
+ is_module ? "module" : "lib", ret);
+ goto out;
+ }
+
+ /*
+ * if bytes_left > 0 then wait for BDL complete interrupt and
+ * copy the next chunk till bytes_left is 0. if bytes_left is
+ * zero, then wait for load module IPC reply
+ */
+ while (bytes_left > 0) {
+ curr_pos = size - bytes_left;
+
+ ret = skl_cldma_wait_interruptible(ctx);
+ if (ret < 0)
+ goto out;
+
+ bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx,
+ data + curr_pos,
+ bytes_left, false);
+ }
+
+ ret = wait_event_timeout(skl->mod_load_wait, skl->mod_load_complete,
+ msecs_to_jiffies(SKL_IPC_BOOT_MSECS));
+ if (ret == 0 || !skl->mod_load_status) {
+ dev_err(ctx->dev, "Module Load failed\n");
+ ret = -EIO;
+ }
+
+out:
+ ctx->cl_dev.ops.cl_stop_dma(ctx);
+
+ return ret;
+}
+
+static int
+skl_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count)
+{
+ struct skl_dev *skl = ctx->thread_context;
+ struct firmware stripped_fw;
+ int ret, i;
+
+ /* library indices start from 1 to N. 0 represents base FW */
+ for (i = 1; i < lib_count; i++) {
+ ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw,
+ SKL_ADSP_FW_BIN_HDR_OFFSET, i);
+ if (ret < 0)
+ goto load_library_failed;
+ ret = skl_transfer_module(ctx, stripped_fw.data,
+ stripped_fw.size, 0, i, false);
+ if (ret < 0)
+ goto load_library_failed;
+ }
+
+ return 0;
+
+load_library_failed:
+ skl_release_library(linfo, lib_count);
+ return ret;
+}
+
+static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid)
+{
+ struct skl_module_table *module_entry = NULL;
+ int ret = 0;
+ char mod_name[64]; /* guid str = 32 chars + 4 hyphens */
+
+ snprintf(mod_name, sizeof(mod_name), "intel/dsp_fw_%pUL.bin", guid);
+
+ module_entry = skl_module_get_from_id(ctx, mod_id);
+ if (module_entry == NULL) {
+ module_entry = skl_fill_module_table(ctx, mod_name, mod_id);
+ if (module_entry == NULL) {
+ dev_err(ctx->dev, "Failed to Load module\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!module_entry->usage_cnt) {
+ ret = skl_transfer_module(ctx, module_entry->mod_info->fw->data,
+ module_entry->mod_info->fw->size,
+ mod_id, 0, true);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to Load module\n");
+ return ret;
+ }
+ }
+
+ ret = skl_get_module(ctx, mod_id);
+
+ return ret;
+}
+
+static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id)
+{
+ int usage_cnt;
+ struct skl_dev *skl = ctx->thread_context;
+ int ret = 0;
+
+ usage_cnt = skl_put_module(ctx, mod_id);
+ if (usage_cnt < 0) {
+ dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt);
+ return -EIO;
+ }
+
+ /* if module is used by others return, no need to unload */
+ if (usage_cnt > 0)
+ return 0;
+
+ ret = skl_ipc_unload_modules(&skl->ipc,
+ SKL_NUM_MODULES, &mod_id);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to UnLoad module\n");
+ skl_get_module(ctx, mod_id);
+ return ret;
+ }
+
+ return ret;
+}
+
+void skl_clear_module_cnt(struct sst_dsp *ctx)
+{
+ struct skl_module_table *module;
+
+ if (list_empty(&ctx->module_list))
+ return;
+
+ list_for_each_entry(module, &ctx->module_list, list) {
+ module->usage_cnt = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(skl_clear_module_cnt);
+
+static void skl_clear_module_table(struct sst_dsp *ctx)
+{
+ struct skl_module_table *module, *tmp;
+
+ if (list_empty(&ctx->module_list))
+ return;
+
+ list_for_each_entry_safe(module, tmp, &ctx->module_list, list) {
+ list_del(&module->list);
+ release_firmware(module->mod_info->fw);
+ }
+}
+
+static const struct skl_dsp_fw_ops skl_fw_ops = {
+ .set_state_D0 = skl_set_dsp_D0,
+ .set_state_D3 = skl_set_dsp_D3,
+ .load_fw = skl_load_base_firmware,
+ .get_fw_errcode = skl_get_errorcode,
+ .load_library = skl_load_library,
+ .load_mod = skl_load_module,
+ .unload_mod = skl_unload_module,
+};
+
+static struct sst_ops skl_ops = {
+ .irq_handler = skl_dsp_sst_interrupt,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .free = skl_dsp_free,
+};
+
+static struct sst_dsp_device skl_dev = {
+ .thread = skl_dsp_irq_thread_handler,
+ .ops = &skl_ops,
+};
+
+int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_dev **dsp)
+{
+ struct skl_dev *skl;
+ struct sst_dsp *sst;
+ int ret;
+
+ ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: no device\n", __func__);
+ return ret;
+ }
+
+ skl = *dsp;
+ sst = skl->dsp;
+ sst->addr.lpe = mmio_base;
+ sst->addr.shim = mmio_base;
+ sst->addr.sram0_base = SKL_ADSP_SRAM0_BASE;
+ sst->addr.sram1_base = SKL_ADSP_SRAM1_BASE;
+ sst->addr.w0_stat_sz = SKL_ADSP_W0_STAT_SZ;
+ sst->addr.w0_up_sz = SKL_ADSP_W0_UP_SZ;
+
+ sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ),
+ SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ);
+
+ ret = skl_ipc_init(dev, skl);
+ if (ret) {
+ skl_dsp_free(sst);
+ return ret;
+ }
+
+ sst->fw_ops = skl_fw_ops;
+
+ return skl_dsp_acquire_irq(sst);
+}
+EXPORT_SYMBOL_GPL(skl_sst_dsp_init);
+
+int skl_sst_init_fw(struct device *dev, struct skl_dev *skl)
+{
+ int ret;
+ struct sst_dsp *sst = skl->dsp;
+
+ ret = sst->fw_ops.load_fw(sst);
+ if (ret < 0) {
+ dev_err(dev, "Load base fw failed : %d\n", ret);
+ return ret;
+ }
+
+ skl_dsp_init_core_state(sst);
+
+ if (skl->lib_count > 1) {
+ ret = sst->fw_ops.load_library(sst, skl->lib_info,
+ skl->lib_count);
+ if (ret < 0) {
+ dev_err(dev, "Load Library failed : %x\n", ret);
+ return ret;
+ }
+ }
+ skl->is_first_boot = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(skl_sst_init_fw);
+
+void skl_sst_dsp_cleanup(struct device *dev, struct skl_dev *skl)
+{
+
+ if (skl->dsp->fw)
+ release_firmware(skl->dsp->fw);
+ skl_clear_module_table(skl->dsp);
+ skl_freeup_uuid_list(skl);
+ skl_ipc_free(&skl->ipc);
+ skl->dsp->ops->free(skl->dsp);
+ if (skl->boot_complete) {
+ skl->dsp->cl_dev.ops.cl_cleanup_controller(skl->dsp);
+ skl_cldma_int_disable(skl->dsp);
+ }
+}
+EXPORT_SYMBOL_GPL(skl_sst_dsp_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Skylake IPC driver");
diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c
new file mode 100644
index 000000000..73976c6df
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-topology.c
@@ -0,0 +1,3777 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl-topology.c - Implements Platform component ALSA controls/widget
+ * handlers.
+ *
+ * Copyright (C) 2014-2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/firmware.h>
+#include <linux/uuid.h>
+#include <sound/intel-nhlt.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-topology.h>
+#include <uapi/sound/snd_sst_tokens.h>
+#include <uapi/sound/skl-tplg-interface.h>
+#include "skl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+#include "skl-topology.h"
+#include "skl.h"
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+
+#define SKL_CH_FIXUP_MASK (1 << 0)
+#define SKL_RATE_FIXUP_MASK (1 << 1)
+#define SKL_FMT_FIXUP_MASK (1 << 2)
+#define SKL_IN_DIR_BIT_MASK BIT(0)
+#define SKL_PIN_COUNT_MASK GENMASK(7, 4)
+
+static const int mic_mono_list[] = {
+0, 1, 2, 3,
+};
+static const int mic_stereo_list[][SKL_CH_STEREO] = {
+{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3},
+};
+static const int mic_trio_list[][SKL_CH_TRIO] = {
+{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3},
+};
+static const int mic_quatro_list[][SKL_CH_QUATRO] = {
+{0, 1, 2, 3},
+};
+
+#define CHECK_HW_PARAMS(ch, freq, bps, prm_ch, prm_freq, prm_bps) \
+ ((ch == prm_ch) && (bps == prm_bps) && (freq == prm_freq))
+
+void skl_tplg_d0i3_get(struct skl_dev *skl, enum d0i3_capability caps)
+{
+ struct skl_d0i3_data *d0i3 = &skl->d0i3;
+
+ switch (caps) {
+ case SKL_D0I3_NONE:
+ d0i3->non_d0i3++;
+ break;
+
+ case SKL_D0I3_STREAMING:
+ d0i3->streaming++;
+ break;
+
+ case SKL_D0I3_NON_STREAMING:
+ d0i3->non_streaming++;
+ break;
+ }
+}
+
+void skl_tplg_d0i3_put(struct skl_dev *skl, enum d0i3_capability caps)
+{
+ struct skl_d0i3_data *d0i3 = &skl->d0i3;
+
+ switch (caps) {
+ case SKL_D0I3_NONE:
+ d0i3->non_d0i3--;
+ break;
+
+ case SKL_D0I3_STREAMING:
+ d0i3->streaming--;
+ break;
+
+ case SKL_D0I3_NON_STREAMING:
+ d0i3->non_streaming--;
+ break;
+ }
+}
+
+/*
+ * SKL DSP driver modelling uses only few DAPM widgets so for rest we will
+ * ignore. This helpers checks if the SKL driver handles this widget type
+ */
+static int is_skl_dsp_widget_type(struct snd_soc_dapm_widget *w,
+ struct device *dev)
+{
+ if (w->dapm->dev != dev)
+ return false;
+
+ switch (w->id) {
+ case snd_soc_dapm_dai_link:
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ case snd_soc_dapm_dai_out:
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_mux:
+
+ return false;
+ default:
+ return true;
+ }
+}
+
+static void skl_dump_mconfig(struct skl_dev *skl, struct skl_module_cfg *mcfg)
+{
+ struct skl_module_iface *iface = &mcfg->module->formats[mcfg->fmt_idx];
+
+ dev_dbg(skl->dev, "Dumping config\n");
+ dev_dbg(skl->dev, "Input Format:\n");
+ dev_dbg(skl->dev, "channels = %d\n", iface->inputs[0].fmt.channels);
+ dev_dbg(skl->dev, "s_freq = %d\n", iface->inputs[0].fmt.s_freq);
+ dev_dbg(skl->dev, "ch_cfg = %d\n", iface->inputs[0].fmt.ch_cfg);
+ dev_dbg(skl->dev, "valid bit depth = %d\n",
+ iface->inputs[0].fmt.valid_bit_depth);
+ dev_dbg(skl->dev, "Output Format:\n");
+ dev_dbg(skl->dev, "channels = %d\n", iface->outputs[0].fmt.channels);
+ dev_dbg(skl->dev, "s_freq = %d\n", iface->outputs[0].fmt.s_freq);
+ dev_dbg(skl->dev, "valid bit depth = %d\n",
+ iface->outputs[0].fmt.valid_bit_depth);
+ dev_dbg(skl->dev, "ch_cfg = %d\n", iface->outputs[0].fmt.ch_cfg);
+}
+
+static void skl_tplg_update_chmap(struct skl_module_fmt *fmt, int chs)
+{
+ int slot_map = 0xFFFFFFFF;
+ int start_slot = 0;
+ int i;
+
+ for (i = 0; i < chs; i++) {
+ /*
+ * For 2 channels with starting slot as 0, slot map will
+ * look like 0xFFFFFF10.
+ */
+ slot_map &= (~(0xF << (4 * i)) | (start_slot << (4 * i)));
+ start_slot++;
+ }
+ fmt->ch_map = slot_map;
+}
+
+static void skl_tplg_update_params(struct skl_module_fmt *fmt,
+ struct skl_pipe_params *params, int fixup)
+{
+ if (fixup & SKL_RATE_FIXUP_MASK)
+ fmt->s_freq = params->s_freq;
+ if (fixup & SKL_CH_FIXUP_MASK) {
+ fmt->channels = params->ch;
+ skl_tplg_update_chmap(fmt, fmt->channels);
+ }
+ if (fixup & SKL_FMT_FIXUP_MASK) {
+ fmt->valid_bit_depth = skl_get_bit_depth(params->s_fmt);
+
+ /*
+ * 16 bit is 16 bit container whereas 24 bit is in 32 bit
+ * container so update bit depth accordingly
+ */
+ switch (fmt->valid_bit_depth) {
+ case SKL_DEPTH_16BIT:
+ fmt->bit_depth = fmt->valid_bit_depth;
+ break;
+
+ default:
+ fmt->bit_depth = SKL_DEPTH_32BIT;
+ break;
+ }
+ }
+
+}
+
+/*
+ * A pipeline may have modules which impact the pcm parameters, like SRC,
+ * channel converter, format converter.
+ * We need to calculate the output params by applying the 'fixup'
+ * Topology will tell driver which type of fixup is to be applied by
+ * supplying the fixup mask, so based on that we calculate the output
+ *
+ * Now In FE the pcm hw_params is source/target format. Same is applicable
+ * for BE with its hw_params invoked.
+ * here based on FE, BE pipeline and direction we calculate the input and
+ * outfix and then apply that for a module
+ */
+static void skl_tplg_update_params_fixup(struct skl_module_cfg *m_cfg,
+ struct skl_pipe_params *params, bool is_fe)
+{
+ int in_fixup, out_fixup;
+ struct skl_module_fmt *in_fmt, *out_fmt;
+
+ /* Fixups will be applied to pin 0 only */
+ in_fmt = &m_cfg->module->formats[m_cfg->fmt_idx].inputs[0].fmt;
+ out_fmt = &m_cfg->module->formats[m_cfg->fmt_idx].outputs[0].fmt;
+
+ if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (is_fe) {
+ in_fixup = m_cfg->params_fixup;
+ out_fixup = (~m_cfg->converter) &
+ m_cfg->params_fixup;
+ } else {
+ out_fixup = m_cfg->params_fixup;
+ in_fixup = (~m_cfg->converter) &
+ m_cfg->params_fixup;
+ }
+ } else {
+ if (is_fe) {
+ out_fixup = m_cfg->params_fixup;
+ in_fixup = (~m_cfg->converter) &
+ m_cfg->params_fixup;
+ } else {
+ in_fixup = m_cfg->params_fixup;
+ out_fixup = (~m_cfg->converter) &
+ m_cfg->params_fixup;
+ }
+ }
+
+ skl_tplg_update_params(in_fmt, params, in_fixup);
+ skl_tplg_update_params(out_fmt, params, out_fixup);
+}
+
+/*
+ * A module needs input and output buffers, which are dependent upon pcm
+ * params, so once we have calculate params, we need buffer calculation as
+ * well.
+ */
+static void skl_tplg_update_buffer_size(struct skl_dev *skl,
+ struct skl_module_cfg *mcfg)
+{
+ int multiplier = 1;
+ struct skl_module_fmt *in_fmt, *out_fmt;
+ struct skl_module_res *res;
+
+ /* Since fixups is applied to pin 0 only, ibs, obs needs
+ * change for pin 0 only
+ */
+ res = &mcfg->module->resources[mcfg->res_idx];
+ in_fmt = &mcfg->module->formats[mcfg->fmt_idx].inputs[0].fmt;
+ out_fmt = &mcfg->module->formats[mcfg->fmt_idx].outputs[0].fmt;
+
+ if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT)
+ multiplier = 5;
+
+ res->ibs = DIV_ROUND_UP(in_fmt->s_freq, 1000) *
+ in_fmt->channels * (in_fmt->bit_depth >> 3) *
+ multiplier;
+
+ res->obs = DIV_ROUND_UP(out_fmt->s_freq, 1000) *
+ out_fmt->channels * (out_fmt->bit_depth >> 3) *
+ multiplier;
+}
+
+static u8 skl_tplg_be_dev_type(int dev_type)
+{
+ int ret;
+
+ switch (dev_type) {
+ case SKL_DEVICE_BT:
+ ret = NHLT_DEVICE_BT;
+ break;
+
+ case SKL_DEVICE_DMIC:
+ ret = NHLT_DEVICE_DMIC;
+ break;
+
+ case SKL_DEVICE_I2S:
+ ret = NHLT_DEVICE_I2S;
+ break;
+
+ default:
+ ret = NHLT_DEVICE_INVALID;
+ break;
+ }
+
+ return ret;
+}
+
+static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *m_cfg = w->priv;
+ int link_type, dir;
+ u32 ch, s_freq, s_fmt;
+ struct nhlt_specific_cfg *cfg;
+ u8 dev_type = skl_tplg_be_dev_type(m_cfg->dev_type);
+ int fmt_idx = m_cfg->fmt_idx;
+ struct skl_module_iface *m_iface = &m_cfg->module->formats[fmt_idx];
+
+ /* check if we already have blob */
+ if (m_cfg->formats_config.caps_size > 0)
+ return 0;
+
+ dev_dbg(skl->dev, "Applying default cfg blob\n");
+ switch (m_cfg->dev_type) {
+ case SKL_DEVICE_DMIC:
+ link_type = NHLT_LINK_DMIC;
+ dir = SNDRV_PCM_STREAM_CAPTURE;
+ s_freq = m_iface->inputs[0].fmt.s_freq;
+ s_fmt = m_iface->inputs[0].fmt.bit_depth;
+ ch = m_iface->inputs[0].fmt.channels;
+ break;
+
+ case SKL_DEVICE_I2S:
+ link_type = NHLT_LINK_SSP;
+ if (m_cfg->hw_conn_type == SKL_CONN_SOURCE) {
+ dir = SNDRV_PCM_STREAM_PLAYBACK;
+ s_freq = m_iface->outputs[0].fmt.s_freq;
+ s_fmt = m_iface->outputs[0].fmt.bit_depth;
+ ch = m_iface->outputs[0].fmt.channels;
+ } else {
+ dir = SNDRV_PCM_STREAM_CAPTURE;
+ s_freq = m_iface->inputs[0].fmt.s_freq;
+ s_fmt = m_iface->inputs[0].fmt.bit_depth;
+ ch = m_iface->inputs[0].fmt.channels;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* update the blob based on virtual bus_id and default params */
+ cfg = skl_get_ep_blob(skl, m_cfg->vbus_id, link_type,
+ s_fmt, ch, s_freq, dir, dev_type);
+ if (cfg) {
+ m_cfg->formats_config.caps_size = cfg->size;
+ m_cfg->formats_config.caps = (u32 *) &cfg->caps;
+ } else {
+ dev_err(skl->dev, "Blob NULL for id %x type %d dirn %d\n",
+ m_cfg->vbus_id, link_type, dir);
+ dev_err(skl->dev, "PCM: ch %d, freq %d, fmt %d\n",
+ ch, s_freq, s_fmt);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void skl_tplg_update_module_params(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *m_cfg = w->priv;
+ struct skl_pipe_params *params = m_cfg->pipe->p_params;
+ int p_conn_type = m_cfg->pipe->conn_type;
+ bool is_fe;
+
+ if (!m_cfg->params_fixup)
+ return;
+
+ dev_dbg(skl->dev, "Mconfig for widget=%s BEFORE updation\n",
+ w->name);
+
+ skl_dump_mconfig(skl, m_cfg);
+
+ if (p_conn_type == SKL_PIPE_CONN_TYPE_FE)
+ is_fe = true;
+ else
+ is_fe = false;
+
+ skl_tplg_update_params_fixup(m_cfg, params, is_fe);
+ skl_tplg_update_buffer_size(skl, m_cfg);
+
+ dev_dbg(skl->dev, "Mconfig for widget=%s AFTER updation\n",
+ w->name);
+
+ skl_dump_mconfig(skl, m_cfg);
+}
+
+/*
+ * some modules can have multiple params set from user control and
+ * need to be set after module is initialized. If set_param flag is
+ * set module params will be done after module is initialised.
+ */
+static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ int i, ret;
+ struct skl_module_cfg *mconfig = w->priv;
+ const struct snd_kcontrol_new *k;
+ struct soc_bytes_ext *sb;
+ struct skl_algo_data *bc;
+ struct skl_specific_cfg *sp_cfg;
+
+ if (mconfig->formats_config.caps_size > 0 &&
+ mconfig->formats_config.set_params == SKL_PARAM_SET) {
+ sp_cfg = &mconfig->formats_config;
+ ret = skl_set_module_params(skl, sp_cfg->caps,
+ sp_cfg->caps_size,
+ sp_cfg->param_id, mconfig);
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ k = &w->kcontrol_news[i];
+ if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ sb = (void *) k->private_value;
+ bc = (struct skl_algo_data *)sb->dobj.private;
+
+ if (bc->set_params == SKL_PARAM_SET) {
+ ret = skl_set_module_params(skl,
+ (u32 *)bc->params, bc->size,
+ bc->param_id, mconfig);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * some module param can set from user control and this is required as
+ * when module is initailzed. if module param is required in init it is
+ * identifed by set_param flag. if set_param flag is not set, then this
+ * parameter needs to set as part of module init.
+ */
+static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w)
+{
+ const struct snd_kcontrol_new *k;
+ struct soc_bytes_ext *sb;
+ struct skl_algo_data *bc;
+ struct skl_module_cfg *mconfig = w->priv;
+ int i;
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ k = &w->kcontrol_news[i];
+ if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ sb = (struct soc_bytes_ext *)k->private_value;
+ bc = (struct skl_algo_data *)sb->dobj.private;
+
+ if (bc->set_params != SKL_PARAM_INIT)
+ continue;
+
+ mconfig->formats_config.caps = (u32 *)bc->params;
+ mconfig->formats_config.caps_size = bc->size;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int skl_tplg_module_prepare(struct skl_dev *skl, struct skl_pipe *pipe,
+ struct snd_soc_dapm_widget *w, struct skl_module_cfg *mcfg)
+{
+ switch (mcfg->dev_type) {
+ case SKL_DEVICE_HDAHOST:
+ return skl_pcm_host_dma_prepare(skl->dev, pipe->p_params);
+
+ case SKL_DEVICE_HDALINK:
+ return skl_pcm_link_dma_prepare(skl->dev, pipe->p_params);
+ }
+
+ return 0;
+}
+
+/*
+ * Inside a pipe instance, we can have various modules. These modules need
+ * to instantiated in DSP by invoking INIT_MODULE IPC, which is achieved by
+ * skl_init_module() routine, so invoke that for all modules in a pipeline
+ */
+static int
+skl_tplg_init_pipe_modules(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ struct skl_pipe_module *w_module;
+ struct snd_soc_dapm_widget *w;
+ struct skl_module_cfg *mconfig;
+ u8 cfg_idx;
+ int ret = 0;
+
+ list_for_each_entry(w_module, &pipe->w_list, node) {
+ guid_t *uuid_mod;
+ w = w_module->w;
+ mconfig = w->priv;
+
+ /* check if module ids are populated */
+ if (mconfig->id.module_id < 0) {
+ dev_err(skl->dev,
+ "module %pUL id not populated\n",
+ (guid_t *)mconfig->guid);
+ return -EIO;
+ }
+
+ cfg_idx = mconfig->pipe->cur_config_idx;
+ mconfig->fmt_idx = mconfig->mod_cfg[cfg_idx].fmt_idx;
+ mconfig->res_idx = mconfig->mod_cfg[cfg_idx].res_idx;
+
+ if (mconfig->module->loadable && skl->dsp->fw_ops.load_mod) {
+ ret = skl->dsp->fw_ops.load_mod(skl->dsp,
+ mconfig->id.module_id, mconfig->guid);
+ if (ret < 0)
+ return ret;
+
+ mconfig->m_state = SKL_MODULE_LOADED;
+ }
+
+ /* prepare the DMA if the module is gateway cpr */
+ ret = skl_tplg_module_prepare(skl, pipe, w, mconfig);
+ if (ret < 0)
+ return ret;
+
+ /* update blob if blob is null for be with default value */
+ skl_tplg_update_be_blob(w, skl);
+
+ /*
+ * apply fix/conversion to module params based on
+ * FE/BE params
+ */
+ skl_tplg_update_module_params(w, skl);
+ uuid_mod = (guid_t *)mconfig->guid;
+ mconfig->id.pvt_id = skl_get_pvt_id(skl, uuid_mod,
+ mconfig->id.instance_id);
+ if (mconfig->id.pvt_id < 0)
+ return ret;
+ skl_tplg_set_module_init_data(w);
+
+ ret = skl_dsp_get_core(skl->dsp, mconfig->core_id);
+ if (ret < 0) {
+ dev_err(skl->dev, "Failed to wake up core %d ret=%d\n",
+ mconfig->core_id, ret);
+ return ret;
+ }
+
+ ret = skl_init_module(skl, mconfig);
+ if (ret < 0) {
+ skl_put_pvt_id(skl, uuid_mod, &mconfig->id.pvt_id);
+ goto err;
+ }
+
+ ret = skl_tplg_set_module_params(w, skl);
+ if (ret < 0)
+ goto err;
+ }
+
+ return 0;
+err:
+ skl_dsp_put_core(skl->dsp, mconfig->core_id);
+ return ret;
+}
+
+static int skl_tplg_unload_pipe_modules(struct skl_dev *skl,
+ struct skl_pipe *pipe)
+{
+ int ret = 0;
+ struct skl_pipe_module *w_module;
+ struct skl_module_cfg *mconfig;
+
+ list_for_each_entry(w_module, &pipe->w_list, node) {
+ guid_t *uuid_mod;
+ mconfig = w_module->w->priv;
+ uuid_mod = (guid_t *)mconfig->guid;
+
+ if (mconfig->module->loadable && skl->dsp->fw_ops.unload_mod &&
+ mconfig->m_state > SKL_MODULE_UNINIT) {
+ ret = skl->dsp->fw_ops.unload_mod(skl->dsp,
+ mconfig->id.module_id);
+ if (ret < 0)
+ return -EIO;
+ }
+ skl_put_pvt_id(skl, uuid_mod, &mconfig->id.pvt_id);
+
+ ret = skl_dsp_put_core(skl->dsp, mconfig->core_id);
+ if (ret < 0) {
+ /* don't return; continue with other modules */
+ dev_err(skl->dev, "Failed to sleep core %d ret=%d\n",
+ mconfig->core_id, ret);
+ }
+ }
+
+ /* no modules to unload in this path, so return */
+ return ret;
+}
+
+static bool skl_tplg_is_multi_fmt(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ struct skl_pipe_fmt *cur_fmt;
+ struct skl_pipe_fmt *next_fmt;
+ int i;
+
+ if (pipe->nr_cfgs <= 1)
+ return false;
+
+ if (pipe->conn_type != SKL_PIPE_CONN_TYPE_FE)
+ return true;
+
+ for (i = 0; i < pipe->nr_cfgs - 1; i++) {
+ if (pipe->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ cur_fmt = &pipe->configs[i].out_fmt;
+ next_fmt = &pipe->configs[i + 1].out_fmt;
+ } else {
+ cur_fmt = &pipe->configs[i].in_fmt;
+ next_fmt = &pipe->configs[i + 1].in_fmt;
+ }
+
+ if (!CHECK_HW_PARAMS(cur_fmt->channels, cur_fmt->freq,
+ cur_fmt->bps,
+ next_fmt->channels,
+ next_fmt->freq,
+ next_fmt->bps))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Here, we select pipe format based on the pipe type and pipe
+ * direction to determine the current config index for the pipeline.
+ * The config index is then used to select proper module resources.
+ * Intermediate pipes currently have a fixed format hence we select the
+ * 0th configuratation by default for such pipes.
+ */
+static int
+skl_tplg_get_pipe_config(struct skl_dev *skl, struct skl_module_cfg *mconfig)
+{
+ struct skl_pipe *pipe = mconfig->pipe;
+ struct skl_pipe_params *params = pipe->p_params;
+ struct skl_path_config *pconfig = &pipe->configs[0];
+ struct skl_pipe_fmt *fmt = NULL;
+ bool in_fmt = false;
+ int i;
+
+ if (pipe->nr_cfgs == 0) {
+ pipe->cur_config_idx = 0;
+ return 0;
+ }
+
+ if (skl_tplg_is_multi_fmt(skl, pipe)) {
+ pipe->cur_config_idx = pipe->pipe_config_idx;
+ pipe->memory_pages = pconfig->mem_pages;
+ dev_dbg(skl->dev, "found pipe config idx:%d\n",
+ pipe->cur_config_idx);
+ return 0;
+ }
+
+ if (pipe->conn_type == SKL_PIPE_CONN_TYPE_NONE) {
+ dev_dbg(skl->dev, "No conn_type detected, take 0th config\n");
+ pipe->cur_config_idx = 0;
+ pipe->memory_pages = pconfig->mem_pages;
+
+ return 0;
+ }
+
+ if ((pipe->conn_type == SKL_PIPE_CONN_TYPE_FE &&
+ pipe->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+ (pipe->conn_type == SKL_PIPE_CONN_TYPE_BE &&
+ pipe->direction == SNDRV_PCM_STREAM_CAPTURE))
+ in_fmt = true;
+
+ for (i = 0; i < pipe->nr_cfgs; i++) {
+ pconfig = &pipe->configs[i];
+ if (in_fmt)
+ fmt = &pconfig->in_fmt;
+ else
+ fmt = &pconfig->out_fmt;
+
+ if (CHECK_HW_PARAMS(params->ch, params->s_freq, params->s_fmt,
+ fmt->channels, fmt->freq, fmt->bps)) {
+ pipe->cur_config_idx = i;
+ pipe->memory_pages = pconfig->mem_pages;
+ dev_dbg(skl->dev, "Using pipe config: %d\n", i);
+
+ return 0;
+ }
+ }
+
+ dev_err(skl->dev, "Invalid pipe config: %d %d %d for pipe: %d\n",
+ params->ch, params->s_freq, params->s_fmt, pipe->ppl_id);
+ return -EINVAL;
+}
+
+/*
+ * Mixer module represents a pipeline. So in the Pre-PMU event of mixer we
+ * need create the pipeline. So we do following:
+ * - Create the pipeline
+ * - Initialize the modules in pipeline
+ * - finally bind all modules together
+ */
+static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ int ret;
+ struct skl_module_cfg *mconfig = w->priv;
+ struct skl_pipe_module *w_module;
+ struct skl_pipe *s_pipe = mconfig->pipe;
+ struct skl_module_cfg *src_module = NULL, *dst_module, *module;
+ struct skl_module_deferred_bind *modules;
+
+ ret = skl_tplg_get_pipe_config(skl, mconfig);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Create a list of modules for pipe.
+ * This list contains modules from source to sink
+ */
+ ret = skl_create_pipeline(skl, mconfig->pipe);
+ if (ret < 0)
+ return ret;
+
+ /* Init all pipe modules from source to sink */
+ ret = skl_tplg_init_pipe_modules(skl, s_pipe);
+ if (ret < 0)
+ return ret;
+
+ /* Bind modules from source to sink */
+ list_for_each_entry(w_module, &s_pipe->w_list, node) {
+ dst_module = w_module->w->priv;
+
+ if (src_module == NULL) {
+ src_module = dst_module;
+ continue;
+ }
+
+ ret = skl_bind_modules(skl, src_module, dst_module);
+ if (ret < 0)
+ return ret;
+
+ src_module = dst_module;
+ }
+
+ /*
+ * When the destination module is initialized, check for these modules
+ * in deferred bind list. If found, bind them.
+ */
+ list_for_each_entry(w_module, &s_pipe->w_list, node) {
+ if (list_empty(&skl->bind_list))
+ break;
+
+ list_for_each_entry(modules, &skl->bind_list, node) {
+ module = w_module->w->priv;
+ if (modules->dst == module)
+ skl_bind_modules(skl, modules->src,
+ modules->dst);
+ }
+ }
+
+ return 0;
+}
+
+static int skl_fill_sink_instance_id(struct skl_dev *skl, u32 *params,
+ int size, struct skl_module_cfg *mcfg)
+{
+ int i, pvt_id;
+
+ if (mcfg->m_type == SKL_MODULE_TYPE_KPB) {
+ struct skl_kpb_params *kpb_params =
+ (struct skl_kpb_params *)params;
+ struct skl_mod_inst_map *inst = kpb_params->u.map;
+
+ for (i = 0; i < kpb_params->num_modules; i++) {
+ pvt_id = skl_get_pvt_instance_id_map(skl, inst->mod_id,
+ inst->inst_id);
+ if (pvt_id < 0)
+ return -EINVAL;
+
+ inst->inst_id = pvt_id;
+ inst++;
+ }
+ }
+
+ return 0;
+}
+/*
+ * Some modules require params to be set after the module is bound to
+ * all pins connected.
+ *
+ * The module provider initializes set_param flag for such modules and we
+ * send params after binding
+ */
+static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w,
+ struct skl_module_cfg *mcfg, struct skl_dev *skl)
+{
+ int i, ret;
+ struct skl_module_cfg *mconfig = w->priv;
+ const struct snd_kcontrol_new *k;
+ struct soc_bytes_ext *sb;
+ struct skl_algo_data *bc;
+ struct skl_specific_cfg *sp_cfg;
+ u32 *params;
+
+ /*
+ * check all out/in pins are in bind state.
+ * if so set the module param
+ */
+ for (i = 0; i < mcfg->module->max_output_pins; i++) {
+ if (mcfg->m_out_pin[i].pin_state != SKL_PIN_BIND_DONE)
+ return 0;
+ }
+
+ for (i = 0; i < mcfg->module->max_input_pins; i++) {
+ if (mcfg->m_in_pin[i].pin_state != SKL_PIN_BIND_DONE)
+ return 0;
+ }
+
+ if (mconfig->formats_config.caps_size > 0 &&
+ mconfig->formats_config.set_params == SKL_PARAM_BIND) {
+ sp_cfg = &mconfig->formats_config;
+ ret = skl_set_module_params(skl, sp_cfg->caps,
+ sp_cfg->caps_size,
+ sp_cfg->param_id, mconfig);
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ k = &w->kcontrol_news[i];
+ if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ sb = (void *) k->private_value;
+ bc = (struct skl_algo_data *)sb->dobj.private;
+
+ if (bc->set_params == SKL_PARAM_BIND) {
+ params = kmemdup(bc->params, bc->max, GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ skl_fill_sink_instance_id(skl, params, bc->max,
+ mconfig);
+
+ ret = skl_set_module_params(skl, params,
+ bc->max, bc->param_id, mconfig);
+ kfree(params);
+
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int skl_get_module_id(struct skl_dev *skl, guid_t *uuid)
+{
+ struct uuid_module *module;
+
+ list_for_each_entry(module, &skl->uuid_list, list) {
+ if (guid_equal(uuid, &module->uuid))
+ return module->id;
+ }
+
+ return -EINVAL;
+}
+
+static int skl_tplg_find_moduleid_from_uuid(struct skl_dev *skl,
+ const struct snd_kcontrol_new *k)
+{
+ struct soc_bytes_ext *sb = (void *) k->private_value;
+ struct skl_algo_data *bc = (struct skl_algo_data *)sb->dobj.private;
+ struct skl_kpb_params *uuid_params, *params;
+ struct hdac_bus *bus = skl_to_bus(skl);
+ int i, size, module_id;
+
+ if (bc->set_params == SKL_PARAM_BIND && bc->max) {
+ uuid_params = (struct skl_kpb_params *)bc->params;
+ size = struct_size(params, u.map, uuid_params->num_modules);
+
+ params = devm_kzalloc(bus->dev, size, GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ params->num_modules = uuid_params->num_modules;
+
+ for (i = 0; i < uuid_params->num_modules; i++) {
+ module_id = skl_get_module_id(skl,
+ &uuid_params->u.map_uuid[i].mod_uuid);
+ if (module_id < 0) {
+ devm_kfree(bus->dev, params);
+ return -EINVAL;
+ }
+
+ params->u.map[i].mod_id = module_id;
+ params->u.map[i].inst_id =
+ uuid_params->u.map_uuid[i].inst_id;
+ }
+
+ devm_kfree(bus->dev, bc->params);
+ bc->params = (char *)params;
+ bc->max = size;
+ }
+
+ return 0;
+}
+
+/*
+ * Retrieve the module id from UUID mentioned in the
+ * post bind params
+ */
+void skl_tplg_add_moduleid_in_bind_params(struct skl_dev *skl,
+ struct snd_soc_dapm_widget *w)
+{
+ struct skl_module_cfg *mconfig = w->priv;
+ int i;
+
+ /*
+ * Post bind params are used for only for KPB
+ * to set copier instances to drain the data
+ * in fast mode
+ */
+ if (mconfig->m_type != SKL_MODULE_TYPE_KPB)
+ return;
+
+ for (i = 0; i < w->num_kcontrols; i++)
+ if ((w->kcontrol_news[i].access &
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) &&
+ (skl_tplg_find_moduleid_from_uuid(skl,
+ &w->kcontrol_news[i]) < 0))
+ dev_err(skl->dev,
+ "%s: invalid kpb post bind params\n",
+ __func__);
+}
+
+static int skl_tplg_module_add_deferred_bind(struct skl_dev *skl,
+ struct skl_module_cfg *src, struct skl_module_cfg *dst)
+{
+ struct skl_module_deferred_bind *m_list, *modules;
+ int i;
+
+ /* only supported for module with static pin connection */
+ for (i = 0; i < dst->module->max_input_pins; i++) {
+ struct skl_module_pin *pin = &dst->m_in_pin[i];
+
+ if (pin->is_dynamic)
+ continue;
+
+ if ((pin->id.module_id == src->id.module_id) &&
+ (pin->id.instance_id == src->id.instance_id)) {
+
+ if (!list_empty(&skl->bind_list)) {
+ list_for_each_entry(modules, &skl->bind_list, node) {
+ if (modules->src == src && modules->dst == dst)
+ return 0;
+ }
+ }
+
+ m_list = kzalloc(sizeof(*m_list), GFP_KERNEL);
+ if (!m_list)
+ return -ENOMEM;
+
+ m_list->src = src;
+ m_list->dst = dst;
+
+ list_add(&m_list->node, &skl->bind_list);
+ }
+ }
+
+ return 0;
+}
+
+static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl,
+ struct snd_soc_dapm_widget *src_w,
+ struct skl_module_cfg *src_mconfig)
+{
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dapm_widget *sink = NULL, *next_sink = NULL;
+ struct skl_module_cfg *sink_mconfig;
+ int ret;
+
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (!p->connect)
+ continue;
+
+ dev_dbg(skl->dev,
+ "%s: src widget=%s\n", __func__, w->name);
+ dev_dbg(skl->dev,
+ "%s: sink widget=%s\n", __func__, p->sink->name);
+
+ next_sink = p->sink;
+
+ if (!is_skl_dsp_widget_type(p->sink, skl->dev))
+ return skl_tplg_bind_sinks(p->sink, skl, src_w, src_mconfig);
+
+ /*
+ * here we will check widgets in sink pipelines, so that
+ * can be any widgets type and we are only interested if
+ * they are ones used for SKL so check that first
+ */
+ if ((p->sink->priv != NULL) &&
+ is_skl_dsp_widget_type(p->sink, skl->dev)) {
+
+ sink = p->sink;
+ sink_mconfig = sink->priv;
+
+ /*
+ * Modules other than PGA leaf can be connected
+ * directly or via switch to a module in another
+ * pipeline. EX: reference path
+ * when the path is enabled, the dst module that needs
+ * to be bound may not be initialized. if the module is
+ * not initialized, add these modules in the deferred
+ * bind list and when the dst module is initialised,
+ * bind this module to the dst_module in deferred list.
+ */
+ if (((src_mconfig->m_state == SKL_MODULE_INIT_DONE)
+ && (sink_mconfig->m_state == SKL_MODULE_UNINIT))) {
+
+ ret = skl_tplg_module_add_deferred_bind(skl,
+ src_mconfig, sink_mconfig);
+
+ if (ret < 0)
+ return ret;
+
+ }
+
+
+ if (src_mconfig->m_state == SKL_MODULE_UNINIT ||
+ sink_mconfig->m_state == SKL_MODULE_UNINIT)
+ continue;
+
+ /* Bind source to sink, mixin is always source */
+ ret = skl_bind_modules(skl, src_mconfig, sink_mconfig);
+ if (ret)
+ return ret;
+
+ /* set module params after bind */
+ skl_tplg_set_module_bind_params(src_w,
+ src_mconfig, skl);
+ skl_tplg_set_module_bind_params(sink,
+ sink_mconfig, skl);
+
+ /* Start sinks pipe first */
+ if (sink_mconfig->pipe->state != SKL_PIPE_STARTED) {
+ if (sink_mconfig->pipe->conn_type !=
+ SKL_PIPE_CONN_TYPE_FE)
+ ret = skl_run_pipe(skl,
+ sink_mconfig->pipe);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ if (!sink && next_sink)
+ return skl_tplg_bind_sinks(next_sink, skl, src_w, src_mconfig);
+
+ return 0;
+}
+
+/*
+ * A PGA represents a module in a pipeline. So in the Pre-PMU event of PGA
+ * we need to do following:
+ * - Bind to sink pipeline
+ * Since the sink pipes can be running and we don't get mixer event on
+ * connect for already running mixer, we need to find the sink pipes
+ * here and bind to them. This way dynamic connect works.
+ * - Start sink pipeline, if not running
+ * - Then run current pipe
+ */
+static int skl_tplg_pga_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *src_mconfig;
+ int ret = 0;
+
+ src_mconfig = w->priv;
+
+ /*
+ * find which sink it is connected to, bind with the sink,
+ * if sink is not started, start sink pipe first, then start
+ * this pipe
+ */
+ ret = skl_tplg_bind_sinks(w, skl, w, src_mconfig);
+ if (ret)
+ return ret;
+
+ /* Start source pipe last after starting all sinks */
+ if (src_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE)
+ return skl_run_pipe(skl, src_mconfig->pipe);
+
+ return 0;
+}
+
+static struct snd_soc_dapm_widget *skl_get_src_dsp_widget(
+ struct snd_soc_dapm_widget *w, struct skl_dev *skl)
+{
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dapm_widget *src_w = NULL;
+
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ src_w = p->source;
+ if (!p->connect)
+ continue;
+
+ dev_dbg(skl->dev, "sink widget=%s\n", w->name);
+ dev_dbg(skl->dev, "src widget=%s\n", p->source->name);
+
+ /*
+ * here we will check widgets in sink pipelines, so that can
+ * be any widgets type and we are only interested if they are
+ * ones used for SKL so check that first
+ */
+ if ((p->source->priv != NULL) &&
+ is_skl_dsp_widget_type(p->source, skl->dev)) {
+ return p->source;
+ }
+ }
+
+ if (src_w != NULL)
+ return skl_get_src_dsp_widget(src_w, skl);
+
+ return NULL;
+}
+
+/*
+ * in the Post-PMU event of mixer we need to do following:
+ * - Check if this pipe is running
+ * - if not, then
+ * - bind this pipeline to its source pipeline
+ * if source pipe is already running, this means it is a dynamic
+ * connection and we need to bind only to that pipe
+ * - start this pipeline
+ */
+static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ int ret = 0;
+ struct snd_soc_dapm_widget *source, *sink;
+ struct skl_module_cfg *src_mconfig, *sink_mconfig;
+ int src_pipe_started = 0;
+
+ sink = w;
+ sink_mconfig = sink->priv;
+
+ /*
+ * If source pipe is already started, that means source is driving
+ * one more sink before this sink got connected, Since source is
+ * started, bind this sink to source and start this pipe.
+ */
+ source = skl_get_src_dsp_widget(w, skl);
+ if (source != NULL) {
+ src_mconfig = source->priv;
+ sink_mconfig = sink->priv;
+ src_pipe_started = 1;
+
+ /*
+ * check pipe state, then no need to bind or start the
+ * pipe
+ */
+ if (src_mconfig->pipe->state != SKL_PIPE_STARTED)
+ src_pipe_started = 0;
+ }
+
+ if (src_pipe_started) {
+ ret = skl_bind_modules(skl, src_mconfig, sink_mconfig);
+ if (ret)
+ return ret;
+
+ /* set module params after bind */
+ skl_tplg_set_module_bind_params(source, src_mconfig, skl);
+ skl_tplg_set_module_bind_params(sink, sink_mconfig, skl);
+
+ if (sink_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE)
+ ret = skl_run_pipe(skl, sink_mconfig->pipe);
+ }
+
+ return ret;
+}
+
+/*
+ * in the Pre-PMD event of mixer we need to do following:
+ * - Stop the pipe
+ * - find the source connections and remove that from dapm_path_list
+ * - unbind with source pipelines if still connected
+ */
+static int skl_tplg_mixer_dapm_pre_pmd_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *src_mconfig, *sink_mconfig;
+ int ret = 0, i;
+
+ sink_mconfig = w->priv;
+
+ /* Stop the pipe */
+ ret = skl_stop_pipe(skl, sink_mconfig->pipe);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < sink_mconfig->module->max_input_pins; i++) {
+ if (sink_mconfig->m_in_pin[i].pin_state == SKL_PIN_BIND_DONE) {
+ src_mconfig = sink_mconfig->m_in_pin[i].tgt_mcfg;
+ if (!src_mconfig)
+ continue;
+
+ ret = skl_unbind_modules(skl,
+ src_mconfig, sink_mconfig);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * in the Post-PMD event of mixer we need to do following:
+ * - Unbind the modules within the pipeline
+ * - Delete the pipeline (modules are not required to be explicitly
+ * deleted, pipeline delete is enough here
+ */
+static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *mconfig = w->priv;
+ struct skl_pipe_module *w_module;
+ struct skl_module_cfg *src_module = NULL, *dst_module;
+ struct skl_pipe *s_pipe = mconfig->pipe;
+ struct skl_module_deferred_bind *modules, *tmp;
+
+ if (s_pipe->state == SKL_PIPE_INVALID)
+ return -EINVAL;
+
+ list_for_each_entry(w_module, &s_pipe->w_list, node) {
+ if (list_empty(&skl->bind_list))
+ break;
+
+ src_module = w_module->w->priv;
+
+ list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) {
+ /*
+ * When the destination module is deleted, Unbind the
+ * modules from deferred bind list.
+ */
+ if (modules->dst == src_module) {
+ skl_unbind_modules(skl, modules->src,
+ modules->dst);
+ }
+
+ /*
+ * When the source module is deleted, remove this entry
+ * from the deferred bind list.
+ */
+ if (modules->src == src_module) {
+ list_del(&modules->node);
+ modules->src = NULL;
+ modules->dst = NULL;
+ kfree(modules);
+ }
+ }
+ }
+
+ list_for_each_entry(w_module, &s_pipe->w_list, node) {
+ dst_module = w_module->w->priv;
+
+ if (src_module == NULL) {
+ src_module = dst_module;
+ continue;
+ }
+
+ skl_unbind_modules(skl, src_module, dst_module);
+ src_module = dst_module;
+ }
+
+ skl_delete_pipe(skl, mconfig->pipe);
+
+ list_for_each_entry(w_module, &s_pipe->w_list, node) {
+ src_module = w_module->w->priv;
+ src_module->m_state = SKL_MODULE_UNINIT;
+ }
+
+ return skl_tplg_unload_pipe_modules(skl, s_pipe);
+}
+
+/*
+ * in the Post-PMD event of PGA we need to do following:
+ * - Stop the pipeline
+ * - In source pipe is connected, unbind with source pipelines
+ */
+static int skl_tplg_pga_dapm_post_pmd_event(struct snd_soc_dapm_widget *w,
+ struct skl_dev *skl)
+{
+ struct skl_module_cfg *src_mconfig, *sink_mconfig;
+ int ret = 0, i;
+
+ src_mconfig = w->priv;
+
+ /* Stop the pipe since this is a mixin module */
+ ret = skl_stop_pipe(skl, src_mconfig->pipe);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < src_mconfig->module->max_output_pins; i++) {
+ if (src_mconfig->m_out_pin[i].pin_state == SKL_PIN_BIND_DONE) {
+ sink_mconfig = src_mconfig->m_out_pin[i].tgt_mcfg;
+ if (!sink_mconfig)
+ continue;
+ /*
+ * This is a connecter and if path is found that means
+ * unbind between source and sink has not happened yet
+ */
+ ret = skl_unbind_modules(skl, src_mconfig,
+ sink_mconfig);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * In modelling, we assume there will be ONLY one mixer in a pipeline. If a
+ * second one is required that is created as another pipe entity.
+ * The mixer is responsible for pipe management and represent a pipeline
+ * instance
+ */
+static int skl_tplg_mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct skl_dev *skl = get_skl_ctx(dapm->dev);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return skl_tplg_mixer_dapm_pre_pmu_event(w, skl);
+
+ case SND_SOC_DAPM_POST_PMU:
+ return skl_tplg_mixer_dapm_post_pmu_event(w, skl);
+
+ case SND_SOC_DAPM_PRE_PMD:
+ return skl_tplg_mixer_dapm_pre_pmd_event(w, skl);
+
+ case SND_SOC_DAPM_POST_PMD:
+ return skl_tplg_mixer_dapm_post_pmd_event(w, skl);
+ }
+
+ return 0;
+}
+
+/*
+ * In modelling, we assumed rest of the modules in pipeline are PGA. But we
+ * are interested in last PGA (leaf PGA) in a pipeline to disconnect with
+ * the sink when it is running (two FE to one BE or one FE to two BE)
+ * scenarios
+ */
+static int skl_tplg_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct skl_dev *skl = get_skl_ctx(dapm->dev);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return skl_tplg_pga_dapm_pre_pmu_event(w, skl);
+
+ case SND_SOC_DAPM_POST_PMD:
+ return skl_tplg_pga_dapm_post_pmd_event(w, skl);
+ }
+
+ return 0;
+}
+
+static int skl_tplg_multi_config_set_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ bool is_set)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct hdac_bus *bus = snd_soc_component_get_drvdata(component);
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_pipeline *ppl;
+ struct skl_pipe *pipe = NULL;
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ u32 *pipe_id;
+
+ if (!ec)
+ return -EINVAL;
+
+ if (is_set && ucontrol->value.enumerated.item[0] > ec->items)
+ return -EINVAL;
+
+ pipe_id = ec->dobj.private;
+
+ list_for_each_entry(ppl, &skl->ppl_list, node) {
+ if (ppl->pipe->ppl_id == *pipe_id) {
+ pipe = ppl->pipe;
+ break;
+ }
+ }
+ if (!pipe)
+ return -EIO;
+
+ if (is_set)
+ pipe->pipe_config_idx = ucontrol->value.enumerated.item[0];
+ else
+ ucontrol->value.enumerated.item[0] = pipe->pipe_config_idx;
+
+ return 0;
+}
+
+static int skl_tplg_multi_config_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return skl_tplg_multi_config_set_get(kcontrol, ucontrol, false);
+}
+
+static int skl_tplg_multi_config_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return skl_tplg_multi_config_set_get(kcontrol, ucontrol, true);
+}
+
+static int skl_tplg_multi_config_get_dmic(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return skl_tplg_multi_config_set_get(kcontrol, ucontrol, false);
+}
+
+static int skl_tplg_multi_config_set_dmic(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return skl_tplg_multi_config_set_get(kcontrol, ucontrol, true);
+}
+
+static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol,
+ unsigned int __user *data, unsigned int size)
+{
+ struct soc_bytes_ext *sb =
+ (struct soc_bytes_ext *)kcontrol->private_value;
+ struct skl_algo_data *bc = (struct skl_algo_data *)sb->dobj.private;
+ struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct skl_module_cfg *mconfig = w->priv;
+ struct skl_dev *skl = get_skl_ctx(w->dapm->dev);
+
+ if (w->power)
+ skl_get_module_params(skl, (u32 *)bc->params,
+ bc->size, bc->param_id, mconfig);
+
+ /* decrement size for TLV header */
+ size -= 2 * sizeof(u32);
+
+ /* check size as we don't want to send kernel data */
+ if (size > bc->max)
+ size = bc->max;
+
+ if (bc->params) {
+ if (copy_to_user(data, &bc->param_id, sizeof(u32)))
+ return -EFAULT;
+ if (copy_to_user(data + 1, &size, sizeof(u32)))
+ return -EFAULT;
+ if (copy_to_user(data + 2, bc->params, size))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+#define SKL_PARAM_VENDOR_ID 0xff
+
+static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *data, unsigned int size)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct skl_module_cfg *mconfig = w->priv;
+ struct soc_bytes_ext *sb =
+ (struct soc_bytes_ext *)kcontrol->private_value;
+ struct skl_algo_data *ac = (struct skl_algo_data *)sb->dobj.private;
+ struct skl_dev *skl = get_skl_ctx(w->dapm->dev);
+
+ if (ac->params) {
+ if (size > ac->max)
+ return -EINVAL;
+ ac->size = size;
+
+ if (copy_from_user(ac->params, data, size))
+ return -EFAULT;
+
+ if (w->power)
+ return skl_set_module_params(skl,
+ (u32 *)ac->params, ac->size,
+ ac->param_id, mconfig);
+ }
+
+ return 0;
+}
+
+static int skl_tplg_mic_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct skl_module_cfg *mconfig = w->priv;
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ u32 ch_type = *((u32 *)ec->dobj.private);
+
+ if (mconfig->dmic_ch_type == ch_type)
+ ucontrol->value.enumerated.item[0] =
+ mconfig->dmic_ch_combo_index;
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+
+ return 0;
+}
+
+static int skl_fill_mic_sel_params(struct skl_module_cfg *mconfig,
+ struct skl_mic_sel_config *mic_cfg, struct device *dev)
+{
+ struct skl_specific_cfg *sp_cfg = &mconfig->formats_config;
+
+ sp_cfg->caps_size = sizeof(struct skl_mic_sel_config);
+ sp_cfg->set_params = SKL_PARAM_SET;
+ sp_cfg->param_id = 0x00;
+ if (!sp_cfg->caps) {
+ sp_cfg->caps = devm_kzalloc(dev, sp_cfg->caps_size, GFP_KERNEL);
+ if (!sp_cfg->caps)
+ return -ENOMEM;
+ }
+
+ mic_cfg->mic_switch = SKL_MIC_SEL_SWITCH;
+ mic_cfg->flags = 0;
+ memcpy(sp_cfg->caps, mic_cfg, sp_cfg->caps_size);
+
+ return 0;
+}
+
+static int skl_tplg_mic_control_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct skl_module_cfg *mconfig = w->priv;
+ struct skl_mic_sel_config mic_cfg = {0};
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ u32 ch_type = *((u32 *)ec->dobj.private);
+ const int *list;
+ u8 in_ch, out_ch, index;
+
+ mconfig->dmic_ch_type = ch_type;
+ mconfig->dmic_ch_combo_index = ucontrol->value.enumerated.item[0];
+
+ /* enum control index 0 is INVALID, so no channels to be set */
+ if (mconfig->dmic_ch_combo_index == 0)
+ return 0;
+
+ /* No valid channel selection map for index 0, so offset by 1 */
+ index = mconfig->dmic_ch_combo_index - 1;
+
+ switch (ch_type) {
+ case SKL_CH_MONO:
+ if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_mono_list))
+ return -EINVAL;
+
+ list = &mic_mono_list[index];
+ break;
+
+ case SKL_CH_STEREO:
+ if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_stereo_list))
+ return -EINVAL;
+
+ list = mic_stereo_list[index];
+ break;
+
+ case SKL_CH_TRIO:
+ if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_trio_list))
+ return -EINVAL;
+
+ list = mic_trio_list[index];
+ break;
+
+ case SKL_CH_QUATRO:
+ if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_quatro_list))
+ return -EINVAL;
+
+ list = mic_quatro_list[index];
+ break;
+
+ default:
+ dev_err(w->dapm->dev,
+ "Invalid channel %d for mic_select module\n",
+ ch_type);
+ return -EINVAL;
+
+ }
+
+ /* channel type enum map to number of chanels for that type */
+ for (out_ch = 0; out_ch < ch_type; out_ch++) {
+ in_ch = list[out_ch];
+ mic_cfg.blob[out_ch][in_ch] = SKL_DEFAULT_MIC_SEL_GAIN;
+ }
+
+ return skl_fill_mic_sel_params(mconfig, &mic_cfg, w->dapm->dev);
+}
+
+/*
+ * Fill the dma id for host and link. In case of passthrough
+ * pipeline, this will both host and link in the same
+ * pipeline, so need to copy the link and host based on dev_type
+ */
+static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg,
+ struct skl_pipe_params *params)
+{
+ struct skl_pipe *pipe = mcfg->pipe;
+
+ if (pipe->passthru) {
+ switch (mcfg->dev_type) {
+ case SKL_DEVICE_HDALINK:
+ pipe->p_params->link_dma_id = params->link_dma_id;
+ pipe->p_params->link_index = params->link_index;
+ pipe->p_params->link_bps = params->link_bps;
+ break;
+
+ case SKL_DEVICE_HDAHOST:
+ pipe->p_params->host_dma_id = params->host_dma_id;
+ pipe->p_params->host_bps = params->host_bps;
+ break;
+
+ default:
+ break;
+ }
+ pipe->p_params->s_fmt = params->s_fmt;
+ pipe->p_params->ch = params->ch;
+ pipe->p_params->s_freq = params->s_freq;
+ pipe->p_params->stream = params->stream;
+ pipe->p_params->format = params->format;
+
+ } else {
+ memcpy(pipe->p_params, params, sizeof(*params));
+ }
+}
+
+/*
+ * The FE params are passed by hw_params of the DAI.
+ * On hw_params, the params are stored in Gateway module of the FE and we
+ * need to calculate the format in DSP module configuration, that
+ * conversion is done here
+ */
+int skl_tplg_update_pipe_params(struct device *dev,
+ struct skl_module_cfg *mconfig,
+ struct skl_pipe_params *params)
+{
+ struct skl_module_res *res;
+ struct skl_dev *skl = get_skl_ctx(dev);
+ struct skl_module_fmt *format = NULL;
+ u8 cfg_idx = mconfig->pipe->cur_config_idx;
+
+ res = &mconfig->module->resources[mconfig->res_idx];
+ skl_tplg_fill_dma_id(mconfig, params);
+ mconfig->fmt_idx = mconfig->mod_cfg[cfg_idx].fmt_idx;
+ mconfig->res_idx = mconfig->mod_cfg[cfg_idx].res_idx;
+
+ if (skl->nr_modules)
+ return 0;
+
+ if (params->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ format = &mconfig->module->formats[mconfig->fmt_idx].inputs[0].fmt;
+ else
+ format = &mconfig->module->formats[mconfig->fmt_idx].outputs[0].fmt;
+
+ /* set the hw_params */
+ format->s_freq = params->s_freq;
+ format->channels = params->ch;
+ format->valid_bit_depth = skl_get_bit_depth(params->s_fmt);
+
+ /*
+ * 16 bit is 16 bit container whereas 24 bit is in 32 bit
+ * container so update bit depth accordingly
+ */
+ switch (format->valid_bit_depth) {
+ case SKL_DEPTH_16BIT:
+ format->bit_depth = format->valid_bit_depth;
+ break;
+
+ case SKL_DEPTH_24BIT:
+ case SKL_DEPTH_32BIT:
+ format->bit_depth = SKL_DEPTH_32BIT;
+ break;
+
+ default:
+ dev_err(dev, "Invalid bit depth %x for pipe\n",
+ format->valid_bit_depth);
+ return -EINVAL;
+ }
+
+ if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ res->ibs = (format->s_freq / 1000) *
+ (format->channels) *
+ (format->bit_depth >> 3);
+ } else {
+ res->obs = (format->s_freq / 1000) *
+ (format->channels) *
+ (format->bit_depth >> 3);
+ }
+
+ return 0;
+}
+
+/*
+ * Query the module config for the FE DAI
+ * This is used to find the hw_params set for that DAI and apply to FE
+ * pipeline
+ */
+struct skl_module_cfg *
+skl_tplg_fe_get_cpr_module(struct snd_soc_dai *dai, int stream)
+{
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_path *p = NULL;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ w = dai->playback_widget;
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (p->connect && p->sink->power &&
+ !is_skl_dsp_widget_type(p->sink, dai->dev))
+ continue;
+
+ if (p->sink->priv) {
+ dev_dbg(dai->dev, "set params for %s\n",
+ p->sink->name);
+ return p->sink->priv;
+ }
+ }
+ } else {
+ w = dai->capture_widget;
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ if (p->connect && p->source->power &&
+ !is_skl_dsp_widget_type(p->source, dai->dev))
+ continue;
+
+ if (p->source->priv) {
+ dev_dbg(dai->dev, "set params for %s\n",
+ p->source->name);
+ return p->source->priv;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static struct skl_module_cfg *skl_get_mconfig_pb_cpr(
+ struct snd_soc_dai *dai, struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+ struct skl_module_cfg *mconfig = NULL;
+
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ if (w->endpoints[SND_SOC_DAPM_DIR_OUT] > 0) {
+ if (p->connect &&
+ (p->sink->id == snd_soc_dapm_aif_out) &&
+ p->source->priv) {
+ mconfig = p->source->priv;
+ return mconfig;
+ }
+ mconfig = skl_get_mconfig_pb_cpr(dai, p->source);
+ if (mconfig)
+ return mconfig;
+ }
+ }
+ return mconfig;
+}
+
+static struct skl_module_cfg *skl_get_mconfig_cap_cpr(
+ struct snd_soc_dai *dai, struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+ struct skl_module_cfg *mconfig = NULL;
+
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (w->endpoints[SND_SOC_DAPM_DIR_IN] > 0) {
+ if (p->connect &&
+ (p->source->id == snd_soc_dapm_aif_in) &&
+ p->sink->priv) {
+ mconfig = p->sink->priv;
+ return mconfig;
+ }
+ mconfig = skl_get_mconfig_cap_cpr(dai, p->sink);
+ if (mconfig)
+ return mconfig;
+ }
+ }
+ return mconfig;
+}
+
+struct skl_module_cfg *
+skl_tplg_be_get_cpr_module(struct snd_soc_dai *dai, int stream)
+{
+ struct snd_soc_dapm_widget *w;
+ struct skl_module_cfg *mconfig;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ w = dai->playback_widget;
+ mconfig = skl_get_mconfig_pb_cpr(dai, w);
+ } else {
+ w = dai->capture_widget;
+ mconfig = skl_get_mconfig_cap_cpr(dai, w);
+ }
+ return mconfig;
+}
+
+static u8 skl_tplg_be_link_type(int dev_type)
+{
+ int ret;
+
+ switch (dev_type) {
+ case SKL_DEVICE_BT:
+ ret = NHLT_LINK_SSP;
+ break;
+
+ case SKL_DEVICE_DMIC:
+ ret = NHLT_LINK_DMIC;
+ break;
+
+ case SKL_DEVICE_I2S:
+ ret = NHLT_LINK_SSP;
+ break;
+
+ case SKL_DEVICE_HDALINK:
+ ret = NHLT_LINK_HDA;
+ break;
+
+ default:
+ ret = NHLT_LINK_INVALID;
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Fill the BE gateway parameters
+ * The BE gateway expects a blob of parameters which are kept in the ACPI
+ * NHLT blob, so query the blob for interface type (i2s/pdm) and instance.
+ * The port can have multiple settings so pick based on the PCM
+ * parameters
+ */
+static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai,
+ struct skl_module_cfg *mconfig,
+ struct skl_pipe_params *params)
+{
+ struct nhlt_specific_cfg *cfg;
+ struct skl_dev *skl = get_skl_ctx(dai->dev);
+ int link_type = skl_tplg_be_link_type(mconfig->dev_type);
+ u8 dev_type = skl_tplg_be_dev_type(mconfig->dev_type);
+
+ skl_tplg_fill_dma_id(mconfig, params);
+
+ if (link_type == NHLT_LINK_HDA)
+ return 0;
+
+ /* update the blob based on virtual bus_id*/
+ cfg = skl_get_ep_blob(skl, mconfig->vbus_id, link_type,
+ params->s_fmt, params->ch,
+ params->s_freq, params->stream,
+ dev_type);
+ if (cfg) {
+ mconfig->formats_config.caps_size = cfg->size;
+ mconfig->formats_config.caps = (u32 *) &cfg->caps;
+ } else {
+ dev_err(dai->dev, "Blob NULL for id %x type %d dirn %d\n",
+ mconfig->vbus_id, link_type,
+ params->stream);
+ dev_err(dai->dev, "PCM: ch %d, freq %d, fmt %d\n",
+ params->ch, params->s_freq, params->s_fmt);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int skl_tplg_be_set_src_pipe_params(struct snd_soc_dai *dai,
+ struct snd_soc_dapm_widget *w,
+ struct skl_pipe_params *params)
+{
+ struct snd_soc_dapm_path *p;
+ int ret = -EIO;
+
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ if (p->connect && is_skl_dsp_widget_type(p->source, dai->dev) &&
+ p->source->priv) {
+
+ ret = skl_tplg_be_fill_pipe_params(dai,
+ p->source->priv, params);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = skl_tplg_be_set_src_pipe_params(dai,
+ p->source, params);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static int skl_tplg_be_set_sink_pipe_params(struct snd_soc_dai *dai,
+ struct snd_soc_dapm_widget *w, struct skl_pipe_params *params)
+{
+ struct snd_soc_dapm_path *p;
+ int ret = -EIO;
+
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (p->connect && is_skl_dsp_widget_type(p->sink, dai->dev) &&
+ p->sink->priv) {
+
+ ret = skl_tplg_be_fill_pipe_params(dai,
+ p->sink->priv, params);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = skl_tplg_be_set_sink_pipe_params(
+ dai, p->sink, params);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * BE hw_params can be a source parameters (capture) or sink parameters
+ * (playback). Based on sink and source we need to either find the source
+ * list or the sink list and set the pipeline parameters
+ */
+int skl_tplg_be_update_params(struct snd_soc_dai *dai,
+ struct skl_pipe_params *params)
+{
+ struct snd_soc_dapm_widget *w;
+
+ if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ w = dai->playback_widget;
+
+ return skl_tplg_be_set_src_pipe_params(dai, w, params);
+
+ } else {
+ w = dai->capture_widget;
+
+ return skl_tplg_be_set_sink_pipe_params(dai, w, params);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_tplg_widget_events skl_tplg_widget_ops[] = {
+ {SKL_MIXER_EVENT, skl_tplg_mixer_event},
+ {SKL_VMIXER_EVENT, skl_tplg_mixer_event},
+ {SKL_PGA_EVENT, skl_tplg_pga_event},
+};
+
+static const struct snd_soc_tplg_bytes_ext_ops skl_tlv_ops[] = {
+ {SKL_CONTROL_TYPE_BYTE_TLV, skl_tplg_tlv_control_get,
+ skl_tplg_tlv_control_set},
+};
+
+static const struct snd_soc_tplg_kcontrol_ops skl_tplg_kcontrol_ops[] = {
+ {
+ .id = SKL_CONTROL_TYPE_MIC_SELECT,
+ .get = skl_tplg_mic_control_get,
+ .put = skl_tplg_mic_control_set,
+ },
+ {
+ .id = SKL_CONTROL_TYPE_MULTI_IO_SELECT,
+ .get = skl_tplg_multi_config_get,
+ .put = skl_tplg_multi_config_set,
+ },
+ {
+ .id = SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC,
+ .get = skl_tplg_multi_config_get_dmic,
+ .put = skl_tplg_multi_config_set_dmic,
+ }
+};
+
+static int skl_tplg_fill_pipe_cfg(struct device *dev,
+ struct skl_pipe *pipe, u32 tkn,
+ u32 tkn_val, int conf_idx, int dir)
+{
+ struct skl_pipe_fmt *fmt;
+ struct skl_path_config *config;
+
+ switch (dir) {
+ case SKL_DIR_IN:
+ fmt = &pipe->configs[conf_idx].in_fmt;
+ break;
+
+ case SKL_DIR_OUT:
+ fmt = &pipe->configs[conf_idx].out_fmt;
+ break;
+
+ default:
+ dev_err(dev, "Invalid direction: %d\n", dir);
+ return -EINVAL;
+ }
+
+ config = &pipe->configs[conf_idx];
+
+ switch (tkn) {
+ case SKL_TKN_U32_CFG_FREQ:
+ fmt->freq = tkn_val;
+ break;
+
+ case SKL_TKN_U8_CFG_CHAN:
+ fmt->channels = tkn_val;
+ break;
+
+ case SKL_TKN_U8_CFG_BPS:
+ fmt->bps = tkn_val;
+ break;
+
+ case SKL_TKN_U32_PATH_MEM_PGS:
+ config->mem_pages = tkn_val;
+ break;
+
+ default:
+ dev_err(dev, "Invalid token config: %d\n", tkn);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int skl_tplg_fill_pipe_tkn(struct device *dev,
+ struct skl_pipe *pipe, u32 tkn,
+ u32 tkn_val)
+{
+
+ switch (tkn) {
+ case SKL_TKN_U32_PIPE_CONN_TYPE:
+ pipe->conn_type = tkn_val;
+ break;
+
+ case SKL_TKN_U32_PIPE_PRIORITY:
+ pipe->pipe_priority = tkn_val;
+ break;
+
+ case SKL_TKN_U32_PIPE_MEM_PGS:
+ pipe->memory_pages = tkn_val;
+ break;
+
+ case SKL_TKN_U32_PMODE:
+ pipe->lp_mode = tkn_val;
+ break;
+
+ case SKL_TKN_U32_PIPE_DIRECTION:
+ pipe->direction = tkn_val;
+ break;
+
+ case SKL_TKN_U32_NUM_CONFIGS:
+ pipe->nr_cfgs = tkn_val;
+ break;
+
+ default:
+ dev_err(dev, "Token not handled %d\n", tkn);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Add pipeline by parsing the relevant tokens
+ * Return an existing pipe if the pipe already exists.
+ */
+static int skl_tplg_add_pipe(struct device *dev,
+ struct skl_module_cfg *mconfig, struct skl_dev *skl,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem)
+{
+ struct skl_pipeline *ppl;
+ struct skl_pipe *pipe;
+ struct skl_pipe_params *params;
+
+ list_for_each_entry(ppl, &skl->ppl_list, node) {
+ if (ppl->pipe->ppl_id == tkn_elem->value) {
+ mconfig->pipe = ppl->pipe;
+ return -EEXIST;
+ }
+ }
+
+ ppl = devm_kzalloc(dev, sizeof(*ppl), GFP_KERNEL);
+ if (!ppl)
+ return -ENOMEM;
+
+ pipe = devm_kzalloc(dev, sizeof(*pipe), GFP_KERNEL);
+ if (!pipe)
+ return -ENOMEM;
+
+ params = devm_kzalloc(dev, sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ pipe->p_params = params;
+ pipe->ppl_id = tkn_elem->value;
+ INIT_LIST_HEAD(&pipe->w_list);
+
+ ppl->pipe = pipe;
+ list_add(&ppl->node, &skl->ppl_list);
+
+ mconfig->pipe = pipe;
+ mconfig->pipe->state = SKL_PIPE_INVALID;
+
+ return 0;
+}
+
+static int skl_tplg_get_uuid(struct device *dev, guid_t *guid,
+ struct snd_soc_tplg_vendor_uuid_elem *uuid_tkn)
+{
+ if (uuid_tkn->token == SKL_TKN_UUID) {
+ import_guid(guid, uuid_tkn->uuid);
+ return 0;
+ }
+
+ dev_err(dev, "Not an UUID token %d\n", uuid_tkn->token);
+
+ return -EINVAL;
+}
+
+static int skl_tplg_fill_pin(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_module_pin *m_pin,
+ int pin_index)
+{
+ int ret;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_U32_PIN_MOD_ID:
+ m_pin[pin_index].id.module_id = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_PIN_INST_ID:
+ m_pin[pin_index].id.instance_id = tkn_elem->value;
+ break;
+
+ case SKL_TKN_UUID:
+ ret = skl_tplg_get_uuid(dev, &m_pin[pin_index].id.mod_uuid,
+ (struct snd_soc_tplg_vendor_uuid_elem *)tkn_elem);
+ if (ret < 0)
+ return ret;
+
+ break;
+
+ default:
+ dev_err(dev, "%d Not a pin token\n", tkn_elem->token);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Parse for pin config specific tokens to fill up the
+ * module private data
+ */
+static int skl_tplg_fill_pins_info(struct device *dev,
+ struct skl_module_cfg *mconfig,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ int dir, int pin_count)
+{
+ int ret;
+ struct skl_module_pin *m_pin;
+
+ switch (dir) {
+ case SKL_DIR_IN:
+ m_pin = mconfig->m_in_pin;
+ break;
+
+ case SKL_DIR_OUT:
+ m_pin = mconfig->m_out_pin;
+ break;
+
+ default:
+ dev_err(dev, "Invalid direction value\n");
+ return -EINVAL;
+ }
+
+ ret = skl_tplg_fill_pin(dev, tkn_elem, m_pin, pin_count);
+ if (ret < 0)
+ return ret;
+
+ m_pin[pin_count].in_use = false;
+ m_pin[pin_count].pin_state = SKL_PIN_UNBIND;
+
+ return 0;
+}
+
+/*
+ * Fill up input/output module config format based
+ * on the direction
+ */
+static int skl_tplg_fill_fmt(struct device *dev,
+ struct skl_module_fmt *dst_fmt,
+ u32 tkn, u32 value)
+{
+ switch (tkn) {
+ case SKL_TKN_U32_FMT_CH:
+ dst_fmt->channels = value;
+ break;
+
+ case SKL_TKN_U32_FMT_FREQ:
+ dst_fmt->s_freq = value;
+ break;
+
+ case SKL_TKN_U32_FMT_BIT_DEPTH:
+ dst_fmt->bit_depth = value;
+ break;
+
+ case SKL_TKN_U32_FMT_SAMPLE_SIZE:
+ dst_fmt->valid_bit_depth = value;
+ break;
+
+ case SKL_TKN_U32_FMT_CH_CONFIG:
+ dst_fmt->ch_cfg = value;
+ break;
+
+ case SKL_TKN_U32_FMT_INTERLEAVE:
+ dst_fmt->interleaving_style = value;
+ break;
+
+ case SKL_TKN_U32_FMT_SAMPLE_TYPE:
+ dst_fmt->sample_type = value;
+ break;
+
+ case SKL_TKN_U32_FMT_CH_MAP:
+ dst_fmt->ch_map = value;
+ break;
+
+ default:
+ dev_err(dev, "Invalid token %d\n", tkn);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int skl_tplg_widget_fill_fmt(struct device *dev,
+ struct skl_module_iface *fmt,
+ u32 tkn, u32 val, u32 dir, int fmt_idx)
+{
+ struct skl_module_fmt *dst_fmt;
+
+ if (!fmt)
+ return -EINVAL;
+
+ switch (dir) {
+ case SKL_DIR_IN:
+ dst_fmt = &fmt->inputs[fmt_idx].fmt;
+ break;
+
+ case SKL_DIR_OUT:
+ dst_fmt = &fmt->outputs[fmt_idx].fmt;
+ break;
+
+ default:
+ dev_err(dev, "Invalid direction: %d\n", dir);
+ return -EINVAL;
+ }
+
+ return skl_tplg_fill_fmt(dev, dst_fmt, tkn, val);
+}
+
+static void skl_tplg_fill_pin_dynamic_val(
+ struct skl_module_pin *mpin, u32 pin_count, u32 value)
+{
+ int i;
+
+ for (i = 0; i < pin_count; i++)
+ mpin[i].is_dynamic = value;
+}
+
+/*
+ * Resource table in the manifest has pin specific resources
+ * like pin and pin buffer size
+ */
+static int skl_tplg_manifest_pin_res_tkn(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_module_res *res, int pin_idx, int dir)
+{
+ struct skl_module_pin_resources *m_pin;
+
+ switch (dir) {
+ case SKL_DIR_IN:
+ m_pin = &res->input[pin_idx];
+ break;
+
+ case SKL_DIR_OUT:
+ m_pin = &res->output[pin_idx];
+ break;
+
+ default:
+ dev_err(dev, "Invalid pin direction: %d\n", dir);
+ return -EINVAL;
+ }
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_MM_U32_RES_PIN_ID:
+ m_pin->pin_index = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_PIN_BUF:
+ m_pin->buf_size = tkn_elem->value;
+ break;
+
+ default:
+ dev_err(dev, "Invalid token: %d\n", tkn_elem->token);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Fill module specific resources from the manifest's resource
+ * table like CPS, DMA size, mem_pages.
+ */
+static int skl_tplg_fill_res_tkn(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_module_res *res,
+ int pin_idx, int dir)
+{
+ int ret, tkn_count = 0;
+
+ if (!res)
+ return -EINVAL;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_MM_U32_DMA_SIZE:
+ res->dma_buffer_size = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_CPC:
+ res->cpc = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_MEM_PAGES:
+ res->is_pages = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_OBS:
+ res->obs = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_IBS:
+ res->ibs = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_RES_PIN_ID:
+ case SKL_TKN_MM_U32_PIN_BUF:
+ ret = skl_tplg_manifest_pin_res_tkn(dev, tkn_elem, res,
+ pin_idx, dir);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case SKL_TKN_MM_U32_CPS:
+ case SKL_TKN_U32_MAX_MCPS:
+ /* ignore unused tokens */
+ break;
+
+ default:
+ dev_err(dev, "Not a res type token: %d", tkn_elem->token);
+ return -EINVAL;
+
+ }
+ tkn_count++;
+
+ return tkn_count;
+}
+
+/*
+ * Parse tokens to fill up the module private data
+ */
+static int skl_tplg_get_token(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_dev *skl, struct skl_module_cfg *mconfig)
+{
+ int tkn_count = 0;
+ int ret;
+ static int is_pipe_exists;
+ static int pin_index, dir, conf_idx;
+ struct skl_module_iface *iface = NULL;
+ struct skl_module_res *res = NULL;
+ int res_idx = mconfig->res_idx;
+ int fmt_idx = mconfig->fmt_idx;
+
+ /*
+ * If the manifest structure contains no modules, fill all
+ * the module data to 0th index.
+ * res_idx and fmt_idx are default set to 0.
+ */
+ if (skl->nr_modules == 0) {
+ res = &mconfig->module->resources[res_idx];
+ iface = &mconfig->module->formats[fmt_idx];
+ }
+
+ if (tkn_elem->token > SKL_TKN_MAX)
+ return -EINVAL;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_U8_IN_QUEUE_COUNT:
+ mconfig->module->max_input_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_OUT_QUEUE_COUNT:
+ mconfig->module->max_output_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_DYN_IN_PIN:
+ if (!mconfig->m_in_pin)
+ mconfig->m_in_pin =
+ devm_kcalloc(dev, MAX_IN_QUEUE,
+ sizeof(*mconfig->m_in_pin),
+ GFP_KERNEL);
+ if (!mconfig->m_in_pin)
+ return -ENOMEM;
+
+ skl_tplg_fill_pin_dynamic_val(mconfig->m_in_pin, MAX_IN_QUEUE,
+ tkn_elem->value);
+ break;
+
+ case SKL_TKN_U8_DYN_OUT_PIN:
+ if (!mconfig->m_out_pin)
+ mconfig->m_out_pin =
+ devm_kcalloc(dev, MAX_IN_QUEUE,
+ sizeof(*mconfig->m_in_pin),
+ GFP_KERNEL);
+ if (!mconfig->m_out_pin)
+ return -ENOMEM;
+
+ skl_tplg_fill_pin_dynamic_val(mconfig->m_out_pin, MAX_OUT_QUEUE,
+ tkn_elem->value);
+ break;
+
+ case SKL_TKN_U8_TIME_SLOT:
+ mconfig->time_slot = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_CORE_ID:
+ mconfig->core_id = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_MOD_TYPE:
+ mconfig->m_type = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_DEV_TYPE:
+ mconfig->dev_type = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_HW_CONN_TYPE:
+ mconfig->hw_conn_type = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U16_MOD_INST_ID:
+ mconfig->id.instance_id =
+ tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_MEM_PAGES:
+ case SKL_TKN_U32_MAX_MCPS:
+ case SKL_TKN_U32_OBS:
+ case SKL_TKN_U32_IBS:
+ ret = skl_tplg_fill_res_tkn(dev, tkn_elem, res, pin_index, dir);
+ if (ret < 0)
+ return ret;
+
+ break;
+
+ case SKL_TKN_U32_VBUS_ID:
+ mconfig->vbus_id = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_PARAMS_FIXUP:
+ mconfig->params_fixup = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_CONVERTER:
+ mconfig->converter = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_D0I3_CAPS:
+ mconfig->d0i3_caps = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_PIPE_ID:
+ ret = skl_tplg_add_pipe(dev,
+ mconfig, skl, tkn_elem);
+
+ if (ret < 0) {
+ if (ret == -EEXIST) {
+ is_pipe_exists = 1;
+ break;
+ }
+ return is_pipe_exists;
+ }
+
+ break;
+
+ case SKL_TKN_U32_PIPE_CONFIG_ID:
+ conf_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_PIPE_CONN_TYPE:
+ case SKL_TKN_U32_PIPE_PRIORITY:
+ case SKL_TKN_U32_PIPE_MEM_PGS:
+ case SKL_TKN_U32_PMODE:
+ case SKL_TKN_U32_PIPE_DIRECTION:
+ case SKL_TKN_U32_NUM_CONFIGS:
+ if (is_pipe_exists) {
+ ret = skl_tplg_fill_pipe_tkn(dev, mconfig->pipe,
+ tkn_elem->token, tkn_elem->value);
+ if (ret < 0)
+ return ret;
+ }
+
+ break;
+
+ case SKL_TKN_U32_PATH_MEM_PGS:
+ case SKL_TKN_U32_CFG_FREQ:
+ case SKL_TKN_U8_CFG_CHAN:
+ case SKL_TKN_U8_CFG_BPS:
+ if (mconfig->pipe->nr_cfgs) {
+ ret = skl_tplg_fill_pipe_cfg(dev, mconfig->pipe,
+ tkn_elem->token, tkn_elem->value,
+ conf_idx, dir);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+
+ case SKL_TKN_CFG_MOD_RES_ID:
+ mconfig->mod_cfg[conf_idx].res_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_CFG_MOD_FMT_ID:
+ mconfig->mod_cfg[conf_idx].fmt_idx = tkn_elem->value;
+ break;
+
+ /*
+ * SKL_TKN_U32_DIR_PIN_COUNT token has the value for both
+ * direction and the pin count. The first four bits represent
+ * direction and next four the pin count.
+ */
+ case SKL_TKN_U32_DIR_PIN_COUNT:
+ dir = tkn_elem->value & SKL_IN_DIR_BIT_MASK;
+ pin_index = (tkn_elem->value &
+ SKL_PIN_COUNT_MASK) >> 4;
+
+ break;
+
+ case SKL_TKN_U32_FMT_CH:
+ case SKL_TKN_U32_FMT_FREQ:
+ case SKL_TKN_U32_FMT_BIT_DEPTH:
+ case SKL_TKN_U32_FMT_SAMPLE_SIZE:
+ case SKL_TKN_U32_FMT_CH_CONFIG:
+ case SKL_TKN_U32_FMT_INTERLEAVE:
+ case SKL_TKN_U32_FMT_SAMPLE_TYPE:
+ case SKL_TKN_U32_FMT_CH_MAP:
+ ret = skl_tplg_widget_fill_fmt(dev, iface, tkn_elem->token,
+ tkn_elem->value, dir, pin_index);
+
+ if (ret < 0)
+ return ret;
+
+ break;
+
+ case SKL_TKN_U32_PIN_MOD_ID:
+ case SKL_TKN_U32_PIN_INST_ID:
+ case SKL_TKN_UUID:
+ ret = skl_tplg_fill_pins_info(dev,
+ mconfig, tkn_elem, dir,
+ pin_index);
+ if (ret < 0)
+ return ret;
+
+ break;
+
+ case SKL_TKN_U32_CAPS_SIZE:
+ mconfig->formats_config.caps_size =
+ tkn_elem->value;
+
+ break;
+
+ case SKL_TKN_U32_CAPS_SET_PARAMS:
+ mconfig->formats_config.set_params =
+ tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_CAPS_PARAMS_ID:
+ mconfig->formats_config.param_id =
+ tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_PROC_DOMAIN:
+ mconfig->domain =
+ tkn_elem->value;
+
+ break;
+
+ case SKL_TKN_U32_DMA_BUF_SIZE:
+ mconfig->dma_buffer_size = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_IN_PIN_TYPE:
+ case SKL_TKN_U8_OUT_PIN_TYPE:
+ case SKL_TKN_U8_CONN_TYPE:
+ break;
+
+ default:
+ dev_err(dev, "Token %d not handled\n",
+ tkn_elem->token);
+ return -EINVAL;
+ }
+
+ tkn_count++;
+
+ return tkn_count;
+}
+
+/*
+ * Parse the vendor array for specific tokens to construct
+ * module private data
+ */
+static int skl_tplg_get_tokens(struct device *dev,
+ char *pvt_data, struct skl_dev *skl,
+ struct skl_module_cfg *mconfig, int block_size)
+{
+ struct snd_soc_tplg_vendor_array *array;
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem;
+ int tkn_count = 0, ret;
+ int off = 0, tuple_size = 0;
+ bool is_module_guid = true;
+
+ if (block_size <= 0)
+ return -EINVAL;
+
+ while (tuple_size < block_size) {
+ array = (struct snd_soc_tplg_vendor_array *)(pvt_data + off);
+
+ off += array->size;
+
+ switch (array->type) {
+ case SND_SOC_TPLG_TUPLE_TYPE_STRING:
+ dev_warn(dev, "no string tokens expected for skl tplg\n");
+ continue;
+
+ case SND_SOC_TPLG_TUPLE_TYPE_UUID:
+ if (is_module_guid) {
+ ret = skl_tplg_get_uuid(dev, (guid_t *)mconfig->guid,
+ array->uuid);
+ is_module_guid = false;
+ } else {
+ ret = skl_tplg_get_token(dev, array->value, skl,
+ mconfig);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ tuple_size += sizeof(*array->uuid);
+
+ continue;
+
+ default:
+ tkn_elem = array->value;
+ tkn_count = 0;
+ break;
+ }
+
+ while (tkn_count <= (array->num_elems - 1)) {
+ ret = skl_tplg_get_token(dev, tkn_elem,
+ skl, mconfig);
+
+ if (ret < 0)
+ return ret;
+
+ tkn_count = tkn_count + ret;
+ tkn_elem++;
+ }
+
+ tuple_size += tkn_count * sizeof(*tkn_elem);
+ }
+
+ return off;
+}
+
+/*
+ * Every data block is preceded by a descriptor to read the number
+ * of data blocks, they type of the block and it's size
+ */
+static int skl_tplg_get_desc_blocks(struct device *dev,
+ struct snd_soc_tplg_vendor_array *array)
+{
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem;
+
+ tkn_elem = array->value;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_U8_NUM_BLOCKS:
+ case SKL_TKN_U8_BLOCK_TYPE:
+ case SKL_TKN_U16_BLOCK_SIZE:
+ return tkn_elem->value;
+
+ default:
+ dev_err(dev, "Invalid descriptor token %d\n", tkn_elem->token);
+ break;
+ }
+
+ return -EINVAL;
+}
+
+/* Functions to parse private data from configuration file format v4 */
+
+/*
+ * Add pipeline from topology binary into driver pipeline list
+ *
+ * If already added we return that instance
+ * Otherwise we create a new instance and add into driver list
+ */
+static int skl_tplg_add_pipe_v4(struct device *dev,
+ struct skl_module_cfg *mconfig, struct skl_dev *skl,
+ struct skl_dfw_v4_pipe *dfw_pipe)
+{
+ struct skl_pipeline *ppl;
+ struct skl_pipe *pipe;
+ struct skl_pipe_params *params;
+
+ list_for_each_entry(ppl, &skl->ppl_list, node) {
+ if (ppl->pipe->ppl_id == dfw_pipe->pipe_id) {
+ mconfig->pipe = ppl->pipe;
+ return 0;
+ }
+ }
+
+ ppl = devm_kzalloc(dev, sizeof(*ppl), GFP_KERNEL);
+ if (!ppl)
+ return -ENOMEM;
+
+ pipe = devm_kzalloc(dev, sizeof(*pipe), GFP_KERNEL);
+ if (!pipe)
+ return -ENOMEM;
+
+ params = devm_kzalloc(dev, sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ pipe->ppl_id = dfw_pipe->pipe_id;
+ pipe->memory_pages = dfw_pipe->memory_pages;
+ pipe->pipe_priority = dfw_pipe->pipe_priority;
+ pipe->conn_type = dfw_pipe->conn_type;
+ pipe->state = SKL_PIPE_INVALID;
+ pipe->p_params = params;
+ INIT_LIST_HEAD(&pipe->w_list);
+
+ ppl->pipe = pipe;
+ list_add(&ppl->node, &skl->ppl_list);
+
+ mconfig->pipe = pipe;
+
+ return 0;
+}
+
+static void skl_fill_module_pin_info_v4(struct skl_dfw_v4_module_pin *dfw_pin,
+ struct skl_module_pin *m_pin,
+ bool is_dynamic, int max_pin)
+{
+ int i;
+
+ for (i = 0; i < max_pin; i++) {
+ m_pin[i].id.module_id = dfw_pin[i].module_id;
+ m_pin[i].id.instance_id = dfw_pin[i].instance_id;
+ m_pin[i].in_use = false;
+ m_pin[i].is_dynamic = is_dynamic;
+ m_pin[i].pin_state = SKL_PIN_UNBIND;
+ }
+}
+
+static void skl_tplg_fill_fmt_v4(struct skl_module_pin_fmt *dst_fmt,
+ struct skl_dfw_v4_module_fmt *src_fmt,
+ int pins)
+{
+ int i;
+
+ for (i = 0; i < pins; i++) {
+ dst_fmt[i].fmt.channels = src_fmt[i].channels;
+ dst_fmt[i].fmt.s_freq = src_fmt[i].freq;
+ dst_fmt[i].fmt.bit_depth = src_fmt[i].bit_depth;
+ dst_fmt[i].fmt.valid_bit_depth = src_fmt[i].valid_bit_depth;
+ dst_fmt[i].fmt.ch_cfg = src_fmt[i].ch_cfg;
+ dst_fmt[i].fmt.ch_map = src_fmt[i].ch_map;
+ dst_fmt[i].fmt.interleaving_style =
+ src_fmt[i].interleaving_style;
+ dst_fmt[i].fmt.sample_type = src_fmt[i].sample_type;
+ }
+}
+
+static int skl_tplg_get_pvt_data_v4(struct snd_soc_tplg_dapm_widget *tplg_w,
+ struct skl_dev *skl, struct device *dev,
+ struct skl_module_cfg *mconfig)
+{
+ struct skl_dfw_v4_module *dfw =
+ (struct skl_dfw_v4_module *)tplg_w->priv.data;
+ int ret;
+
+ dev_dbg(dev, "Parsing Skylake v4 widget topology data\n");
+
+ ret = guid_parse(dfw->uuid, (guid_t *)mconfig->guid);
+ if (ret)
+ return ret;
+ mconfig->id.module_id = -1;
+ mconfig->id.instance_id = dfw->instance_id;
+ mconfig->module->resources[0].cpc = dfw->max_mcps / 1000;
+ mconfig->module->resources[0].ibs = dfw->ibs;
+ mconfig->module->resources[0].obs = dfw->obs;
+ mconfig->core_id = dfw->core_id;
+ mconfig->module->max_input_pins = dfw->max_in_queue;
+ mconfig->module->max_output_pins = dfw->max_out_queue;
+ mconfig->module->loadable = dfw->is_loadable;
+ skl_tplg_fill_fmt_v4(mconfig->module->formats[0].inputs, dfw->in_fmt,
+ MAX_IN_QUEUE);
+ skl_tplg_fill_fmt_v4(mconfig->module->formats[0].outputs, dfw->out_fmt,
+ MAX_OUT_QUEUE);
+
+ mconfig->params_fixup = dfw->params_fixup;
+ mconfig->converter = dfw->converter;
+ mconfig->m_type = dfw->module_type;
+ mconfig->vbus_id = dfw->vbus_id;
+ mconfig->module->resources[0].is_pages = dfw->mem_pages;
+
+ ret = skl_tplg_add_pipe_v4(dev, mconfig, skl, &dfw->pipe);
+ if (ret)
+ return ret;
+
+ mconfig->dev_type = dfw->dev_type;
+ mconfig->hw_conn_type = dfw->hw_conn_type;
+ mconfig->time_slot = dfw->time_slot;
+ mconfig->formats_config.caps_size = dfw->caps.caps_size;
+
+ mconfig->m_in_pin = devm_kcalloc(dev,
+ MAX_IN_QUEUE, sizeof(*mconfig->m_in_pin),
+ GFP_KERNEL);
+ if (!mconfig->m_in_pin)
+ return -ENOMEM;
+
+ mconfig->m_out_pin = devm_kcalloc(dev,
+ MAX_OUT_QUEUE, sizeof(*mconfig->m_out_pin),
+ GFP_KERNEL);
+ if (!mconfig->m_out_pin)
+ return -ENOMEM;
+
+ skl_fill_module_pin_info_v4(dfw->in_pin, mconfig->m_in_pin,
+ dfw->is_dynamic_in_pin,
+ mconfig->module->max_input_pins);
+ skl_fill_module_pin_info_v4(dfw->out_pin, mconfig->m_out_pin,
+ dfw->is_dynamic_out_pin,
+ mconfig->module->max_output_pins);
+
+ if (mconfig->formats_config.caps_size) {
+ mconfig->formats_config.set_params = dfw->caps.set_params;
+ mconfig->formats_config.param_id = dfw->caps.param_id;
+ mconfig->formats_config.caps =
+ devm_kzalloc(dev, mconfig->formats_config.caps_size,
+ GFP_KERNEL);
+ if (!mconfig->formats_config.caps)
+ return -ENOMEM;
+ memcpy(mconfig->formats_config.caps, dfw->caps.caps,
+ dfw->caps.caps_size);
+ }
+
+ return 0;
+}
+
+/*
+ * Parse the private data for the token and corresponding value.
+ * The private data can have multiple data blocks. So, a data block
+ * is preceded by a descriptor for number of blocks and a descriptor
+ * for the type and size of the suceeding data block.
+ */
+static int skl_tplg_get_pvt_data(struct snd_soc_tplg_dapm_widget *tplg_w,
+ struct skl_dev *skl, struct device *dev,
+ struct skl_module_cfg *mconfig)
+{
+ struct snd_soc_tplg_vendor_array *array;
+ int num_blocks, block_size, block_type, off = 0;
+ char *data;
+ int ret;
+
+ /*
+ * v4 configuration files have a valid UUID at the start of
+ * the widget's private data.
+ */
+ if (uuid_is_valid((char *)tplg_w->priv.data))
+ return skl_tplg_get_pvt_data_v4(tplg_w, skl, dev, mconfig);
+
+ /* Read the NUM_DATA_BLOCKS descriptor */
+ array = (struct snd_soc_tplg_vendor_array *)tplg_w->priv.data;
+ ret = skl_tplg_get_desc_blocks(dev, array);
+ if (ret < 0)
+ return ret;
+ num_blocks = ret;
+
+ off += array->size;
+ /* Read the BLOCK_TYPE and BLOCK_SIZE descriptor */
+ while (num_blocks > 0) {
+ array = (struct snd_soc_tplg_vendor_array *)
+ (tplg_w->priv.data + off);
+
+ ret = skl_tplg_get_desc_blocks(dev, array);
+
+ if (ret < 0)
+ return ret;
+ block_type = ret;
+ off += array->size;
+
+ array = (struct snd_soc_tplg_vendor_array *)
+ (tplg_w->priv.data + off);
+
+ ret = skl_tplg_get_desc_blocks(dev, array);
+
+ if (ret < 0)
+ return ret;
+ block_size = ret;
+ off += array->size;
+
+ array = (struct snd_soc_tplg_vendor_array *)
+ (tplg_w->priv.data + off);
+
+ data = (tplg_w->priv.data + off);
+
+ if (block_type == SKL_TYPE_TUPLE) {
+ ret = skl_tplg_get_tokens(dev, data,
+ skl, mconfig, block_size);
+
+ if (ret < 0)
+ return ret;
+
+ --num_blocks;
+ } else {
+ if (mconfig->formats_config.caps_size > 0)
+ memcpy(mconfig->formats_config.caps, data,
+ mconfig->formats_config.caps_size);
+ --num_blocks;
+ ret = mconfig->formats_config.caps_size;
+ }
+ off += ret;
+ }
+
+ return 0;
+}
+
+static void skl_clear_pin_config(struct snd_soc_component *component,
+ struct snd_soc_dapm_widget *w)
+{
+ int i;
+ struct skl_module_cfg *mconfig;
+ struct skl_pipe *pipe;
+
+ if (!strncmp(w->dapm->component->name, component->name,
+ strlen(component->name))) {
+ mconfig = w->priv;
+ pipe = mconfig->pipe;
+ for (i = 0; i < mconfig->module->max_input_pins; i++) {
+ mconfig->m_in_pin[i].in_use = false;
+ mconfig->m_in_pin[i].pin_state = SKL_PIN_UNBIND;
+ }
+ for (i = 0; i < mconfig->module->max_output_pins; i++) {
+ mconfig->m_out_pin[i].in_use = false;
+ mconfig->m_out_pin[i].pin_state = SKL_PIN_UNBIND;
+ }
+ pipe->state = SKL_PIPE_INVALID;
+ mconfig->m_state = SKL_MODULE_UNINIT;
+ }
+}
+
+void skl_cleanup_resources(struct skl_dev *skl)
+{
+ struct snd_soc_component *soc_component = skl->component;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_card *card;
+
+ if (soc_component == NULL)
+ return;
+
+ card = soc_component->card;
+ if (!card || !card->instantiated)
+ return;
+
+ list_for_each_entry(w, &card->widgets, list) {
+ if (is_skl_dsp_widget_type(w, skl->dev) && w->priv != NULL)
+ skl_clear_pin_config(soc_component, w);
+ }
+
+ skl_clear_module_cnt(skl->dsp);
+}
+
+/*
+ * Topology core widget load callback
+ *
+ * This is used to save the private data for each widget which gives
+ * information to the driver about module and pipeline parameters which DSP
+ * FW expects like ids, resource values, formats etc
+ */
+static int skl_tplg_widget_load(struct snd_soc_component *cmpnt, int index,
+ struct snd_soc_dapm_widget *w,
+ struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ int ret;
+ struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt);
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_module_cfg *mconfig;
+
+ if (!tplg_w->priv.size)
+ goto bind_event;
+
+ mconfig = devm_kzalloc(bus->dev, sizeof(*mconfig), GFP_KERNEL);
+
+ if (!mconfig)
+ return -ENOMEM;
+
+ if (skl->nr_modules == 0) {
+ mconfig->module = devm_kzalloc(bus->dev,
+ sizeof(*mconfig->module), GFP_KERNEL);
+ if (!mconfig->module)
+ return -ENOMEM;
+ }
+
+ w->priv = mconfig;
+
+ /*
+ * module binary can be loaded later, so set it to query when
+ * module is load for a use case
+ */
+ mconfig->id.module_id = -1;
+
+ /* Parse private data for tuples */
+ ret = skl_tplg_get_pvt_data(tplg_w, skl, bus->dev, mconfig);
+ if (ret < 0)
+ return ret;
+
+ skl_debug_init_module(skl->debugfs, w, mconfig);
+
+bind_event:
+ if (tplg_w->event_type == 0) {
+ dev_dbg(bus->dev, "ASoC: No event handler required\n");
+ return 0;
+ }
+
+ ret = snd_soc_tplg_widget_bind_event(w, skl_tplg_widget_ops,
+ ARRAY_SIZE(skl_tplg_widget_ops),
+ tplg_w->event_type);
+
+ if (ret) {
+ dev_err(bus->dev, "%s: No matching event handlers found for %d\n",
+ __func__, tplg_w->event_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int skl_init_algo_data(struct device *dev, struct soc_bytes_ext *be,
+ struct snd_soc_tplg_bytes_control *bc)
+{
+ struct skl_algo_data *ac;
+ struct skl_dfw_algo_data *dfw_ac =
+ (struct skl_dfw_algo_data *)bc->priv.data;
+
+ ac = devm_kzalloc(dev, sizeof(*ac), GFP_KERNEL);
+ if (!ac)
+ return -ENOMEM;
+
+ /* Fill private data */
+ ac->max = dfw_ac->max;
+ ac->param_id = dfw_ac->param_id;
+ ac->set_params = dfw_ac->set_params;
+ ac->size = dfw_ac->max;
+
+ if (ac->max) {
+ ac->params = devm_kzalloc(dev, ac->max, GFP_KERNEL);
+ if (!ac->params)
+ return -ENOMEM;
+
+ memcpy(ac->params, dfw_ac->params, ac->max);
+ }
+
+ be->dobj.private = ac;
+ return 0;
+}
+
+static int skl_init_enum_data(struct device *dev, struct soc_enum *se,
+ struct snd_soc_tplg_enum_control *ec)
+{
+
+ void *data;
+
+ if (ec->priv.size) {
+ data = devm_kzalloc(dev, sizeof(ec->priv.size), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ memcpy(data, ec->priv.data, ec->priv.size);
+ se->dobj.private = data;
+ }
+
+ return 0;
+
+}
+
+static int skl_tplg_control_load(struct snd_soc_component *cmpnt,
+ int index,
+ struct snd_kcontrol_new *kctl,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ struct soc_bytes_ext *sb;
+ struct snd_soc_tplg_bytes_control *tplg_bc;
+ struct snd_soc_tplg_enum_control *tplg_ec;
+ struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *se;
+
+ switch (hdr->ops.info) {
+ case SND_SOC_TPLG_CTL_BYTES:
+ tplg_bc = container_of(hdr,
+ struct snd_soc_tplg_bytes_control, hdr);
+ if (kctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+ sb = (struct soc_bytes_ext *)kctl->private_value;
+ if (tplg_bc->priv.size)
+ return skl_init_algo_data(
+ bus->dev, sb, tplg_bc);
+ }
+ break;
+
+ case SND_SOC_TPLG_CTL_ENUM:
+ tplg_ec = container_of(hdr,
+ struct snd_soc_tplg_enum_control, hdr);
+ if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READ) {
+ se = (struct soc_enum *)kctl->private_value;
+ if (tplg_ec->priv.size)
+ skl_init_enum_data(bus->dev, se, tplg_ec);
+ }
+
+ /*
+ * now that the control initializations are done, remove
+ * write permission for the DMIC configuration enums to
+ * avoid conflicts between NHLT settings and user interaction
+ */
+
+ if (hdr->ops.get == SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC)
+ kctl->access = SNDRV_CTL_ELEM_ACCESS_READ;
+
+ break;
+
+ default:
+ dev_dbg(bus->dev, "Control load not supported %d:%d:%d\n",
+ hdr->ops.get, hdr->ops.put, hdr->ops.info);
+ break;
+ }
+
+ return 0;
+}
+
+static int skl_tplg_fill_str_mfest_tkn(struct device *dev,
+ struct snd_soc_tplg_vendor_string_elem *str_elem,
+ struct skl_dev *skl)
+{
+ int tkn_count = 0;
+ static int ref_count;
+
+ switch (str_elem->token) {
+ case SKL_TKN_STR_LIB_NAME:
+ if (ref_count > skl->lib_count - 1) {
+ ref_count = 0;
+ return -EINVAL;
+ }
+
+ strncpy(skl->lib_info[ref_count].name,
+ str_elem->string,
+ ARRAY_SIZE(skl->lib_info[ref_count].name));
+ ref_count++;
+ break;
+
+ default:
+ dev_err(dev, "Not a string token %d\n", str_elem->token);
+ break;
+ }
+ tkn_count++;
+
+ return tkn_count;
+}
+
+static int skl_tplg_get_str_tkn(struct device *dev,
+ struct snd_soc_tplg_vendor_array *array,
+ struct skl_dev *skl)
+{
+ int tkn_count = 0, ret;
+ struct snd_soc_tplg_vendor_string_elem *str_elem;
+
+ str_elem = (struct snd_soc_tplg_vendor_string_elem *)array->value;
+ while (tkn_count < array->num_elems) {
+ ret = skl_tplg_fill_str_mfest_tkn(dev, str_elem, skl);
+ str_elem++;
+
+ if (ret < 0)
+ return ret;
+
+ tkn_count = tkn_count + ret;
+ }
+
+ return tkn_count;
+}
+
+static int skl_tplg_manifest_fill_fmt(struct device *dev,
+ struct skl_module_iface *fmt,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ u32 dir, int fmt_idx)
+{
+ struct skl_module_pin_fmt *dst_fmt;
+ struct skl_module_fmt *mod_fmt;
+ int ret;
+
+ if (!fmt)
+ return -EINVAL;
+
+ switch (dir) {
+ case SKL_DIR_IN:
+ dst_fmt = &fmt->inputs[fmt_idx];
+ break;
+
+ case SKL_DIR_OUT:
+ dst_fmt = &fmt->outputs[fmt_idx];
+ break;
+
+ default:
+ dev_err(dev, "Invalid direction: %d\n", dir);
+ return -EINVAL;
+ }
+
+ mod_fmt = &dst_fmt->fmt;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_MM_U32_INTF_PIN_ID:
+ dst_fmt->id = tkn_elem->value;
+ break;
+
+ default:
+ ret = skl_tplg_fill_fmt(dev, mod_fmt, tkn_elem->token,
+ tkn_elem->value);
+ if (ret < 0)
+ return ret;
+ break;
+ }
+
+ return 0;
+}
+
+static int skl_tplg_fill_mod_info(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_module *mod)
+{
+
+ if (!mod)
+ return -EINVAL;
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_U8_IN_PIN_TYPE:
+ mod->input_pin_type = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_OUT_PIN_TYPE:
+ mod->output_pin_type = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_IN_QUEUE_COUNT:
+ mod->max_input_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_OUT_QUEUE_COUNT:
+ mod->max_output_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U8_NUM_RES:
+ mod->nr_resources = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U8_NUM_INTF:
+ mod->nr_interfaces = tkn_elem->value;
+ break;
+
+ default:
+ dev_err(dev, "Invalid mod info token %d", tkn_elem->token);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int skl_tplg_get_int_tkn(struct device *dev,
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem,
+ struct skl_dev *skl)
+{
+ int tkn_count = 0, ret;
+ static int mod_idx, res_val_idx, intf_val_idx, dir, pin_idx;
+ struct skl_module_res *res = NULL;
+ struct skl_module_iface *fmt = NULL;
+ struct skl_module *mod = NULL;
+ static struct skl_astate_param *astate_table;
+ static int astate_cfg_idx, count;
+ int i;
+ size_t size;
+
+ if (skl->modules) {
+ mod = skl->modules[mod_idx];
+ res = &mod->resources[res_val_idx];
+ fmt = &mod->formats[intf_val_idx];
+ }
+
+ switch (tkn_elem->token) {
+ case SKL_TKN_U32_LIB_COUNT:
+ skl->lib_count = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_NUM_MOD:
+ skl->nr_modules = tkn_elem->value;
+ skl->modules = devm_kcalloc(dev, skl->nr_modules,
+ sizeof(*skl->modules), GFP_KERNEL);
+ if (!skl->modules)
+ return -ENOMEM;
+
+ for (i = 0; i < skl->nr_modules; i++) {
+ skl->modules[i] = devm_kzalloc(dev,
+ sizeof(struct skl_module), GFP_KERNEL);
+ if (!skl->modules[i])
+ return -ENOMEM;
+ }
+ break;
+
+ case SKL_TKN_MM_U8_MOD_IDX:
+ mod_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_ASTATE_COUNT:
+ if (astate_table != NULL) {
+ dev_err(dev, "More than one entry for A-State count");
+ return -EINVAL;
+ }
+
+ if (tkn_elem->value > SKL_MAX_ASTATE_CFG) {
+ dev_err(dev, "Invalid A-State count %d\n",
+ tkn_elem->value);
+ return -EINVAL;
+ }
+
+ size = struct_size(skl->cfg.astate_cfg, astate_table,
+ tkn_elem->value);
+ skl->cfg.astate_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!skl->cfg.astate_cfg)
+ return -ENOMEM;
+
+ astate_table = skl->cfg.astate_cfg->astate_table;
+ count = skl->cfg.astate_cfg->count = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_ASTATE_IDX:
+ if (tkn_elem->value >= count) {
+ dev_err(dev, "Invalid A-State index %d\n",
+ tkn_elem->value);
+ return -EINVAL;
+ }
+
+ astate_cfg_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_ASTATE_KCPS:
+ astate_table[astate_cfg_idx].kcps = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_ASTATE_CLK_SRC:
+ astate_table[astate_cfg_idx].clk_src = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U8_IN_PIN_TYPE:
+ case SKL_TKN_U8_OUT_PIN_TYPE:
+ case SKL_TKN_U8_IN_QUEUE_COUNT:
+ case SKL_TKN_U8_OUT_QUEUE_COUNT:
+ case SKL_TKN_MM_U8_NUM_RES:
+ case SKL_TKN_MM_U8_NUM_INTF:
+ ret = skl_tplg_fill_mod_info(dev, tkn_elem, mod);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case SKL_TKN_U32_DIR_PIN_COUNT:
+ dir = tkn_elem->value & SKL_IN_DIR_BIT_MASK;
+ pin_idx = (tkn_elem->value & SKL_PIN_COUNT_MASK) >> 4;
+ break;
+
+ case SKL_TKN_MM_U32_RES_ID:
+ if (!res)
+ return -EINVAL;
+
+ res->id = tkn_elem->value;
+ res_val_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_FMT_ID:
+ if (!fmt)
+ return -EINVAL;
+
+ fmt->fmt_idx = tkn_elem->value;
+ intf_val_idx = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_CPS:
+ case SKL_TKN_MM_U32_DMA_SIZE:
+ case SKL_TKN_MM_U32_CPC:
+ case SKL_TKN_U32_MEM_PAGES:
+ case SKL_TKN_U32_OBS:
+ case SKL_TKN_U32_IBS:
+ case SKL_TKN_MM_U32_RES_PIN_ID:
+ case SKL_TKN_MM_U32_PIN_BUF:
+ ret = skl_tplg_fill_res_tkn(dev, tkn_elem, res, pin_idx, dir);
+ if (ret < 0)
+ return ret;
+
+ break;
+
+ case SKL_TKN_MM_U32_NUM_IN_FMT:
+ if (!fmt)
+ return -EINVAL;
+
+ res->nr_input_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_MM_U32_NUM_OUT_FMT:
+ if (!fmt)
+ return -EINVAL;
+
+ res->nr_output_pins = tkn_elem->value;
+ break;
+
+ case SKL_TKN_U32_FMT_CH:
+ case SKL_TKN_U32_FMT_FREQ:
+ case SKL_TKN_U32_FMT_BIT_DEPTH:
+ case SKL_TKN_U32_FMT_SAMPLE_SIZE:
+ case SKL_TKN_U32_FMT_CH_CONFIG:
+ case SKL_TKN_U32_FMT_INTERLEAVE:
+ case SKL_TKN_U32_FMT_SAMPLE_TYPE:
+ case SKL_TKN_U32_FMT_CH_MAP:
+ case SKL_TKN_MM_U32_INTF_PIN_ID:
+ ret = skl_tplg_manifest_fill_fmt(dev, fmt, tkn_elem,
+ dir, pin_idx);
+ if (ret < 0)
+ return ret;
+ break;
+
+ default:
+ dev_err(dev, "Not a manifest token %d\n", tkn_elem->token);
+ return -EINVAL;
+ }
+ tkn_count++;
+
+ return tkn_count;
+}
+
+/*
+ * Fill the manifest structure by parsing the tokens based on the
+ * type.
+ */
+static int skl_tplg_get_manifest_tkn(struct device *dev,
+ char *pvt_data, struct skl_dev *skl,
+ int block_size)
+{
+ int tkn_count = 0, ret;
+ int off = 0, tuple_size = 0;
+ u8 uuid_index = 0;
+ struct snd_soc_tplg_vendor_array *array;
+ struct snd_soc_tplg_vendor_value_elem *tkn_elem;
+
+ if (block_size <= 0)
+ return -EINVAL;
+
+ while (tuple_size < block_size) {
+ array = (struct snd_soc_tplg_vendor_array *)(pvt_data + off);
+ off += array->size;
+ switch (array->type) {
+ case SND_SOC_TPLG_TUPLE_TYPE_STRING:
+ ret = skl_tplg_get_str_tkn(dev, array, skl);
+
+ if (ret < 0)
+ return ret;
+ tkn_count = ret;
+
+ tuple_size += tkn_count *
+ sizeof(struct snd_soc_tplg_vendor_string_elem);
+ continue;
+
+ case SND_SOC_TPLG_TUPLE_TYPE_UUID:
+ if (array->uuid->token != SKL_TKN_UUID) {
+ dev_err(dev, "Not an UUID token: %d\n",
+ array->uuid->token);
+ return -EINVAL;
+ }
+ if (uuid_index >= skl->nr_modules) {
+ dev_err(dev, "Too many UUID tokens\n");
+ return -EINVAL;
+ }
+ import_guid(&skl->modules[uuid_index++]->uuid,
+ array->uuid->uuid);
+
+ tuple_size += sizeof(*array->uuid);
+ continue;
+
+ default:
+ tkn_elem = array->value;
+ tkn_count = 0;
+ break;
+ }
+
+ while (tkn_count <= array->num_elems - 1) {
+ ret = skl_tplg_get_int_tkn(dev,
+ tkn_elem, skl);
+ if (ret < 0)
+ return ret;
+
+ tkn_count = tkn_count + ret;
+ tkn_elem++;
+ }
+ tuple_size += (tkn_count * sizeof(*tkn_elem));
+ tkn_count = 0;
+ }
+
+ return off;
+}
+
+/*
+ * Parse manifest private data for tokens. The private data block is
+ * preceded by descriptors for type and size of data block.
+ */
+static int skl_tplg_get_manifest_data(struct snd_soc_tplg_manifest *manifest,
+ struct device *dev, struct skl_dev *skl)
+{
+ struct snd_soc_tplg_vendor_array *array;
+ int num_blocks, block_size = 0, block_type, off = 0;
+ char *data;
+ int ret;
+
+ /* Read the NUM_DATA_BLOCKS descriptor */
+ array = (struct snd_soc_tplg_vendor_array *)manifest->priv.data;
+ ret = skl_tplg_get_desc_blocks(dev, array);
+ if (ret < 0)
+ return ret;
+ num_blocks = ret;
+
+ off += array->size;
+ /* Read the BLOCK_TYPE and BLOCK_SIZE descriptor */
+ while (num_blocks > 0) {
+ array = (struct snd_soc_tplg_vendor_array *)
+ (manifest->priv.data + off);
+ ret = skl_tplg_get_desc_blocks(dev, array);
+
+ if (ret < 0)
+ return ret;
+ block_type = ret;
+ off += array->size;
+
+ array = (struct snd_soc_tplg_vendor_array *)
+ (manifest->priv.data + off);
+
+ ret = skl_tplg_get_desc_blocks(dev, array);
+
+ if (ret < 0)
+ return ret;
+ block_size = ret;
+ off += array->size;
+
+ array = (struct snd_soc_tplg_vendor_array *)
+ (manifest->priv.data + off);
+
+ data = (manifest->priv.data + off);
+
+ if (block_type == SKL_TYPE_TUPLE) {
+ ret = skl_tplg_get_manifest_tkn(dev, data, skl,
+ block_size);
+
+ if (ret < 0)
+ return ret;
+
+ --num_blocks;
+ } else {
+ return -EINVAL;
+ }
+ off += ret;
+ }
+
+ return 0;
+}
+
+static int skl_manifest_load(struct snd_soc_component *cmpnt, int index,
+ struct snd_soc_tplg_manifest *manifest)
+{
+ struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt);
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ /* proceed only if we have private data defined */
+ if (manifest->priv.size == 0)
+ return 0;
+
+ skl_tplg_get_manifest_data(manifest, bus->dev, skl);
+
+ if (skl->lib_count > SKL_MAX_LIB) {
+ dev_err(bus->dev, "Exceeding max Library count. Got:%d\n",
+ skl->lib_count);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void skl_tplg_complete(struct snd_soc_component *component)
+{
+ struct snd_soc_dobj *dobj;
+ struct snd_soc_acpi_mach *mach =
+ dev_get_platdata(component->card->dev);
+ int i;
+
+ list_for_each_entry(dobj, &component->dobj_list, list) {
+ struct snd_kcontrol *kcontrol = dobj->control.kcontrol;
+ struct soc_enum *se;
+ char **texts;
+ char chan_text[4];
+
+ if (dobj->type != SND_SOC_DOBJ_ENUM || !kcontrol ||
+ kcontrol->put != skl_tplg_multi_config_set_dmic)
+ continue;
+
+ se = (struct soc_enum *)kcontrol->private_value;
+ texts = dobj->control.dtexts;
+ sprintf(chan_text, "c%d", mach->mach_params.dmic_num);
+
+ for (i = 0; i < se->items; i++) {
+ struct snd_ctl_elem_value val = {};
+
+ if (strstr(texts[i], chan_text)) {
+ val.value.enumerated.item[0] = i;
+ kcontrol->put(kcontrol, &val);
+ }
+ }
+ }
+}
+
+static struct snd_soc_tplg_ops skl_tplg_ops = {
+ .widget_load = skl_tplg_widget_load,
+ .control_load = skl_tplg_control_load,
+ .bytes_ext_ops = skl_tlv_ops,
+ .bytes_ext_ops_count = ARRAY_SIZE(skl_tlv_ops),
+ .io_ops = skl_tplg_kcontrol_ops,
+ .io_ops_count = ARRAY_SIZE(skl_tplg_kcontrol_ops),
+ .manifest = skl_manifest_load,
+ .dai_load = skl_dai_load,
+ .complete = skl_tplg_complete,
+};
+
+/*
+ * A pipe can have multiple modules, each of them will be a DAPM widget as
+ * well. While managing a pipeline we need to get the list of all the
+ * widgets in a pipelines, so this helper - skl_tplg_create_pipe_widget_list()
+ * helps to get the SKL type widgets in that pipeline
+ */
+static int skl_tplg_create_pipe_widget_list(struct snd_soc_component *component)
+{
+ struct snd_soc_dapm_widget *w;
+ struct skl_module_cfg *mcfg = NULL;
+ struct skl_pipe_module *p_module = NULL;
+ struct skl_pipe *pipe;
+
+ list_for_each_entry(w, &component->card->widgets, list) {
+ if (is_skl_dsp_widget_type(w, component->dev) && w->priv) {
+ mcfg = w->priv;
+ pipe = mcfg->pipe;
+
+ p_module = devm_kzalloc(component->dev,
+ sizeof(*p_module), GFP_KERNEL);
+ if (!p_module)
+ return -ENOMEM;
+
+ p_module->w = w;
+ list_add_tail(&p_module->node, &pipe->w_list);
+ }
+ }
+
+ return 0;
+}
+
+static void skl_tplg_set_pipe_type(struct skl_dev *skl, struct skl_pipe *pipe)
+{
+ struct skl_pipe_module *w_module;
+ struct snd_soc_dapm_widget *w;
+ struct skl_module_cfg *mconfig;
+ bool host_found = false, link_found = false;
+
+ list_for_each_entry(w_module, &pipe->w_list, node) {
+ w = w_module->w;
+ mconfig = w->priv;
+
+ if (mconfig->dev_type == SKL_DEVICE_HDAHOST)
+ host_found = true;
+ else if (mconfig->dev_type != SKL_DEVICE_NONE)
+ link_found = true;
+ }
+
+ if (host_found && link_found)
+ pipe->passthru = true;
+ else
+ pipe->passthru = false;
+}
+
+/*
+ * SKL topology init routine
+ */
+int skl_tplg_init(struct snd_soc_component *component, struct hdac_bus *bus)
+{
+ int ret;
+ const struct firmware *fw;
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_pipeline *ppl;
+
+ ret = request_firmware(&fw, skl->tplg_name, bus->dev);
+ if (ret < 0) {
+ char alt_tplg_name[64];
+
+ snprintf(alt_tplg_name, sizeof(alt_tplg_name), "%s-tplg.bin",
+ skl->mach->drv_name);
+ dev_info(bus->dev, "tplg fw %s load failed with %d, trying alternative tplg name %s",
+ skl->tplg_name, ret, alt_tplg_name);
+
+ ret = request_firmware(&fw, alt_tplg_name, bus->dev);
+ if (!ret)
+ goto component_load;
+
+ dev_info(bus->dev, "tplg %s failed with %d, falling back to dfw_sst.bin",
+ alt_tplg_name, ret);
+
+ ret = request_firmware(&fw, "dfw_sst.bin", bus->dev);
+ if (ret < 0) {
+ dev_err(bus->dev, "Fallback tplg fw %s load failed with %d\n",
+ "dfw_sst.bin", ret);
+ return ret;
+ }
+ }
+
+component_load:
+
+ /*
+ * The complete tplg for SKL is loaded as index 0, we don't use
+ * any other index
+ */
+ ret = snd_soc_tplg_component_load(component, &skl_tplg_ops, fw, 0);
+ if (ret < 0) {
+ dev_err(bus->dev, "tplg component load failed%d\n", ret);
+ goto err;
+ }
+
+ ret = skl_tplg_create_pipe_widget_list(component);
+ if (ret < 0) {
+ dev_err(bus->dev, "tplg create pipe widget list failed%d\n",
+ ret);
+ goto err;
+ }
+
+ list_for_each_entry(ppl, &skl->ppl_list, node)
+ skl_tplg_set_pipe_type(skl, ppl->pipe);
+
+err:
+ release_firmware(fw);
+ return ret;
+}
+
+void skl_tplg_exit(struct snd_soc_component *component, struct hdac_bus *bus)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct skl_pipeline *ppl, *tmp;
+
+ list_for_each_entry_safe(ppl, tmp, &skl->ppl_list, node)
+ list_del(&ppl->node);
+
+ /* clean up topology */
+ snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL);
+}
diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h
new file mode 100644
index 000000000..fb011862f
--- /dev/null
+++ b/sound/soc/intel/skylake/skl-topology.h
@@ -0,0 +1,505 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * skl_topology.h - Intel HDA Platform topology header file
+ *
+ * Copyright (C) 2014-15 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SKL_TOPOLOGY_H__
+#define __SKL_TOPOLOGY_H__
+
+#include <linux/types.h>
+
+#include <sound/hdaudio_ext.h>
+#include <sound/soc.h>
+#include <uapi/sound/skl-tplg-interface.h>
+#include "skl.h"
+
+#define BITS_PER_BYTE 8
+#define MAX_TS_GROUPS 8
+#define MAX_DMIC_TS_GROUPS 4
+#define MAX_FIXED_DMIC_PARAMS_SIZE 727
+
+/* Maximum number of coefficients up down mixer module */
+#define UP_DOWN_MIXER_MAX_COEFF 8
+
+#define MODULE_MAX_IN_PINS 8
+#define MODULE_MAX_OUT_PINS 8
+
+#define SKL_MIC_CH_SUPPORT 4
+#define SKL_MIC_MAX_CH_SUPPORT 8
+#define SKL_DEFAULT_MIC_SEL_GAIN 0x3FF
+#define SKL_MIC_SEL_SWITCH 0x3
+
+#define SKL_OUTPUT_PIN 0
+#define SKL_INPUT_PIN 1
+#define SKL_MAX_PATH_CONFIGS 8
+#define SKL_MAX_MODULES_IN_PIPE 8
+#define SKL_MAX_MODULE_FORMATS 32
+#define SKL_MAX_MODULE_RESOURCES 32
+
+enum skl_channel_index {
+ SKL_CHANNEL_LEFT = 0,
+ SKL_CHANNEL_RIGHT = 1,
+ SKL_CHANNEL_CENTER = 2,
+ SKL_CHANNEL_LEFT_SURROUND = 3,
+ SKL_CHANNEL_CENTER_SURROUND = 3,
+ SKL_CHANNEL_RIGHT_SURROUND = 4,
+ SKL_CHANNEL_LFE = 7,
+ SKL_CHANNEL_INVALID = 0xF,
+};
+
+enum skl_bitdepth {
+ SKL_DEPTH_8BIT = 8,
+ SKL_DEPTH_16BIT = 16,
+ SKL_DEPTH_24BIT = 24,
+ SKL_DEPTH_32BIT = 32,
+ SKL_DEPTH_INVALID
+};
+
+
+enum skl_s_freq {
+ SKL_FS_8000 = 8000,
+ SKL_FS_11025 = 11025,
+ SKL_FS_12000 = 12000,
+ SKL_FS_16000 = 16000,
+ SKL_FS_22050 = 22050,
+ SKL_FS_24000 = 24000,
+ SKL_FS_32000 = 32000,
+ SKL_FS_44100 = 44100,
+ SKL_FS_48000 = 48000,
+ SKL_FS_64000 = 64000,
+ SKL_FS_88200 = 88200,
+ SKL_FS_96000 = 96000,
+ SKL_FS_128000 = 128000,
+ SKL_FS_176400 = 176400,
+ SKL_FS_192000 = 192000,
+ SKL_FS_INVALID
+};
+
+enum skl_widget_type {
+ SKL_WIDGET_VMIXER = 1,
+ SKL_WIDGET_MIXER = 2,
+ SKL_WIDGET_PGA = 3,
+ SKL_WIDGET_MUX = 4
+};
+
+struct skl_audio_data_format {
+ enum skl_s_freq s_freq;
+ enum skl_bitdepth bit_depth;
+ u32 channel_map;
+ enum skl_ch_cfg ch_cfg;
+ enum skl_interleaving interleaving;
+ u8 number_of_channels;
+ u8 valid_bit_depth;
+ u8 sample_type;
+ u8 reserved;
+} __packed;
+
+struct skl_base_cfg {
+ u32 cpc;
+ u32 ibs;
+ u32 obs;
+ u32 is_pages;
+ struct skl_audio_data_format audio_fmt;
+};
+
+struct skl_cpr_gtw_cfg {
+ u32 node_id;
+ u32 dma_buffer_size;
+ u32 config_length;
+ /* not mandatory; required only for DMIC/I2S */
+ u32 config_data[1];
+} __packed;
+
+struct skl_dma_control {
+ u32 node_id;
+ u32 config_length;
+ u32 config_data[];
+} __packed;
+
+struct skl_cpr_cfg {
+ struct skl_base_cfg base_cfg;
+ struct skl_audio_data_format out_fmt;
+ u32 cpr_feature_mask;
+ struct skl_cpr_gtw_cfg gtw_cfg;
+} __packed;
+
+struct skl_cpr_pin_fmt {
+ u32 sink_id;
+ struct skl_audio_data_format src_fmt;
+ struct skl_audio_data_format dst_fmt;
+} __packed;
+
+struct skl_src_module_cfg {
+ struct skl_base_cfg base_cfg;
+ enum skl_s_freq src_cfg;
+} __packed;
+
+struct skl_up_down_mixer_cfg {
+ struct skl_base_cfg base_cfg;
+ enum skl_ch_cfg out_ch_cfg;
+ /* This should be set to 1 if user coefficients are required */
+ u32 coeff_sel;
+ /* Pass the user coeff in this array */
+ s32 coeff[UP_DOWN_MIXER_MAX_COEFF];
+ u32 ch_map;
+} __packed;
+
+struct skl_algo_cfg {
+ struct skl_base_cfg base_cfg;
+ char params[];
+} __packed;
+
+struct skl_base_outfmt_cfg {
+ struct skl_base_cfg base_cfg;
+ struct skl_audio_data_format out_fmt;
+} __packed;
+
+enum skl_dma_type {
+ SKL_DMA_HDA_HOST_OUTPUT_CLASS = 0,
+ SKL_DMA_HDA_HOST_INPUT_CLASS = 1,
+ SKL_DMA_HDA_HOST_INOUT_CLASS = 2,
+ SKL_DMA_HDA_LINK_OUTPUT_CLASS = 8,
+ SKL_DMA_HDA_LINK_INPUT_CLASS = 9,
+ SKL_DMA_HDA_LINK_INOUT_CLASS = 0xA,
+ SKL_DMA_DMIC_LINK_INPUT_CLASS = 0xB,
+ SKL_DMA_I2S_LINK_OUTPUT_CLASS = 0xC,
+ SKL_DMA_I2S_LINK_INPUT_CLASS = 0xD,
+};
+
+union skl_ssp_dma_node {
+ u8 val;
+ struct {
+ u8 time_slot_index:4;
+ u8 i2s_instance:4;
+ } dma_node;
+};
+
+union skl_connector_node_id {
+ u32 val;
+ struct {
+ u32 vindex:8;
+ u32 dma_type:4;
+ u32 rsvd:20;
+ } node;
+};
+
+struct skl_module_fmt {
+ u32 channels;
+ u32 s_freq;
+ u32 bit_depth;
+ u32 valid_bit_depth;
+ u32 ch_cfg;
+ u32 interleaving_style;
+ u32 sample_type;
+ u32 ch_map;
+};
+
+struct skl_module_cfg;
+
+struct skl_mod_inst_map {
+ u16 mod_id;
+ u16 inst_id;
+};
+
+struct skl_uuid_inst_map {
+ u16 inst_id;
+ u16 reserved;
+ guid_t mod_uuid;
+} __packed;
+
+struct skl_kpb_params {
+ u32 num_modules;
+ union {
+ struct skl_mod_inst_map map[0];
+ struct skl_uuid_inst_map map_uuid[0];
+ } u;
+};
+
+struct skl_module_inst_id {
+ guid_t mod_uuid;
+ int module_id;
+ u32 instance_id;
+ int pvt_id;
+};
+
+enum skl_module_pin_state {
+ SKL_PIN_UNBIND = 0,
+ SKL_PIN_BIND_DONE = 1,
+};
+
+struct skl_module_pin {
+ struct skl_module_inst_id id;
+ bool is_dynamic;
+ bool in_use;
+ enum skl_module_pin_state pin_state;
+ struct skl_module_cfg *tgt_mcfg;
+};
+
+struct skl_specific_cfg {
+ u32 set_params;
+ u32 param_id;
+ u32 caps_size;
+ u32 *caps;
+};
+
+enum skl_pipe_state {
+ SKL_PIPE_INVALID = 0,
+ SKL_PIPE_CREATED = 1,
+ SKL_PIPE_PAUSED = 2,
+ SKL_PIPE_STARTED = 3,
+ SKL_PIPE_RESET = 4
+};
+
+struct skl_pipe_module {
+ struct snd_soc_dapm_widget *w;
+ struct list_head node;
+};
+
+struct skl_pipe_params {
+ u8 host_dma_id;
+ u8 link_dma_id;
+ u32 ch;
+ u32 s_freq;
+ u32 s_fmt;
+ u8 linktype;
+ snd_pcm_format_t format;
+ int link_index;
+ int stream;
+ unsigned int host_bps;
+ unsigned int link_bps;
+};
+
+struct skl_pipe_fmt {
+ u32 freq;
+ u8 channels;
+ u8 bps;
+};
+
+struct skl_pipe_mcfg {
+ u8 res_idx;
+ u8 fmt_idx;
+};
+
+struct skl_path_config {
+ u8 mem_pages;
+ struct skl_pipe_fmt in_fmt;
+ struct skl_pipe_fmt out_fmt;
+};
+
+struct skl_pipe {
+ u8 ppl_id;
+ u8 pipe_priority;
+ u16 conn_type;
+ u32 memory_pages;
+ u8 lp_mode;
+ struct skl_pipe_params *p_params;
+ enum skl_pipe_state state;
+ u8 direction;
+ u8 cur_config_idx;
+ u8 nr_cfgs;
+ struct skl_path_config configs[SKL_MAX_PATH_CONFIGS];
+ struct list_head w_list;
+ bool passthru;
+ u32 pipe_config_idx;
+};
+
+enum skl_module_state {
+ SKL_MODULE_UNINIT = 0,
+ SKL_MODULE_LOADED = 1,
+ SKL_MODULE_INIT_DONE = 2,
+ SKL_MODULE_BIND_DONE = 3,
+ SKL_MODULE_UNLOADED = 4,
+};
+
+enum d0i3_capability {
+ SKL_D0I3_NONE = 0,
+ SKL_D0I3_STREAMING = 1,
+ SKL_D0I3_NON_STREAMING = 2,
+};
+
+struct skl_module_pin_fmt {
+ u8 id;
+ struct skl_module_fmt fmt;
+};
+
+struct skl_module_iface {
+ u8 fmt_idx;
+ u8 nr_in_fmt;
+ u8 nr_out_fmt;
+ struct skl_module_pin_fmt inputs[MAX_IN_QUEUE];
+ struct skl_module_pin_fmt outputs[MAX_OUT_QUEUE];
+};
+
+struct skl_module_pin_resources {
+ u8 pin_index;
+ u32 buf_size;
+};
+
+struct skl_module_res {
+ u8 id;
+ u32 is_pages;
+ u32 ibs;
+ u32 obs;
+ u32 dma_buffer_size;
+ u32 cpc;
+ u8 nr_input_pins;
+ u8 nr_output_pins;
+ struct skl_module_pin_resources input[MAX_IN_QUEUE];
+ struct skl_module_pin_resources output[MAX_OUT_QUEUE];
+};
+
+struct skl_module {
+ guid_t uuid;
+ u8 loadable;
+ u8 input_pin_type;
+ u8 output_pin_type;
+ u8 max_input_pins;
+ u8 max_output_pins;
+ u8 nr_resources;
+ u8 nr_interfaces;
+ struct skl_module_res resources[SKL_MAX_MODULE_RESOURCES];
+ struct skl_module_iface formats[SKL_MAX_MODULE_FORMATS];
+};
+
+struct skl_module_cfg {
+ u8 guid[16];
+ struct skl_module_inst_id id;
+ struct skl_module *module;
+ int res_idx;
+ int fmt_idx;
+ u8 domain;
+ bool homogenous_inputs;
+ bool homogenous_outputs;
+ struct skl_module_fmt in_fmt[MODULE_MAX_IN_PINS];
+ struct skl_module_fmt out_fmt[MODULE_MAX_OUT_PINS];
+ u8 max_in_queue;
+ u8 max_out_queue;
+ u8 in_queue_mask;
+ u8 out_queue_mask;
+ u8 in_queue;
+ u8 out_queue;
+ u8 is_loadable;
+ u8 core_id;
+ u8 dev_type;
+ u8 dma_id;
+ u8 time_slot;
+ u8 dmic_ch_combo_index;
+ u32 dmic_ch_type;
+ u32 params_fixup;
+ u32 converter;
+ u32 vbus_id;
+ u32 mem_pages;
+ enum d0i3_capability d0i3_caps;
+ u32 dma_buffer_size; /* in milli seconds */
+ struct skl_module_pin *m_in_pin;
+ struct skl_module_pin *m_out_pin;
+ enum skl_module_type m_type;
+ enum skl_hw_conn_type hw_conn_type;
+ enum skl_module_state m_state;
+ struct skl_pipe *pipe;
+ struct skl_specific_cfg formats_config;
+ struct skl_pipe_mcfg mod_cfg[SKL_MAX_MODULES_IN_PIPE];
+};
+
+struct skl_algo_data {
+ u32 param_id;
+ u32 set_params;
+ u32 max;
+ u32 size;
+ char *params;
+};
+
+struct skl_pipeline {
+ struct skl_pipe *pipe;
+ struct list_head node;
+};
+
+struct skl_module_deferred_bind {
+ struct skl_module_cfg *src;
+ struct skl_module_cfg *dst;
+ struct list_head node;
+};
+
+struct skl_mic_sel_config {
+ u16 mic_switch;
+ u16 flags;
+ u16 blob[SKL_MIC_MAX_CH_SUPPORT][SKL_MIC_MAX_CH_SUPPORT];
+} __packed;
+
+enum skl_channel {
+ SKL_CH_MONO = 1,
+ SKL_CH_STEREO = 2,
+ SKL_CH_TRIO = 3,
+ SKL_CH_QUATRO = 4,
+};
+
+static inline struct skl_dev *get_skl_ctx(struct device *dev)
+{
+ struct hdac_bus *bus = dev_get_drvdata(dev);
+
+ return bus_to_skl(bus);
+}
+
+int skl_tplg_be_update_params(struct snd_soc_dai *dai,
+ struct skl_pipe_params *params);
+int skl_dsp_set_dma_control(struct skl_dev *skl, u32 *caps,
+ u32 caps_size, u32 node_id);
+void skl_tplg_set_be_dmic_config(struct snd_soc_dai *dai,
+ struct skl_pipe_params *params, int stream);
+int skl_tplg_init(struct snd_soc_component *component,
+ struct hdac_bus *bus);
+void skl_tplg_exit(struct snd_soc_component *component,
+ struct hdac_bus *bus);
+struct skl_module_cfg *skl_tplg_fe_get_cpr_module(
+ struct snd_soc_dai *dai, int stream);
+int skl_tplg_update_pipe_params(struct device *dev,
+ struct skl_module_cfg *mconfig, struct skl_pipe_params *params);
+
+void skl_tplg_d0i3_get(struct skl_dev *skl, enum d0i3_capability caps);
+void skl_tplg_d0i3_put(struct skl_dev *skl, enum d0i3_capability caps);
+
+int skl_create_pipeline(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_run_pipe(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_pause_pipe(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_delete_pipe(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_stop_pipe(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_reset_pipe(struct skl_dev *skl, struct skl_pipe *pipe);
+
+int skl_init_module(struct skl_dev *skl, struct skl_module_cfg *mconfig);
+
+int skl_bind_modules(struct skl_dev *skl, struct skl_module_cfg
+ *src_mcfg, struct skl_module_cfg *dst_mcfg);
+
+int skl_unbind_modules(struct skl_dev *skl, struct skl_module_cfg
+ *src_mcfg, struct skl_module_cfg *dst_mcfg);
+
+int skl_set_module_params(struct skl_dev *skl, u32 *params, int size,
+ u32 param_id, struct skl_module_cfg *mcfg);
+int skl_get_module_params(struct skl_dev *skl, u32 *params, int size,
+ u32 param_id, struct skl_module_cfg *mcfg);
+
+struct skl_module_cfg *skl_tplg_be_get_cpr_module(struct snd_soc_dai *dai,
+ int stream);
+enum skl_bitdepth skl_get_bit_depth(int params);
+int skl_pcm_host_dma_prepare(struct device *dev,
+ struct skl_pipe_params *params);
+int skl_pcm_link_dma_prepare(struct device *dev,
+ struct skl_pipe_params *params);
+
+int skl_dai_load(struct snd_soc_component *cmp, int index,
+ struct snd_soc_dai_driver *dai_drv,
+ struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai);
+void skl_tplg_add_moduleid_in_bind_params(struct skl_dev *skl,
+ struct snd_soc_dapm_widget *w);
+#endif
diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c
new file mode 100644
index 000000000..2085e12dc
--- /dev/null
+++ b/sound/soc/intel/skylake/skl.c
@@ -0,0 +1,1199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * skl.c - Implementation of ASoC Intel SKL HD Audio driver
+ *
+ * Copyright (C) 2014-2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ *
+ * Derived mostly from Intel HDA driver with following copyrights:
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ * PeiSen Hou <pshou@realtek.com.tw>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/hda_register.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+#include <sound/hda_codec.h>
+#include <sound/intel-nhlt.h>
+#include <sound/intel-dsp-config.h>
+#include "skl.h"
+#include "skl-sst-dsp.h"
+#include "skl-sst-ipc.h"
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+#include "../../../soc/codecs/hdac_hda.h"
+#endif
+static int skl_pci_binding;
+module_param_named(pci_binding, skl_pci_binding, int, 0444);
+MODULE_PARM_DESC(pci_binding, "PCI binding (0=auto, 1=only legacy, 2=only asoc");
+
+/*
+ * initialize the PCI registers
+ */
+static void skl_update_pci_byte(struct pci_dev *pci, unsigned int reg,
+ unsigned char mask, unsigned char val)
+{
+ unsigned char data;
+
+ pci_read_config_byte(pci, reg, &data);
+ data &= ~mask;
+ data |= (val & mask);
+ pci_write_config_byte(pci, reg, data);
+}
+
+static void skl_init_pci(struct skl_dev *skl)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+
+ /*
+ * Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
+ * TCSEL == Traffic Class Select Register, which sets PCI express QOS
+ * Ensuring these bits are 0 clears playback static on some HD Audio
+ * codecs.
+ * The PCI register TCSEL is defined in the Intel manuals.
+ */
+ dev_dbg(bus->dev, "Clearing TCSEL\n");
+ skl_update_pci_byte(skl->pci, AZX_PCIREG_TCSEL, 0x07, 0);
+}
+
+static void update_pci_dword(struct pci_dev *pci,
+ unsigned int reg, u32 mask, u32 val)
+{
+ u32 data = 0;
+
+ pci_read_config_dword(pci, reg, &data);
+ data &= ~mask;
+ data |= (val & mask);
+ pci_write_config_dword(pci, reg, data);
+}
+
+/*
+ * skl_enable_miscbdcge - enable/dsiable CGCTL.MISCBDCGE bits
+ *
+ * @dev: device pointer
+ * @enable: enable/disable flag
+ */
+static void skl_enable_miscbdcge(struct device *dev, bool enable)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ u32 val;
+
+ val = enable ? AZX_CGCTL_MISCBDCGE_MASK : 0;
+
+ update_pci_dword(pci, AZX_PCIREG_CGCTL, AZX_CGCTL_MISCBDCGE_MASK, val);
+}
+
+/**
+ * skl_clock_power_gating: Enable/Disable clock and power gating
+ *
+ * @dev: Device pointer
+ * @enable: Enable/Disable flag
+ */
+static void skl_clock_power_gating(struct device *dev, bool enable)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ u32 val;
+
+ /* Update PDCGE bit of CGCTL register */
+ val = enable ? AZX_CGCTL_ADSPDCGE : 0;
+ update_pci_dword(pci, AZX_PCIREG_CGCTL, AZX_CGCTL_ADSPDCGE, val);
+
+ /* Update L1SEN bit of EM2 register */
+ val = enable ? AZX_REG_VS_EM2_L1SEN : 0;
+ snd_hdac_chip_updatel(bus, VS_EM2, AZX_REG_VS_EM2_L1SEN, val);
+
+ /* Update ADSPPGD bit of PGCTL register */
+ val = enable ? 0 : AZX_PGCTL_ADSPPGD;
+ update_pci_dword(pci, AZX_PCIREG_PGCTL, AZX_PGCTL_ADSPPGD, val);
+}
+
+/*
+ * While performing reset, controller may not come back properly causing
+ * issues, so recommendation is to set CGCTL.MISCBDCGE to 0 then do reset
+ * (init chip) and then again set CGCTL.MISCBDCGE to 1
+ */
+static int skl_init_chip(struct hdac_bus *bus, bool full_reset)
+{
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ snd_hdac_set_codec_wakeup(bus, true);
+ skl_enable_miscbdcge(bus->dev, false);
+ ret = snd_hdac_bus_init_chip(bus, full_reset);
+
+ /* Reset stream-to-link mapping */
+ list_for_each_entry(hlink, &bus->hlink_list, list)
+ writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
+
+ skl_enable_miscbdcge(bus->dev, true);
+ snd_hdac_set_codec_wakeup(bus, false);
+
+ return ret;
+}
+
+void skl_update_d0i3c(struct device *dev, bool enable)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ u8 reg;
+ int timeout = 50;
+
+ reg = snd_hdac_chip_readb(bus, VS_D0I3C);
+ /* Do not write to D0I3C until command in progress bit is cleared */
+ while ((reg & AZX_REG_VS_D0I3C_CIP) && --timeout) {
+ udelay(10);
+ reg = snd_hdac_chip_readb(bus, VS_D0I3C);
+ }
+
+ /* Highly unlikely. But if it happens, flag error explicitly */
+ if (!timeout) {
+ dev_err(bus->dev, "Before D0I3C update: D0I3C CIP timeout\n");
+ return;
+ }
+
+ if (enable)
+ reg = reg | AZX_REG_VS_D0I3C_I3;
+ else
+ reg = reg & (~AZX_REG_VS_D0I3C_I3);
+
+ snd_hdac_chip_writeb(bus, VS_D0I3C, reg);
+
+ timeout = 50;
+ /* Wait for cmd in progress to be cleared before exiting the function */
+ reg = snd_hdac_chip_readb(bus, VS_D0I3C);
+ while ((reg & AZX_REG_VS_D0I3C_CIP) && --timeout) {
+ udelay(10);
+ reg = snd_hdac_chip_readb(bus, VS_D0I3C);
+ }
+
+ /* Highly unlikely. But if it happens, flag error explicitly */
+ if (!timeout) {
+ dev_err(bus->dev, "After D0I3C update: D0I3C CIP timeout\n");
+ return;
+ }
+
+ dev_dbg(bus->dev, "D0I3C register = 0x%x\n",
+ snd_hdac_chip_readb(bus, VS_D0I3C));
+}
+
+/**
+ * skl_dum_set - set DUM bit in EM2 register
+ * @bus: HD-audio core bus
+ *
+ * Addresses incorrect position reporting for capture streams.
+ * Used on device power up.
+ */
+static void skl_dum_set(struct hdac_bus *bus)
+{
+ /* For the DUM bit to be set, CRST needs to be out of reset state */
+ if (!(snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET)) {
+ skl_enable_miscbdcge(bus->dev, false);
+ snd_hdac_bus_exit_link_reset(bus);
+ skl_enable_miscbdcge(bus->dev, true);
+ }
+
+ snd_hdac_chip_updatel(bus, VS_EM2, AZX_VS_EM2_DUM, AZX_VS_EM2_DUM);
+}
+
+/* called from IRQ */
+static void skl_stream_update(struct hdac_bus *bus, struct hdac_stream *hstr)
+{
+ snd_pcm_period_elapsed(hstr->substream);
+}
+
+static irqreturn_t skl_interrupt(int irq, void *dev_id)
+{
+ struct hdac_bus *bus = dev_id;
+ u32 status;
+
+ if (!pm_runtime_active(bus->dev))
+ return IRQ_NONE;
+
+ spin_lock(&bus->reg_lock);
+
+ status = snd_hdac_chip_readl(bus, INTSTS);
+ if (status == 0 || status == 0xffffffff) {
+ spin_unlock(&bus->reg_lock);
+ return IRQ_NONE;
+ }
+
+ /* clear rirb int */
+ status = snd_hdac_chip_readb(bus, RIRBSTS);
+ if (status & RIRB_INT_MASK) {
+ if (status & RIRB_INT_RESPONSE)
+ snd_hdac_bus_update_rirb(bus);
+ snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
+ }
+
+ spin_unlock(&bus->reg_lock);
+
+ return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+static irqreturn_t skl_threaded_handler(int irq, void *dev_id)
+{
+ struct hdac_bus *bus = dev_id;
+ u32 status;
+
+ status = snd_hdac_chip_readl(bus, INTSTS);
+
+ snd_hdac_bus_handle_stream_irq(bus, status, skl_stream_update);
+
+ return IRQ_HANDLED;
+}
+
+static int skl_acquire_irq(struct hdac_bus *bus, int do_disconnect)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+ int ret;
+
+ ret = request_threaded_irq(skl->pci->irq, skl_interrupt,
+ skl_threaded_handler,
+ IRQF_SHARED,
+ KBUILD_MODNAME, bus);
+ if (ret) {
+ dev_err(bus->dev,
+ "unable to grab IRQ %d, disabling device\n",
+ skl->pci->irq);
+ return ret;
+ }
+
+ bus->irq = skl->pci->irq;
+ pci_intx(skl->pci, 1);
+
+ return 0;
+}
+
+static int skl_suspend_late(struct device *dev)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ return skl_suspend_late_dsp(skl);
+}
+
+#ifdef CONFIG_PM
+static int _skl_suspend(struct hdac_bus *bus)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct pci_dev *pci = to_pci_dev(bus->dev);
+ int ret;
+
+ snd_hdac_ext_bus_link_power_down_all(bus);
+
+ ret = skl_suspend_dsp(skl);
+ if (ret < 0)
+ return ret;
+
+ snd_hdac_bus_stop_chip(bus);
+ update_pci_dword(pci, AZX_PCIREG_PGCTL,
+ AZX_PGCTL_LSRMD_MASK, AZX_PGCTL_LSRMD_MASK);
+ skl_enable_miscbdcge(bus->dev, false);
+ snd_hdac_bus_enter_link_reset(bus);
+ skl_enable_miscbdcge(bus->dev, true);
+ skl_cleanup_resources(skl);
+
+ return 0;
+}
+
+static int _skl_resume(struct hdac_bus *bus)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ skl_init_pci(skl);
+ skl_dum_set(bus);
+ skl_init_chip(bus, true);
+
+ return skl_resume_dsp(skl);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * power management
+ */
+static int skl_suspend(struct device *dev)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct skl_dev *skl = bus_to_skl(bus);
+ int ret;
+
+ /*
+ * Do not suspend if streams which are marked ignore suspend are
+ * running, we need to save the state for these and continue
+ */
+ if (skl->supend_active) {
+ /* turn off the links and stop the CORB/RIRB DMA if it is On */
+ snd_hdac_ext_bus_link_power_down_all(bus);
+
+ if (bus->cmd_dma_state)
+ snd_hdac_bus_stop_cmd_io(bus);
+
+ enable_irq_wake(bus->irq);
+ pci_save_state(pci);
+ } else {
+ ret = _skl_suspend(bus);
+ if (ret < 0)
+ return ret;
+ skl->fw_loaded = false;
+ }
+
+ return 0;
+}
+
+static int skl_resume(struct device *dev)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ /*
+ * resume only when we are not in suspend active, otherwise need to
+ * restore the device
+ */
+ if (skl->supend_active) {
+ pci_restore_state(pci);
+ snd_hdac_ext_bus_link_power_up_all(bus);
+ disable_irq_wake(bus->irq);
+ /*
+ * turn On the links which are On before active suspend
+ * and start the CORB/RIRB DMA if On before
+ * active suspend.
+ */
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ if (hlink->ref_count)
+ snd_hdac_ext_bus_link_power_up(hlink);
+ }
+
+ ret = 0;
+ if (bus->cmd_dma_state)
+ snd_hdac_bus_init_cmd_io(bus);
+ } else {
+ ret = _skl_resume(bus);
+
+ /* turn off the links which are off before suspend */
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ if (!hlink->ref_count)
+ snd_hdac_ext_bus_link_power_down(hlink);
+ }
+
+ if (!bus->cmd_dma_state)
+ snd_hdac_bus_stop_cmd_io(bus);
+ }
+
+ return ret;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM
+static int skl_runtime_suspend(struct device *dev)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+
+ dev_dbg(bus->dev, "in %s\n", __func__);
+
+ return _skl_suspend(bus);
+}
+
+static int skl_runtime_resume(struct device *dev)
+{
+ struct pci_dev *pci = to_pci_dev(dev);
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+
+ dev_dbg(bus->dev, "in %s\n", __func__);
+
+ return _skl_resume(bus);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops skl_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(skl_suspend, skl_resume)
+ SET_RUNTIME_PM_OPS(skl_runtime_suspend, skl_runtime_resume, NULL)
+ .suspend_late = skl_suspend_late,
+};
+
+/*
+ * destructor
+ */
+static int skl_free(struct hdac_bus *bus)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ skl->init_done = 0; /* to be sure */
+
+ snd_hdac_stop_streams_and_chip(bus);
+
+ if (bus->irq >= 0)
+ free_irq(bus->irq, (void *)bus);
+ snd_hdac_bus_free_stream_pages(bus);
+ snd_hdac_stream_free_all(bus);
+ snd_hdac_link_free_all(bus);
+
+ if (bus->remap_addr)
+ iounmap(bus->remap_addr);
+
+ pci_release_regions(skl->pci);
+ pci_disable_device(skl->pci);
+
+ snd_hdac_ext_bus_exit(bus);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) {
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
+ snd_hdac_i915_exit(bus);
+ }
+
+ return 0;
+}
+
+/*
+ * For each ssp there are 3 clocks (mclk/sclk/sclkfs).
+ * e.g. for ssp0, clocks will be named as
+ * "ssp0_mclk", "ssp0_sclk", "ssp0_sclkfs"
+ * So for skl+, there are 6 ssps, so 18 clocks will be created.
+ */
+static struct skl_ssp_clk skl_ssp_clks[] = {
+ {.name = "ssp0_mclk"}, {.name = "ssp1_mclk"}, {.name = "ssp2_mclk"},
+ {.name = "ssp3_mclk"}, {.name = "ssp4_mclk"}, {.name = "ssp5_mclk"},
+ {.name = "ssp0_sclk"}, {.name = "ssp1_sclk"}, {.name = "ssp2_sclk"},
+ {.name = "ssp3_sclk"}, {.name = "ssp4_sclk"}, {.name = "ssp5_sclk"},
+ {.name = "ssp0_sclkfs"}, {.name = "ssp1_sclkfs"},
+ {.name = "ssp2_sclkfs"},
+ {.name = "ssp3_sclkfs"}, {.name = "ssp4_sclkfs"},
+ {.name = "ssp5_sclkfs"},
+};
+
+static struct snd_soc_acpi_mach *skl_find_hda_machine(struct skl_dev *skl,
+ struct snd_soc_acpi_mach *machines)
+{
+ struct snd_soc_acpi_mach *mach;
+
+ /* point to common table */
+ mach = snd_soc_acpi_intel_hda_machines;
+
+ /* all entries in the machine table use the same firmware */
+ mach->fw_filename = machines->fw_filename;
+
+ return mach;
+}
+
+static int skl_find_machine(struct skl_dev *skl, void *driver_data)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct snd_soc_acpi_mach *mach = driver_data;
+ struct skl_machine_pdata *pdata;
+
+ mach = snd_soc_acpi_find_machine(mach);
+ if (!mach) {
+ dev_dbg(bus->dev, "No matching I2S machine driver found\n");
+ mach = skl_find_hda_machine(skl, driver_data);
+ if (!mach) {
+ dev_err(bus->dev, "No matching machine driver found\n");
+ return -ENODEV;
+ }
+ }
+
+ skl->mach = mach;
+ skl->fw_name = mach->fw_filename;
+ pdata = mach->pdata;
+
+ if (pdata) {
+ skl->use_tplg_pcm = pdata->use_tplg_pcm;
+ mach->mach_params.dmic_num =
+ intel_nhlt_get_dmic_geo(&skl->pci->dev,
+ skl->nhlt);
+ }
+
+ return 0;
+}
+
+static int skl_machine_device_register(struct skl_dev *skl)
+{
+ struct snd_soc_acpi_mach *mach = skl->mach;
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = platform_device_alloc(mach->drv_name, -1);
+ if (pdev == NULL) {
+ dev_err(bus->dev, "platform device alloc failed\n");
+ return -EIO;
+ }
+
+ mach->mach_params.platform = dev_name(bus->dev);
+ mach->mach_params.codec_mask = bus->codec_mask;
+
+ ret = platform_device_add_data(pdev, (const void *)mach, sizeof(*mach));
+ if (ret) {
+ dev_err(bus->dev, "failed to add machine device platform data\n");
+ platform_device_put(pdev);
+ return ret;
+ }
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ dev_err(bus->dev, "failed to add machine device\n");
+ platform_device_put(pdev);
+ return -EIO;
+ }
+
+
+ skl->i2s_dev = pdev;
+
+ return 0;
+}
+
+static void skl_machine_device_unregister(struct skl_dev *skl)
+{
+ if (skl->i2s_dev)
+ platform_device_unregister(skl->i2s_dev);
+}
+
+static int skl_dmic_device_register(struct skl_dev *skl)
+{
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct platform_device *pdev;
+ int ret;
+
+ /* SKL has one dmic port, so allocate dmic device for this */
+ pdev = platform_device_alloc("dmic-codec", -1);
+ if (!pdev) {
+ dev_err(bus->dev, "failed to allocate dmic device\n");
+ return -ENOMEM;
+ }
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ dev_err(bus->dev, "failed to add dmic device: %d\n", ret);
+ platform_device_put(pdev);
+ return ret;
+ }
+ skl->dmic_dev = pdev;
+
+ return 0;
+}
+
+static void skl_dmic_device_unregister(struct skl_dev *skl)
+{
+ if (skl->dmic_dev)
+ platform_device_unregister(skl->dmic_dev);
+}
+
+static struct skl_clk_parent_src skl_clk_src[] = {
+ { .clk_id = SKL_XTAL, .name = "xtal" },
+ { .clk_id = SKL_CARDINAL, .name = "cardinal", .rate = 24576000 },
+ { .clk_id = SKL_PLL, .name = "pll", .rate = 96000000 },
+};
+
+struct skl_clk_parent_src *skl_get_parent_clk(u8 clk_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(skl_clk_src); i++) {
+ if (skl_clk_src[i].clk_id == clk_id)
+ return &skl_clk_src[i];
+ }
+
+ return NULL;
+}
+
+static void init_skl_xtal_rate(int pci_id)
+{
+ switch (pci_id) {
+ case 0x9d70:
+ case 0x9d71:
+ skl_clk_src[0].rate = 24000000;
+ return;
+
+ default:
+ skl_clk_src[0].rate = 19200000;
+ return;
+ }
+}
+
+static int skl_clock_device_register(struct skl_dev *skl)
+{
+ struct platform_device_info pdevinfo = {NULL};
+ struct skl_clk_pdata *clk_pdata;
+
+ if (!skl->nhlt)
+ return 0;
+
+ clk_pdata = devm_kzalloc(&skl->pci->dev, sizeof(*clk_pdata),
+ GFP_KERNEL);
+ if (!clk_pdata)
+ return -ENOMEM;
+
+ init_skl_xtal_rate(skl->pci->device);
+
+ clk_pdata->parent_clks = skl_clk_src;
+ clk_pdata->ssp_clks = skl_ssp_clks;
+ clk_pdata->num_clks = ARRAY_SIZE(skl_ssp_clks);
+
+ /* Query NHLT to fill the rates and parent */
+ skl_get_clks(skl, clk_pdata->ssp_clks);
+ clk_pdata->pvt_data = skl;
+
+ /* Register Platform device */
+ pdevinfo.parent = &skl->pci->dev;
+ pdevinfo.id = -1;
+ pdevinfo.name = "skl-ssp-clk";
+ pdevinfo.data = clk_pdata;
+ pdevinfo.size_data = sizeof(*clk_pdata);
+ skl->clk_dev = platform_device_register_full(&pdevinfo);
+ return PTR_ERR_OR_ZERO(skl->clk_dev);
+}
+
+static void skl_clock_device_unregister(struct skl_dev *skl)
+{
+ if (skl->clk_dev)
+ platform_device_unregister(skl->clk_dev);
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+
+#define IDISP_INTEL_VENDOR_ID 0x80860000
+
+/*
+ * load the legacy codec driver
+ */
+static void load_codec_module(struct hda_codec *codec)
+{
+#ifdef MODULE
+ char modalias[MODULE_NAME_LEN];
+ const char *mod = NULL;
+
+ snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias));
+ mod = modalias;
+ dev_dbg(&codec->core.dev, "loading %s codec module\n", mod);
+ request_module(mod);
+#endif
+}
+
+#endif /* CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC */
+
+/*
+ * Probe the given codec address
+ */
+static int probe_codec(struct hdac_bus *bus, int addr)
+{
+ unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+ (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+ unsigned int res = -1;
+ struct skl_dev *skl = bus_to_skl(bus);
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+ struct hdac_hda_priv *hda_codec;
+ int err;
+#endif
+ struct hdac_device *hdev;
+
+ mutex_lock(&bus->cmd_mutex);
+ snd_hdac_bus_send_cmd(bus, cmd);
+ snd_hdac_bus_get_response(bus, addr, &res);
+ mutex_unlock(&bus->cmd_mutex);
+ if (res == -1)
+ return -EIO;
+ dev_dbg(bus->dev, "codec #%d probed OK: %x\n", addr, res);
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+ hda_codec = devm_kzalloc(&skl->pci->dev, sizeof(*hda_codec),
+ GFP_KERNEL);
+ if (!hda_codec)
+ return -ENOMEM;
+
+ hda_codec->codec.bus = skl_to_hbus(skl);
+ hdev = &hda_codec->codec.core;
+
+ err = snd_hdac_ext_bus_device_init(bus, addr, hdev, HDA_DEV_ASOC);
+ if (err < 0)
+ return err;
+
+ /* use legacy bus only for HDA codecs, idisp uses ext bus */
+ if ((res & 0xFFFF0000) != IDISP_INTEL_VENDOR_ID) {
+ hdev->type = HDA_DEV_LEGACY;
+ load_codec_module(&hda_codec->codec);
+ }
+ return 0;
+#else
+ hdev = devm_kzalloc(&skl->pci->dev, sizeof(*hdev), GFP_KERNEL);
+ if (!hdev)
+ return -ENOMEM;
+
+ return snd_hdac_ext_bus_device_init(bus, addr, hdev, HDA_DEV_ASOC);
+#endif /* CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC */
+}
+
+/* Codec initialization */
+static void skl_codec_create(struct hdac_bus *bus)
+{
+ int c, max_slots;
+
+ max_slots = HDA_MAX_CODECS;
+
+ /* First try to probe all given codec slots */
+ for (c = 0; c < max_slots; c++) {
+ if ((bus->codec_mask & (1 << c))) {
+ if (probe_codec(bus, c) < 0) {
+ /*
+ * Some BIOSen give you wrong codec addresses
+ * that don't exist
+ */
+ dev_warn(bus->dev,
+ "Codec #%d probe error; disabling it...\n", c);
+ bus->codec_mask &= ~(1 << c);
+ /*
+ * More badly, accessing to a non-existing
+ * codec often screws up the controller bus,
+ * and disturbs the further communications.
+ * Thus if an error occurs during probing,
+ * better to reset the controller bus to get
+ * back to the sanity state.
+ */
+ snd_hdac_bus_stop_chip(bus);
+ skl_init_chip(bus, true);
+ }
+ }
+ }
+}
+
+static int skl_i915_init(struct hdac_bus *bus)
+{
+ int err;
+
+ /*
+ * The HDMI codec is in GPU so we need to ensure that it is powered
+ * up and ready for probe
+ */
+ err = snd_hdac_i915_init(bus);
+ if (err < 0)
+ return err;
+
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true);
+
+ return 0;
+}
+
+static void skl_probe_work(struct work_struct *work)
+{
+ struct skl_dev *skl = container_of(work, struct skl_dev, probe_work);
+ struct hdac_bus *bus = skl_to_bus(skl);
+ struct hdac_ext_link *hlink;
+ int err;
+
+ if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) {
+ err = skl_i915_init(bus);
+ if (err < 0)
+ return;
+ }
+
+ skl_init_pci(skl);
+ skl_dum_set(bus);
+
+ err = skl_init_chip(bus, true);
+ if (err < 0) {
+ dev_err(bus->dev, "Init chip failed with err: %d\n", err);
+ goto out_err;
+ }
+
+ /* codec detection */
+ if (!bus->codec_mask)
+ dev_info(bus->dev, "no hda codecs found!\n");
+
+ /* create codec instances */
+ skl_codec_create(bus);
+
+ /* register platform dai and controls */
+ err = skl_platform_register(bus->dev);
+ if (err < 0) {
+ dev_err(bus->dev, "platform register failed: %d\n", err);
+ goto out_err;
+ }
+
+ err = skl_machine_device_register(skl);
+ if (err < 0) {
+ dev_err(bus->dev, "machine register failed: %d\n", err);
+ goto out_err;
+ }
+
+ /*
+ * we are done probing so decrement link counts
+ */
+ list_for_each_entry(hlink, &bus->hlink_list, list)
+ snd_hdac_ext_bus_link_put(bus, hlink);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI))
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
+
+ /* configure PM */
+ pm_runtime_put_noidle(bus->dev);
+ pm_runtime_allow(bus->dev);
+ skl->init_done = 1;
+
+ return;
+
+out_err:
+ if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI))
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
+}
+
+/*
+ * constructor
+ */
+static int skl_create(struct pci_dev *pci,
+ struct skl_dev **rskl)
+{
+ struct hdac_ext_bus_ops *ext_ops = NULL;
+ struct skl_dev *skl;
+ struct hdac_bus *bus;
+ struct hda_bus *hbus;
+ int err;
+
+ *rskl = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ skl = devm_kzalloc(&pci->dev, sizeof(*skl), GFP_KERNEL);
+ if (!skl) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ hbus = skl_to_hbus(skl);
+ bus = skl_to_bus(skl);
+
+ INIT_LIST_HEAD(&skl->ppl_list);
+ INIT_LIST_HEAD(&skl->bind_list);
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+ ext_ops = snd_soc_hdac_hda_get_ops();
+#endif
+ snd_hdac_ext_bus_init(bus, &pci->dev, NULL, ext_ops);
+ bus->use_posbuf = 1;
+ skl->pci = pci;
+ INIT_WORK(&skl->probe_work, skl_probe_work);
+ bus->bdl_pos_adj = 0;
+
+ mutex_init(&hbus->prepare_mutex);
+ hbus->pci = pci;
+ hbus->mixer_assigned = -1;
+ hbus->modelname = "sklbus";
+
+ *rskl = skl;
+
+ return 0;
+}
+
+static int skl_first_init(struct hdac_bus *bus)
+{
+ struct skl_dev *skl = bus_to_skl(bus);
+ struct pci_dev *pci = skl->pci;
+ int err;
+ unsigned short gcap;
+ int cp_streams, pb_streams, start_idx;
+
+ err = pci_request_regions(pci, "Skylake HD audio");
+ if (err < 0)
+ return err;
+
+ bus->addr = pci_resource_start(pci, 0);
+ bus->remap_addr = pci_ioremap_bar(pci, 0);
+ if (bus->remap_addr == NULL) {
+ dev_err(bus->dev, "ioremap error\n");
+ return -ENXIO;
+ }
+
+ snd_hdac_bus_parse_capabilities(bus);
+
+ /* check if PPCAP exists */
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "bus ppcap not set, HDAudio or DSP not present?\n");
+ return -ENODEV;
+ }
+
+ if (skl_acquire_irq(bus, 0) < 0)
+ return -EBUSY;
+
+ pci_set_master(pci);
+ synchronize_irq(bus->irq);
+
+ gcap = snd_hdac_chip_readw(bus, GCAP);
+ dev_dbg(bus->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+ /* read number of streams from GCAP register */
+ cp_streams = (gcap >> 8) & 0x0f;
+ pb_streams = (gcap >> 12) & 0x0f;
+
+ if (!pb_streams && !cp_streams) {
+ dev_err(bus->dev, "no streams found in GCAP definitions?\n");
+ return -EIO;
+ }
+
+ bus->num_streams = cp_streams + pb_streams;
+
+ /* allow 64bit DMA address if supported by H/W */
+ if (!dma_set_mask(bus->dev, DMA_BIT_MASK(64))) {
+ dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(64));
+ } else {
+ dma_set_mask(bus->dev, DMA_BIT_MASK(32));
+ dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(32));
+ }
+
+ /* initialize streams */
+ snd_hdac_ext_stream_init_all
+ (bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE);
+ start_idx = cp_streams;
+ snd_hdac_ext_stream_init_all
+ (bus, start_idx, pb_streams, SNDRV_PCM_STREAM_PLAYBACK);
+
+ err = snd_hdac_bus_alloc_stream_pages(bus);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int skl_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ struct skl_dev *skl;
+ struct hdac_bus *bus = NULL;
+ int err;
+
+ switch (skl_pci_binding) {
+ case SND_SKL_PCI_BIND_AUTO:
+ err = snd_intel_dsp_driver_probe(pci);
+ if (err != SND_INTEL_DSP_DRIVER_ANY &&
+ err != SND_INTEL_DSP_DRIVER_SST)
+ return -ENODEV;
+ break;
+ case SND_SKL_PCI_BIND_LEGACY:
+ dev_info(&pci->dev, "Module parameter forced binding with HDAudio legacy, aborting probe\n");
+ return -ENODEV;
+ case SND_SKL_PCI_BIND_ASOC:
+ dev_info(&pci->dev, "Module parameter forced binding with SKL driver, bypassed detection logic\n");
+ break;
+ default:
+ dev_err(&pci->dev, "invalid value for skl_pci_binding module parameter, ignored\n");
+ break;
+ }
+
+ /* we use ext core ops, so provide NULL for ops here */
+ err = skl_create(pci, &skl);
+ if (err < 0)
+ return err;
+
+ bus = skl_to_bus(skl);
+
+ err = skl_first_init(bus);
+ if (err < 0) {
+ dev_err(bus->dev, "skl_first_init failed with err: %d\n", err);
+ goto out_free;
+ }
+
+ skl->pci_id = pci->device;
+
+ device_disable_async_suspend(bus->dev);
+
+ skl->nhlt = intel_nhlt_init(bus->dev);
+
+ if (skl->nhlt == NULL) {
+#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC)
+ dev_err(bus->dev, "no nhlt info found\n");
+ err = -ENODEV;
+ goto out_free;
+#else
+ dev_warn(bus->dev, "no nhlt info found, continuing to try to enable HDAudio codec\n");
+#endif
+ } else {
+
+ err = skl_nhlt_create_sysfs(skl);
+ if (err < 0) {
+ dev_err(bus->dev, "skl_nhlt_create_sysfs failed with err: %d\n", err);
+ goto out_nhlt_free;
+ }
+
+ skl_nhlt_update_topology_bin(skl);
+
+ /* create device for dsp clk */
+ err = skl_clock_device_register(skl);
+ if (err < 0) {
+ dev_err(bus->dev, "skl_clock_device_register failed with err: %d\n", err);
+ goto out_clk_free;
+ }
+ }
+
+ pci_set_drvdata(skl->pci, bus);
+
+
+ err = skl_find_machine(skl, (void *)pci_id->driver_data);
+ if (err < 0) {
+ dev_err(bus->dev, "skl_find_machine failed with err: %d\n", err);
+ goto out_nhlt_free;
+ }
+
+ err = skl_init_dsp(skl);
+ if (err < 0) {
+ dev_dbg(bus->dev, "error failed to register dsp\n");
+ goto out_nhlt_free;
+ }
+ skl->enable_miscbdcge = skl_enable_miscbdcge;
+ skl->clock_power_gating = skl_clock_power_gating;
+
+ if (bus->mlcap)
+ snd_hdac_ext_bus_get_ml_capabilities(bus);
+
+ /* create device for soc dmic */
+ err = skl_dmic_device_register(skl);
+ if (err < 0) {
+ dev_err(bus->dev, "skl_dmic_device_register failed with err: %d\n", err);
+ goto out_dsp_free;
+ }
+
+ schedule_work(&skl->probe_work);
+
+ return 0;
+
+out_dsp_free:
+ skl_free_dsp(skl);
+out_clk_free:
+ skl_clock_device_unregister(skl);
+out_nhlt_free:
+ if (skl->nhlt)
+ intel_nhlt_free(skl->nhlt);
+out_free:
+ skl_free(bus);
+
+ return err;
+}
+
+static void skl_shutdown(struct pci_dev *pci)
+{
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct hdac_stream *s;
+ struct hdac_ext_stream *stream;
+ struct skl_dev *skl;
+
+ if (!bus)
+ return;
+
+ skl = bus_to_skl(bus);
+
+ if (!skl->init_done)
+ return;
+
+ snd_hdac_stop_streams(bus);
+ snd_hdac_ext_bus_link_power_down_all(bus);
+ skl_dsp_sleep(skl->dsp);
+
+ list_for_each_entry(s, &bus->stream_list, list) {
+ stream = stream_to_hdac_ext_stream(s);
+ snd_hdac_ext_stream_decouple(bus, stream, false);
+ }
+
+ snd_hdac_bus_stop_chip(bus);
+}
+
+static void skl_remove(struct pci_dev *pci)
+{
+ struct hdac_bus *bus = pci_get_drvdata(pci);
+ struct skl_dev *skl = bus_to_skl(bus);
+
+ cancel_work_sync(&skl->probe_work);
+
+ pm_runtime_get_noresume(&pci->dev);
+
+ /* codec removal, invoke bus_device_remove */
+ snd_hdac_ext_bus_device_remove(bus);
+
+ skl_platform_unregister(&pci->dev);
+ skl_free_dsp(skl);
+ skl_machine_device_unregister(skl);
+ skl_dmic_device_unregister(skl);
+ skl_clock_device_unregister(skl);
+ skl_nhlt_remove_sysfs(skl);
+ if (skl->nhlt)
+ intel_nhlt_free(skl->nhlt);
+ skl_free(bus);
+ dev_set_drvdata(&pci->dev, NULL);
+}
+
+/* PCI IDs */
+static const struct pci_device_id skl_ids[] = {
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKL)
+ /* Sunrise Point-LP */
+ { PCI_DEVICE(0x8086, 0x9d70),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_skl_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_APL)
+ /* BXT-P */
+ { PCI_DEVICE(0x8086, 0x5a98),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_bxt_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_KBL)
+ /* KBL */
+ { PCI_DEVICE(0x8086, 0x9D71),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_kbl_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_GLK)
+ /* GLK */
+ { PCI_DEVICE(0x8086, 0x3198),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_glk_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CNL)
+ /* CNL */
+ { PCI_DEVICE(0x8086, 0x9dc8),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_cnl_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CFL)
+ /* CFL */
+ { PCI_DEVICE(0x8086, 0xa348),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_cnl_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CML_LP)
+ /* CML-LP */
+ { PCI_DEVICE(0x8086, 0x02c8),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_cnl_machines},
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CML_H)
+ /* CML-H */
+ { PCI_DEVICE(0x8086, 0x06c8),
+ .driver_data = (unsigned long)&snd_soc_acpi_intel_cnl_machines},
+#endif
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, skl_ids);
+
+/* pci_driver definition */
+static struct pci_driver skl_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = skl_ids,
+ .probe = skl_probe,
+ .remove = skl_remove,
+ .shutdown = skl_shutdown,
+ .driver = {
+ .pm = &skl_pm,
+ },
+};
+module_pci_driver(skl_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Skylake ASoC HDA driver");
diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h
new file mode 100644
index 000000000..857ea17e3
--- /dev/null
+++ b/sound/soc/intel/skylake/skl.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * skl.h - HD Audio skylake defintions.
+ *
+ * Copyright (C) 2015 Intel Corp
+ * Author: Jeeja KP <jeeja.kp@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SOUND_SOC_SKL_H
+#define __SOUND_SOC_SKL_H
+
+#include <sound/hda_register.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/hda_codec.h>
+#include <sound/soc.h>
+#include "skl-ssp-clk.h"
+#include "skl-sst-ipc.h"
+
+#define SKL_SUSPEND_DELAY 2000
+
+#define SKL_MAX_ASTATE_CFG 3
+
+#define AZX_PCIREG_PGCTL 0x44
+#define AZX_PGCTL_LSRMD_MASK (1 << 4)
+#define AZX_PGCTL_ADSPPGD BIT(2)
+#define AZX_PCIREG_CGCTL 0x48
+#define AZX_CGCTL_MISCBDCGE_MASK (1 << 6)
+#define AZX_CGCTL_ADSPDCGE BIT(1)
+/* D0I3C Register fields */
+#define AZX_REG_VS_D0I3C_CIP 0x1 /* Command in progress */
+#define AZX_REG_VS_D0I3C_I3 0x4 /* D0i3 enable */
+#define SKL_MAX_DMACTRL_CFG 18
+#define DMA_CLK_CONTROLS 1
+#define DMA_TRANSMITION_START 2
+#define DMA_TRANSMITION_STOP 3
+
+#define AZX_VS_EM2_DUM BIT(23)
+#define AZX_REG_VS_EM2_L1SEN BIT(13)
+
+struct skl_debug;
+
+struct skl_astate_param {
+ u32 kcps;
+ u32 clk_src;
+};
+
+struct skl_astate_config {
+ u32 count;
+ struct skl_astate_param astate_table[];
+};
+
+struct skl_fw_config {
+ struct skl_astate_config *astate_cfg;
+};
+
+struct skl_dev {
+ struct hda_bus hbus;
+ struct pci_dev *pci;
+
+ unsigned int init_done:1; /* delayed init status */
+ struct platform_device *dmic_dev;
+ struct platform_device *i2s_dev;
+ struct platform_device *clk_dev;
+ struct snd_soc_component *component;
+ struct snd_soc_dai_driver *dais;
+
+ struct nhlt_acpi_table *nhlt; /* nhlt ptr */
+
+ struct list_head ppl_list;
+ struct list_head bind_list;
+
+ const char *fw_name;
+ char tplg_name[64];
+ unsigned short pci_id;
+
+ int supend_active;
+
+ struct work_struct probe_work;
+
+ struct skl_debug *debugfs;
+ u8 nr_modules;
+ struct skl_module **modules;
+ bool use_tplg_pcm;
+ struct skl_fw_config cfg;
+ struct snd_soc_acpi_mach *mach;
+
+ struct device *dev;
+ struct sst_dsp *dsp;
+
+ /* boot */
+ wait_queue_head_t boot_wait;
+ bool boot_complete;
+
+ /* module load */
+ wait_queue_head_t mod_load_wait;
+ bool mod_load_complete;
+ bool mod_load_status;
+
+ /* IPC messaging */
+ struct sst_generic_ipc ipc;
+
+ /* callback for miscbdge */
+ void (*enable_miscbdcge)(struct device *dev, bool enable);
+ /* Is CGCTL.MISCBDCGE disabled */
+ bool miscbdcg_disabled;
+
+ /* Populate module information */
+ struct list_head uuid_list;
+
+ /* Is firmware loaded */
+ bool fw_loaded;
+
+ /* first boot ? */
+ bool is_first_boot;
+
+ /* multi-core */
+ struct skl_dsp_cores cores;
+
+ /* library info */
+ struct skl_lib_info lib_info[SKL_MAX_LIB];
+ int lib_count;
+
+ /* Callback to update D0i3C register */
+ void (*update_d0i3c)(struct device *dev, bool enable);
+
+ struct skl_d0i3_data d0i3;
+
+ const struct skl_dsp_ops *dsp_ops;
+
+ /* Callback to update dynamic clock and power gating registers */
+ void (*clock_power_gating)(struct device *dev, bool enable);
+};
+
+#define skl_to_bus(s) (&(s)->hbus.core)
+#define bus_to_skl(bus) container_of(bus, struct skl_dev, hbus.core)
+
+#define skl_to_hbus(s) (&(s)->hbus)
+#define hbus_to_skl(hbus) container_of((hbus), struct skl_dev, (hbus))
+
+/* to pass dai dma data */
+struct skl_dma_params {
+ u32 format;
+ u8 stream_tag;
+};
+
+struct skl_machine_pdata {
+ bool use_tplg_pcm; /* use dais and dai links from topology */
+};
+
+struct skl_dsp_ops {
+ int id;
+ unsigned int num_cores;
+ struct skl_dsp_loader_ops (*loader_ops)(void);
+ int (*init)(struct device *dev, void __iomem *mmio_base,
+ int irq, const char *fw_name,
+ struct skl_dsp_loader_ops loader_ops,
+ struct skl_dev **skl_sst);
+ int (*init_fw)(struct device *dev, struct skl_dev *skl);
+ void (*cleanup)(struct device *dev, struct skl_dev *skl);
+};
+
+int skl_platform_unregister(struct device *dev);
+int skl_platform_register(struct device *dev);
+
+struct nhlt_specific_cfg *skl_get_ep_blob(struct skl_dev *skl, u32 instance,
+ u8 link_type, u8 s_fmt, u8 num_ch,
+ u32 s_rate, u8 dirn, u8 dev_type);
+
+int skl_nhlt_update_topology_bin(struct skl_dev *skl);
+int skl_init_dsp(struct skl_dev *skl);
+int skl_free_dsp(struct skl_dev *skl);
+int skl_suspend_late_dsp(struct skl_dev *skl);
+int skl_suspend_dsp(struct skl_dev *skl);
+int skl_resume_dsp(struct skl_dev *skl);
+void skl_cleanup_resources(struct skl_dev *skl);
+const struct skl_dsp_ops *skl_get_dsp_ops(int pci_id);
+void skl_update_d0i3c(struct device *dev, bool enable);
+int skl_nhlt_create_sysfs(struct skl_dev *skl);
+void skl_nhlt_remove_sysfs(struct skl_dev *skl);
+void skl_get_clks(struct skl_dev *skl, struct skl_ssp_clk *ssp_clks);
+struct skl_clk_parent_src *skl_get_parent_clk(u8 clk_id);
+int skl_dsp_set_dma_control(struct skl_dev *skl, u32 *caps,
+ u32 caps_size, u32 node_id);
+
+struct skl_module_cfg;
+
+#ifdef CONFIG_DEBUG_FS
+struct skl_debug *skl_debugfs_init(struct skl_dev *skl);
+void skl_debugfs_exit(struct skl_dev *skl);
+void skl_debug_init_module(struct skl_debug *d,
+ struct snd_soc_dapm_widget *w,
+ struct skl_module_cfg *mconfig);
+#else
+static inline struct skl_debug *skl_debugfs_init(struct skl_dev *skl)
+{
+ return NULL;
+}
+
+static inline void skl_debugfs_exit(struct skl_dev *skl)
+{}
+
+static inline void skl_debug_init_module(struct skl_debug *d,
+ struct snd_soc_dapm_widget *w,
+ struct skl_module_cfg *mconfig)
+{}
+#endif
+
+#endif /* __SOUND_SOC_SKL_H */