summaryrefslogtreecommitdiff
path: root/sound/soc/intel
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/intel')
-rw-r--r--sound/soc/intel/Kconfig123
-rw-r--r--sound/soc/intel/Makefile10
-rw-r--r--sound/soc/intel/atom/Makefile7
-rw-r--r--sound/soc/intel/atom/sst-atom-controls.c1422
-rw-r--r--sound/soc/intel/atom/sst-atom-controls.h870
-rw-r--r--sound/soc/intel/atom/sst-mfld-dsp.h533
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform-compress.c268
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform-pcm.c804
-rw-r--r--sound/soc/intel/atom/sst-mfld-platform.h181
-rw-r--r--sound/soc/intel/atom/sst/Makefile7
-rw-r--r--sound/soc/intel/atom/sst/sst.c557
-rw-r--r--sound/soc/intel/atom/sst/sst.h559
-rw-r--r--sound/soc/intel/atom/sst/sst_acpi.c384
-rw-r--r--sound/soc/intel/atom/sst/sst_drv_interface.c741
-rw-r--r--sound/soc/intel/atom/sst/sst_ipc.c373
-rw-r--r--sound/soc/intel/atom/sst/sst_loader.c463
-rw-r--r--sound/soc/intel/atom/sst/sst_pci.c208
-rw-r--r--sound/soc/intel/atom/sst/sst_pvt.c425
-rw-r--r--sound/soc/intel/atom/sst/sst_stream.c437
-rw-r--r--sound/soc/intel/baytrail/Makefile4
-rw-r--r--sound/soc/intel/baytrail/sst-baytrail-dsp.c366
-rw-r--r--sound/soc/intel/baytrail/sst-baytrail-ipc.c776
-rw-r--r--sound/soc/intel/baytrail/sst-baytrail-ipc.h73
-rw-r--r--sound/soc/intel/baytrail/sst-baytrail-pcm.c505
-rw-r--r--sound/soc/intel/boards/Makefile15
-rw-r--r--sound/soc/intel/boards/broadwell.c292
-rw-r--r--sound/soc/intel/boards/byt-max98090.c187
-rw-r--r--sound/soc/intel/boards/byt-rt5640.c229
-rw-r--r--sound/soc/intel/boards/bytcr_rt5640.c227
-rw-r--r--sound/soc/intel/boards/cht_bsw_rt5645.c324
-rw-r--r--sound/soc/intel/boards/cht_bsw_rt5672.c366
-rw-r--r--sound/soc/intel/boards/haswell.c209
-rw-r--r--sound/soc/intel/boards/mfld_machine.c430
-rw-r--r--sound/soc/intel/common/Makefile7
-rw-r--r--sound/soc/intel/common/sst-acpi.c286
-rw-r--r--sound/soc/intel/common/sst-dsp-priv.h373
-rw-r--r--sound/soc/intel/common/sst-dsp.c420
-rw-r--r--sound/soc/intel/common/sst-dsp.h285
-rw-r--r--sound/soc/intel/common/sst-firmware.c1205
-rw-r--r--sound/soc/intel/common/sst-ipc.c294
-rw-r--r--sound/soc/intel/common/sst-ipc.h91
-rw-r--r--sound/soc/intel/haswell/Makefile4
-rw-r--r--sound/soc/intel/haswell/sst-haswell-dsp.c709
-rw-r--r--sound/soc/intel/haswell/sst-haswell-ipc.c2219
-rw-r--r--sound/soc/intel/haswell/sst-haswell-ipc.h534
-rw-r--r--sound/soc/intel/haswell/sst-haswell-pcm.c1405
46 files changed, 20207 insertions, 0 deletions
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig
new file mode 100644
index 000000000..ee03dbdda
--- /dev/null
+++ b/sound/soc/intel/Kconfig
@@ -0,0 +1,123 @@
+config SND_MFLD_MACHINE
+ tristate "SOC Machine Audio driver for Intel Medfield MID platform"
+ depends on INTEL_SCU_IPC
+ select SND_SOC_SN95031
+ select SND_SST_MFLD_PLATFORM
+ select SND_SST_IPC_PCI
+ help
+ This adds support for ASoC machine driver for Intel(R) MID Medfield platform
+ used as alsa device in audio substem in Intel(R) MID devices
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SST_MFLD_PLATFORM
+ tristate
+
+config SND_SST_IPC
+ tristate
+
+config SND_SST_IPC_PCI
+ tristate
+ select SND_SST_IPC
+
+config SND_SST_IPC_ACPI
+ tristate
+ select SND_SST_IPC
+ depends on ACPI
+
+config SND_SOC_INTEL_SST
+ tristate "ASoC support for Intel(R) Smart Sound Technology"
+ select SND_SOC_INTEL_SST_ACPI if ACPI
+ depends on (X86 || COMPILE_TEST)
+ depends on DW_DMAC_CORE
+ help
+ This adds support for Intel(R) Smart Sound Technology (SST).
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SOC_INTEL_SST_ACPI
+ tristate
+
+config SND_SOC_INTEL_HASWELL
+ tristate
+
+config SND_SOC_INTEL_BAYTRAIL
+ tristate
+
+config SND_SOC_INTEL_HASWELL_MACH
+ tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint"
+ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C && \
+ I2C_DESIGNWARE_PLATFORM
+ select SND_SOC_INTEL_HASWELL
+ select SND_SOC_RT5640
+ help
+ This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell
+ Ultrabook platforms.
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYT_RT5640_MACH
+ tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec"
+ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C
+ select SND_SOC_INTEL_BAYTRAIL
+ select SND_SOC_RT5640
+ help
+ This adds audio driver for Intel Baytrail platform based boards
+ with the RT5640 audio codec.
+
+config SND_SOC_INTEL_BYT_MAX98090_MACH
+ tristate "ASoC Audio driver for Intel Baytrail with MAX98090 codec"
+ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C
+ select SND_SOC_INTEL_BAYTRAIL
+ select SND_SOC_MAX98090
+ help
+ This adds audio driver for Intel Baytrail platform based boards
+ with the MAX98090 audio codec.
+
+config SND_SOC_INTEL_BROADWELL_MACH
+ tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint"
+ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC && \
+ I2C_DESIGNWARE_PLATFORM
+ select SND_SOC_INTEL_HASWELL
+ select SND_COMPRESS_OFFLOAD
+ select SND_SOC_RT286
+ help
+ This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell
+ Ultrabook platforms.
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SOC_INTEL_BYTCR_RT5640_MACH
+ tristate "ASoC Audio DSP Support for MID BYT Platform"
+ depends on X86 && I2C
+ select SND_SOC_RT5640
+ select SND_SST_MFLD_PLATFORM
+ select SND_SST_IPC_ACPI
+ help
+ This adds support for ASoC machine driver for Intel(R) MID Baytrail platform
+ used as alsa device in audio substem in Intel(R) MID devices
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_RT5672_MACH
+ tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec"
+ depends on X86_INTEL_LPSS && I2C
+ select SND_SOC_RT5670
+ select SND_SST_MFLD_PLATFORM
+ select SND_SST_IPC_ACPI
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with RT5672 audio codec.
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SOC_INTEL_CHT_BSW_RT5645_MACH
+ tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5645 codec"
+ depends on X86_INTEL_LPSS
+ select SND_SOC_RT5645
+ select SND_SST_MFLD_PLATFORM
+ select SND_SST_IPC_ACPI
+ help
+ This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell
+ platforms with RT5645 audio codec.
+ If unsure select "N".
diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile
new file mode 100644
index 000000000..3853ec2dd
--- /dev/null
+++ b/sound/soc/intel/Makefile
@@ -0,0 +1,10 @@
+# Core support
+obj-$(CONFIG_SND_SOC_INTEL_SST) += common/
+
+# Platform Support
+obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/
+obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/
+obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += atom/
+
+# Machine support
+obj-$(CONFIG_SND_SOC_INTEL_SST) += boards/
diff --git a/sound/soc/intel/atom/Makefile b/sound/soc/intel/atom/Makefile
new file mode 100644
index 000000000..ce8074fa6
--- /dev/null
+++ b/sound/soc/intel/atom/Makefile
@@ -0,0 +1,7 @@
+snd-soc-sst-mfld-platform-objs := sst-mfld-platform-pcm.o \
+ sst-mfld-platform-compress.o sst-atom-controls.o
+
+obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o
+
+# DSP driver
+obj-$(CONFIG_SND_SST_IPC) += 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..90aa5c047
--- /dev/null
+++ b/sound/soc/intel/atom/sst-atom-controls.c
@@ -0,0 +1,1422 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * 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;
+
+ 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
+ * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS)
+ * @cmd_data: the IPC payload
+ */
+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);
+}
+
+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
+ *
+ * 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
+ 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
+ *
+ * (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
+ *
+ * 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 = NULL;
+
+ 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
+ * @gv: the stored value of gain (also contains rampduration)
+ * @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 ? 1 : 0;
+ 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_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
+ *
+ * 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 = NULL;
+
+ 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 == false)
+ 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("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, "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(sst_mix_voip_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls);
+SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_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;
+}
+
+/**
+ * sst_ssp_config - contains SSP configuration for media UC
+ */
+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,
+};
+
+int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable)
+{
+ struct sst_cmd_sba_hw_set_ssp cmd;
+ struct sst_data *drv = snd_soc_dai_get_drvdata(dai);
+ const struct sst_ssp_config *config;
+
+ dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id);
+
+ SST_FILL_DEFAULT_DESTINATION(cmd.header.dst);
+ cmd.header.command_id = SBA_HW_SET_SSP;
+ cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp)
+ - sizeof(struct sst_dsp_header);
+
+ config = &sst_ssp_configs;
+ dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id);
+
+ if (enable)
+ cmd.switch_state = SST_SWITCH_ON;
+ else
+ cmd.switch_state = SST_SWITCH_OFF;
+
+ cmd.selection = config->ssp_id;
+ cmd.nb_bits_per_slots = config->bits_per_slot;
+ cmd.nb_slots = config->slots;
+ cmd.mode = config->ssp_mode | (config->pcm_mode << 1);
+ cmd.duplex = config->duplex;
+ cmd.active_tx_slot_map = config->active_slot_map;
+ cmd.active_rx_slot_map = config->active_slot_map;
+ cmd.frame_sync_frequency = config->fs_frequency;
+ cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH;
+ cmd.data_polarity = 1;
+ cmd.frame_sync_width = config->fs_width;
+ cmd.ssp_protocol = config->ssp_protocol;
+ cmd.start_delay = config->start_delay;
+ cmd.reserved1 = cmd.reserved2 = 0xFF;
+
+ return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED,
+ SST_TASK_SBA, 0, &cmd,
+ sizeof(cmd.header) + 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)) {
+ ret = sst_send_slot_map(drv);
+ 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("codec_in0", sst_set_be_modules),
+ SST_AIF_IN("codec_in1", 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_MONO, sst_set_media_loop),
+ SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, 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),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ {"media0_in", NULL, "Compress Playback"},
+ {"media1_in", NULL, "Headset Playback"},
+ {"media2_in", NULL, "pcm0_out"},
+
+ {"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"),
+
+};
+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]),
+};
+
+#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
+ *
+ * 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;
+ list_for_each_entry(p, &w->sinks, list_source) {
+ 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;
+ list_for_each_entry(p, &w->sources, list_sink) {
+ if (p->connected && !p->connected(w, p->sink))
+ 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
+ *
+ *
+ * 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
+ * @widget: pipe modelled as a DAPM widget
+ *
+ * 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_platform *platform)
+{
+ struct snd_kcontrol *kctl;
+ int index, ret = 0;
+ struct snd_card *card = platform->component.card->snd_card;
+ char *idx;
+
+ down_read(&card->controls_rwsem);
+
+ list_for_each_entry(kctl, &card->controls, list) {
+ idx = strstr(kctl->id.name, " ");
+ if (idx == NULL)
+ continue;
+ index = strlen(kctl->id.name) - strlen(idx);
+
+ if (strstr(kctl->id.name, "Volume") &&
+ !strncmp(kctl->id.name, w->name, index))
+ ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN);
+
+ else if (strstr(kctl->id.name, "params") &&
+ !strncmp(kctl->id.name, w->name, index))
+ ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO);
+
+ else if (strstr(kctl->id.name, "Switch") &&
+ !strncmp(kctl->id.name, w->name, index) &&
+ 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") &&
+ !strncmp(kctl->id.name, w->name, index)) {
+ struct sst_enum *e = (void *)kctl->private_value;
+
+ e->w = w;
+
+ } else if (strstr(kctl->id.name, "deinterleaver") &&
+ !strncmp(kctl->id.name, w->name, index)) {
+
+ 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
+ */
+static void sst_fill_linked_widgets(struct snd_soc_platform *platform,
+ struct sst_ids *ids)
+{
+ struct snd_soc_dapm_widget *w;
+ unsigned int len = strlen(ids->parent_wname);
+
+ list_for_each_entry(w, &platform->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
+ */
+static int sst_map_modules_to_pipe(struct snd_soc_platform *platform)
+{
+ struct snd_soc_dapm_widget *w;
+ int ret = 0;
+
+ list_for_each_entry(w, &platform->component.card->widgets, list) {
+ if (is_sst_dapm_widget(w) && (w->priv)) {
+ struct sst_ids *ids = w->priv;
+
+ dev_dbg(platform->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, platform);
+
+ if (ret < 0)
+ return ret;
+
+ /* fill linked widgets */
+ if (ids->parent_wname != NULL)
+ sst_fill_linked_widgets(platform, ids);
+ }
+ }
+ return 0;
+}
+
+int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform)
+{
+ int i, ret = 0;
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(&platform->component);
+ struct sst_data *drv = snd_soc_platform_get_drvdata(platform);
+ unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3;
+
+ drv->byte_stream = devm_kzalloc(platform->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_platform_controls(platform, sst_gain_controls,
+ ARRAY_SIZE(sst_gain_controls));
+ if (ret)
+ return ret;
+
+ /* Initialize algo control params */
+ ret = sst_algo_control_init(platform->dev);
+ if (ret)
+ return ret;
+ ret = snd_soc_add_platform_controls(platform, sst_algo_controls,
+ ARRAY_SIZE(sst_algo_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_add_platform_controls(platform, sst_slot_controls,
+ ARRAY_SIZE(sst_slot_controls));
+ if (ret)
+ return ret;
+
+ ret = sst_map_modules_to_pipe(platform);
+
+ 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..daecc58f2
--- /dev/null
+++ b/sound/soc/intel/atom/sst-atom-controls.h
@@ -0,0 +1,870 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#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_COMPR,
+};
+
+/* define a bit for each mixer input */
+#define SST_MIX_IP(x) (x)
+
+#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_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_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_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_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[0];
+} __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;
+};
+
+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[44];
+ 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))
+
+#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..425726315
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-dsp.h
@@ -0,0 +1,533 @@
+#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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#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..395168986
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform-compress.c
@@ -0,0 +1,268 @@
+/*
+ * sst_mfld_platform.c - Intel MID Platform driver
+ *
+ * Copyright (C) 2010-2014 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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_compr_stream *cstream)
+{
+
+ int ret_val = 0;
+ 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_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_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 snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct sst_data *ctx = snd_soc_platform_get_drvdata(platform);
+
+ 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_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);
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (stream->compr_ops->stream_drop)
+ return stream->compr_ops->stream_drop(sst->dev, stream->id);
+ case SND_COMPR_TRIGGER_DRAIN:
+ if (stream->compr_ops->stream_drain)
+ return stream->compr_ops->stream_drain(sst->dev, stream->id);
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ if (stream->compr_ops->stream_partial_drain)
+ return stream->compr_ops->stream_partial_drain(sst->dev, stream->id);
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (stream->compr_ops->stream_pause)
+ return stream->compr_ops->stream_pause(sst->dev, stream->id);
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (stream->compr_ops->stream_pause_release)
+ return stream->compr_ops->stream_pause_release(sst->dev, stream->id);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sst_platform_compr_pointer(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_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_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_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_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);
+}
+
+struct snd_compr_ops sst_platform_compr_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..2fbaf2c75
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c
@@ -0,0 +1,804 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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);
+extern struct snd_compr_ops sst_platform_compr_ops;
+
+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 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},
+};
+
+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->dma_buffer.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 == true)
+ 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);
+ substream->runtime->dma_area = substream->dma_buffer.area;
+ 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 = substream->private_data;
+ 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)
+ return ret_val;
+
+ /* 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:
+ kfree(stream);
+ mutex_unlock(&sst_lock);
+ return ret_val;
+}
+
+static void sst_media_close(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ stream = substream->runtime->private_data;
+ power_down_sst(stream);
+
+ str_id = stream->stream_info.str_id;
+ if (str_id)
+ ret_val = stream->ops->close(sst->dev, str_id);
+ module_put(sst->dev->driver->owner);
+ kfree(stream);
+}
+
+static inline unsigned int get_current_pipe_id(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream)
+{
+ struct sst_data *sst = snd_soc_dai_get_drvdata(dai);
+ struct sst_dev_stream_map *map = sst->pdata->pdev_strm_map;
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ u32 str_id = stream->stream_info.str_id;
+ unsigned int pipe_id;
+
+ pipe_id = map[str_id].device_id;
+
+ dev_dbg(dai->dev, "got pipe_id = %#x for str_id = %d\n",
+ pipe_id, str_id);
+ return pipe_id;
+}
+
+static int sst_media_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, 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 ret_val;
+}
+
+static int sst_media_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
+ return 0;
+}
+
+static int sst_media_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int sst_enable_ssp(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ if (!dai->active) {
+ ret = sst_handle_vb_timer(dai, true);
+ if (ret)
+ return ret;
+ ret = send_ssp_cmd(dai, dai->name, 1);
+ }
+ return ret;
+}
+
+static void sst_disable_ssp(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (!dai->active) {
+ send_ssp_cmd(dai, dai->name, 0);
+ sst_handle_vb_timer(dai, false);
+ }
+}
+
+static struct snd_soc_dai_ops sst_media_dai_ops = {
+ .startup = sst_media_open,
+ .shutdown = sst_media_close,
+ .prepare = sst_media_prepare,
+ .hw_params = sst_media_hw_params,
+ .hw_free = sst_media_hw_free,
+ .mute_stream = sst_media_digital_mute,
+};
+
+static struct snd_soc_dai_ops sst_compr_dai_ops = {
+ .mute_stream = sst_media_digital_mute,
+};
+
+static struct snd_soc_dai_ops sst_be_dai_ops = {
+ .startup = sst_enable_ssp,
+ .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 = "compress-cpu-dai",
+ .compress_dai = 1,
+ .ops = &sst_compr_dai_ops,
+ .playback = {
+ .stream_name = "Compress Playback",
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+/* 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_platform_open(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_platform_pcm_trigger(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 = substream->private_data;
+
+ dev_dbg(rtd->dev, "sst_platform_pcm_trigger called\n");
+ 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_platform_pcm_pointer
+ (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 = substream->private_data;
+
+ 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 struct snd_pcm_ops sst_platform_ops = {
+ .open = sst_platform_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .trigger = sst_platform_pcm_trigger,
+ .pointer = sst_platform_pcm_pointer,
+};
+
+static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ struct snd_pcm *pcm = rtd->pcm;
+ int retval = 0;
+
+ if (dai->driver->playback.channels_min ||
+ dai->driver->capture.channels_min) {
+ retval = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_DMA),
+ SST_MIN_BUFFER, SST_MAX_BUFFER);
+ if (retval) {
+ dev_err(rtd->dev, "dma buffer allocationf fail\n");
+ return retval;
+ }
+ }
+ return retval;
+}
+
+static int sst_soc_probe(struct snd_soc_platform *platform)
+{
+ struct sst_data *drv = dev_get_drvdata(platform->dev);
+
+ drv->soc_card = platform->component.card;
+ return sst_dsp_init_v2_dpcm(platform);
+}
+
+static struct snd_soc_platform_driver sst_soc_platform_drv = {
+ .probe = sst_soc_probe,
+ .ops = &sst_platform_ops,
+ .compr_ops = &sst_platform_compr_ops,
+ .pcm_new = sst_pcm_new,
+};
+
+static const struct snd_soc_component_driver sst_component = {
+ .name = "sst",
+};
+
+
+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 = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv);
+ if (ret) {
+ dev_err(&pdev->dev, "registering soc platform failed\n");
+ return ret;
+ }
+
+ ret = snd_soc_register_component(&pdev->dev, &sst_component,
+ sst_platform_dai, ARRAY_SIZE(sst_platform_dai));
+ if (ret) {
+ dev_err(&pdev->dev, "registering cpu dais failed\n");
+ snd_soc_unregister_platform(&pdev->dev);
+ }
+ return ret;
+}
+
+static int sst_platform_remove(struct platform_device *pdev)
+{
+
+ snd_soc_unregister_component(&pdev->dev);
+ snd_soc_unregister_platform(&pdev->dev);
+ 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);
+ int i;
+
+ /* suspend all pcms first */
+ snd_soc_suspend(drv->soc_card->dev);
+ snd_soc_poweroff(drv->soc_card->dev);
+
+ /* set the SSPs to idle */
+ for (i = 0; i < drv->soc_card->num_rtd; i++) {
+ struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai;
+
+ if (dai->active) {
+ 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);
+ int i;
+
+ /* restart SSPs */
+ for (i = 0; i < drv->soc_card->num_rtd; i++) {
+ struct snd_soc_dai *dai = drv->soc_card->rtd[i].cpu_dai;
+
+ if (dai->active) {
+ 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-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..9094314be
--- /dev/null
+++ b/sound/soc/intel/atom/sst-mfld-platform.h
@@ -0,0 +1,181 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __SST_PLATFORMDRV_H__
+#define __SST_PLATFORMDRV_H__
+
+#include "sst-mfld-dsp.h"
+
+extern struct sst_device *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_platform *platform);
+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;
+};
+int sst_register_dsp(struct sst_device *sst);
+int sst_unregister_dsp(struct sst_device *sst);
+#endif
diff --git a/sound/soc/intel/atom/sst/Makefile b/sound/soc/intel/atom/sst/Makefile
new file mode 100644
index 000000000..fd2172636
--- /dev/null
+++ b/sound/soc/intel/atom/sst/Makefile
@@ -0,0 +1,7 @@
+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_IPC) += snd-intel-sst-core.o
+obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o
+obj-$(CONFIG_SND_SST_IPC_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..61d9aafe5
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst.c
@@ -0,0 +1,557 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/async.h>
+#include <linux/acpi.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <asm/platform_sst_audio.h>
+#include "../sst-mfld-platform.h"
+#include "sst.h"
+#include "../../common/sst-dsp.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 = 0;
+ 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 capablities 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);
+
+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;
+ }
+ pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY,
+ PM_QOS_DEFAULT_VALUE);
+
+ dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name);
+ ret = reject_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;
+ }
+ sst_register(ctx->dev);
+ return 0;
+
+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);
+ flush_scheduled_work();
+ destroy_workqueue(ctx->post_msg_wq);
+ pm_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);
+ ctx = NULL;
+}
+EXPORT_SYMBOL_GPL(sst_context_cleanup);
+
+static inline void sst_save_shim64(struct intel_sst_drv *ctx,
+ void __iomem *shim,
+ struct sst_shim_regs64 *shim_regs)
+{
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags);
+
+ shim_regs->imrx = sst_shim_read64(shim, SST_IMRX);
+ shim_regs->csr = sst_shim_read64(shim, SST_CSR);
+
+
+ spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags);
+}
+
+static inline void sst_restore_shim64(struct intel_sst_drv *ctx,
+ void __iomem *shim,
+ struct sst_shim_regs64 *shim_regs)
+{
+ unsigned long irq_flags;
+
+ /*
+ * we only need to restore IMRX for this case, rest will be
+ * initialize by FW or driver when firmware is loaded
+ */
+ spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags);
+ sst_shim_write64(shim, SST_IMRX, shim_regs->imrx),
+ sst_shim_write64(shim, SST_CSR, shim_regs->csr),
+ spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags);
+}
+
+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);
+
+ sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64);
+}
+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);
+ /* save the shim registers because PMC doesn't save state */
+ sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64);
+
+ 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 = 0;
+
+ /* 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, cant susupend, abort\n", i);
+ return -EBUSY;
+ }
+ }
+ 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 = kzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL);
+ if (!fw_save->iram) {
+ ret = -ENOMEM;
+ goto iram;
+ }
+ fw_save->dram = kzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL);
+ if (!fw_save->dram) {
+ ret = -ENOMEM;
+ goto dram;
+ }
+ fw_save->sram = kzalloc(SST_MAILBOX_SIZE, GFP_KERNEL);
+ if (!fw_save->sram) {
+ ret = -ENOMEM;
+ goto sram;
+ }
+
+ fw_save->ddr = kzalloc(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:
+ kfree(fw_save->sram);
+sram:
+ kfree(fw_save->dram);
+dram:
+ kfree(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;
+ int ret = 0;
+ struct sst_block *block;
+
+ 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);
+
+ kfree(fw_save->sram);
+ kfree(fw_save->dram);
+ kfree(fw_save->iram);
+ kfree(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);
+ }
+
+ 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..3f493862e
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst.h
@@ -0,0 +1,559 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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
+
+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_DECODE = 3, /* stream is in decoding only state */
+ STREAM_INIT = 4, /* stream init, waiting for data */
+ STREAM_RESET = 5, /* force reset on recovery */
+};
+
+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
+ * @ops : stream operation pb/cp/drm...
+ * @bufs: stream buffer list
+ * @lock : stream mutex for protecting state
+ * @pcm_substream : PCM substream
+ * @period_elapsed : PCM period elapsed callback
+ * @sfreq : stream sampling freq
+ * @str_type : stream type
+ * @cumm_bytes : cummulative bytes decoded
+ * @str_type : stream type
+ * @src : stream source
+ */
+struct stream_info {
+ unsigned int status;
+ unsigned int prev;
+ unsigned int ops;
+ struct mutex lock;
+
+ 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 str_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_shim_regs64 {
+ u64 csr;
+ u64 pisr;
+ u64 pimr;
+ u64 isrx;
+ u64 isrd;
+ u64 imrx;
+ u64 imrd;
+ u64 ipcx;
+ u64 ipcd;
+ u64 isrsc;
+ u64 isrlpesc;
+ u64 imrsc;
+ u64 imrlpesc;
+ u64 ipcsc;
+ u64 ipclpesc;
+ u64 clkctl;
+ u64 csr2;
+};
+
+struct sst_fw_save {
+ void *iram;
+ void *dram;
+ void *sram;
+ void *ddr;
+};
+
+/**
+ * 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
+ * @shim_regs64: Struct to save shim registers
+ * @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 sst_shim_regs64 *shim_regs64;
+ 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 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_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id);
+int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id);
+int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id);
+int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int 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 *ctx,
+ struct snd_sst_bytes_v2 *sbytes);
+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 *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 *ctx,
+ struct ipc_post *msg, bool sync);
+void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg);
+int sst_start_mrfld(struct intel_sst_drv *ctx);
+int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx);
+void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx);
+
+int sst_load_fw(struct intel_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 *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 *sst_drv_ctx);
+void sst_init_stream(struct stream_info *stream,
+ int codec, int sst_id, int ops, u8 slot);
+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..ea6358a7b
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_acpi.c
@@ -0,0 +1,384 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.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 "../sst-mfld-platform.h"
+#include "../../common/sst-dsp.h"
+#include "sst.h"
+
+struct sst_machines {
+ char *codec_id;
+ char board[32];
+ char machine[32];
+ void (*machine_quirk)(void);
+ char firmware[FW_NAME_SIZE];
+ struct sst_platform_info *pdata;
+
+};
+
+/* 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,
+};
+
+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",
+};
+
+/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail,
+ * so pdata is same as Baytrail.
+ */
+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");
+ 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_nocache(ctx->dev, ctx->iram_base,
+ ctx->pdata->res_info->iram_size);
+ if (!ctx->iram) {
+ dev_err(ctx->dev, "unable to map IRAM");
+ 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_nocache(ctx->dev, ctx->dram_base,
+ ctx->pdata->res_info->dram_size);
+ if (!ctx->dram) {
+ dev_err(ctx->dev, "unable to map DRAM");
+ 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_nocache(ctx->dev, ctx->shim_phy_add,
+ ctx->pdata->res_info->shim_size);
+ if (!ctx->shim) {
+ dev_err(ctx->dev, "unable to map SHIM");
+ 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_nocache(ctx->dev, ctx->mailbox_add,
+ ctx->pdata->res_info->mbox_size);
+ if (!ctx->mailbox) {
+ dev_err(ctx->dev, "unable to map mailbox");
+ 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");
+ 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_nocache(ctx->dev, ctx->ddr_base,
+ resource_size(rsrc));
+ if (!ctx->ddr) {
+ dev_err(ctx->dev, "unable to map DDR");
+ return -EIO;
+ }
+
+ /* Find the IRQ */
+ ctx->irq_num = platform_get_irq(pdev,
+ ctx->pdata->res_info->acpi_ipc_irq_index);
+ return 0;
+}
+
+static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
+ void *context, void **ret)
+{
+ *(bool *)context = true;
+ return AE_OK;
+}
+
+static struct sst_machines *sst_acpi_find_machine(
+ struct sst_machines *machines)
+{
+ struct sst_machines *mach;
+ bool found = false;
+
+ for (mach = machines; mach->codec_id; mach++)
+ if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id,
+ sst_acpi_mach_match,
+ &found, NULL)) && found)
+ return mach;
+
+ return NULL;
+}
+
+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 sst_machines *mach;
+ struct platform_device *mdev;
+ struct platform_device *plat_dev;
+ unsigned int dev_id;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+ dev_dbg(dev, "for %s", id->id);
+
+ mach = (struct sst_machines *)id->driver_data;
+ mach = sst_acpi_find_machine(mach);
+ if (mach == NULL) {
+ dev_err(dev, "No matching machine driver found\n");
+ return -ENODEV;
+ }
+
+ 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);
+
+ plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0);
+ if (IS_ERR(plat_dev)) {
+ dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform);
+ return PTR_ERR(plat_dev);
+ }
+
+ /* Create platform device for sst machine driver */
+ mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0);
+ if (IS_ERR(mdev)) {
+ dev_err(dev, "Failed to create machine device: %s\n", mach->machine);
+ return PTR_ERR(mdev);
+ }
+
+ ret = sst_alloc_drv_context(&ctx, dev, dev_id);
+ if (ret < 0)
+ return ret;
+
+ /* Fill sst platform data */
+ ctx->pdata = mach->pdata;
+ strcpy(ctx->firmware_name, mach->firmware);
+
+ ret = sst_platform_get_resources(ctx);
+ if (ret)
+ return ret;
+
+ ret = sst_context_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* need to save shim registers in BYT */
+ ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64),
+ GFP_KERNEL);
+ if (!ctx->shim_regs64) {
+ ret = -ENOMEM;
+ goto do_sst_cleanup;
+ }
+
+ sst_configure_runtime_pm(ctx);
+ platform_set_drvdata(pdev, ctx);
+ return ret;
+
+do_sst_cleanup:
+ sst_context_cleanup(ctx);
+ platform_set_drvdata(pdev, NULL);
+ dev_err(ctx->dev, "failed with %d\n", ret);
+ 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 struct sst_machines sst_acpi_bytcr[] = {
+ {"10EC5640", "T100", "bytt100_rt5640", NULL, "/*(DEBLOBBED)*/",
+ &byt_rvp_platform_data },
+ {},
+};
+
+/* Cherryview-based platforms: CherryTrail and Braswell */
+static struct sst_machines sst_acpi_chv[] = {
+ {"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "/*(DEBLOBBED)*/",
+ &chv_platform_data },
+ {"10EC5645", "cht-bsw", "cht-bsw-rt5645", NULL, "/*(DEBLOBBED)*/",
+ &chv_platform_data },
+ {},
+};
+
+static const struct acpi_device_id sst_acpi_ids[] = {
+ { "80860F28", (unsigned long)&sst_acpi_bytcr},
+ { "808622A8", (unsigned long) &sst_acpi_chv},
+ { },
+};
+
+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..7b50a9d17
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_drv_interface.c
@@ -0,0 +1,741 @@
+/*
+ * 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)
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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"
+#include "../../common/sst-dsp.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)
+
+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;
+
+#ifdef CONFIG_PM
+ usage_count = atomic_read(&dev->power.usage_count);
+#else
+ usage_count = 1;
+#endif
+
+ if (state == true) {
+ ret = pm_runtime_get_sync(dev);
+
+ dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count);
+ if (ret < 0) {
+ 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 {
+ 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)
+ 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;
+ }
+
+ if (stream->status == STREAM_RESET) {
+ dev_dbg(dev, "stream in reset state...\n");
+ stream->status = STREAM_UN_INIT;
+
+ retval = 0;
+ goto put;
+ }
+
+ retval = sst_free_stream(ctx, str_id);
+put:
+ 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);
+
+ memcpy_fromio(&fw_tstamp,
+ ((void *)(ctx->mailbox + ctx->tstamp)
+ +(str_id * sizeof(fw_tstamp))),
+ 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);
+
+ addr = ((void *)(ctx->mailbox + ctx->tstamp)) +
+ (str_id * sizeof(fw_tstamp));
+ 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);
+
+ memcpy_fromio(&fw_tstamp,
+ ((void *)(ctx->mailbox + ctx->tstamp)
+ +(str_id * sizeof(fw_tstamp))),
+ 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 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 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;
+ }
+
+ if (stream->status == STREAM_RESET) {
+ /* silently fail here as we have cleaned the stream earlier */
+ dev_dbg(ctx->dev, "stream in reset state...\n");
+
+ retval = 0;
+ goto put;
+ }
+
+ retval = free_stream_context(ctx, str_id);
+put:
+ 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 / substream->runtime->channels;
+ 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);
+
+ 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;
+
+ memcpy_fromio(&fw_tstamp,
+ ((void *)(ctx->mailbox + ctx->tstamp)
+ + (str_id * sizeof(fw_tstamp))),
+ 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)
+ 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..5a2786184
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_ipc.c
@@ -0,0 +1,373 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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"
+#include "../../common/sst-dsp.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;
+ }
+
+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;
+ union ipc_header_high msg_high;
+ u32 msg_low, pipe_id;
+
+ msg_high = msg->mrfld_header.p.header_high;
+ 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->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;
+ unsigned int cmd_id;
+
+ 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 = kzalloc(msg_low, GFP_KERNEL);
+ if (!data)
+ return;
+ memcpy(data, (void *) msg->mailbox_data, msg_low);
+ /* Copy command id so that we can use to put sst to reset */
+ dsp_hdr = (struct ipc_dsp_hdr *)data;
+ cmd_id = dsp_hdr->cmd_id;
+ 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..7997900ad
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_loader.c
@@ -0,0 +1,463 @@
+/*
+ * 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 program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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"
+#include "../../common/sst-dsp.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)
+{
+ /* __iowrite32_copy uses 32-bit count values so divide by 4 for
+ * right count in words
+ */
+ __iowrite32_copy(dst, src, count/4);
+}
+
+/**
+ * intel_sst_reset_dsp_mrfld - Resetting SST DSP
+ *
+ * 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
+ *
+ * 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 == true)
+ 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 */
+ if (!list_empty(&sst_drv_ctx->memcpy_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 = reject_firmware(&fw, sst->firmware_name, sst->dev);
+ if (fw == NULL) {
+ dev_err(sst->dev, "fw is returning as null\n");
+ return -EINVAL;
+ }
+ if (retval) {
+ dev_err(sst->dev, "request fw failed %d\n", retval);
+ return retval;
+ }
+ 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
+ * 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 ||
+ sst_drv_ctx->sst_state == SST_SHUTDOWN)
+ 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;
+ }
+
+ BUG_ON(!sst_drv_ctx->fw_in_mem);
+ block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID);
+ if (block == NULL)
+ return -ENOMEM;
+
+ /* Prevent C-states beyond C6 */
+ pm_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 */
+ pm_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..3c95c4cbf
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_pci.c
@@ -0,0 +1,208 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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 0;
+}
+
+/*
+ * 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),
+ "/*(DEBLOBBED)*/");
+
+ 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 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..adb32fefd
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_pvt.c
@@ -0,0 +1,425 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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"
+#include "../../common/sst-dsp.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(struct ipc_post), 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 = 0;
+
+ 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 retval;
+}
+
+/*
+ * 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 ipc_post *msg = NULL;
+ struct ipc_dsp_hdr dsp_hdr;
+ struct sst_block *block;
+ 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;
+ } else if(block->data) {
+ if (!data)
+ goto out;
+ *data = kzalloc(block->size, GFP_KERNEL);
+ if (!(*data)) {
+ ret = -ENOMEM;
+ goto out;
+ } else
+ memcpy(data, (void *) block->data, block->size);
+ }
+ }
+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;
+}
+
+void sst_init_stream(struct stream_info *stream,
+ int codec, int sst_id, int ops, u8 slot)
+{
+ stream->status = STREAM_INIT;
+ stream->prev = STREAM_UN_INIT;
+ stream->ops = ops;
+}
+
+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..a74c64c70
--- /dev/null
+++ b/sound/soc/intel/atom/sst/sst_stream.c
@@ -0,0 +1,437 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#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"
+#include "../../common/sst-dsp.h"
+
+int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params)
+{
+ struct snd_sst_alloc_mrfld alloc_param;
+ struct snd_sst_params *str_params;
+ struct snd_sst_tstamp fw_tstamp;
+ struct stream_info *str_info;
+ struct snd_sst_alloc_response *response;
+ unsigned int str_id, pipe_id, task_id;
+ int i, num_ch, ret = 0;
+ void *data = NULL;
+
+ dev_dbg(sst_drv_ctx->dev, "Enter\n");
+ BUG_ON(!params);
+
+ str_params = (struct snd_sst_params *)params;
+ memset(&alloc_param, 0, sizeof(alloc_param));
+ alloc_param.operation = str_params->ops;
+ alloc_param.codec_type = str_params->codec;
+ alloc_param.sg_count = str_params->aparams.sg_count;
+ alloc_param.ring_buf_info[0].addr =
+ str_params->aparams.ring_buf_info[0].addr;
+ alloc_param.ring_buf_info[0].size =
+ str_params->aparams.ring_buf_info[0].size;
+ alloc_param.frag_size = str_params->aparams.frag_size;
+
+ memcpy(&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);
+ for (i = 0; i < 8; i++) {
+ if (i < num_ch)
+ alloc_param.codec_params.uc.pcm_params.channel_map[i] = i;
+ else
+ alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF;
+ }
+
+ str_id = str_params->stream_id;
+ str_info = get_stream_info(sst_drv_ctx, str_id);
+ if (str_info == NULL) {
+ dev_err(sst_drv_ctx->dev, "get stream info returned null\n");
+ return -EINVAL;
+ }
+
+ pipe_id = str_params->device_type;
+ task_id = str_params->task;
+ sst_drv_ctx->streams[str_id].pipe_id = pipe_id;
+ sst_drv_ctx->streams[str_id].task_id = task_id;
+ sst_drv_ctx->streams[str_id].num_ch = num_ch;
+
+ if (sst_drv_ctx->info.lpe_viewpt_rqd)
+ alloc_param.ts = sst_drv_ctx->info.mailbox_start +
+ sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp));
+ else
+ 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",
+ alloc_param.ts);
+ dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n",
+ pipe_id, task_id);
+
+ /* allocate device type context */
+ sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type,
+ str_id, alloc_param.operation, 0);
+
+ dev_info(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n",
+ str_id, pipe_id);
+ ret = sst_prepare_and_post_msg(sst_drv_ctx, task_id, IPC_CMD,
+ IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param),
+ &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
+* @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 0;
+}
+
+/*
+ * sst_pause_stream - Send msg for a pausing stream
+ * @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
+ * @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->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
+ * @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
+* @str_id: stream ID
+*
+* 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
+ * @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;
+ struct intel_sst_ops *ops;
+
+ 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;
+ ops = sst_drv_ctx->ops;
+
+ 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_info(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/baytrail/Makefile b/sound/soc/intel/baytrail/Makefile
new file mode 100644
index 000000000..488408cad
--- /dev/null
+++ b/sound/soc/intel/baytrail/Makefile
@@ -0,0 +1,4 @@
+snd-soc-sst-baytrail-pcm-objs := \
+ sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o
+
+obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o
diff --git a/sound/soc/intel/baytrail/sst-baytrail-dsp.c b/sound/soc/intel/baytrail/sst-baytrail-dsp.c
new file mode 100644
index 000000000..01d023cc0
--- /dev/null
+++ b/sound/soc/intel/baytrail/sst-baytrail-dsp.c
@@ -0,0 +1,366 @@
+/*
+ * Intel Baytrail SST DSP driver
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "sst-baytrail-ipc.h"
+
+#define SST_BYT_FW_SIGNATURE_SIZE 4
+#define SST_BYT_FW_SIGN "$SST"
+
+#define SST_BYT_IRAM_OFFSET 0xC0000
+#define SST_BYT_DRAM_OFFSET 0x100000
+#define SST_BYT_SHIM_OFFSET 0x140000
+
+enum sst_ram_type {
+ SST_BYT_IRAM = 1,
+ SST_BYT_DRAM = 2,
+ SST_BYT_CACHE = 3,
+};
+
+struct dma_block_info {
+ enum sst_ram_type type; /* IRAM/DRAM */
+ u32 size; /* Bytes */
+ u32 ram_offset; /* Offset in I/DRAM */
+ u32 rsvd; /* Reserved field */
+};
+
+struct fw_header {
+ unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE];
+ u32 file_size; /* size of fw minus this header */
+ u32 modules; /* # of modules */
+ u32 file_format; /* version of header format */
+ u32 reserved[4];
+};
+
+struct sst_byt_fw_module_header {
+ unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE];
+ u32 mod_size; /* size of module */
+ u32 blocks; /* # of blocks */
+ u32 type; /* codec type, pp lib */
+ u32 entry_point;
+};
+
+static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
+ struct sst_byt_fw_module_header *module)
+{
+ struct dma_block_info *block;
+ struct sst_module *mod;
+ struct sst_module_template template;
+ int count;
+
+ memset(&template, 0, sizeof(template));
+ template.id = module->type;
+ template.entry = module->entry_point;
+
+ mod = sst_module_new(fw, &template, NULL);
+ if (mod == NULL)
+ return -ENOMEM;
+
+ block = (void *)module + sizeof(*module);
+
+ for (count = 0; count < module->blocks; count++) {
+
+ if (block->size <= 0) {
+ dev_err(dsp->dev, "block %d size invalid\n", count);
+ return -EINVAL;
+ }
+
+ switch (block->type) {
+ case SST_BYT_IRAM:
+ mod->offset = block->ram_offset +
+ dsp->addr.iram_offset;
+ mod->type = SST_MEM_IRAM;
+ break;
+ case SST_BYT_DRAM:
+ mod->offset = block->ram_offset +
+ dsp->addr.dram_offset;
+ mod->type = SST_MEM_DRAM;
+ break;
+ case SST_BYT_CACHE:
+ mod->offset = block->ram_offset +
+ (dsp->addr.fw_ext - dsp->addr.lpe);
+ mod->type = SST_MEM_CACHE;
+ break;
+ default:
+ dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n",
+ block->type, count);
+ return -EINVAL;
+ }
+
+ mod->size = block->size;
+ mod->data = (void *)block + sizeof(*block);
+
+ sst_module_alloc_blocks(mod);
+
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ return 0;
+}
+
+static int sst_byt_parse_fw_image(struct sst_fw *sst_fw)
+{
+ struct fw_header *header;
+ struct sst_byt_fw_module_header *module;
+ struct sst_dsp *dsp = sst_fw->dsp;
+ int ret, count;
+
+ /* Read the header information from the data pointer */
+ header = (struct fw_header *)sst_fw->dma_buf;
+
+ /* verify FW */
+ if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) ||
+ (sst_fw->size != header->file_size + sizeof(*header))) {
+ /* Invalid FW signature */
+ dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dsp->dev,
+ "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n",
+ header->signature, header->file_size, header->modules,
+ header->file_format, sizeof(*header));
+
+ module = (void *)sst_fw->dma_buf + sizeof(*header);
+ for (count = 0; count < header->modules; count++) {
+ /* module */
+ ret = sst_byt_parse_module(dsp, sst_fw, module);
+ if (ret < 0) {
+ dev_err(dsp->dev, "invalid module %d\n", count);
+ return ret;
+ }
+ module = (void *)module + sizeof(*module) + module->mod_size;
+ }
+
+ return 0;
+}
+
+static void sst_byt_dump_shim(struct sst_dsp *sst)
+{
+ int i;
+ u64 reg;
+
+ for (i = 0; i <= 0xF0; i += 8) {
+ reg = sst_dsp_shim_read64_unlocked(sst, i);
+ if (reg)
+ dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n",
+ i, reg);
+ }
+
+ for (i = 0x00; i <= 0xff; i += 4) {
+ reg = readl(sst->addr.pci_cfg + i);
+ if (reg)
+ dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n",
+ i, (u32)reg);
+ }
+}
+
+static irqreturn_t sst_byt_irq(int irq, void *context)
+{
+ struct sst_dsp *sst = (struct sst_dsp *) context;
+ u64 isrx;
+ irqreturn_t ret = IRQ_NONE;
+
+ spin_lock(&sst->spinlock);
+
+ isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX);
+ if (isrx & SST_ISRX_DONE) {
+ /* ADSP has processed the message request from IA */
+ sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX,
+ SST_BYT_IPCX_DONE, 0);
+ ret = IRQ_WAKE_THREAD;
+ }
+ if (isrx & SST_BYT_ISRX_REQUEST) {
+ /* mask message request from ADSP and do processing later */
+ sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX,
+ SST_BYT_IMRX_REQUEST,
+ SST_BYT_IMRX_REQUEST);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ spin_unlock(&sst->spinlock);
+
+ return ret;
+}
+
+static void sst_byt_boot(struct sst_dsp *sst)
+{
+ int tries = 10;
+
+ /*
+ * save the physical address of extended firmware block in the first
+ * 4 bytes of the mailbox
+ */
+ memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET,
+ &sst->pdata->fw_base, sizeof(u32));
+
+ /* release stall and wait to unstall */
+ sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0);
+ while (tries--) {
+ if (!(sst_dsp_shim_read64(sst, SST_CSR) &
+ SST_BYT_CSR_PWAITMODE))
+ break;
+ msleep(100);
+ }
+ if (tries < 0) {
+ dev_err(sst->dev, "unable to start DSP\n");
+ sst_byt_dump_shim(sst);
+ }
+}
+
+static void sst_byt_reset(struct sst_dsp *sst)
+{
+ /* put DSP into reset, set reset vector and stall */
+ sst_dsp_shim_update_bits64(sst, SST_CSR,
+ SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL,
+ SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL);
+
+ udelay(10);
+
+ /* take DSP out of reset and keep stalled for FW loading */
+ sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0);
+}
+
+struct sst_adsp_memregion {
+ u32 start;
+ u32 end;
+ int blocks;
+ enum sst_mem_type type;
+};
+
+/* BYT test stuff */
+static const struct sst_adsp_memregion byt_region[] = {
+ {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */
+ {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */
+};
+
+static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ sst->addr.lpe_base = pdata->lpe_base;
+ sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size);
+ if (!sst->addr.lpe)
+ return -ENODEV;
+
+ /* ADSP PCI MMIO config space */
+ sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size);
+ if (!sst->addr.pci_cfg) {
+ iounmap(sst->addr.lpe);
+ return -ENODEV;
+ }
+
+ /* SST Extended FW allocation */
+ sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size);
+ if (!sst->addr.fw_ext) {
+ iounmap(sst->addr.pci_cfg);
+ iounmap(sst->addr.lpe);
+ return -ENODEV;
+ }
+
+ /* SST Shim */
+ sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset;
+
+ sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204,
+ SST_BYT_IPC_MAX_PAYLOAD_SIZE,
+ SST_BYT_MAILBOX_OFFSET,
+ SST_BYT_IPC_MAX_PAYLOAD_SIZE);
+
+ sst->irq = pdata->irq;
+
+ return 0;
+}
+
+static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ const struct sst_adsp_memregion *region;
+ struct device *dev;
+ int ret = -ENODEV, i, j, region_count;
+ u32 offset, size;
+
+ dev = sst->dev;
+
+ switch (sst->id) {
+ case SST_DEV_ID_BYT:
+ region = byt_region;
+ region_count = ARRAY_SIZE(byt_region);
+ sst->addr.iram_offset = SST_BYT_IRAM_OFFSET;
+ sst->addr.dram_offset = SST_BYT_DRAM_OFFSET;
+ sst->addr.shim_offset = SST_BYT_SHIM_OFFSET;
+ break;
+ default:
+ dev_err(dev, "failed to get mem resources\n");
+ return ret;
+ }
+
+ ret = sst_byt_resource_map(sst, pdata);
+ if (ret < 0) {
+ dev_err(dev, "failed to map resources\n");
+ return ret;
+ }
+
+ ret = dma_coerce_mask_and_coherent(sst->dma_dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ /* enable Interrupt from both sides */
+ sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0);
+ sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0);
+
+ /* register DSP memory blocks - ideally we should get this from ACPI */
+ for (i = 0; i < region_count; i++) {
+ offset = region[i].start;
+ size = (region[i].end - region[i].start) / region[i].blocks;
+
+ /* register individual memory blocks */
+ for (j = 0; j < region[i].blocks; j++) {
+ sst_mem_block_register(sst, offset, size,
+ region[i].type, NULL, j, sst);
+ offset += size;
+ }
+ }
+
+ return 0;
+}
+
+static void sst_byt_free(struct sst_dsp *sst)
+{
+ sst_mem_block_unregister_all(sst);
+ iounmap(sst->addr.lpe);
+ iounmap(sst->addr.pci_cfg);
+ iounmap(sst->addr.fw_ext);
+}
+
+struct sst_ops sst_byt_ops = {
+ .reset = sst_byt_reset,
+ .boot = sst_byt_boot,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .write64 = sst_shim32_write64,
+ .read64 = sst_shim32_read64,
+ .ram_read = sst_memcpy_fromio_32,
+ .ram_write = sst_memcpy_toio_32,
+ .irq_handler = sst_byt_irq,
+ .init = sst_byt_init,
+ .free = sst_byt_free,
+ .parse_fw = sst_byt_parse_fw_image,
+};
diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.c b/sound/soc/intel/baytrail/sst-baytrail-ipc.c
new file mode 100644
index 000000000..a839dbfa5
--- /dev/null
+++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.c
@@ -0,0 +1,776 @@
+/*
+ * Intel Baytrail SST IPC Support
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <asm/div64.h>
+
+#include "sst-baytrail-ipc.h"
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-ipc.h"
+
+/* IPC message timeout */
+#define IPC_TIMEOUT_MSECS 300
+#define IPC_BOOT_MSECS 200
+
+#define IPC_EMPTY_LIST_SIZE 8
+
+/* IPC header bits */
+#define IPC_HEADER_MSG_ID_MASK 0xff
+#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK)
+#define IPC_HEADER_STR_ID_SHIFT 8
+#define IPC_HEADER_STR_ID_MASK 0x1f
+#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT)
+#define IPC_HEADER_LARGE_SHIFT 13
+#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT)
+#define IPC_HEADER_DATA_SHIFT 16
+#define IPC_HEADER_DATA_MASK 0x3fff
+#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT)
+
+/* mask for differentiating between notification and reply message */
+#define IPC_NOTIFICATION (0x1 << 7)
+
+/* I2L Stream config/control msgs */
+#define IPC_IA_ALLOC_STREAM 0x20
+#define IPC_IA_FREE_STREAM 0x21
+#define IPC_IA_PAUSE_STREAM 0x24
+#define IPC_IA_RESUME_STREAM 0x25
+#define IPC_IA_DROP_STREAM 0x26
+#define IPC_IA_START_STREAM 0x30
+
+/* notification messages */
+#define IPC_IA_FW_INIT_CMPLT 0x81
+#define IPC_SST_PERIOD_ELAPSED 0x97
+
+/* IPC messages between host and ADSP */
+struct sst_byt_address_info {
+ u32 addr;
+ u32 size;
+} __packed;
+
+struct sst_byt_str_type {
+ u8 codec_type;
+ u8 str_type;
+ u8 operation;
+ u8 protected_str;
+ u8 time_slots;
+ u8 reserved;
+ u16 result;
+} __packed;
+
+struct sst_byt_pcm_params {
+ u8 num_chan;
+ u8 pcm_wd_sz;
+ u8 use_offload_path;
+ u8 reserved;
+ u32 sfreq;
+ u8 channel_map[8];
+} __packed;
+
+struct sst_byt_frames_info {
+ u16 num_entries;
+ u16 rsrvd;
+ u32 frag_size;
+ struct sst_byt_address_info ring_buf_info[8];
+} __packed;
+
+struct sst_byt_alloc_params {
+ struct sst_byt_str_type str_type;
+ struct sst_byt_pcm_params pcm_params;
+ struct sst_byt_frames_info frame_info;
+} __packed;
+
+struct sst_byt_alloc_response {
+ struct sst_byt_str_type str_type;
+ u8 reserved[88];
+} __packed;
+
+struct sst_byt_start_stream_params {
+ u32 byte_offset;
+} __packed;
+
+struct sst_byt_tstamp {
+ u64 ring_buffer_counter;
+ u64 hardware_counter;
+ u64 frames_decoded;
+ u64 bytes_decoded;
+ u64 bytes_copied;
+ u32 sampling_frequency;
+ u32 channel_peak[8];
+} __packed;
+
+struct sst_byt_fw_version {
+ u8 build;
+ u8 minor;
+ u8 major;
+ u8 type;
+} __packed;
+
+struct sst_byt_fw_build_info {
+ u8 date[16];
+ u8 time[16];
+} __packed;
+
+struct sst_byt_fw_init {
+ struct sst_byt_fw_version fw_version;
+ struct sst_byt_fw_build_info build_info;
+ u16 result;
+ u8 module_id;
+ u8 debug_info;
+} __packed;
+
+struct sst_byt_stream;
+struct sst_byt;
+
+/* stream infomation */
+struct sst_byt_stream {
+ struct list_head node;
+
+ /* configuration */
+ struct sst_byt_alloc_params request;
+ struct sst_byt_alloc_response reply;
+
+ /* runtime info */
+ struct sst_byt *byt;
+ int str_id;
+ bool commited;
+ bool running;
+
+ /* driver callback */
+ u32 (*notify_position)(struct sst_byt_stream *stream, void *data);
+ void *pdata;
+};
+
+/* SST Baytrail IPC data */
+struct sst_byt {
+ struct device *dev;
+ struct sst_dsp *dsp;
+
+ /* stream */
+ struct list_head stream_list;
+
+ /* boot */
+ wait_queue_head_t boot_wait;
+ bool boot_complete;
+ struct sst_fw *fw;
+
+ /* IPC messaging */
+ struct sst_generic_ipc ipc;
+};
+
+static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id)
+{
+ u64 header;
+
+ header = IPC_HEADER_MSG_ID(msg_id) |
+ IPC_HEADER_STR_ID(str_id) |
+ IPC_HEADER_LARGE(large) |
+ IPC_HEADER_DATA(data) |
+ SST_BYT_IPCX_BUSY;
+
+ return header;
+}
+
+static inline u16 sst_byt_header_msg_id(u64 header)
+{
+ return header & IPC_HEADER_MSG_ID_MASK;
+}
+
+static inline u8 sst_byt_header_str_id(u64 header)
+{
+ return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK;
+}
+
+static inline u16 sst_byt_header_data(u64 header)
+{
+ return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK;
+}
+
+static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt,
+ int stream_id)
+{
+ struct sst_byt_stream *stream;
+
+ list_for_each_entry(stream, &byt->stream_list, node) {
+ if (stream->str_id == stream_id)
+ return stream;
+ }
+
+ return NULL;
+}
+
+static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg)
+{
+ struct sst_byt_stream *stream;
+ u64 header = msg->header;
+ u8 stream_id = sst_byt_header_str_id(header);
+ u8 stream_msg = sst_byt_header_msg_id(header);
+
+ stream = sst_byt_get_stream(byt, stream_id);
+ if (stream == NULL)
+ return;
+
+ switch (stream_msg) {
+ case IPC_IA_DROP_STREAM:
+ case IPC_IA_PAUSE_STREAM:
+ case IPC_IA_FREE_STREAM:
+ stream->running = false;
+ break;
+ case IPC_IA_START_STREAM:
+ case IPC_IA_RESUME_STREAM:
+ stream->running = true;
+ break;
+ }
+}
+
+static int sst_byt_process_reply(struct sst_byt *byt, u64 header)
+{
+ struct ipc_message *msg;
+
+ msg = sst_ipc_reply_find_msg(&byt->ipc, header);
+ if (msg == NULL)
+ return 1;
+
+ if (header & IPC_HEADER_LARGE(true)) {
+ msg->rx_size = sst_byt_header_data(header);
+ sst_dsp_inbox_read(byt->dsp, msg->rx_data, msg->rx_size);
+ }
+
+ /* update any stream states */
+ sst_byt_stream_update(byt, msg);
+
+ list_del(&msg->list);
+ /* wake up */
+ sst_ipc_tx_msg_reply_complete(&byt->ipc, msg);
+
+ return 1;
+}
+
+static void sst_byt_fw_ready(struct sst_byt *byt, u64 header)
+{
+ dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header);
+
+ byt->boot_complete = true;
+ wake_up(&byt->boot_wait);
+}
+
+static int sst_byt_process_notification(struct sst_byt *byt,
+ unsigned long *flags)
+{
+ struct sst_dsp *sst = byt->dsp;
+ struct sst_byt_stream *stream;
+ u64 header;
+ u8 msg_id, stream_id;
+ int handled = 1;
+
+ header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD);
+ msg_id = sst_byt_header_msg_id(header);
+
+ switch (msg_id) {
+ case IPC_SST_PERIOD_ELAPSED:
+ stream_id = sst_byt_header_str_id(header);
+ stream = sst_byt_get_stream(byt, stream_id);
+ if (stream && stream->running && stream->notify_position) {
+ spin_unlock_irqrestore(&sst->spinlock, *flags);
+ stream->notify_position(stream, stream->pdata);
+ spin_lock_irqsave(&sst->spinlock, *flags);
+ }
+ break;
+ case IPC_IA_FW_INIT_CMPLT:
+ sst_byt_fw_ready(byt, header);
+ break;
+ }
+
+ return handled;
+}
+
+static irqreturn_t sst_byt_irq_thread(int irq, void *context)
+{
+ struct sst_dsp *sst = (struct sst_dsp *) context;
+ struct sst_byt *byt = sst_dsp_get_thread_context(sst);
+ struct sst_generic_ipc *ipc = &byt->ipc;
+ u64 header;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+
+ header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD);
+ if (header & SST_BYT_IPCD_BUSY) {
+ if (header & IPC_NOTIFICATION) {
+ /* message from ADSP */
+ sst_byt_process_notification(byt, &flags);
+ } else {
+ /* reply from ADSP */
+ sst_byt_process_reply(byt, header);
+ }
+ /*
+ * clear IPCD BUSY bit and set DONE bit. Tell DSP we have
+ * processed the message and can accept new. Clear data part
+ * of the header
+ */
+ sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD,
+ SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY |
+ IPC_HEADER_DATA(IPC_HEADER_DATA_MASK),
+ SST_BYT_IPCD_DONE);
+ /* unmask message request interrupts */
+ sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX,
+ SST_BYT_IMRX_REQUEST, 0);
+ }
+
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ /* continue to send any remaining messages... */
+ queue_kthread_work(&ipc->kworker, &ipc->kwork);
+
+ return IRQ_HANDLED;
+}
+
+/* stream API */
+struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id,
+ u32 (*notify_position)(struct sst_byt_stream *stream, void *data),
+ void *data)
+{
+ struct sst_byt_stream *stream;
+ struct sst_dsp *sst = byt->dsp;
+ unsigned long flags;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (stream == NULL)
+ return NULL;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ list_add(&stream->node, &byt->stream_list);
+ stream->notify_position = notify_position;
+ stream->pdata = data;
+ stream->byt = byt;
+ stream->str_id = id;
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return stream;
+}
+
+int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream,
+ int bits)
+{
+ stream->request.pcm_params.pcm_wd_sz = bits;
+ return 0;
+}
+
+int sst_byt_stream_set_channels(struct sst_byt *byt,
+ struct sst_byt_stream *stream, u8 channels)
+{
+ stream->request.pcm_params.num_chan = channels;
+ return 0;
+}
+
+int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream,
+ unsigned int rate)
+{
+ stream->request.pcm_params.sfreq = rate;
+ return 0;
+}
+
+/* stream sonfiguration */
+int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream,
+ int codec_type, int stream_type, int operation)
+{
+ stream->request.str_type.codec_type = codec_type;
+ stream->request.str_type.str_type = stream_type;
+ stream->request.str_type.operation = operation;
+ stream->request.str_type.time_slots = 0xc;
+
+ return 0;
+}
+
+int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream,
+ uint32_t buffer_addr, uint32_t buffer_size)
+{
+ stream->request.frame_info.num_entries = 1;
+ stream->request.frame_info.ring_buf_info[0].addr = buffer_addr;
+ stream->request.frame_info.ring_buf_info[0].size = buffer_size;
+ /* calculate bytes per 4 ms fragment */
+ stream->request.frame_info.frag_size =
+ stream->request.pcm_params.sfreq *
+ stream->request.pcm_params.num_chan *
+ stream->request.pcm_params.pcm_wd_sz / 8 *
+ 4 / 1000;
+ return 0;
+}
+
+int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream)
+{
+ struct sst_byt_alloc_params *str_req = &stream->request;
+ struct sst_byt_alloc_response *reply = &stream->reply;
+ u64 header;
+ int ret;
+
+ header = sst_byt_header(IPC_IA_ALLOC_STREAM,
+ sizeof(*str_req) + sizeof(u32),
+ true, stream->str_id);
+ ret = sst_ipc_tx_message_wait(&byt->ipc, header, str_req,
+ sizeof(*str_req),
+ reply, sizeof(*reply));
+ if (ret < 0) {
+ dev_err(byt->dev, "ipc: error stream commit failed\n");
+ return ret;
+ }
+
+ stream->commited = true;
+
+ return 0;
+}
+
+int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream)
+{
+ u64 header;
+ int ret = 0;
+ struct sst_dsp *sst = byt->dsp;
+ unsigned long flags;
+
+ if (!stream->commited)
+ goto out;
+
+ header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id);
+ ret = sst_ipc_tx_message_wait(&byt->ipc, header, NULL, 0, NULL, 0);
+ if (ret < 0) {
+ dev_err(byt->dev, "ipc: free stream %d failed\n",
+ stream->str_id);
+ return -EAGAIN;
+ }
+
+ stream->commited = false;
+out:
+ spin_lock_irqsave(&sst->spinlock, flags);
+ list_del(&stream->node);
+ kfree(stream);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return ret;
+}
+
+static int sst_byt_stream_operations(struct sst_byt *byt, int type,
+ int stream_id, int wait)
+{
+ u64 header;
+
+ header = sst_byt_header(type, 0, false, stream_id);
+ if (wait)
+ return sst_ipc_tx_message_wait(&byt->ipc, header, NULL,
+ 0, NULL, 0);
+ else
+ return sst_ipc_tx_message_nowait(&byt->ipc, header,
+ NULL, 0);
+}
+
+/* stream ALSA trigger operations */
+int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream,
+ u32 start_offset)
+{
+ struct sst_byt_start_stream_params start_stream;
+ void *tx_msg;
+ size_t size;
+ u64 header;
+ int ret;
+
+ start_stream.byte_offset = start_offset;
+ header = sst_byt_header(IPC_IA_START_STREAM,
+ sizeof(start_stream) + sizeof(u32),
+ true, stream->str_id);
+ tx_msg = &start_stream;
+ size = sizeof(start_stream);
+
+ ret = sst_ipc_tx_message_nowait(&byt->ipc, header, tx_msg, size);
+ if (ret < 0)
+ dev_err(byt->dev, "ipc: error failed to start stream %d\n",
+ stream->str_id);
+
+ return ret;
+}
+
+int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream)
+{
+ int ret;
+
+ /* don't stop streams that are not commited */
+ if (!stream->commited)
+ return 0;
+
+ ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM,
+ stream->str_id, 0);
+ if (ret < 0)
+ dev_err(byt->dev, "ipc: error failed to stop stream %d\n",
+ stream->str_id);
+ return ret;
+}
+
+int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream)
+{
+ int ret;
+
+ ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM,
+ stream->str_id, 0);
+ if (ret < 0)
+ dev_err(byt->dev, "ipc: error failed to pause stream %d\n",
+ stream->str_id);
+
+ return ret;
+}
+
+int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream)
+{
+ int ret;
+
+ ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM,
+ stream->str_id, 0);
+ if (ret < 0)
+ dev_err(byt->dev, "ipc: error failed to resume stream %d\n",
+ stream->str_id);
+
+ return ret;
+}
+
+int sst_byt_get_dsp_position(struct sst_byt *byt,
+ struct sst_byt_stream *stream, int buffer_size)
+{
+ struct sst_dsp *sst = byt->dsp;
+ struct sst_byt_tstamp fw_tstamp;
+ u8 str_id = stream->str_id;
+ u32 tstamp_offset;
+
+ tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp);
+ memcpy_fromio(&fw_tstamp,
+ sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp));
+
+ return do_div(fw_tstamp.ring_buffer_counter, buffer_size);
+}
+
+struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt)
+{
+ return byt->dsp;
+}
+
+static struct sst_dsp_device byt_dev = {
+ .thread = sst_byt_irq_thread,
+ .ops = &sst_byt_ops,
+};
+
+int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_byt *byt = pdata->dsp;
+
+ dev_dbg(byt->dev, "dsp reset\n");
+ sst_dsp_reset(byt->dsp);
+ sst_ipc_drop_all(&byt->ipc);
+ dev_dbg(byt->dev, "dsp in reset\n");
+
+ dev_dbg(byt->dev, "free all blocks and unload fw\n");
+ sst_fw_unload(byt->fw);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_late);
+
+int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_byt *byt = pdata->dsp;
+ int ret;
+
+ dev_dbg(byt->dev, "reload dsp fw\n");
+
+ sst_dsp_reset(byt->dsp);
+
+ ret = sst_fw_reload(byt->fw);
+ if (ret < 0) {
+ dev_err(dev, "error: failed to reload firmware\n");
+ return ret;
+ }
+
+ /* wait for DSP boot completion */
+ byt->boot_complete = false;
+ sst_dsp_boot(byt->dsp);
+ dev_dbg(byt->dev, "dsp booting...\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_byt_dsp_boot);
+
+int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_byt *byt = pdata->dsp;
+ int err;
+
+ dev_dbg(byt->dev, "wait for dsp reboot\n");
+
+ err = wait_event_timeout(byt->boot_wait, byt->boot_complete,
+ msecs_to_jiffies(IPC_BOOT_MSECS));
+ if (err == 0) {
+ dev_err(byt->dev, "ipc: error DSP boot timeout\n");
+ return -EIO;
+ }
+
+ dev_dbg(byt->dev, "dsp rebooted\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready);
+
+static void byt_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg)
+{
+ if (msg->header & IPC_HEADER_LARGE(true))
+ sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size);
+
+ sst_dsp_shim_write64_unlocked(ipc->dsp, SST_IPCX, msg->header);
+}
+
+static void byt_shim_dbg(struct sst_generic_ipc *ipc, const char *text)
+{
+ struct sst_dsp *sst = ipc->dsp;
+ u64 isr, ipcd, imrx, ipcx;
+
+ ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX);
+ isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX);
+ ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD);
+ imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX);
+
+ dev_err(ipc->dev,
+ "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n",
+ text, ipcx, isr, ipcd, imrx);
+}
+
+static void byt_tx_data_copy(struct ipc_message *msg, char *tx_data,
+ size_t tx_size)
+{
+ /* msg content = lower 32-bit of the header + data */
+ *(u32 *)msg->tx_data = (u32)(msg->header & (u32)-1);
+ memcpy(msg->tx_data + sizeof(u32), tx_data, tx_size);
+ msg->tx_size += sizeof(u32);
+}
+
+static u64 byt_reply_msg_match(u64 header, u64 *mask)
+{
+ /* match reply to message sent based on msg and stream IDs */
+ *mask = IPC_HEADER_MSG_ID_MASK |
+ IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT;
+ header &= *mask;
+
+ return header;
+}
+
+int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_byt *byt;
+ struct sst_generic_ipc *ipc;
+ struct sst_fw *byt_sst_fw;
+ struct sst_byt_fw_init init;
+ int err;
+
+ dev_dbg(dev, "initialising Byt DSP IPC\n");
+
+ byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL);
+ if (byt == NULL)
+ return -ENOMEM;
+
+ ipc = &byt->ipc;
+ ipc->dev = dev;
+ ipc->ops.tx_msg = byt_tx_msg;
+ ipc->ops.shim_dbg = byt_shim_dbg;
+ ipc->ops.tx_data_copy = byt_tx_data_copy;
+ ipc->ops.reply_msg_match = byt_reply_msg_match;
+
+ err = sst_ipc_init(ipc);
+ if (err != 0)
+ goto ipc_init_err;
+
+ INIT_LIST_HEAD(&byt->stream_list);
+ init_waitqueue_head(&byt->boot_wait);
+ byt_dev.thread_context = byt;
+
+ /* init SST shim */
+ byt->dsp = sst_dsp_new(dev, &byt_dev, pdata);
+ if (byt->dsp == NULL) {
+ err = -ENODEV;
+ goto dsp_new_err;
+ }
+
+ ipc->dsp = byt->dsp;
+
+ /* keep the DSP in reset state for base FW loading */
+ sst_dsp_reset(byt->dsp);
+
+ byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt);
+ if (byt_sst_fw == NULL) {
+ err = -ENODEV;
+ dev_err(dev, "error: failed to load firmware\n");
+ goto fw_err;
+ }
+
+ /* wait for DSP boot completion */
+ sst_dsp_boot(byt->dsp);
+ err = wait_event_timeout(byt->boot_wait, byt->boot_complete,
+ msecs_to_jiffies(IPC_BOOT_MSECS));
+ if (err == 0) {
+ err = -EIO;
+ dev_err(byt->dev, "ipc: error DSP boot timeout\n");
+ goto boot_err;
+ }
+
+ /* show firmware information */
+ sst_dsp_inbox_read(byt->dsp, &init, sizeof(init));
+ dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n",
+ init.fw_version.major, init.fw_version.minor,
+ init.fw_version.build, init.fw_version.type);
+ dev_info(byt->dev, "Build type: %x\n", init.fw_version.type);
+ dev_info(byt->dev, "Build date: %s %s\n",
+ init.build_info.date, init.build_info.time);
+
+ pdata->dsp = byt;
+ byt->fw = byt_sst_fw;
+
+ return 0;
+
+boot_err:
+ sst_dsp_reset(byt->dsp);
+ sst_fw_free(byt_sst_fw);
+fw_err:
+ sst_dsp_free(byt->dsp);
+dsp_new_err:
+ sst_ipc_fini(ipc);
+ipc_init_err:
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(sst_byt_dsp_init);
+
+void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_byt *byt = pdata->dsp;
+
+ sst_dsp_reset(byt->dsp);
+ sst_fw_free_all(byt->dsp);
+ sst_dsp_free(byt->dsp);
+ sst_ipc_fini(&byt->ipc);
+}
+EXPORT_SYMBOL_GPL(sst_byt_dsp_free);
diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.h b/sound/soc/intel/baytrail/sst-baytrail-ipc.h
new file mode 100644
index 000000000..8faff6dcf
--- /dev/null
+++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.h
@@ -0,0 +1,73 @@
+/*
+ * Intel Baytrail SST IPC Support
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef __SST_BYT_IPC_H
+#define __SST_BYT_IPC_H
+
+#include <linux/types.h>
+
+struct sst_byt;
+struct sst_byt_stream;
+struct sst_pdata;
+extern struct sst_ops sst_byt_ops;
+
+
+#define SST_BYT_MAILBOX_OFFSET 0x144000
+#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800)
+
+/**
+ * Upfront defined maximum message size that is
+ * expected by the in/out communication pipes in FW.
+ */
+#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200
+
+/* stream API */
+struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id,
+ uint32_t (*get_write_position)(struct sst_byt_stream *stream,
+ void *data),
+ void *data);
+
+/* stream configuration */
+int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream,
+ int bits);
+int sst_byt_stream_set_channels(struct sst_byt *byt,
+ struct sst_byt_stream *stream, u8 channels);
+int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream,
+ unsigned int rate);
+int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream,
+ int codec_type, int stream_type, int operation);
+int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream,
+ uint32_t buffer_addr, uint32_t buffer_size);
+int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream);
+int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream);
+
+/* stream ALSA trigger operations */
+int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream,
+ u32 start_offset);
+int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream);
+int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream);
+int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream);
+
+int sst_byt_get_dsp_position(struct sst_byt *byt,
+ struct sst_byt_stream *stream, int buffer_size);
+
+/* init */
+int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata);
+void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata);
+struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt);
+int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata);
+int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata);
+int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata);
+
+#endif
diff --git a/sound/soc/intel/baytrail/sst-baytrail-pcm.c b/sound/soc/intel/baytrail/sst-baytrail-pcm.c
new file mode 100644
index 000000000..79547bec5
--- /dev/null
+++ b/sound/soc/intel/baytrail/sst-baytrail-pcm.c
@@ -0,0 +1,505 @@
+/*
+ * Intel Baytrail SST PCM Support
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "sst-baytrail-ipc.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-dsp.h"
+
+#define BYT_PCM_COUNT 2
+
+static const struct snd_pcm_hardware sst_byt_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .period_bytes_min = 384,
+ .period_bytes_max = 48000,
+ .periods_min = 2,
+ .periods_max = 250,
+ .buffer_bytes_max = 96000,
+};
+
+/* private data for each PCM DSP stream */
+struct sst_byt_pcm_data {
+ struct sst_byt_stream *stream;
+ struct snd_pcm_substream *substream;
+ struct mutex mutex;
+
+ /* latest DSP DMA hw pointer */
+ u32 hw_ptr;
+
+ struct work_struct work;
+};
+
+/* private data for the driver */
+struct sst_byt_priv_data {
+ /* runtime DSP */
+ struct sst_byt *byt;
+
+ /* DAI data */
+ struct sst_byt_pcm_data pcm[BYT_PCM_COUNT];
+
+ /* flag indicating is stream context restore needed after suspend */
+ bool restore_stream;
+};
+
+/* this may get called several times by oss emulation */
+static int sst_byt_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+ struct sst_byt *byt = pdata->byt;
+ u32 rate, bits;
+ u8 channels;
+ int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data);
+
+ ret = sst_byt_stream_type(byt, pcm_data->stream,
+ 1, 1, !playback);
+ if (ret < 0) {
+ dev_err(rtd->dev, "failed to set stream format %d\n", ret);
+ return ret;
+ }
+
+ rate = params_rate(params);
+ ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate);
+ if (ret < 0) {
+ dev_err(rtd->dev, "could not set rate %d\n", rate);
+ return ret;
+ }
+
+ bits = snd_pcm_format_width(params_format(params));
+ ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits);
+ if (ret < 0) {
+ dev_err(rtd->dev, "could not set formats %d\n",
+ params_rate(params));
+ return ret;
+ }
+
+ channels = (u8)(params_channels(params) & 0xF);
+ ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels);
+ if (ret < 0) {
+ dev_err(rtd->dev, "could not set channels %d\n",
+ params_rate(params));
+ return ret;
+ }
+
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+
+ ret = sst_byt_stream_buffer(byt, pcm_data->stream,
+ substream->dma_buffer.addr,
+ params_buffer_bytes(params));
+ if (ret < 0) {
+ dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret);
+ return ret;
+ }
+
+ ret = sst_byt_stream_commit(byt, pcm_data->stream);
+ if (ret < 0) {
+ dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sst_byt_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ dev_dbg(rtd->dev, "PCM: hw_free\n");
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static int sst_byt_pcm_restore_stream_context(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+ struct sst_byt *byt = pdata->byt;
+ int ret;
+
+ /* commit stream using existing stream params */
+ ret = sst_byt_stream_commit(byt, pcm_data->stream);
+ if (ret < 0) {
+ dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret);
+ return ret;
+ }
+
+ sst_byt_stream_start(byt, pcm_data->stream, pcm_data->hw_ptr);
+
+ dev_dbg(rtd->dev, "stream context restored at offset %d\n",
+ pcm_data->hw_ptr);
+
+ return 0;
+}
+
+static void sst_byt_pcm_work(struct work_struct *work)
+{
+ struct sst_byt_pcm_data *pcm_data =
+ container_of(work, struct sst_byt_pcm_data, work);
+
+ if (snd_pcm_running(pcm_data->substream))
+ sst_byt_pcm_restore_stream_context(pcm_data->substream);
+}
+
+static int sst_byt_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+ struct sst_byt *byt = pdata->byt;
+
+ dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ pcm_data->hw_ptr = 0;
+ sst_byt_stream_start(byt, pcm_data->stream, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (pdata->restore_stream == true)
+ schedule_work(&pcm_data->work);
+ else
+ sst_byt_stream_resume(byt, pcm_data->stream);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ sst_byt_stream_resume(byt, pcm_data->stream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ sst_byt_stream_stop(byt, pcm_data->stream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pdata->restore_stream = false;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ sst_byt_stream_pause(byt, pcm_data->stream);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data)
+{
+ struct sst_byt_pcm_data *pcm_data = data;
+ struct snd_pcm_substream *substream = pcm_data->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt *byt = pdata->byt;
+ u32 pos, hw_pos;
+
+ hw_pos = sst_byt_get_dsp_position(byt, pcm_data->stream,
+ snd_pcm_lib_buffer_bytes(substream));
+ pcm_data->hw_ptr = hw_pos;
+ pos = frames_to_bytes(runtime,
+ (runtime->control->appl_ptr %
+ runtime->buffer_size));
+
+ dev_dbg(rtd->dev, "PCM: App/DMA pointer %u/%u bytes\n", pos, hw_pos);
+
+ snd_pcm_period_elapsed(substream);
+ return pos;
+}
+
+static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+
+ dev_dbg(rtd->dev, "PCM: DMA pointer %u bytes\n", pcm_data->hw_ptr);
+
+ return bytes_to_frames(runtime, pcm_data->hw_ptr);
+}
+
+static int sst_byt_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+ struct sst_byt *byt = pdata->byt;
+
+ dev_dbg(rtd->dev, "PCM: open\n");
+
+ mutex_lock(&pcm_data->mutex);
+
+ pcm_data->substream = substream;
+
+ snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware);
+
+ pcm_data->stream = sst_byt_stream_new(byt, substream->stream + 1,
+ byt_notify_pointer, pcm_data);
+ if (pcm_data->stream == NULL) {
+ dev_err(rtd->dev, "failed to create stream\n");
+ mutex_unlock(&pcm_data->mutex);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&pcm_data->mutex);
+ return 0;
+}
+
+static int sst_byt_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sst_byt_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream];
+ struct sst_byt *byt = pdata->byt;
+ int ret;
+
+ dev_dbg(rtd->dev, "PCM: close\n");
+
+ cancel_work_sync(&pcm_data->work);
+ mutex_lock(&pcm_data->mutex);
+ ret = sst_byt_stream_free(byt, pcm_data->stream);
+ if (ret < 0) {
+ dev_dbg(rtd->dev, "Free stream fail\n");
+ goto out;
+ }
+ pcm_data->stream = NULL;
+
+out:
+ mutex_unlock(&pcm_data->mutex);
+ return ret;
+}
+
+static int sst_byt_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ dev_dbg(rtd->dev, "PCM: mmap\n");
+ return snd_pcm_lib_default_mmap(substream, vma);
+}
+
+static struct snd_pcm_ops sst_byt_pcm_ops = {
+ .open = sst_byt_pcm_open,
+ .close = sst_byt_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = sst_byt_pcm_hw_params,
+ .hw_free = sst_byt_pcm_hw_free,
+ .trigger = sst_byt_pcm_trigger,
+ .pointer = sst_byt_pcm_pointer,
+ .mmap = sst_byt_pcm_mmap,
+};
+
+static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ size_t size;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct sst_pdata *pdata = dev_get_platdata(platform->dev);
+ int ret = 0;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream ||
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ size = sst_byt_pcm_hardware.buffer_bytes_max;
+ ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV,
+ pdata->dma_dev,
+ size, size);
+ if (ret) {
+ dev_err(rtd->dev, "dma buffer allocation failed %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static struct snd_soc_dai_driver byt_dais[] = {
+ {
+ .name = "Baytrail PCM",
+ .playback = {
+ .stream_name = "System Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Analog Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+static int sst_byt_pcm_probe(struct snd_soc_platform *platform)
+{
+ struct sst_pdata *plat_data = dev_get_platdata(platform->dev);
+ struct sst_byt_priv_data *priv_data;
+ int i;
+
+ if (!plat_data)
+ return -ENODEV;
+
+ priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data),
+ GFP_KERNEL);
+ priv_data->byt = plat_data->dsp;
+ snd_soc_platform_set_drvdata(platform, priv_data);
+
+ for (i = 0; i < BYT_PCM_COUNT; i++) {
+ mutex_init(&priv_data->pcm[i].mutex);
+ INIT_WORK(&priv_data->pcm[i].work, sst_byt_pcm_work);
+ }
+
+ return 0;
+}
+
+static int sst_byt_pcm_remove(struct snd_soc_platform *platform)
+{
+ return 0;
+}
+
+static struct snd_soc_platform_driver byt_soc_platform = {
+ .probe = sst_byt_pcm_probe,
+ .remove = sst_byt_pcm_remove,
+ .ops = &sst_byt_pcm_ops,
+ .pcm_new = sst_byt_pcm_new,
+};
+
+static const struct snd_soc_component_driver byt_dai_component = {
+ .name = "byt-dai",
+};
+
+#ifdef CONFIG_PM
+static int sst_byt_pcm_dev_suspend_late(struct device *dev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(dev);
+ struct sst_byt_priv_data *priv_data = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "suspending late\n");
+
+ ret = sst_byt_dsp_suspend_late(dev, sst_pdata);
+ if (ret < 0) {
+ dev_err(dev, "failed to suspend %d\n", ret);
+ return ret;
+ }
+
+ priv_data->restore_stream = true;
+
+ return ret;
+}
+
+static int sst_byt_pcm_dev_resume_early(struct device *dev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(dev);
+ int ret;
+
+ dev_dbg(dev, "resume early\n");
+
+ /* load fw and boot DSP */
+ ret = sst_byt_dsp_boot(dev, sst_pdata);
+ if (ret)
+ return ret;
+
+ /* wait for FW to finish booting */
+ return sst_byt_dsp_wait_for_ready(dev, sst_pdata);
+}
+
+static const struct dev_pm_ops sst_byt_pm_ops = {
+ .suspend_late = sst_byt_pcm_dev_suspend_late,
+ .resume_early = sst_byt_pcm_dev_resume_early,
+};
+
+#define SST_BYT_PM_OPS (&sst_byt_pm_ops)
+#else
+#define SST_BYT_PM_OPS NULL
+#endif
+
+static int sst_byt_pcm_dev_probe(struct platform_device *pdev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev);
+ int ret;
+
+ ret = sst_byt_dsp_init(&pdev->dev, sst_pdata);
+ if (ret < 0)
+ return -ENODEV;
+
+ ret = snd_soc_register_platform(&pdev->dev, &byt_soc_platform);
+ if (ret < 0)
+ goto err_plat;
+
+ ret = snd_soc_register_component(&pdev->dev, &byt_dai_component,
+ byt_dais, ARRAY_SIZE(byt_dais));
+ if (ret < 0)
+ goto err_comp;
+
+ return 0;
+
+err_comp:
+ snd_soc_unregister_platform(&pdev->dev);
+err_plat:
+ sst_byt_dsp_free(&pdev->dev, sst_pdata);
+ return ret;
+}
+
+static int sst_byt_pcm_dev_remove(struct platform_device *pdev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev);
+
+ snd_soc_unregister_platform(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+ sst_byt_dsp_free(&pdev->dev, sst_pdata);
+
+ return 0;
+}
+
+static struct platform_driver sst_byt_pcm_driver = {
+ .driver = {
+ .name = "baytrail-pcm-audio",
+ .pm = SST_BYT_PM_OPS,
+ },
+
+ .probe = sst_byt_pcm_dev_probe,
+ .remove = sst_byt_pcm_dev_remove,
+};
+module_platform_driver(sst_byt_pcm_driver);
+
+MODULE_AUTHOR("Jarkko Nikula");
+MODULE_DESCRIPTION("Baytrail PCM");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:baytrail-pcm-audio");
diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile
new file mode 100644
index 000000000..f8237f004
--- /dev/null
+++ b/sound/soc/intel/boards/Makefile
@@ -0,0 +1,15 @@
+snd-soc-sst-haswell-objs := haswell.o
+snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o
+snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o
+snd-soc-sst-broadwell-objs := broadwell.o
+snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o
+snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o
+snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o
+
+obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o
+obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o
+obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o
+obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.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
diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c
new file mode 100644
index 000000000..8bafaf6ce
--- /dev/null
+++ b/sound/soc/intel/boards/broadwell.c
@@ -0,0 +1,292 @@
+/*
+ * Intel Broadwell Wildcatpoint SST Audio
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#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 "../common/sst-dsp.h"
+#include "../haswell/sst-haswell-ipc.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_codec *codec = rtd->codec;
+ 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(codec, &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 *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 broadwell_rt286_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ 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 struct snd_soc_ops broadwell_rt286_ops = {
+ .hw_params = broadwell_rt286_hw_params,
+};
+
+static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev);
+ struct sst_hsw *broadwell = pdata->dsp;
+ int ret;
+
+ /* Set ADSP SSP port settings */
+ ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0,
+ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ,
+ SST_HSW_DEVICE_CLOCK_MASTER, 9);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: failed to set device config\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* 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",
+ .cpu_dai_name = "System Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .init = broadwell_rtd_init,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ },
+ {
+ .name = "Offload0",
+ .stream_name = "Offload0 Playback",
+ .cpu_dai_name = "Offload0 Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Offload1",
+ .stream_name = "Offload1 Playback",
+ .cpu_dai_name = "Offload1 Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Loopback PCM",
+ .stream_name = "Loopback",
+ .cpu_dai_name = "Loopback Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 0,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ },
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .be_id = 0,
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "snd-soc-dummy",
+ .no_pcm = 1,
+ .codec_name = "i2c-INT343A:00",
+ .codec_dai_name = "rt286-aif1",
+ .init = broadwell_rt286_codec_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_suspend = 1,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = broadwell_ssp0_fixup,
+ .ops = &broadwell_rt286_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ },
+};
+
+static int broadwell_suspend(struct snd_soc_card *card){
+ struct snd_soc_codec *codec;
+
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ if (!strcmp(codec->component.name, "i2c-INT343A:00")) {
+ dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n");
+ rt286_mic_detect(codec, NULL);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int broadwell_resume(struct snd_soc_card *card){
+ struct snd_soc_codec *codec;
+
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ if (!strcmp(codec->component.name, "i2c-INT343A:00")) {
+ dev_dbg(codec->dev, "enabling jack detect for resume.\n");
+ rt286_mic_detect(codec, &broadwell_headset);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* broadwell audio machine driver for WPT + RT286S */
+static struct snd_soc_card broadwell_rt286 = {
+ .name = "broadwell-rt286",
+ .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)
+{
+ broadwell_rt286.dev = &pdev->dev;
+
+ return snd_soc_register_card(&broadwell_rt286);
+}
+
+static int broadwell_audio_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&broadwell_rt286);
+ return 0;
+}
+
+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/byt-max98090.c b/sound/soc/intel/boards/byt-max98090.c
new file mode 100644
index 000000000..7ab8cc9fb
--- /dev/null
+++ b/sound/soc/intel/boards/byt-max98090.c
@@ -0,0 +1,187 @@
+/*
+ * Intel Baytrail SST MAX98090 machine driver
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "../../codecs/max98090.h"
+
+struct byt_max98090_private {
+ struct snd_soc_jack jack;
+};
+
+static const struct snd_soc_dapm_widget byt_max98090_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_max98090_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"},
+};
+
+static const struct snd_kcontrol_new byt_max98090_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_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-gpio",
+ .idx = 0,
+ .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT,
+ .debounce_time = 200,
+ },
+ {
+ .name = "mic-gpio",
+ .idx = 1,
+ .invert = 1,
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_card *card = runtime->card;
+ struct byt_max98090_private *drv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_jack *jack = &drv->jack;
+
+ card->dapm.idle_bias_off = true;
+
+ ret = snd_soc_dai_set_sysclk(runtime->codec_dai,
+ M98090_REG_SYSTEM_CLOCK,
+ 25000000, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "Can't set codec clock %d\n", ret);
+ return ret;
+ }
+
+ /* Enable jack detection */
+ ret = snd_soc_card_jack_new(runtime->card, "Headset",
+ SND_JACK_LINEOUT | SND_JACK_HEADSET, jack,
+ hs_jack_pins, ARRAY_SIZE(hs_jack_pins));
+ if (ret)
+ return ret;
+
+ return snd_soc_jack_add_gpiods(card->dev->parent, jack,
+ ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+}
+
+static struct snd_soc_dai_link byt_max98090_dais[] = {
+ {
+ .name = "Baytrail Audio",
+ .stream_name = "Audio",
+ .cpu_dai_name = "baytrail-pcm-audio",
+ .codec_dai_name = "HiFi",
+ .codec_name = "i2c-193C9890:00",
+ .platform_name = "baytrail-pcm-audio",
+ .init = byt_max98090_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ },
+};
+
+static struct snd_soc_card byt_max98090_card = {
+ .name = "byt-max98090",
+ .dai_link = byt_max98090_dais,
+ .num_links = ARRAY_SIZE(byt_max98090_dais),
+ .dapm_widgets = byt_max98090_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_max98090_widgets),
+ .dapm_routes = byt_max98090_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_max98090_audio_map),
+ .controls = byt_max98090_controls,
+ .num_controls = ARRAY_SIZE(byt_max98090_controls),
+ .fully_routed = true,
+};
+
+static int byt_max98090_probe(struct platform_device *pdev)
+{
+ int ret_val = 0;
+ struct byt_max98090_private *priv;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC);
+ if (!priv) {
+ dev_err(&pdev->dev, "allocation failed\n");
+ return -ENOMEM;
+ }
+
+ byt_max98090_card.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&byt_max98090_card, priv);
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_max98090_card);
+ if (ret_val) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+
+ return ret_val;
+}
+
+static int byt_max98090_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct byt_max98090_private *priv = snd_soc_card_get_drvdata(card);
+
+ snd_soc_jack_free_gpios(&priv->jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+
+ return 0;
+}
+
+static struct platform_driver byt_max98090_driver = {
+ .probe = byt_max98090_probe,
+ .remove = byt_max98090_remove,
+ .driver = {
+ .name = "byt-max98090",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(byt_max98090_driver)
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver");
+MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:byt-max98090");
diff --git a/sound/soc/intel/boards/byt-rt5640.c b/sound/soc/intel/boards/byt-rt5640.c
new file mode 100644
index 000000000..ae89b9b96
--- /dev/null
+++ b/sound/soc/intel/boards/byt-rt5640.c
@@ -0,0 +1,229 @@
+/*
+ * Intel Baytrail SST RT5640 machine driver
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/device.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 "../../codecs/rt5640.h"
+
+#include "../common/sst-dsp.h"
+
+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),
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = {
+ {"Headset Mic", NULL, "MICBIAS1"},
+ {"IN2P", NULL, "Headset Mic"},
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Speaker", NULL, "SPOLP"},
+ {"Speaker", NULL, "SPOLN"},
+ {"Speaker", NULL, "SPORP"},
+ {"Speaker", NULL, "SPORN"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = {
+ {"DMIC1", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = {
+ {"DMIC2", NULL, "Internal Mic"},
+};
+
+static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = {
+ {"Internal Mic", NULL, "MICBIAS1"},
+ {"IN1P", NULL, "Internal Mic"},
+};
+
+enum {
+ BYT_RT5640_DMIC1_MAP,
+ BYT_RT5640_DMIC2_MAP,
+ BYT_RT5640_IN1_MAP,
+};
+
+#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff)
+#define BYT_RT5640_DMIC_EN BIT(16)
+
+static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP |
+ BYT_RT5640_DMIC_EN;
+
+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 int byt_rt5640_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1,
+ params_rate(params) * 256,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set codec clock %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1,
+ params_rate(params) * 64,
+ params_rate(params) * 256);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int byt_rt5640_quirk_cb(const struct dmi_system_id *id)
+{
+ byt_rt5640_quirk = (unsigned long)id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id byt_rt5640_quirk_table[] = {
+ {
+ .callback = byt_rt5640_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"),
+ },
+ .driver_data = (unsigned long *)BYT_RT5640_IN1_MAP,
+ },
+ {
+ .callback = byt_rt5640_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "DellInc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"),
+ },
+ .driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP |
+ BYT_RT5640_DMIC_EN),
+ },
+ {}
+};
+
+static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_card *card = runtime->card;
+ const struct snd_soc_dapm_route *custom_map;
+ int num_routes;
+
+ card->dapm.idle_bias_off = true;
+
+ 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;
+ }
+
+ dmi_check_system(byt_rt5640_quirk_table);
+ 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_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_DMIC_EN) {
+ ret = rt5640_dmic_enable(codec, 0, 0);
+ if (ret)
+ return ret;
+ }
+
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker");
+
+ return ret;
+}
+
+static struct snd_soc_ops byt_rt5640_ops = {
+ .hw_params = byt_rt5640_hw_params,
+};
+
+static struct snd_soc_dai_link byt_rt5640_dais[] = {
+ {
+ .name = "Baytrail Audio",
+ .stream_name = "Audio",
+ .cpu_dai_name = "baytrail-pcm-audio",
+ .codec_dai_name = "rt5640-aif1",
+ .codec_name = "i2c-10EC5640:00",
+ .platform_name = "baytrail-pcm-audio",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .init = byt_rt5640_init,
+ .ops = &byt_rt5640_ops,
+ },
+};
+
+static struct snd_soc_card byt_rt5640_card = {
+ .name = "byt-rt5640",
+ .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,
+};
+
+static int byt_rt5640_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &byt_rt5640_card;
+
+ card->dev = &pdev->dev;
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static struct platform_driver byt_rt5640_audio = {
+ .probe = byt_rt5640_probe,
+ .driver = {
+ .name = "byt-rt5640",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(byt_rt5640_audio)
+
+MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver");
+MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:byt-rt5640");
diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c
new file mode 100644
index 000000000..7f55d5902
--- /dev/null
+++ b/sound/soc/intel/boards/bytcr_rt5640.c
@@ -0,0 +1,227 @@
+/*
+ * 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>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../codecs/rt5640.h"
+#include "../atom/sst-atom-controls.h"
+
+static const struct snd_soc_dapm_widget byt_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 byt_audio_map[] = {
+ {"IN2P", NULL, "Headset Mic"},
+ {"IN2N", NULL, "Headset Mic"},
+ {"Headset Mic", NULL, "MICBIAS1"},
+ {"IN1P", NULL, "MICBIAS1"},
+ {"LDO2", 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"},
+};
+
+static const struct snd_kcontrol_new byt_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 byt_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+
+ snd_soc_dai_set_bclk_ratio(codec_dai, 50);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1,
+ params_rate(params) * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec clock %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1,
+ params_rate(params) * 50,
+ params_rate(params) * 512);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_pcm_stream byt_dai_params = {
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+static int byt_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);
+
+ /* 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);
+ return 0;
+}
+
+static unsigned int rates_48000[] = {
+ 48000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_48000 = {
+ .count = ARRAY_SIZE(rates_48000),
+ .list = rates_48000,
+};
+
+static int byt_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 byt_aif1_ops = {
+ .startup = byt_aif1_startup,
+};
+
+static struct snd_soc_ops byt_be_ssp2_ops = {
+ .hw_params = byt_aif1_hw_params,
+};
+
+static struct snd_soc_dai_link byt_dailink[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Baytrail Audio Port",
+ .stream_name = "Baytrail Audio",
+ .cpu_dai_name = "media-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ .ignore_suspend = 1,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_aif1_ops,
+ },
+ [MERR_DPCM_COMPR] = {
+ .name = "Baytrail Compressed Port",
+ .stream_name = "Baytrail Compress",
+ .cpu_dai_name = "compress-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ },
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .be_id = 1,
+ .cpu_dai_name = "ssp2-port",
+ .platform_name = "sst-mfld-platform",
+ .no_pcm = 1,
+ .codec_dai_name = "rt5640-aif1",
+ .codec_name = "i2c-10EC5640:00",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .be_hw_params_fixup = byt_codec_fixup,
+ .ignore_suspend = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &byt_be_ssp2_ops,
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_byt = {
+ .name = "baytrailcraudio",
+ .dai_link = byt_dailink,
+ .num_links = ARRAY_SIZE(byt_dailink),
+ .dapm_widgets = byt_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(byt_dapm_widgets),
+ .dapm_routes = byt_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(byt_audio_map),
+ .controls = byt_mc_controls,
+ .num_controls = ARRAY_SIZE(byt_mc_controls),
+};
+
+static int snd_byt_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0;
+
+ /* register the soc card */
+ snd_soc_card_byt.dev = &pdev->dev;
+
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_byt);
+ if (ret_val) {
+ dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, &snd_soc_card_byt);
+ return ret_val;
+}
+
+static struct platform_driver snd_byt_mc_driver = {
+ .driver = {
+ .name = "bytt100_rt5640",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = snd_byt_mc_probe,
+};
+
+module_platform_driver(snd_byt_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:bytt100_rt5640");
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..20a28b22e
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_rt5645.c
@@ -0,0 +1,324 @@
+/*
+ * 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
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#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/jack.h>
+#include "../../codecs/rt5645.h"
+#include "../atom/sst-atom-controls.h"
+
+#define CHT_PLAT_CLK_3_HZ 19200000
+#define CHT_CODEC_DAI "rt5645-aif1"
+
+struct cht_mc_private {
+ struct snd_soc_jack hp_jack;
+ struct snd_soc_jack mic_jack;
+};
+
+static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
+{
+ int i;
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_pcm_runtime *rtd;
+
+ rtd = card->rtd + i;
+ if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
+ strlen(CHT_CODEC_DAI)))
+ return rtd->codec_dai;
+ }
+ return NULL;
+}
+
+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 = cht_get_codec_dai(card);
+ if (!codec_dai) {
+ dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n");
+ return -EIO;
+ }
+
+ if (!SND_SOC_DAPM_EVENT_OFF(event))
+ return 0;
+
+ /* 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.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK,
+ 0, SND_SOC_CLOCK_IN);
+ 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 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_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, "SPOL"},
+ {"Ext Spk", NULL, "SPOR"},
+ {"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 = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ 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_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dai *codec_dai = runtime->codec_dai;
+ struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card);
+
+ /* Select clk_i2s1_asrc as ASRC clock source */
+ rt5645_sel_asrc_clk_src(codec,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER |
+ RT5645_AD_STEREO_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(runtime->dev, "can't set codec TDM slot %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(runtime->card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &ctx->hp_jack,
+ NULL, 0);
+ if (ret) {
+ dev_err(runtime->dev, "HP jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(runtime->card, "Mic Jack",
+ SND_JACK_MICROPHONE, &ctx->mic_jack,
+ NULL, 0);
+ if (ret) {
+ dev_err(runtime->dev, "Mic jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ rt5645_set_jack_detect(codec, &ctx->hp_jack, &ctx->mic_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);
+
+ /* 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);
+ return 0;
+}
+
+static unsigned int rates_48000[] = {
+ 48000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_48000 = {
+ .count = ARRAY_SIZE(rates_48000),
+ .list = rates_48000,
+};
+
+static int cht_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 cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .cpu_dai_name = "media-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ .ignore_suspend = 1,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ },
+ [MERR_DPCM_COMPR] = {
+ .name = "Compressed Port",
+ .stream_name = "Compress",
+ .cpu_dai_name = "compress-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ },
+ /* CODEC<->CODEC link */
+ /* back ends */
+ {
+ .name = "SSP2-Codec",
+ .be_id = 1,
+ .cpu_dai_name = "ssp2-port",
+ .platform_name = "sst-mfld-platform",
+ .no_pcm = 1,
+ .codec_dai_name = "rt5645-aif1",
+ .codec_name = "i2c-10EC5645:00",
+ .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,
+ .ignore_suspend = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_be_ssp2_ops,
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_cht = {
+ .name = "chtrt5645",
+ .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)
+{
+ int ret_val = 0;
+ struct cht_mc_private *drv;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC);
+ if (!drv)
+ return -ENOMEM;
+
+ snd_soc_card_cht.dev = &pdev->dev;
+ snd_soc_card_set_drvdata(&snd_soc_card_cht, drv);
+ 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-rt5645",
+ .pm = &snd_soc_pm_ops,
+ },
+ .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..2c9cc5be4
--- /dev/null
+++ b/sound/soc/intel/boards/cht_bsw_rt5672.c
@@ -0,0 +1,366 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#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/jack.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"
+
+static struct snd_soc_jack cht_bsw_headset;
+
+/* 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 inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card)
+{
+ int i;
+
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_pcm_runtime *rtd;
+
+ rtd = card->rtd + i;
+ if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI,
+ strlen(CHT_CODEC_DAI)))
+ return rtd->codec_dai;
+ }
+ return NULL;
+}
+
+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 = cht_get_codec_dai(card);
+ 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)) {
+ /* 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);
+ }
+ 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 = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ 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 int cht_codec_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_dai *codec_dai = runtime->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+
+ /* 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(runtime->dev, "can't set codec TDM slot %d\n", ret);
+ return ret;
+ }
+
+ /* 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(codec,
+ 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, &cht_bsw_headset,
+ cht_bsw_headset_pins, ARRAY_SIZE(cht_bsw_headset_pins));
+ if (ret)
+ return ret;
+
+ rt5670_set_jack_detect(codec, &cht_bsw_headset);
+ 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);
+
+ /* 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);
+ return 0;
+}
+
+static unsigned int rates_48000[] = {
+ 48000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_48000 = {
+ .count = ARRAY_SIZE(rates_48000),
+ .list = rates_48000,
+};
+
+static int cht_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 cht_aif1_ops = {
+ .startup = cht_aif1_startup,
+};
+
+static struct snd_soc_ops cht_be_ssp2_ops = {
+ .hw_params = cht_aif1_hw_params,
+};
+
+static struct snd_soc_dai_link cht_dailink[] = {
+ /* Front End DAI links */
+ [MERR_DPCM_AUDIO] = {
+ .name = "Audio Port",
+ .stream_name = "Audio",
+ .cpu_dai_name = "media-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ .nonatomic = true,
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &cht_aif1_ops,
+ },
+ [MERR_DPCM_COMPR] = {
+ .name = "Compressed Port",
+ .stream_name = "Compress",
+ .cpu_dai_name = "compress-cpu-dai",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .platform_name = "sst-mfld-platform",
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP2 - Codec */
+ .name = "SSP2-Codec",
+ .be_id = 1,
+ .cpu_dai_name = "ssp2-port",
+ .platform_name = "sst-mfld-platform",
+ .no_pcm = 1,
+ .nonatomic = true,
+ .codec_dai_name = "rt5670-aif1",
+ .codec_name = "i2c-10EC5670:00",
+ .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,
+ },
+};
+
+static int cht_suspend_pre(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec;
+
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ if (!strcmp(codec->component.name, "i2c-10EC5670:00")) {
+ dev_dbg(codec->dev, "disabling jack detect before going to suspend.\n");
+ rt5670_jack_suspend(codec);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int cht_resume_post(struct snd_soc_card *card)
+{
+ struct snd_soc_codec *codec;
+
+ list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+ if (!strcmp(codec->component.name, "i2c-10EC5670:00")) {
+ dev_dbg(codec->dev, "enabling jack detect for resume.\n");
+ rt5670_jack_resume(codec);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_cht = {
+ .name = "cherrytrailcraudio",
+ .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,
+};
+
+static int snd_cht_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0;
+
+ /* register the soc card */
+ snd_soc_card_cht.dev = &pdev->dev;
+ 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",
+ },
+ .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/haswell.c b/sound/soc/intel/boards/haswell.c
new file mode 100644
index 000000000..22558572c
--- /dev/null
+++ b/sound/soc/intel/boards/haswell.c
@@ -0,0 +1,209 @@
+/*
+ * Intel Haswell Lynxpoint SST Audio
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "../common/sst-dsp.h"
+#include "../haswell/sst-haswell-ipc.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 = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ 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_update_bits(rtd->codec, 0x83, 0xffff, 0x8000);
+
+ return ret;
+}
+
+static struct snd_soc_ops haswell_rt5640_ops = {
+ .hw_params = haswell_rt5640_hw_params,
+};
+
+static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev);
+ struct sst_hsw *haswell = pdata->dsp;
+ int ret;
+
+ /* Set ADSP SSP port settings */
+ ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0,
+ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ,
+ SST_HSW_DEVICE_CLOCK_MASTER, 9);
+ if (ret < 0) {
+ dev_err(rtd->dev, "failed to set device config\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_link haswell_rt5640_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "System",
+ .stream_name = "System Playback/Capture",
+ .cpu_dai_name = "System Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .init = haswell_rtd_init,
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ },
+ {
+ .name = "Offload0",
+ .stream_name = "Offload0 Playback",
+ .cpu_dai_name = "Offload0 Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Offload1",
+ .stream_name = "Offload1 Playback",
+ .cpu_dai_name = "Offload1 Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Loopback",
+ .stream_name = "Loopback",
+ .cpu_dai_name = "Loopback Pin",
+ .platform_name = "haswell-pcm-audio",
+ .dynamic = 0,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
+ .dpcm_capture = 1,
+ },
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - Codec */
+ .name = "Codec",
+ .be_id = 0,
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "snd-soc-dummy",
+ .no_pcm = 1,
+ .codec_name = "i2c-INT33CA:00",
+ .codec_dai_name = "rt5640-aif1",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_suspend = 1,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = haswell_ssp0_fixup,
+ .ops = &haswell_rt5640_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ },
+};
+
+/* 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)
+{
+ haswell_rt5640.dev = &pdev->dev;
+
+ return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640);
+}
+
+static struct platform_driver haswell_audio = {
+ .probe = haswell_audio_probe,
+ .driver = {
+ .name = "haswell-audio",
+ },
+};
+
+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/mfld_machine.c b/sound/soc/intel/boards/mfld_machine.c
new file mode 100644
index 000000000..49c09a0ad
--- /dev/null
+++ b/sound/soc/intel/boards/mfld_machine.c
@@ -0,0 +1,430 @@
+/*
+ * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "../codecs/sn95031.h"
+
+#define MID_MONO 1
+#define MID_STEREO 2
+#define MID_MAX_CAP 5
+#define MFLD_JACK_INSERT 0x04
+
+enum soc_mic_bias_zones {
+ MFLD_MV_START = 0,
+ /* mic bias volutage range for Headphones*/
+ MFLD_MV_HP = 400,
+ /* mic bias volutage range for American Headset*/
+ MFLD_MV_AM_HS = 650,
+ /* mic bias volutage range for Headset*/
+ MFLD_MV_HS = 2000,
+ MFLD_MV_UNDEFINED,
+};
+
+static unsigned int hs_switch;
+static unsigned int lo_dac;
+static struct snd_soc_codec *mfld_codec;
+
+struct mfld_mc_private {
+ void __iomem *int_base;
+ u8 interrupt_status;
+};
+
+struct snd_soc_jack mfld_jack;
+
+/*Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin mfld_jack_pins[] = {
+ {
+ .pin = "Headphones",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "AMIC1",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+/* jack detection voltage zones */
+static struct snd_soc_jack_zone mfld_zones[] = {
+ {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE},
+ {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET},
+};
+
+/* sound card controls */
+static const char *headset_switch_text[] = {"Earpiece", "Headset"};
+
+static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"};
+
+static const struct soc_enum headset_enum =
+ SOC_ENUM_SINGLE_EXT(2, headset_switch_text);
+
+static const struct soc_enum lo_enum =
+ SOC_ENUM_SINGLE_EXT(4, lo_text);
+
+static int headset_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = hs_switch;
+ return 0;
+}
+
+static int headset_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+
+ if (ucontrol->value.integer.value[0] == hs_switch)
+ return 0;
+
+ snd_soc_dapm_mutex_lock(dapm);
+
+ if (ucontrol->value.integer.value[0]) {
+ pr_debug("hs_set HS path\n");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
+ } else {
+ pr_debug("hs_set EP path\n");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT");
+ }
+
+ snd_soc_dapm_sync_unlocked(dapm);
+
+ snd_soc_dapm_mutex_unlock(dapm);
+
+ hs_switch = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static void lo_enable_out_pins(struct snd_soc_dapm_context *dapm)
+{
+ snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTL");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "IHFOUTR");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTL");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "LINEOUTR");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "VIB1OUT");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "VIB2OUT");
+ if (hs_switch) {
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Headphones");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
+ } else {
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "EPOUT");
+ }
+}
+
+static int lo_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lo_dac;
+ return 0;
+}
+
+static int lo_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+
+ if (ucontrol->value.integer.value[0] == lo_dac)
+ return 0;
+
+ snd_soc_dapm_mutex_lock(dapm);
+
+ /* we dont want to work with last state of lineout so just enable all
+ * pins and then disable pins not required
+ */
+ lo_enable_out_pins(dapm);
+
+ switch (ucontrol->value.integer.value[0]) {
+ case 0:
+ pr_debug("set vibra path\n");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "VIB1OUT");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "VIB2OUT");
+ snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0);
+ break;
+
+ case 1:
+ pr_debug("set hs path\n");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphones");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "EPOUT");
+ snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x22);
+ break;
+
+ case 2:
+ pr_debug("set spkr path\n");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTL");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "IHFOUTR");
+ snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x44);
+ break;
+
+ case 3:
+ pr_debug("set null path\n");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "LINEOUTR");
+ snd_soc_update_bits(mfld_codec, SN95031_LOCTL, 0x66, 0x66);
+ break;
+ }
+
+ snd_soc_dapm_sync_unlocked(dapm);
+
+ snd_soc_dapm_mutex_unlock(dapm);
+
+ lo_dac = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new mfld_snd_controls[] = {
+ SOC_ENUM_EXT("Playback Switch", headset_enum,
+ headset_get_switch, headset_set_switch),
+ SOC_ENUM_EXT("Lineout Mux", lo_enum,
+ lo_get_switch, lo_set_switch),
+};
+
+static const struct snd_soc_dapm_widget mfld_widgets[] = {
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route mfld_map[] = {
+ {"Headphones", NULL, "HPOUTR"},
+ {"Headphones", NULL, "HPOUTL"},
+ {"Mic", NULL, "AMIC1"},
+};
+
+static void mfld_jack_check(unsigned int intr_status)
+{
+ struct mfld_jack_data jack_data;
+
+ if (!mfld_codec)
+ return;
+
+ jack_data.mfld_jack = &mfld_jack;
+ jack_data.intr_id = intr_status;
+
+ sn95031_jack_detection(mfld_codec, &jack_data);
+ /* TODO: add american headset detection post gpiolib support */
+}
+
+static int mfld_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_dapm_context *dapm = &runtime->card->dapm;
+ int ret_val;
+
+ /* default is earpiece pin, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "Headphones");
+ /* default is lineout NC, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTR");
+ lo_dac = 3;
+ hs_switch = 0;
+ /* we dont use linein in this so set to NC */
+ snd_soc_dapm_disable_pin(dapm, "LINEINL");
+ snd_soc_dapm_disable_pin(dapm, "LINEINR");
+
+ /* Headset and button jack detection */
+ ret_val = snd_soc_card_jack_new(runtime->card,
+ "Intel(R) MID Audio Jack", SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1, &mfld_jack,
+ mfld_jack_pins, ARRAY_SIZE(mfld_jack_pins));
+ if (ret_val) {
+ pr_err("jack creation failed\n");
+ return ret_val;
+ }
+
+ ret_val = snd_soc_jack_add_zones(&mfld_jack,
+ ARRAY_SIZE(mfld_zones), mfld_zones);
+ if (ret_val) {
+ pr_err("adding jack zones failed\n");
+ return ret_val;
+ }
+
+ mfld_codec = runtime->codec;
+
+ /* we want to check if anything is inserted at boot,
+ * so send a fake event to codec and it will read adc
+ * to find if anything is there or not */
+ mfld_jack_check(MFLD_JACK_INSERT);
+ return ret_val;
+}
+
+static struct snd_soc_dai_link mfld_msic_dailink[] = {
+ {
+ .name = "Medfield Headset",
+ .stream_name = "Headset",
+ .cpu_dai_name = "Headset-cpu-dai",
+ .codec_dai_name = "SN95031 Headset",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = mfld_init,
+ },
+ {
+ .name = "Medfield Speaker",
+ .stream_name = "Speaker",
+ .cpu_dai_name = "Speaker-cpu-dai",
+ .codec_dai_name = "SN95031 Speaker",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Vibra",
+ .stream_name = "Vibra1",
+ .cpu_dai_name = "Vibra1-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra1",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Haptics",
+ .stream_name = "Vibra2",
+ .cpu_dai_name = "Vibra2-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra2",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Compress",
+ .stream_name = "Speaker",
+ .cpu_dai_name = "Compress-cpu-dai",
+ .codec_dai_name = "SN95031 Speaker",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_mfld = {
+ .name = "medfield_audio",
+ .owner = THIS_MODULE,
+ .dai_link = mfld_msic_dailink,
+ .num_links = ARRAY_SIZE(mfld_msic_dailink),
+
+ .controls = mfld_snd_controls,
+ .num_controls = ARRAY_SIZE(mfld_snd_controls),
+ .dapm_widgets = mfld_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(mfld_widgets),
+ .dapm_routes = mfld_map,
+ .num_dapm_routes = ARRAY_SIZE(mfld_map),
+};
+
+static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev)
+{
+ struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev;
+
+ memcpy_fromio(&mc_private->interrupt_status,
+ ((void *)(mc_private->int_base)),
+ sizeof(u8));
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t snd_mfld_jack_detection(int irq, void *data)
+{
+ struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data;
+
+ mfld_jack_check(mc_drv_ctx->interrupt_status);
+
+ return IRQ_HANDLED;
+}
+
+static int snd_mfld_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0, irq;
+ struct mfld_mc_private *mc_drv_ctx;
+ struct resource *irq_mem;
+
+ pr_debug("snd_mfld_mc_probe called\n");
+
+ /* retrive the irq number */
+ irq = platform_get_irq(pdev, 0);
+
+ /* audio interrupt base of SRAM location where
+ * interrupts are stored by System FW */
+ mc_drv_ctx = devm_kzalloc(&pdev->dev, sizeof(*mc_drv_ctx), GFP_ATOMIC);
+ if (!mc_drv_ctx) {
+ pr_err("allocation failed\n");
+ return -ENOMEM;
+ }
+
+ irq_mem = platform_get_resource_byname(
+ pdev, IORESOURCE_MEM, "IRQ_BASE");
+ if (!irq_mem) {
+ pr_err("no mem resource given\n");
+ return -ENODEV;
+ }
+ mc_drv_ctx->int_base = devm_ioremap_nocache(&pdev->dev, irq_mem->start,
+ resource_size(irq_mem));
+ if (!mc_drv_ctx->int_base) {
+ pr_err("Mapping of cache failed\n");
+ return -ENOMEM;
+ }
+ /* register for interrupt */
+ ret_val = devm_request_threaded_irq(&pdev->dev, irq,
+ snd_mfld_jack_intr_handler,
+ snd_mfld_jack_detection,
+ IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx);
+ if (ret_val) {
+ pr_err("cannot register IRQ\n");
+ return ret_val;
+ }
+ /* register the soc card */
+ snd_soc_card_mfld.dev = &pdev->dev;
+ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_mfld);
+ if (ret_val) {
+ pr_debug("snd_soc_register_card failed %d\n", ret_val);
+ return ret_val;
+ }
+ platform_set_drvdata(pdev, mc_drv_ctx);
+ pr_debug("successfully exited probe\n");
+ return 0;
+}
+
+static struct platform_driver snd_mfld_mc_driver = {
+ .driver = {
+ .name = "msic_audio",
+ },
+ .probe = snd_mfld_mc_probe,
+};
+
+module_platform_driver(snd_mfld_mc_driver);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:msic-audio");
diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile
new file mode 100644
index 000000000..f24154ca4
--- /dev/null
+++ b/sound/soc/intel/common/Makefile
@@ -0,0 +1,7 @@
+snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o
+snd-soc-sst-acpi-objs := sst-acpi.o
+snd-soc-sst-ipc-objs := sst-ipc.o
+
+obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o
+obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o
+
diff --git a/sound/soc/intel/common/sst-acpi.c b/sound/soc/intel/common/sst-acpi.c
new file mode 100644
index 000000000..23cd229e5
--- /dev/null
+++ b/sound/soc/intel/common/sst-acpi.c
@@ -0,0 +1,286 @@
+/*
+ * Intel SST loader on ACPI systems
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "sst-dsp.h"
+
+#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000
+#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000
+#define SST_LPT_DSP_DMA_SIZE (1024 - 1)
+
+/* Descriptor for SST ASoC machine driver */
+struct sst_acpi_mach {
+ /* ACPI ID for the matching machine driver. Audio codec for instance */
+ const u8 id[ACPI_ID_LEN];
+ /* machine driver name */
+ const char *drv_name;
+ /* firmware file name */
+ const char *fw_filename;
+};
+
+/* Descriptor for setting up SST platform data */
+struct sst_acpi_desc {
+ const char *drv_name;
+ struct sst_acpi_mach *machines;
+ /* Platform resource indexes. Must set to -1 if not used */
+ int resindex_lpe_base;
+ int resindex_pcicfg_base;
+ int resindex_fw_base;
+ int irqindex_host_ipc;
+ int resindex_dma_base;
+ /* Unique number identifying the SST core on platform */
+ int sst_id;
+ /* DMA only valid when resindex_dma_base != -1*/
+ int dma_engine;
+ int dma_size;
+};
+
+struct sst_acpi_priv {
+ struct platform_device *pdev_mach;
+ struct platform_device *pdev_pcm;
+ struct sst_pdata sst_pdata;
+ struct sst_acpi_desc *desc;
+ struct sst_acpi_mach *mach;
+};
+
+static void sst_acpi_fw_cb(const struct firmware *fw, void *context)
+{
+ struct platform_device *pdev = context;
+ struct device *dev = &pdev->dev;
+ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev);
+ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata;
+ struct sst_acpi_desc *desc = sst_acpi->desc;
+ struct sst_acpi_mach *mach = sst_acpi->mach;
+
+ sst_pdata->fw = fw;
+ if (!fw) {
+ dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename);
+ return;
+ }
+
+ /* register PCM and DAI driver */
+ sst_acpi->pdev_pcm =
+ platform_device_register_data(dev, desc->drv_name, -1,
+ sst_pdata, sizeof(*sst_pdata));
+ if (IS_ERR(sst_acpi->pdev_pcm)) {
+ dev_err(dev, "Cannot register device %s. Error %d\n",
+ desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm));
+ }
+
+ return;
+}
+
+static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
+ void *context, void **ret)
+{
+ *(bool *)context = true;
+ return AE_OK;
+}
+
+static struct sst_acpi_mach *sst_acpi_find_machine(
+ struct sst_acpi_mach *machines)
+{
+ struct sst_acpi_mach *mach;
+ bool found = false;
+
+ for (mach = machines; mach->id[0]; mach++)
+ if (ACPI_SUCCESS(acpi_get_devices(mach->id,
+ sst_acpi_mach_match,
+ &found, NULL)) && found)
+ return mach;
+
+ return NULL;
+}
+
+static int sst_acpi_probe(struct platform_device *pdev)
+{
+ const struct acpi_device_id *id;
+ struct device *dev = &pdev->dev;
+ struct sst_acpi_priv *sst_acpi;
+ struct sst_pdata *sst_pdata;
+ struct sst_acpi_mach *mach;
+ struct sst_acpi_desc *desc;
+ struct resource *mmio;
+ int ret = 0;
+
+ sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL);
+ if (sst_acpi == NULL)
+ return -ENOMEM;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+
+ desc = (struct sst_acpi_desc *)id->driver_data;
+ mach = sst_acpi_find_machine(desc->machines);
+ if (mach == NULL) {
+ dev_err(dev, "No matching ASoC machine driver found\n");
+ return -ENODEV;
+ }
+
+ sst_pdata = &sst_acpi->sst_pdata;
+ sst_pdata->id = desc->sst_id;
+ sst_pdata->dma_dev = dev;
+ sst_acpi->desc = desc;
+ sst_acpi->mach = mach;
+
+ sst_pdata->resindex_dma_base = desc->resindex_dma_base;
+ if (desc->resindex_dma_base >= 0) {
+ sst_pdata->dma_engine = desc->dma_engine;
+ sst_pdata->dma_base = desc->resindex_dma_base;
+ sst_pdata->dma_size = desc->dma_size;
+ }
+
+ if (desc->irqindex_host_ipc >= 0)
+ sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
+
+ if (desc->resindex_lpe_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_lpe_base);
+ if (mmio) {
+ sst_pdata->lpe_base = mmio->start;
+ sst_pdata->lpe_size = resource_size(mmio);
+ }
+ }
+
+ if (desc->resindex_pcicfg_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_pcicfg_base);
+ if (mmio) {
+ sst_pdata->pcicfg_base = mmio->start;
+ sst_pdata->pcicfg_size = resource_size(mmio);
+ }
+ }
+
+ if (desc->resindex_fw_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_fw_base);
+ if (mmio) {
+ sst_pdata->fw_base = mmio->start;
+ sst_pdata->fw_size = resource_size(mmio);
+ }
+ }
+
+ platform_set_drvdata(pdev, sst_acpi);
+
+ /* register machine driver */
+ sst_acpi->pdev_mach =
+ platform_device_register_data(dev, mach->drv_name, -1,
+ sst_pdata, sizeof(*sst_pdata));
+ if (IS_ERR(sst_acpi->pdev_mach))
+ return PTR_ERR(sst_acpi->pdev_mach);
+
+ /* continue SST probing after firmware is loaded */
+ ret = reject_firmware_nowait(THIS_MODULE, true, mach->fw_filename,
+ dev, GFP_KERNEL, pdev, sst_acpi_fw_cb);
+ if (ret)
+ platform_device_unregister(sst_acpi->pdev_mach);
+
+ return ret;
+}
+
+static int sst_acpi_remove(struct platform_device *pdev)
+{
+ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev);
+ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata;
+
+ platform_device_unregister(sst_acpi->pdev_mach);
+ if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm))
+ platform_device_unregister(sst_acpi->pdev_pcm);
+ release_firmware(sst_pdata->fw);
+
+ return 0;
+}
+
+static struct sst_acpi_mach haswell_machines[] = {
+ { "INT33CA", "haswell-audio", "/*(DEBLOBBED)*/" },
+ {}
+};
+
+static struct sst_acpi_desc sst_acpi_haswell_desc = {
+ .drv_name = "haswell-pcm-audio",
+ .machines = haswell_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = -1,
+ .irqindex_host_ipc = 0,
+ .sst_id = SST_DEV_ID_LYNX_POINT,
+ .dma_engine = SST_DMA_TYPE_DW,
+ .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET,
+ .dma_size = SST_LPT_DSP_DMA_SIZE,
+};
+
+static struct sst_acpi_mach broadwell_machines[] = {
+ { "INT343A", "broadwell-audio", "/*(DEBLOBBED)*/" },
+ {}
+};
+
+static struct sst_acpi_desc sst_acpi_broadwell_desc = {
+ .drv_name = "haswell-pcm-audio",
+ .machines = broadwell_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = -1,
+ .irqindex_host_ipc = 0,
+ .sst_id = SST_DEV_ID_WILDCAT_POINT,
+ .dma_engine = SST_DMA_TYPE_DW,
+ .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET,
+ .dma_size = SST_LPT_DSP_DMA_SIZE,
+};
+
+static struct sst_acpi_mach baytrail_machines[] = {
+ { "10EC5640", "byt-rt5640", "/*(DEBLOBBED)*/" },
+ { "193C9890", "byt-max98090", "/*(DEBLOBBED)*/" },
+ {}
+};
+
+static struct sst_acpi_desc sst_acpi_baytrail_desc = {
+ .drv_name = "baytrail-pcm-audio",
+ .machines = baytrail_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = 2,
+ .irqindex_host_ipc = 5,
+ .sst_id = SST_DEV_ID_BYT,
+ .resindex_dma_base = -1,
+};
+
+static struct acpi_device_id sst_acpi_match[] = {
+ { "INT33C8", (unsigned long)&sst_acpi_haswell_desc },
+ { "INT3438", (unsigned long)&sst_acpi_broadwell_desc },
+ { "80860F28", (unsigned long)&sst_acpi_baytrail_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, sst_acpi_match);
+
+static struct platform_driver sst_acpi_driver = {
+ .probe = sst_acpi_probe,
+ .remove = sst_acpi_remove,
+ .driver = {
+ .name = "sst-acpi",
+ .acpi_match_table = ACPI_PTR(sst_acpi_match),
+ },
+};
+module_platform_driver(sst_acpi_driver);
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
+MODULE_DESCRIPTION("Intel SST loader on ACPI systems");
+MODULE_LICENSE("GPL v2");
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..396d54510
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp-priv.h
@@ -0,0 +1,373 @@
+/*
+ * Intel Smart Sound Technology
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#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>
+
+struct sst_mem_block;
+struct sst_module;
+struct sst_fw;
+
+/* do we need to remove or keep */
+#define DSP_DRAM_ADDR_OFFSET 0x400000
+
+/*
+ * DSP Operations exported by platform Audio DSP driver.
+ */
+struct sst_ops {
+ /* DSP core boot / reset */
+ void (*boot)(struct sst_dsp *);
+ void (*reset)(struct sst_dsp *);
+ int (*wake)(struct sst_dsp *);
+ void (*sleep)(struct sst_dsp *);
+ void (*stall)(struct sst_dsp *);
+
+ /* Shim IO */
+ void (*write)(void __iomem *addr, u32 offset, u32 value);
+ u32 (*read)(void __iomem *addr, u32 offset);
+ void (*write64)(void __iomem *addr, u32 offset, u64 value);
+ u64 (*read64)(void __iomem *addr, u32 offset);
+
+ /* DSP I/DRAM IO */
+ void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src,
+ size_t bytes);
+ void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src,
+ size_t bytes);
+
+ void (*dump)(struct sst_dsp *);
+
+ /* IRQ handlers */
+ irqreturn_t (*irq_handler)(int irq, void *context);
+
+ /* SST init and free */
+ int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata);
+ void (*free)(struct sst_dsp *sst);
+
+ /* FW module parser/loader */
+ int (*parse_fw)(struct sst_fw *sst_fw);
+};
+
+/*
+ * Audio DSP memory offsets and addresses.
+ */
+struct sst_addr {
+ u32 lpe_base;
+ u32 shim_offset;
+ u32 iram_offset;
+ u32 dram_offset;
+ u32 dsp_iram_offset;
+ u32 dsp_dram_offset;
+ void __iomem *lpe;
+ void __iomem *shim;
+ void __iomem *pci_cfg;
+ void __iomem *fw_ext;
+};
+
+/*
+ * Audio DSP Mailbox configuration.
+ */
+struct sst_mailbox {
+ void __iomem *in_base;
+ void __iomem *out_base;
+ size_t in_size;
+ size_t out_size;
+};
+
+/*
+ * Audio DSP memory block types.
+ */
+enum sst_mem_type {
+ SST_MEM_IRAM = 0,
+ SST_MEM_DRAM = 1,
+ SST_MEM_ANY = 2,
+ SST_MEM_CACHE= 3,
+};
+
+/*
+ * Audio DSP Generic Firmware File.
+ *
+ * SST Firmware files can consist of 1..N modules. This generic structure is
+ * used to manage each firmware file and it's modules regardless of SST firmware
+ * type. A SST driver may load multiple FW files.
+ */
+struct sst_fw {
+ struct sst_dsp *dsp;
+
+ /* base addresses of FW file data */
+ dma_addr_t dmable_fw_paddr; /* physical address of fw data */
+ void *dma_buf; /* virtual address of fw data */
+ u32 size; /* size of fw data */
+
+ /* lists */
+ struct list_head list; /* DSP list of FW */
+ struct list_head module_list; /* FW list of modules */
+
+ void *private; /* core doesn't touch this */
+};
+
+/*
+ * Audio DSP Generic Module Template.
+ *
+ * Used to define and register a new FW module. This data is extracted from
+ * FW module header information.
+ */
+struct sst_module_template {
+ u32 id;
+ u32 entry; /* entry point */
+ u32 scratch_size;
+ u32 persistent_size;
+};
+
+/*
+ * Block Allocator - Used to allocate blocks of DSP memory.
+ */
+struct sst_block_allocator {
+ u32 id;
+ u32 offset;
+ int size;
+ enum sst_mem_type type;
+};
+
+/*
+ * Runtime Module Instance - A module object can be instanciated multiple
+ * times within the DSP FW.
+ */
+struct sst_module_runtime {
+ struct sst_dsp *dsp;
+ int id;
+ struct sst_module *module; /* parent module we belong too */
+
+ u32 persistent_offset; /* private memory offset */
+ void *private;
+
+ struct list_head list;
+ struct list_head block_list; /* list of blocks used */
+};
+
+/*
+ * Runtime Module Context - The runtime context must be manually stored by the
+ * driver prior to enter S3 and restored after leaving S3. This should really be
+ * part of the memory context saved by the enter D3 message IPC ???
+ */
+struct sst_module_runtime_context {
+ dma_addr_t dma_buffer;
+ u32 *buffer;
+};
+
+/*
+ * Audio DSP Module State
+ */
+enum sst_module_state {
+ SST_MODULE_STATE_UNLOADED = 0, /* default state */
+ SST_MODULE_STATE_LOADED,
+ SST_MODULE_STATE_INITIALIZED, /* and inactive */
+ SST_MODULE_STATE_ACTIVE,
+};
+
+/*
+ * Audio DSP Generic Module.
+ *
+ * Each Firmware file can consist of 1..N modules. A module can span multiple
+ * ADSP memory blocks. The simplest FW will be a file with 1 module. A module
+ * can be instanciated multiple times in the DSP.
+ */
+struct sst_module {
+ struct sst_dsp *dsp;
+ struct sst_fw *sst_fw; /* parent FW we belong too */
+
+ /* module configuration */
+ u32 id;
+ u32 entry; /* module entry point */
+ s32 offset; /* module offset in firmware file */
+ u32 size; /* module size */
+ u32 scratch_size; /* global scratch memory required */
+ u32 persistent_size; /* private memory required */
+ enum sst_mem_type type; /* destination memory type */
+ u32 data_offset; /* offset in ADSP memory space */
+ void *data; /* module data */
+
+ /* runtime */
+ u32 usage_count; /* can be unloaded if count == 0 */
+ void *private; /* core doesn't touch this */
+
+ /* lists */
+ struct list_head block_list; /* Module list of blocks in use */
+ struct list_head list; /* DSP list of modules */
+ struct list_head list_fw; /* FW list of modules */
+ struct list_head runtime_list; /* list of runtime module objects*/
+
+ /* state */
+ enum sst_module_state state;
+};
+
+/*
+ * SST Memory Block operations.
+ */
+struct sst_block_ops {
+ int (*enable)(struct sst_mem_block *block);
+ int (*disable)(struct sst_mem_block *block);
+};
+
+/*
+ * SST Generic Memory Block.
+ *
+ * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be
+ * power gated.
+ */
+struct sst_mem_block {
+ struct sst_dsp *dsp;
+ struct sst_module *module; /* module that uses this block */
+
+ /* block config */
+ u32 offset; /* offset from base */
+ u32 size; /* block size */
+ u32 index; /* block index 0..N */
+ enum sst_mem_type type; /* block memory type IRAM/DRAM */
+ struct sst_block_ops *ops; /* block operations, if any */
+
+ /* block status */
+ u32 bytes_used; /* bytes in use by modules */
+ void *private; /* generic core does not touch this */
+ int users; /* number of modules using this block */
+
+ /* block lists */
+ struct list_head module_list; /* Module list of blocks */
+ struct list_head list; /* Map list of free/used blocks */
+};
+
+/*
+ * Generic SST Shim Interface.
+ */
+struct sst_dsp {
+
+ /* runtime */
+ struct sst_dsp_device *sst_dev;
+ spinlock_t spinlock; /* IPC locking */
+ struct mutex mutex; /* DSP FW lock */
+ struct device *dev;
+ struct device *dma_dev;
+ void *thread_context;
+ int irq;
+ u32 id;
+
+ /* list of free and used ADSP memory blocks */
+ struct list_head used_block_list;
+ struct list_head free_block_list;
+
+ /* 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;
+ struct list_head fw_list;
+
+ /* scratch buffer */
+ struct list_head scratch_block_list;
+ u32 scratch_offset;
+ u32 scratch_size;
+
+ /* platform data */
+ struct sst_pdata *pdata;
+
+ /* DMA FW loading */
+ struct sst_dma *dma;
+ bool fw_use_dma;
+};
+
+/* Size optimised DRAM/IRAM memcpy */
+static inline void sst_dsp_write(struct sst_dsp *sst, void *src,
+ u32 dest_offset, size_t bytes)
+{
+ sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes);
+}
+
+static inline void sst_dsp_read(struct sst_dsp *sst, void *dest,
+ u32 src_offset, size_t bytes)
+{
+ sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes);
+}
+
+static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst)
+{
+ return sst->thread_context;
+}
+
+/* Create/Free FW files - can contain multiple modules */
+struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
+ const struct firmware *fw, void *private);
+void sst_fw_free(struct sst_fw *sst_fw);
+void sst_fw_free_all(struct sst_dsp *dsp);
+int sst_fw_reload(struct sst_fw *sst_fw);
+void sst_fw_unload(struct sst_fw *sst_fw);
+
+/* Create/Free firmware modules */
+struct sst_module *sst_module_new(struct sst_fw *sst_fw,
+ struct sst_module_template *template, void *private);
+void sst_module_free(struct sst_module *module);
+struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id);
+int sst_module_alloc_blocks(struct sst_module *module);
+int sst_module_free_blocks(struct sst_module *module);
+
+/* Create/Free firmware module runtime instances */
+struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
+ int id, void *private);
+void sst_module_runtime_free(struct sst_module_runtime *runtime);
+struct sst_module_runtime *sst_module_runtime_get_from_id(
+ struct sst_module *module, u32 id);
+int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime,
+ int offset);
+int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime);
+int sst_module_runtime_save(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context);
+int sst_module_runtime_restore(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context);
+
+/* generic block allocation */
+int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list);
+int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list);
+
+/* scratch allocation */
+int sst_block_alloc_scratch(struct sst_dsp *dsp);
+void sst_block_free_scratch(struct sst_dsp *dsp);
+
+/* Register the DSPs memory blocks - would be nice to read from ACPI */
+struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
+ u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index,
+ void *private);
+void sst_mem_block_unregister_all(struct sst_dsp *dsp);
+
+/* Create/Free DMA resources */
+int sst_dma_new(struct sst_dsp *sst);
+void sst_dma_free(struct sst_dma *dma);
+
+u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset,
+ enum sst_mem_type type);
+#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..64e94212d
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp.c
@@ -0,0 +1,420 @@
+/*
+ * Intel Smart Sound Technology (SST) DSP Core Driver
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.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)
+{
+ memcpy_toio(addr + offset, &value, sizeof(value));
+}
+EXPORT_SYMBOL_GPL(sst_shim32_write64);
+
+u64 sst_shim32_read64(void __iomem *addr, u32 offset)
+{
+ u64 val;
+
+ memcpy_fromio(&val, addr + offset, sizeof(val));
+ return val;
+}
+EXPORT_SYMBOL_GPL(sst_shim32_read64);
+
+static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest,
+ u32 *src, size_t bytes)
+{
+ int i, words = bytes >> 2;
+
+ for (i = 0; i < words; i++)
+ writel(src[i], dest + i);
+}
+
+static inline void _sst_memcpy_fromio_32(u32 *dest,
+ const volatile __iomem u32 *src, size_t bytes)
+{
+ int i, words = bytes >> 2;
+
+ for (i = 0; i < words; i++)
+ dest[i] = readl(src + i);
+}
+
+void sst_memcpy_toio_32(struct sst_dsp *sst,
+ void __iomem *dest, void *src, size_t bytes)
+{
+ _sst_memcpy_toio_32(dest, src, bytes);
+}
+EXPORT_SYMBOL_GPL(sst_memcpy_toio_32);
+
+void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest,
+ void __iomem *src, size_t bytes)
+{
+ _sst_memcpy_fromio_32(dest, src, bytes);
+}
+EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32);
+
+/* 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_write64(struct sst_dsp *sst, u32 offset, u64 value)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ sst->ops->write64(sst->addr.shim, offset, value);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_write64);
+
+u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset)
+{
+ unsigned long flags;
+ u64 val;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ val = sst->ops->read64(sst->addr.shim, offset);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_read64);
+
+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);
+
+void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value)
+{
+ sst->ops->write64(sst->addr.shim, offset, value);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked);
+
+u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset)
+{
+ return sst->ops->read64(sst->addr.shim, offset);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_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);
+
+int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 value)
+{
+ bool change;
+ u64 old, new;
+
+ old = sst_dsp_shim_read64_unlocked(sst, offset);
+
+ new = (old & (~mask)) | (value & mask);
+
+ change = (old != new);
+ if (change)
+ sst_dsp_shim_write64_unlocked(sst, offset, new);
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_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);
+
+int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 value)
+{
+ unsigned long flags;
+ bool change;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+ return change;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64);
+
+void sst_dsp_dump(struct sst_dsp *sst)
+{
+ if (sst->ops->dump)
+ sst->ops->dump(sst);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_dump);
+
+void sst_dsp_reset(struct sst_dsp *sst)
+{
+ if (sst->ops->reset)
+ sst->ops->reset(sst);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_reset);
+
+int sst_dsp_boot(struct sst_dsp *sst)
+{
+ if (sst->ops->boot)
+ sst->ops->boot(sst);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_boot);
+
+int sst_dsp_wake(struct sst_dsp *sst)
+{
+ if (sst->ops->wake)
+ return sst->ops->wake(sst);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_wake);
+
+void sst_dsp_sleep(struct sst_dsp *sst)
+{
+ if (sst->ops->sleep)
+ sst->ops->sleep(sst);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_sleep);
+
+void sst_dsp_stall(struct sst_dsp *sst)
+{
+ if (sst->ops->stall)
+ sst->ops->stall(sst);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_stall);
+
+void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg)
+{
+ sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY);
+ trace_sst_ipc_msg_tx(msg);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx);
+
+u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp)
+{
+ u32 msg;
+
+ msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX);
+ trace_sst_ipc_msg_rx(msg);
+
+ return msg;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx);
+
+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);
+
+struct sst_dsp *sst_dsp_new(struct device *dev,
+ struct sst_dsp_device *sst_dev, struct sst_pdata *pdata)
+{
+ struct sst_dsp *sst;
+ int err;
+
+ dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id);
+
+ 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->dma_dev = pdata->dma_dev;
+ sst->thread_context = sst_dev->thread_context;
+ sst->sst_dev = sst_dev;
+ sst->id = pdata->id;
+ sst->irq = pdata->irq;
+ sst->ops = sst_dev->ops;
+ sst->pdata = pdata;
+ INIT_LIST_HEAD(&sst->used_block_list);
+ INIT_LIST_HEAD(&sst->free_block_list);
+ INIT_LIST_HEAD(&sst->module_list);
+ INIT_LIST_HEAD(&sst->fw_list);
+ INIT_LIST_HEAD(&sst->scratch_block_list);
+
+ /* Initialise SST Audio DSP */
+ if (sst->ops->init) {
+ err = sst->ops->init(sst, pdata);
+ if (err < 0)
+ return NULL;
+ }
+
+ /* Register the ISR */
+ err = request_threaded_irq(sst->irq, sst->ops->irq_handler,
+ sst_dev->thread, IRQF_SHARED, "AudioDSP", sst);
+ if (err)
+ goto irq_err;
+
+ err = sst_dma_new(sst);
+ if (err)
+ dev_warn(dev, "sst_dma_new failed %d\n", err);
+
+ return sst;
+
+irq_err:
+ if (sst->ops->free)
+ sst->ops->free(sst);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_new);
+
+void sst_dsp_free(struct sst_dsp *sst)
+{
+ free_irq(sst->irq, sst);
+ if (sst->ops->free)
+ sst->ops->free(sst);
+
+ sst_dma_free(sst->dma);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_free);
+
+/* 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..96aeb2556
--- /dev/null
+++ b/sound/soc/intel/common/sst-dsp.h
@@ -0,0 +1,285 @@
+/*
+ * Intel Smart Sound Technology (SST) Core
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __SOUND_SOC_SST_DSP_H
+#define __SOUND_SOC_SST_DSP_H
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+/* SST Device IDs */
+#define SST_DEV_ID_LYNX_POINT 0x33C8
+#define SST_DEV_ID_WILDCAT_POINT 0x3438
+#define SST_DEV_ID_BYT 0x0F28
+
+/* Supported SST DMA Devices */
+#define SST_DMA_TYPE_DW 1
+
+/* autosuspend delay 5s*/
+#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000)
+
+/* SST Shim register map
+ * The register naming can differ between products. Some products also
+ * contain extra functionality.
+ */
+#define SST_CSR 0x00
+#define SST_PISR 0x08
+#define SST_PIMR 0x10
+#define SST_ISRX 0x18
+#define SST_ISRD 0x20
+#define SST_IMRX 0x28
+#define SST_IMRD 0x30
+#define SST_IPCX 0x38 /* IPC IA -> SST */
+#define SST_IPCD 0x40 /* IPC SST -> IA */
+#define SST_ISRSC 0x48
+#define SST_ISRLPESC 0x50
+#define SST_IMRSC 0x58
+#define SST_IMRLPESC 0x60
+#define SST_IPCSC 0x68
+#define SST_IPCLPESC 0x70
+#define SST_CLKCTL 0x78
+#define SST_CSR2 0x80
+#define SST_LTRC 0xE0
+#define SST_HMDC 0xE8
+
+#define SST_SHIM_BEGIN SST_CSR
+#define SST_SHIM_END SST_HDMC
+
+#define SST_DBGO 0xF0
+
+#define SST_SHIM_SIZE 0x100
+#define SST_PWMCTRL 0x1000
+
+/* SST Shim Register bits
+ * The register bit naming can differ between products. Some products also
+ * contain extra functionality.
+ */
+
+/* CSR / CS */
+#define SST_CSR_RST (0x1 << 1)
+#define SST_CSR_SBCS0 (0x1 << 2)
+#define SST_CSR_SBCS1 (0x1 << 3)
+#define SST_CSR_DCS(x) (x << 4)
+#define SST_CSR_DCS_MASK (0x7 << 4)
+#define SST_CSR_STALL (0x1 << 10)
+#define SST_CSR_S0IOCS (0x1 << 21)
+#define SST_CSR_S1IOCS (0x1 << 23)
+#define SST_CSR_LPCS (0x1 << 31)
+#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS)
+#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1)
+#define SST_BYT_CSR_RST (0x1 << 0)
+#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1)
+#define SST_BYT_CSR_STALL (0x1 << 2)
+#define SST_BYT_CSR_PWAITMODE (0x1 << 3)
+
+/* ISRX / ISC */
+#define SST_ISRX_BUSY (0x1 << 1)
+#define SST_ISRX_DONE (0x1 << 0)
+#define SST_BYT_ISRX_REQUEST (0x1 << 1)
+
+/* ISRD / ISD */
+#define SST_ISRD_BUSY (0x1 << 1)
+#define SST_ISRD_DONE (0x1 << 0)
+
+/* IMRX / IMC */
+#define SST_IMRX_BUSY (0x1 << 1)
+#define SST_IMRX_DONE (0x1 << 0)
+#define SST_BYT_IMRX_REQUEST (0x1 << 1)
+
+/* IMRD / IMD */
+#define SST_IMRD_DONE (0x1 << 0)
+#define SST_IMRD_BUSY (0x1 << 1)
+#define SST_IMRD_SSP0 (0x1 << 16)
+#define SST_IMRD_DMAC0 (0x1 << 21)
+#define SST_IMRD_DMAC1 (0x1 << 22)
+#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1)
+
+/* IPCX / IPCC */
+#define SST_IPCX_DONE (0x1 << 30)
+#define SST_IPCX_BUSY (0x1 << 31)
+#define SST_BYT_IPCX_DONE ((u64)0x1 << 62)
+#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63)
+
+/* IPCD */
+#define SST_IPCD_DONE (0x1 << 30)
+#define SST_IPCD_BUSY (0x1 << 31)
+#define SST_BYT_IPCD_DONE ((u64)0x1 << 62)
+#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63)
+
+/* CLKCTL */
+#define SST_CLKCTL_SMOS(x) (x << 24)
+#define SST_CLKCTL_MASK (3 << 24)
+#define SST_CLKCTL_DCPLCG (1 << 18)
+#define SST_CLKCTL_SCOE1 (1 << 17)
+#define SST_CLKCTL_SCOE0 (1 << 16)
+
+/* CSR2 / CS2 */
+#define SST_CSR2_SDFD_SSP0 (1 << 1)
+#define SST_CSR2_SDFD_SSP1 (1 << 2)
+
+/* LTRC */
+#define SST_LTRC_VAL(x) (x << 0)
+
+/* HMDC */
+#define SST_HMDC_HDDA0(x) (x << 0)
+#define SST_HMDC_HDDA1(x) (x << 7)
+#define SST_HMDC_HDDA_E0_CH0 1
+#define SST_HMDC_HDDA_E0_CH1 2
+#define SST_HMDC_HDDA_E0_CH2 4
+#define SST_HMDC_HDDA_E0_CH3 8
+#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0)
+#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1)
+#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2)
+#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3)
+#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \
+ SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3)
+#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \
+ SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3)
+
+
+/* SST Vendor Defined Registers and bits */
+#define SST_VDRTCTL0 0xa0
+#define SST_VDRTCTL1 0xa4
+#define SST_VDRTCTL2 0xa8
+#define SST_VDRTCTL3 0xaC
+
+/* VDRTCTL0 */
+#define SST_VDRTCL0_D3PGD (1 << 0)
+#define SST_VDRTCL0_D3SRAMPGD (1 << 1)
+#define SST_VDRTCL0_DSRAMPGE_SHIFT 12
+#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT)
+#define SST_VDRTCL0_ISRAMPGE_SHIFT 2
+#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT)
+
+/* VDRTCTL2 */
+#define SST_VDRTCL2_DCLCGE (1 << 1)
+#define SST_VDRTCL2_DTCGE (1 << 10)
+#define SST_VDRTCL2_APLLSE_MASK (1 << 31)
+
+/* PMCS */
+#define SST_PMCS 0x84
+#define SST_PMCS_PS_MASK 0x3
+
+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;
+};
+
+/*
+ * SST Platform Data.
+ */
+struct sst_pdata {
+ /* ACPI data */
+ u32 lpe_base;
+ u32 lpe_size;
+ u32 pcicfg_base;
+ u32 pcicfg_size;
+ u32 fw_base;
+ u32 fw_size;
+ int irq;
+
+ /* Firmware */
+ const struct firmware *fw;
+
+ /* DMA */
+ int resindex_dma_base; /* other fields invalid if equals to -1 */
+ u32 dma_base;
+ u32 dma_size;
+ int dma_engine;
+ struct device *dma_dev;
+
+ /* DSP */
+ u32 id;
+ void *dsp;
+};
+
+/* Initialization */
+struct sst_dsp *sst_dsp_new(struct device *dev,
+ struct sst_dsp_device *sst_dev, struct sst_pdata *pdata);
+void sst_dsp_free(struct sst_dsp *sst);
+
+/* 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_write64(struct sst_dsp *sst, u32 offset, u64 value);
+u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset);
+int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 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_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value);
+u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset);
+int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 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);
+void sst_memcpy_toio_32(struct sst_dsp *sst,
+ void __iomem *dest, void *src, size_t bytes);
+void sst_memcpy_fromio_32(struct sst_dsp *sst,
+ void *dest, void __iomem *src, size_t bytes);
+
+/* DSP reset & boot */
+void sst_dsp_reset(struct sst_dsp *sst);
+int sst_dsp_boot(struct sst_dsp *sst);
+int sst_dsp_wake(struct sst_dsp *sst);
+void sst_dsp_sleep(struct sst_dsp *sst);
+void sst_dsp_stall(struct sst_dsp *sst);
+
+/* DMA */
+int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id);
+void sst_dsp_dma_put_channel(struct sst_dsp *dsp);
+int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size);
+int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size);
+
+/* Msg IO */
+void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg);
+u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp);
+
+/* Mailbox management */
+int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset,
+ size_t inbox_size, u32 outbox_offset, size_t outbox_size);
+void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes);
+void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes);
+void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes);
+void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes);
+void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes);
+
+/* Debug */
+void sst_dsp_dump(struct sst_dsp *sst);
+
+#endif
diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c
new file mode 100644
index 000000000..ebcca6dc4
--- /dev/null
+++ b/sound/soc/intel/common/sst-firmware.c
@@ -0,0 +1,1205 @@
+/*
+ * Intel SST Firmware Loader
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/firmware.h>
+#include <linux/export.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/pci.h>
+#include <linux/acpi.h>
+
+/* supported DMA engine drivers */
+#include <linux/platform_data/dma-dw.h>
+#include <linux/dma/dw.h>
+
+#include <asm/page.h>
+#include <asm/pgtable.h>
+
+#include "sst-dsp.h"
+#include "sst-dsp-priv.h"
+
+#define SST_DMA_RESOURCES 2
+#define SST_DSP_DMA_MAX_BURST 0x3
+#define SST_HSW_BLOCK_ANY 0xffffffff
+
+#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000
+
+struct sst_dma {
+ struct sst_dsp *sst;
+
+ struct dw_dma_chip *chip;
+
+ struct dma_async_tx_descriptor *desc;
+ struct dma_chan *ch;
+};
+
+static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes)
+{
+ /* __iowrite32_copy use 32bit size values so divide by 4 */
+ __iowrite32_copy((void *)dest, src, bytes/4);
+}
+
+static void sst_dma_transfer_complete(void *arg)
+{
+ struct sst_dsp *sst = (struct sst_dsp *)arg;
+
+ dev_dbg(sst->dev, "DMA: callback\n");
+}
+
+static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+{
+ struct dma_async_tx_descriptor *desc;
+ struct sst_dma *dma = sst->dma;
+
+ if (dma->ch == NULL) {
+ dev_err(sst->dev, "error: no DMA channel\n");
+ return -ENODEV;
+ }
+
+ dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n",
+ (unsigned long)src_addr, (unsigned long)dest_addr, size);
+
+ desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr,
+ src_addr, size, DMA_CTRL_ACK);
+ if (!desc){
+ dev_err(sst->dev, "error: dma prep memcpy failed\n");
+ return -EINVAL;
+ }
+
+ desc->callback = sst_dma_transfer_complete;
+ desc->callback_param = sst;
+
+ desc->tx_submit(desc);
+ dma_wait_for_async_tx(desc);
+
+ return 0;
+}
+
+/* copy to DSP */
+int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+{
+ return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP,
+ src_addr, size);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto);
+
+/* copy from DSP */
+int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+{
+ return sst_dsp_dma_copy(sst, dest_addr,
+ src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size);
+}
+EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom);
+
+/* remove module from memory - callers hold locks */
+static void block_list_remove(struct sst_dsp *dsp,
+ struct list_head *block_list)
+{
+ struct sst_mem_block *block, *tmp;
+ int err;
+
+ /* disable each block */
+ list_for_each_entry(block, block_list, module_list) {
+
+ if (block->ops && block->ops->disable) {
+ err = block->ops->disable(block);
+ if (err < 0)
+ dev_err(dsp->dev,
+ "error: cant disable block %d:%d\n",
+ block->type, block->index);
+ }
+ }
+
+ /* mark each block as free */
+ list_for_each_entry_safe(block, tmp, block_list, module_list) {
+ list_del(&block->module_list);
+ list_move(&block->list, &dsp->free_block_list);
+ dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ }
+}
+
+/* prepare the memory block to receive data from host - callers hold locks */
+static int block_list_prepare(struct sst_dsp *dsp,
+ struct list_head *block_list)
+{
+ struct sst_mem_block *block;
+ int ret = 0;
+
+ /* enable each block so that's it'e ready for data */
+ list_for_each_entry(block, block_list, module_list) {
+
+ if (block->ops && block->ops->enable && !block->users) {
+ ret = block->ops->enable(block);
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: cant disable block %d:%d\n",
+ block->type, block->index);
+ goto err;
+ }
+ }
+ }
+ return ret;
+
+err:
+ list_for_each_entry(block, block_list, module_list) {
+ if (block->ops && block->ops->disable)
+ block->ops->disable(block);
+ }
+ return ret;
+}
+
+static struct dw_dma_platform_data dw_pdata = {
+ .is_private = 1,
+ .chan_allocation_order = CHAN_ALLOCATION_ASCENDING,
+ .chan_priority = CHAN_PRIORITY_ASCENDING,
+};
+
+static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem,
+ int irq)
+{
+ struct dw_dma_chip *chip;
+ int err;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ chip->irq = irq;
+ chip->regs = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(chip->regs))
+ return ERR_CAST(chip->regs);
+
+ err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31));
+ if (err)
+ return ERR_PTR(err);
+
+ chip->dev = dev;
+ err = dw_dma_probe(chip, &dw_pdata);
+ if (err)
+ return ERR_PTR(err);
+
+ return chip;
+}
+
+static void dw_remove(struct dw_dma_chip *chip)
+{
+ dw_dma_remove(chip);
+}
+
+static bool dma_chan_filter(struct dma_chan *chan, void *param)
+{
+ struct sst_dsp *dsp = (struct sst_dsp *)param;
+
+ return chan->device->dev == dsp->dma_dev;
+}
+
+int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id)
+{
+ struct sst_dma *dma = dsp->dma;
+ struct dma_slave_config slave;
+ dma_cap_mask_t mask;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dma->ch = dma_request_channel(mask, dma_chan_filter, dsp);
+ if (dma->ch == NULL) {
+ dev_err(dsp->dev, "error: DMA request channel failed\n");
+ return -EIO;
+ }
+
+ memset(&slave, 0, sizeof(slave));
+ slave.direction = DMA_MEM_TO_DEV;
+ slave.src_addr_width =
+ slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST;
+
+ ret = dmaengine_slave_config(dma->ch, &slave);
+ if (ret) {
+ dev_err(dsp->dev, "error: unable to set DMA slave config %d\n",
+ ret);
+ dma_release_channel(dma->ch);
+ dma->ch = NULL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel);
+
+void sst_dsp_dma_put_channel(struct sst_dsp *dsp)
+{
+ struct sst_dma *dma = dsp->dma;
+
+ if (!dma->ch)
+ return;
+
+ dma_release_channel(dma->ch);
+ dma->ch = NULL;
+}
+EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel);
+
+int sst_dma_new(struct sst_dsp *sst)
+{
+ struct sst_pdata *sst_pdata = sst->pdata;
+ struct sst_dma *dma;
+ struct resource mem;
+ const char *dma_dev_name;
+ int ret = 0;
+
+ if (sst->pdata->resindex_dma_base == -1)
+ /* DMA is not used, return and squelsh error messages */
+ return 0;
+
+ /* configure the correct platform data for whatever DMA engine
+ * is attached to the ADSP IP. */
+ switch (sst->pdata->dma_engine) {
+ case SST_DMA_TYPE_DW:
+ dma_dev_name = "dw_dmac";
+ break;
+ default:
+ dev_err(sst->dev, "error: invalid DMA engine %d\n",
+ sst->pdata->dma_engine);
+ return -EINVAL;
+ }
+
+ dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ dma->sst = sst;
+
+ memset(&mem, 0, sizeof(mem));
+
+ mem.start = sst->addr.lpe_base + sst_pdata->dma_base;
+ mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1;
+ mem.flags = IORESOURCE_MEM;
+
+ /* now register DMA engine device */
+ dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq);
+ if (IS_ERR(dma->chip)) {
+ dev_err(sst->dev, "error: DMA device register failed\n");
+ ret = PTR_ERR(dma->chip);
+ goto err_dma_dev;
+ }
+
+ sst->dma = dma;
+ sst->fw_use_dma = true;
+ return 0;
+
+err_dma_dev:
+ devm_kfree(sst->dev, dma);
+ return ret;
+}
+EXPORT_SYMBOL(sst_dma_new);
+
+void sst_dma_free(struct sst_dma *dma)
+{
+
+ if (dma == NULL)
+ return;
+
+ if (dma->ch)
+ dma_release_channel(dma->ch);
+
+ if (dma->chip)
+ dw_remove(dma->chip);
+
+}
+EXPORT_SYMBOL(sst_dma_free);
+
+/* create new generic firmware object */
+struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
+ const struct firmware *fw, void *private)
+{
+ struct sst_fw *sst_fw;
+ int err;
+
+ if (!dsp->ops->parse_fw)
+ return NULL;
+
+ sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL);
+ if (sst_fw == NULL)
+ return NULL;
+
+ sst_fw->dsp = dsp;
+ sst_fw->private = private;
+ sst_fw->size = fw->size;
+
+ /* allocate DMA buffer to store FW data */
+ sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size,
+ &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL);
+ if (!sst_fw->dma_buf) {
+ dev_err(dsp->dev, "error: DMA alloc failed\n");
+ kfree(sst_fw);
+ return NULL;
+ }
+
+ /* copy FW data to DMA-able memory */
+ memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size);
+
+ if (dsp->fw_use_dma) {
+ err = sst_dsp_dma_get_channel(dsp, 0);
+ if (err < 0)
+ goto chan_err;
+ }
+
+ /* call core specific FW paser to load FW data into DSP */
+ err = dsp->ops->parse_fw(sst_fw);
+ if (err < 0) {
+ dev_err(dsp->dev, "error: parse fw failed %d\n", err);
+ goto parse_err;
+ }
+
+ if (dsp->fw_use_dma)
+ sst_dsp_dma_put_channel(dsp);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&sst_fw->list, &dsp->fw_list);
+ mutex_unlock(&dsp->mutex);
+
+ return sst_fw;
+
+parse_err:
+ if (dsp->fw_use_dma)
+ sst_dsp_dma_put_channel(dsp);
+chan_err:
+ dma_free_coherent(dsp->dma_dev, sst_fw->size,
+ sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ sst_fw->dma_buf = NULL;
+ kfree(sst_fw);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(sst_fw_new);
+
+int sst_fw_reload(struct sst_fw *sst_fw)
+{
+ struct sst_dsp *dsp = sst_fw->dsp;
+ int ret;
+
+ dev_dbg(dsp->dev, "reloading firmware\n");
+
+ /* call core specific FW paser to load FW data into DSP */
+ ret = dsp->ops->parse_fw(sst_fw);
+ if (ret < 0)
+ dev_err(dsp->dev, "error: parse fw failed %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_fw_reload);
+
+void sst_fw_unload(struct sst_fw *sst_fw)
+{
+ struct sst_dsp *dsp = sst_fw->dsp;
+ struct sst_module *module, *mtmp;
+ struct sst_module_runtime *runtime, *rtmp;
+
+ dev_dbg(dsp->dev, "unloading firmware\n");
+
+ mutex_lock(&dsp->mutex);
+
+ /* check module by module */
+ list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) {
+ if (module->sst_fw == sst_fw) {
+
+ /* remove runtime modules */
+ list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) {
+
+ block_list_remove(dsp, &runtime->block_list);
+ list_del(&runtime->list);
+ kfree(runtime);
+ }
+
+ /* now remove the module */
+ block_list_remove(dsp, &module->block_list);
+ list_del(&module->list);
+ kfree(module);
+ }
+ }
+
+ /* remove all scratch blocks */
+ block_list_remove(dsp, &dsp->scratch_block_list);
+
+ mutex_unlock(&dsp->mutex);
+}
+EXPORT_SYMBOL_GPL(sst_fw_unload);
+
+/* free single firmware object */
+void sst_fw_free(struct sst_fw *sst_fw)
+{
+ struct sst_dsp *dsp = sst_fw->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&sst_fw->list);
+ mutex_unlock(&dsp->mutex);
+
+ if (sst_fw->dma_buf)
+ dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ kfree(sst_fw);
+}
+EXPORT_SYMBOL_GPL(sst_fw_free);
+
+/* free all firmware objects */
+void sst_fw_free_all(struct sst_dsp *dsp)
+{
+ struct sst_fw *sst_fw, *t;
+
+ mutex_lock(&dsp->mutex);
+ list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) {
+
+ list_del(&sst_fw->list);
+ dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ kfree(sst_fw);
+ }
+ mutex_unlock(&dsp->mutex);
+}
+EXPORT_SYMBOL_GPL(sst_fw_free_all);
+
+/* create a new SST generic module from FW template */
+struct sst_module *sst_module_new(struct sst_fw *sst_fw,
+ struct sst_module_template *template, void *private)
+{
+ struct sst_dsp *dsp = sst_fw->dsp;
+ struct sst_module *sst_module;
+
+ sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL);
+ if (sst_module == NULL)
+ return NULL;
+
+ sst_module->id = template->id;
+ sst_module->dsp = dsp;
+ sst_module->sst_fw = sst_fw;
+ sst_module->scratch_size = template->scratch_size;
+ sst_module->persistent_size = template->persistent_size;
+ sst_module->entry = template->entry;
+ sst_module->state = SST_MODULE_STATE_UNLOADED;
+
+ INIT_LIST_HEAD(&sst_module->block_list);
+ INIT_LIST_HEAD(&sst_module->runtime_list);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&sst_module->list, &dsp->module_list);
+ mutex_unlock(&dsp->mutex);
+
+ return sst_module;
+}
+EXPORT_SYMBOL_GPL(sst_module_new);
+
+/* free firmware module and remove from available list */
+void sst_module_free(struct sst_module *sst_module)
+{
+ struct sst_dsp *dsp = sst_module->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&sst_module->list);
+ mutex_unlock(&dsp->mutex);
+
+ kfree(sst_module);
+}
+EXPORT_SYMBOL_GPL(sst_module_free);
+
+struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
+ int id, void *private)
+{
+ struct sst_dsp *dsp = module->dsp;
+ struct sst_module_runtime *runtime;
+
+ runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
+ if (runtime == NULL)
+ return NULL;
+
+ runtime->id = id;
+ runtime->dsp = dsp;
+ runtime->module = module;
+ INIT_LIST_HEAD(&runtime->block_list);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&runtime->list, &module->runtime_list);
+ mutex_unlock(&dsp->mutex);
+
+ return runtime;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_new);
+
+void sst_module_runtime_free(struct sst_module_runtime *runtime)
+{
+ struct sst_dsp *dsp = runtime->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&runtime->list);
+ mutex_unlock(&dsp->mutex);
+
+ kfree(runtime);
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_free);
+
+static struct sst_mem_block *find_block(struct sst_dsp *dsp,
+ struct sst_block_allocator *ba)
+{
+ struct sst_mem_block *block;
+
+ list_for_each_entry(block, &dsp->free_block_list, list) {
+ if (block->type == ba->type && block->offset == ba->offset)
+ return block;
+ }
+
+ return NULL;
+}
+
+/* Block allocator must be on block boundary */
+static int block_alloc_contiguous(struct sst_dsp *dsp,
+ struct sst_block_allocator *ba, struct list_head *block_list)
+{
+ struct list_head tmp = LIST_HEAD_INIT(tmp);
+ struct sst_mem_block *block;
+ u32 block_start = SST_HSW_BLOCK_ANY;
+ int size = ba->size, offset = ba->offset;
+
+ while (ba->size > 0) {
+
+ block = find_block(dsp, ba);
+ if (!block) {
+ list_splice(&tmp, &dsp->free_block_list);
+
+ ba->size = size;
+ ba->offset = offset;
+ return -ENOMEM;
+ }
+
+ list_move_tail(&block->list, &tmp);
+ ba->offset += block->size;
+ ba->size -= block->size;
+ }
+ ba->size = size;
+ ba->offset = offset;
+
+ list_for_each_entry(block, &tmp, list) {
+
+ if (block->offset < block_start)
+ block_start = block->offset;
+
+ list_add(&block->module_list, block_list);
+
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ }
+
+ list_splice(&tmp, &dsp->used_block_list);
+ return 0;
+}
+
+/* allocate first free DSP blocks for data - callers hold locks */
+static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+{
+ struct sst_mem_block *block, *tmp;
+ int ret = 0;
+
+ if (ba->size == 0)
+ return 0;
+
+ /* find first free whole blocks that can hold module */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ if (ba->size > block->size)
+ continue;
+
+ ba->offset = block->offset;
+ block->bytes_used = ba->size % block->size;
+ list_add(&block->module_list, block_list);
+ list_move(&block->list, &dsp->used_block_list);
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ return 0;
+ }
+
+ /* then find free multiple blocks that can hold module */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ /* do we span > 1 blocks */
+ if (ba->size > block->size) {
+
+ /* align ba to block boundary */
+ ba->offset = block->offset;
+
+ ret = block_alloc_contiguous(dsp, ba, block_list);
+ if (ret == 0)
+ return ret;
+
+ }
+ }
+
+ /* not enough free block space */
+ return -ENOMEM;
+}
+
+int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+{
+ int ret;
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
+ ba->size, ba->offset, ba->type);
+
+ mutex_lock(&dsp->mutex);
+
+ ret = block_alloc(dsp, ba, block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret);
+ goto out;
+ }
+
+ /* prepare DSP blocks for module usage */
+ ret = block_list_prepare(dsp, block_list);
+ if (ret < 0)
+ dev_err(dsp->dev, "error: prepare failed\n");
+
+out:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_alloc_blocks);
+
+int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list)
+{
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_free_blocks);
+
+/* allocate memory blocks for static module addresses - callers hold locks */
+static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+{
+ struct sst_mem_block *block, *tmp;
+ struct sst_block_allocator ba_tmp = *ba;
+ u32 end = ba->offset + ba->size, block_end;
+ int err;
+
+ /* only IRAM/DRAM blocks are managed */
+ if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM)
+ return 0;
+
+ /* are blocks already attached to this module */
+ list_for_each_entry_safe(block, tmp, block_list, module_list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ block_end = block->offset + block->size;
+
+ /* find block that holds section */
+ if (ba->offset >= block->offset && end <= block_end)
+ return 0;
+
+ /* does block span more than 1 section */
+ if (ba->offset >= block->offset && ba->offset < block_end) {
+
+ /* align ba to block boundary */
+ ba_tmp.size -= block_end - ba->offset;
+ ba_tmp.offset = block_end;
+ err = block_alloc_contiguous(dsp, &ba_tmp, block_list);
+ if (err < 0)
+ return -ENOMEM;
+
+ /* module already owns blocks */
+ return 0;
+ }
+ }
+
+ /* find first free blocks that can hold section in free list */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+ block_end = block->offset + block->size;
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ /* find block that holds section */
+ if (ba->offset >= block->offset && end <= block_end) {
+
+ /* add block */
+ list_move(&block->list, &dsp->used_block_list);
+ list_add(&block->module_list, block_list);
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ return 0;
+ }
+
+ /* does block span more than 1 section */
+ if (ba->offset >= block->offset && ba->offset < block_end) {
+
+ /* add block */
+ list_move(&block->list, &dsp->used_block_list);
+ list_add(&block->module_list, block_list);
+ /* align ba to block boundary */
+ ba_tmp.size -= block_end - ba->offset;
+ ba_tmp.offset = block_end;
+
+ err = block_alloc_contiguous(dsp, &ba_tmp, block_list);
+ if (err < 0)
+ return -ENOMEM;
+
+ return 0;
+ }
+ }
+
+ return -ENOMEM;
+}
+
+/* Load fixed module data into DSP memory blocks */
+int sst_module_alloc_blocks(struct sst_module *module)
+{
+ struct sst_dsp *dsp = module->dsp;
+ struct sst_fw *sst_fw = module->sst_fw;
+ struct sst_block_allocator ba;
+ int ret;
+
+ memset(&ba, 0, sizeof(ba));
+ ba.size = module->size;
+ ba.type = module->type;
+ ba.offset = module->offset;
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
+ ba.size, ba.offset, ba.type);
+
+ mutex_lock(&dsp->mutex);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &module->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: no free blocks for section at offset 0x%x size 0x%x\n",
+ module->offset, module->size);
+ mutex_unlock(&dsp->mutex);
+ return -ENOMEM;
+ }
+
+ /* prepare DSP blocks for module copy */
+ ret = block_list_prepare(dsp, &module->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: fw module prepare failed\n");
+ goto err;
+ }
+
+ /* copy partial module data to blocks */
+ if (dsp->fw_use_dma) {
+ ret = sst_dsp_dma_copyto(dsp,
+ dsp->addr.lpe_base + module->offset,
+ sst_fw->dmable_fw_paddr + module->data_offset,
+ module->size);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: module copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(dsp->addr.lpe + module->offset, module->data,
+ module->size);
+
+ mutex_unlock(&dsp->mutex);
+ return ret;
+
+err:
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_module_alloc_blocks);
+
+/* Unload entire module from DSP memory */
+int sst_module_free_blocks(struct sst_module *module)
+{
+ struct sst_dsp *dsp = module->dsp;
+
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_module_free_blocks);
+
+int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime,
+ int offset)
+{
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ struct sst_block_allocator ba;
+ int ret;
+
+ if (module->persistent_size == 0)
+ return 0;
+
+ memset(&ba, 0, sizeof(ba));
+ ba.size = module->persistent_size;
+ ba.type = SST_MEM_DRAM;
+
+ mutex_lock(&dsp->mutex);
+
+ /* do we need to allocate at a fixed address ? */
+ if (offset != 0) {
+
+ ba.offset = offset;
+
+ dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n",
+ ba.size, ba.type, ba.offset);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &runtime->block_list);
+
+ } else {
+ dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n",
+ ba.size, ba.type);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc(dsp, &ba, &runtime->block_list);
+ }
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: no free blocks for runtime module size 0x%x\n",
+ module->persistent_size);
+ mutex_unlock(&dsp->mutex);
+ return -ENOMEM;
+ }
+ runtime->persistent_offset = ba.offset;
+
+ /* prepare DSP blocks for module copy */
+ ret = block_list_prepare(dsp, &runtime->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: runtime block prepare failed\n");
+ goto err;
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return ret;
+
+err:
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks);
+
+int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime)
+{
+ struct sst_dsp *dsp = runtime->dsp;
+
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &runtime->block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks);
+
+int sst_module_runtime_save(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context)
+{
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ int ret = 0;
+
+ dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n",
+ runtime->id, runtime->persistent_offset,
+ module->persistent_size);
+
+ context->buffer = dma_alloc_coherent(dsp->dma_dev,
+ module->persistent_size,
+ &context->dma_buffer, GFP_DMA | GFP_KERNEL);
+ if (!context->buffer) {
+ dev_err(dsp->dev, "error: DMA context alloc failed\n");
+ return -ENOMEM;
+ }
+
+ mutex_lock(&dsp->mutex);
+
+ if (dsp->fw_use_dma) {
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0)
+ goto err;
+
+ ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer,
+ dsp->addr.lpe_base + runtime->persistent_offset,
+ module->persistent_size);
+ sst_dsp_dma_put_channel(dsp);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: context copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(context->buffer, dsp->addr.lpe +
+ runtime->persistent_offset,
+ module->persistent_size);
+
+err:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_save);
+
+int sst_module_runtime_restore(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context)
+{
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ int ret = 0;
+
+ dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n",
+ runtime->id, runtime->persistent_offset,
+ module->persistent_size);
+
+ mutex_lock(&dsp->mutex);
+
+ if (!context->buffer) {
+ dev_info(dsp->dev, "no context buffer need to restore!\n");
+ goto err;
+ }
+
+ if (dsp->fw_use_dma) {
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0)
+ goto err;
+
+ ret = sst_dsp_dma_copyto(dsp,
+ dsp->addr.lpe_base + runtime->persistent_offset,
+ context->dma_buffer, module->persistent_size);
+ sst_dsp_dma_put_channel(dsp);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: module copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset,
+ context->buffer, module->persistent_size);
+
+ dma_free_coherent(dsp->dma_dev, module->persistent_size,
+ context->buffer, context->dma_buffer);
+ context->buffer = NULL;
+
+err:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_restore);
+
+/* register a DSP memory block for use with FW based modules */
+struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
+ u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index,
+ void *private)
+{
+ struct sst_mem_block *block;
+
+ block = kzalloc(sizeof(*block), GFP_KERNEL);
+ if (block == NULL)
+ return NULL;
+
+ block->offset = offset;
+ block->size = size;
+ block->index = index;
+ block->type = type;
+ block->dsp = dsp;
+ block->private = private;
+ block->ops = ops;
+
+ mutex_lock(&dsp->mutex);
+ list_add(&block->list, &dsp->free_block_list);
+ mutex_unlock(&dsp->mutex);
+
+ return block;
+}
+EXPORT_SYMBOL_GPL(sst_mem_block_register);
+
+/* unregister all DSP memory blocks */
+void sst_mem_block_unregister_all(struct sst_dsp *dsp)
+{
+ struct sst_mem_block *block, *tmp;
+
+ mutex_lock(&dsp->mutex);
+
+ /* unregister used blocks */
+ list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) {
+ list_del(&block->list);
+ kfree(block);
+ }
+
+ /* unregister free blocks */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+ list_del(&block->list);
+ kfree(block);
+ }
+
+ mutex_unlock(&dsp->mutex);
+}
+EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all);
+
+/* allocate scratch buffer blocks */
+int sst_block_alloc_scratch(struct sst_dsp *dsp)
+{
+ struct sst_module *module;
+ struct sst_block_allocator ba;
+ int ret;
+
+ mutex_lock(&dsp->mutex);
+
+ /* calculate required scratch size */
+ dsp->scratch_size = 0;
+ list_for_each_entry(module, &dsp->module_list, list) {
+ dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n",
+ module->id, module->scratch_size);
+ if (dsp->scratch_size < module->scratch_size)
+ dsp->scratch_size = module->scratch_size;
+ }
+
+ dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n",
+ dsp->scratch_size);
+
+ if (dsp->scratch_size == 0) {
+ dev_info(dsp->dev, "no modules need scratch buffer\n");
+ mutex_unlock(&dsp->mutex);
+ return 0;
+ }
+
+ /* allocate blocks for module scratch buffers */
+ dev_dbg(dsp->dev, "allocating scratch blocks\n");
+
+ ba.size = dsp->scratch_size;
+ ba.type = SST_MEM_DRAM;
+
+ /* do we need to allocate at fixed offset */
+ if (dsp->scratch_offset != 0) {
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n",
+ ba.size, ba.type, ba.offset);
+
+ ba.offset = dsp->scratch_offset;
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list);
+
+ } else {
+ dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n",
+ ba.size, ba.type);
+
+ ba.offset = 0;
+ ret = block_alloc(dsp, &ba, &dsp->scratch_block_list);
+ }
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: can't alloc scratch blocks\n");
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+
+ ret = block_list_prepare(dsp, &dsp->scratch_block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: scratch block prepare failed\n");
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+
+ /* assign the same offset of scratch to each module */
+ dsp->scratch_offset = ba.offset;
+ mutex_unlock(&dsp->mutex);
+ return dsp->scratch_size;
+}
+EXPORT_SYMBOL_GPL(sst_block_alloc_scratch);
+
+/* free all scratch blocks */
+void sst_block_free_scratch(struct sst_dsp *dsp)
+{
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &dsp->scratch_block_list);
+ mutex_unlock(&dsp->mutex);
+}
+EXPORT_SYMBOL_GPL(sst_block_free_scratch);
+
+/* get a module from it's unique ID */
+struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id)
+{
+ struct sst_module *module;
+
+ mutex_lock(&dsp->mutex);
+
+ list_for_each_entry(module, &dsp->module_list, list) {
+ if (module->id == id) {
+ mutex_unlock(&dsp->mutex);
+ return module;
+ }
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(sst_module_get_from_id);
+
+struct sst_module_runtime *sst_module_runtime_get_from_id(
+ struct sst_module *module, u32 id)
+{
+ struct sst_module_runtime *runtime;
+ struct sst_dsp *dsp = module->dsp;
+
+ mutex_lock(&dsp->mutex);
+
+ list_for_each_entry(runtime, &module->runtime_list, list) {
+ if (runtime->id == id) {
+ mutex_unlock(&dsp->mutex);
+ return runtime;
+ }
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id);
+
+/* returns block address in DSP address space */
+u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset,
+ enum sst_mem_type type)
+{
+ switch (type) {
+ case SST_MEM_IRAM:
+ return offset - dsp->addr.iram_offset +
+ dsp->addr.dsp_iram_offset;
+ case SST_MEM_DRAM:
+ return offset - dsp->addr.dram_offset +
+ dsp->addr.dsp_dram_offset;
+ default:
+ return 0;
+ }
+}
+EXPORT_SYMBOL_GPL(sst_dsp_get_offset);
diff --git a/sound/soc/intel/common/sst-ipc.c b/sound/soc/intel/common/sst-ipc.c
new file mode 100644
index 000000000..4b62a5538
--- /dev/null
+++ b/sound/soc/intel/common/sst-ipc.c
@@ -0,0 +1,294 @@
+/*
+ * Intel SST generic IPC Support
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#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 <linux/kthread.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, void *rx_data)
+{
+ 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 (msg->rx_size)
+ memcpy(rx_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, u64 header,
+ void *tx_data, size_t tx_bytes, void *rx_data,
+ size_t rx_bytes, 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->header = header;
+ msg->tx_size = tx_bytes;
+ msg->rx_size = rx_bytes;
+ msg->wait = wait;
+ msg->errno = 0;
+ msg->pending = false;
+ msg->complete = false;
+
+ if ((tx_bytes) && (ipc->ops.tx_data_copy != NULL))
+ ipc->ops.tx_data_copy(msg, tx_data, tx_bytes);
+
+ list_add_tail(&msg->list, &ipc->tx_list);
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+
+ queue_kthread_work(&ipc->kworker, &ipc->kwork);
+
+ if (wait)
+ return tx_wait_done(ipc, msg, rx_data);
+ else
+ return 0;
+}
+
+static int msg_empty_list_init(struct sst_generic_ipc *ipc)
+{
+ int i;
+
+ ipc->msg = kzalloc(sizeof(struct ipc_message) *
+ IPC_EMPTY_LIST_SIZE, GFP_KERNEL);
+ if (ipc->msg == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) {
+ init_waitqueue_head(&ipc->msg[i].waitq);
+ list_add(&ipc->msg[i].list, &ipc->empty_list);
+ }
+
+ return 0;
+}
+
+static void ipc_tx_msgs(struct kthread_work *work)
+{
+ struct sst_generic_ipc *ipc =
+ container_of(work, struct sst_generic_ipc, kwork);
+ struct ipc_message *msg;
+ unsigned long flags;
+ u64 ipcx;
+
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+
+ if (list_empty(&ipc->tx_list) || ipc->pending) {
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+ return;
+ }
+
+ /* if the DSP is busy, we will TX messages after IRQ.
+ * also postpone if we are in the middle of procesing completion irq*/
+ ipcx = sst_dsp_shim_read_unlocked(ipc->dsp, SST_IPCX);
+ if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) {
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+ return;
+ }
+
+ 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_irqrestore(&ipc->dsp->spinlock, flags);
+}
+
+int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header,
+ void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes)
+{
+ return ipc_tx_message(ipc, header, tx_data, tx_bytes,
+ rx_data, rx_bytes, 1);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_message_wait);
+
+int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header,
+ void *tx_data, size_t tx_bytes)
+{
+ return ipc_tx_message(ipc, header, tx_data, tx_bytes,
+ NULL, 0, 0);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nowait);
+
+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);
+
+ 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->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);
+
+void sst_ipc_drop_all(struct sst_generic_ipc *ipc)
+{
+ struct ipc_message *msg, *tmp;
+ unsigned long flags;
+ int tx_drop_cnt = 0, rx_drop_cnt = 0;
+
+ /* drop all TX and Rx messages before we stall + reset DSP */
+ spin_lock_irqsave(&ipc->dsp->spinlock, flags);
+
+ list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) {
+ list_move(&msg->list, &ipc->empty_list);
+ tx_drop_cnt++;
+ }
+
+ list_for_each_entry_safe(msg, tmp, &ipc->rx_list, list) {
+ list_move(&msg->list, &ipc->empty_list);
+ rx_drop_cnt++;
+ }
+
+ spin_unlock_irqrestore(&ipc->dsp->spinlock, flags);
+
+ if (tx_drop_cnt || rx_drop_cnt)
+ dev_err(ipc->dev, "dropped IPC msg RX=%d, TX=%d\n",
+ tx_drop_cnt, rx_drop_cnt);
+}
+EXPORT_SYMBOL_GPL(sst_ipc_drop_all);
+
+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;
+
+ /* start the IPC message thread */
+ init_kthread_worker(&ipc->kworker);
+ ipc->tx_thread = kthread_run(kthread_worker_fn,
+ &ipc->kworker, "%s",
+ dev_name(ipc->dev));
+ if (IS_ERR(ipc->tx_thread)) {
+ dev_err(ipc->dev, "error: failed to create message TX task\n");
+ ret = PTR_ERR(ipc->tx_thread);
+ kfree(ipc->msg);
+ return ret;
+ }
+
+ init_kthread_work(&ipc->kwork, ipc_tx_msgs);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sst_ipc_init);
+
+void sst_ipc_fini(struct sst_generic_ipc *ipc)
+{
+ if (ipc->tx_thread)
+ kthread_stop(ipc->tx_thread);
+
+ if (ipc->msg)
+ 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..125ea451a
--- /dev/null
+++ b/sound/soc/intel/common/sst-ipc.h
@@ -0,0 +1,91 @@
+/*
+ * Intel SST generic IPC Support
+ *
+ * Copyright (C) 2015, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#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>
+#include <linux/kthread.h>
+
+#define IPC_MAX_MAILBOX_BYTES 256
+
+struct ipc_message {
+ struct list_head list;
+ u64 header;
+
+ /* direction wrt host CPU */
+ char tx_data[IPC_MAX_MAILBOX_BYTES];
+ size_t tx_size;
+ char rx_data[IPC_MAX_MAILBOX_BYTES];
+ size_t rx_size;
+
+ wait_queue_head_t waitq;
+ bool pending;
+ bool complete;
+ bool wait;
+ int errno;
+};
+
+struct sst_generic_ipc;
+
+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);
+};
+
+/* 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 kthread_worker kworker;
+ struct kthread_work kwork;
+ bool pending;
+ struct ipc_message *msg;
+
+ struct sst_plat_ipc_ops ops;
+};
+
+int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header,
+ void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes);
+
+int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header,
+ void *tx_data, size_t tx_bytes);
+
+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);
+
+void sst_ipc_drop_all(struct sst_generic_ipc *ipc);
+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/haswell/Makefile b/sound/soc/intel/haswell/Makefile
new file mode 100644
index 000000000..9c1723112
--- /dev/null
+++ b/sound/soc/intel/haswell/Makefile
@@ -0,0 +1,4 @@
+snd-soc-sst-haswell-pcm-objs := \
+ sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o
+
+obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o
diff --git a/sound/soc/intel/haswell/sst-haswell-dsp.c b/sound/soc/intel/haswell/sst-haswell-dsp.c
new file mode 100644
index 000000000..7f94920c8
--- /dev/null
+++ b/sound/soc/intel/haswell/sst-haswell-dsp.c
@@ -0,0 +1,709 @@
+/*
+ * Intel Haswell SST DSP driver
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "../haswell/sst-haswell-ipc.h"
+
+#include <trace/events/hswadsp.h>
+
+#define SST_HSW_FW_SIGNATURE_SIZE 4
+#define SST_HSW_FW_SIGN "$SST"
+#define SST_HSW_FW_LIB_SIGN "$LIB"
+
+#define SST_WPT_SHIM_OFFSET 0xFB000
+#define SST_LP_SHIM_OFFSET 0xE7000
+#define SST_WPT_IRAM_OFFSET 0xA0000
+#define SST_LP_IRAM_OFFSET 0x80000
+#define SST_WPT_DSP_DRAM_OFFSET 0x400000
+#define SST_WPT_DSP_IRAM_OFFSET 0x00000
+#define SST_LPT_DSP_DRAM_OFFSET 0x400000
+#define SST_LPT_DSP_IRAM_OFFSET 0x00000
+
+#define SST_SHIM_PM_REG 0x84
+
+#define SST_HSW_IRAM 1
+#define SST_HSW_DRAM 2
+#define SST_HSW_REGS 3
+
+struct dma_block_info {
+ __le32 type; /* IRAM/DRAM */
+ __le32 size; /* Bytes */
+ __le32 ram_offset; /* Offset in I/DRAM */
+ __le32 rsvd; /* Reserved field */
+} __attribute__((packed));
+
+struct fw_module_info {
+ __le32 persistent_size;
+ __le32 scratch_size;
+} __attribute__((packed));
+
+struct fw_header {
+ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */
+ __le32 file_size; /* size of fw minus this header */
+ __le32 modules; /* # of modules */
+ __le32 file_format; /* version of header format */
+ __le32 reserved[4];
+} __attribute__((packed));
+
+struct fw_module_header {
+ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */
+ __le32 mod_size; /* size of module */
+ __le32 blocks; /* # of blocks */
+ __le16 padding;
+ __le16 type; /* codec type, pp lib */
+ __le32 entry_point;
+ struct fw_module_info info;
+} __attribute__((packed));
+
+static void hsw_free(struct sst_dsp *sst);
+
+static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
+ struct fw_module_header *module)
+{
+ struct dma_block_info *block;
+ struct sst_module *mod;
+ struct sst_module_template template;
+ int count, ret;
+ void __iomem *ram;
+
+ /* TODO: allowed module types need to be configurable */
+ if (module->type != SST_HSW_MODULE_BASE_FW
+ && module->type != SST_HSW_MODULE_PCM_SYSTEM
+ && module->type != SST_HSW_MODULE_PCM
+ && module->type != SST_HSW_MODULE_PCM_REFERENCE
+ && module->type != SST_HSW_MODULE_PCM_CAPTURE
+ && module->type != SST_HSW_MODULE_WAVES
+ && module->type != SST_HSW_MODULE_LPAL)
+ return 0;
+
+ dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n",
+ module->signature, module->mod_size,
+ module->blocks, module->type);
+ dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->entry_point);
+ dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n",
+ module->info.persistent_size, module->info.scratch_size);
+
+ memset(&template, 0, sizeof(template));
+ template.id = module->type;
+ template.entry = module->entry_point - 4;
+ template.persistent_size = module->info.persistent_size;
+ template.scratch_size = module->info.scratch_size;
+
+ mod = sst_module_new(fw, &template, NULL);
+ if (mod == NULL)
+ return -ENOMEM;
+
+ block = (void *)module + sizeof(*module);
+
+ for (count = 0; count < module->blocks; count++) {
+
+ if (block->size <= 0) {
+ dev_err(dsp->dev,
+ "error: block %d size invalid\n", count);
+ sst_module_free(mod);
+ return -EINVAL;
+ }
+
+ switch (block->type) {
+ case SST_HSW_IRAM:
+ ram = dsp->addr.lpe;
+ mod->offset =
+ block->ram_offset + dsp->addr.iram_offset;
+ mod->type = SST_MEM_IRAM;
+ break;
+ case SST_HSW_DRAM:
+ case SST_HSW_REGS:
+ ram = dsp->addr.lpe;
+ mod->offset = block->ram_offset;
+ mod->type = SST_MEM_DRAM;
+ break;
+ default:
+ dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n",
+ block->type, count);
+ sst_module_free(mod);
+ return -EINVAL;
+ }
+
+ mod->size = block->size;
+ mod->data = (void *)block + sizeof(*block);
+ mod->data_offset = mod->data - fw->dma_buf;
+
+ dev_dbg(dsp->dev, "module block %d type 0x%x "
+ "size 0x%x ==> ram %p offset 0x%x\n",
+ count, mod->type, block->size, ram,
+ block->ram_offset);
+
+ ret = sst_module_alloc_blocks(mod);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: could not allocate blocks for module %d\n",
+ count);
+ sst_module_free(mod);
+ return ret;
+ }
+
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ mod->state = SST_MODULE_STATE_LOADED;
+
+ return 0;
+}
+
+static int hsw_parse_fw_image(struct sst_fw *sst_fw)
+{
+ struct fw_header *header;
+ struct fw_module_header *module;
+ struct sst_dsp *dsp = sst_fw->dsp;
+ int ret, count;
+
+ /* Read the header information from the data pointer */
+ header = (struct fw_header *)sst_fw->dma_buf;
+
+ /* verify FW */
+ if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) ||
+ (sst_fw->size != header->file_size + sizeof(*header))) {
+ dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n",
+ header->file_size, header->modules,
+ header->file_format, sizeof(*header));
+
+ /* parse each module */
+ module = (void *)sst_fw->dma_buf + sizeof(*header);
+ for (count = 0; count < header->modules; count++) {
+
+ /* module */
+ ret = hsw_parse_module(dsp, sst_fw, module);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: invalid module %d\n", count);
+ return ret;
+ }
+ module = (void *)module + sizeof(*module) + module->mod_size;
+ }
+
+ return 0;
+}
+
+static irqreturn_t hsw_irq(int irq, void *context)
+{
+ struct sst_dsp *sst = (struct sst_dsp *) context;
+ u32 isr;
+ int ret = IRQ_NONE;
+
+ spin_lock(&sst->spinlock);
+
+ /* Interrupt arrived, check src */
+ isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX);
+ if (isr & SST_ISRX_DONE) {
+ trace_sst_irq_done(isr,
+ sst_dsp_shim_read_unlocked(sst, SST_IMRX));
+
+ /* Mask Done interrupt before return */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_DONE, SST_IMRX_DONE);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ if (isr & SST_ISRX_BUSY) {
+ trace_sst_irq_busy(isr,
+ sst_dsp_shim_read_unlocked(sst, SST_IMRX));
+
+ /* Mask Busy interrupt before return */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_BUSY, SST_IMRX_BUSY);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ spin_unlock(&sst->spinlock);
+ return ret;
+}
+
+static void hsw_set_dsp_D3(struct sst_dsp *sst)
+{
+ u32 val;
+ u32 reg;
+
+ /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE);
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ /* enable power gating and switch off DRAM & IRAM blocks */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ val |= SST_VDRTCL0_DSRAMPGE_MASK |
+ SST_VDRTCL0_ISRAMPGE_MASK;
+ val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD);
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ /* switch off audio PLL */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ val |= SST_VDRTCL2_APLLSE_MASK;
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ /* disable MCLK(clkctl.smos = 0) */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL,
+ SST_CLKCTL_MASK, 0);
+
+ /* Set D3 state, delay 50 us */
+ val = readl(sst->addr.pci_cfg + SST_PMCS);
+ val |= SST_PMCS_PS_MASK;
+ writel(val, sst->addr.pci_cfg + SST_PMCS);
+ udelay(50);
+
+ /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE;
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ udelay(50);
+
+}
+
+static void hsw_reset(struct sst_dsp *sst)
+{
+ /* put DSP into reset and stall */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_RST | SST_CSR_STALL,
+ SST_CSR_RST | SST_CSR_STALL);
+
+ /* keep in reset for 10ms */
+ mdelay(10);
+
+ /* take DSP out of reset and keep stalled for FW loading */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL);
+}
+
+static int hsw_set_dsp_D0(struct sst_dsp *sst)
+{
+ int tries = 10;
+ u32 reg, fw_dump_bit;
+
+ /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE);
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ /* Disable D3PG (VDRTCTL0.D3PGD = 1) */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ reg |= SST_VDRTCL0_D3PGD;
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ /* Set D0 state */
+ reg = readl(sst->addr.pci_cfg + SST_PMCS);
+ reg &= ~SST_PMCS_PS_MASK;
+ writel(reg, sst->addr.pci_cfg + SST_PMCS);
+
+ /* check that ADSP shim is enabled */
+ while (tries--) {
+ reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK;
+ if (reg == 0)
+ goto finish;
+
+ msleep(1);
+ }
+
+ return -ENODEV;
+
+finish:
+ /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0);
+
+ /* stall DSP core, set clk to 192/96Mhz */
+ sst_dsp_shim_update_bits_unlocked(sst,
+ SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK,
+ SST_CSR_STALL | SST_CSR_DCS(4));
+
+ /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL,
+ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0,
+ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0);
+
+ /* Stall and reset core, set CSR */
+ hsw_reset(sst);
+
+ /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE;
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ udelay(50);
+
+ /* switch on audio PLL */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ reg &= ~SST_VDRTCL2_APLLSE_MASK;
+ writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ /* set default power gating control, enable power gating control for all blocks. that is,
+ can't be accessed, please enable each block before accessing. */
+ reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK;
+ /* for D0, always enable the block(DSRAM[0]) used for FW dump */
+ fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT;
+ writel(reg & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+
+ /* disable DMA finish function for SSP0 & SSP1 */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1,
+ SST_CSR2_SDFD_SSP1);
+
+ /* set on-demond mode on engine 0,1 for all channels */
+ sst_dsp_shim_update_bits(sst, SST_HMDC,
+ SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH,
+ SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH);
+
+ /* Enable Interrupt from both sides */
+ sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE),
+ 0x0);
+ sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY |
+ SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0);
+
+ /* clear IPC registers */
+ sst_dsp_shim_write(sst, SST_IPCX, 0x0);
+ sst_dsp_shim_write(sst, SST_IPCD, 0x0);
+ sst_dsp_shim_write(sst, 0x80, 0x6);
+ sst_dsp_shim_write(sst, 0xe0, 0x300a);
+
+ return 0;
+}
+
+static void hsw_boot(struct sst_dsp *sst)
+{
+ /* set oportunistic mode on engine 0,1 for all channels */
+ sst_dsp_shim_update_bits(sst, SST_HMDC,
+ SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0);
+
+ /* set DSP to RUN */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0);
+}
+
+static void hsw_stall(struct sst_dsp *sst)
+{
+ /* stall DSP */
+ sst_dsp_shim_update_bits(sst, SST_CSR,
+ SST_CSR_24MHZ_LPCS | SST_CSR_STALL,
+ SST_CSR_STALL | SST_CSR_24MHZ_LPCS);
+}
+
+static void hsw_sleep(struct sst_dsp *sst)
+{
+ dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n");
+
+ /* put DSP into reset and stall */
+ sst_dsp_shim_update_bits(sst, SST_CSR,
+ SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL,
+ SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS);
+
+ hsw_set_dsp_D3(sst);
+ dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n");
+}
+
+static int hsw_wake(struct sst_dsp *sst)
+{
+ int ret;
+
+ dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n");
+
+ ret = hsw_set_dsp_D0(sst);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n");
+
+ return 0;
+}
+
+struct sst_adsp_memregion {
+ u32 start;
+ u32 end;
+ int blocks;
+ enum sst_mem_type type;
+};
+
+/* lynx point ADSP mem regions */
+static const struct sst_adsp_memregion lp_region[] = {
+ {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */
+ {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */
+ {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */
+};
+
+/* wild cat point ADSP mem regions */
+static const struct sst_adsp_memregion wpt_region[] = {
+ {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */
+ {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */
+};
+
+static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ /* ADSP DRAM & IRAM */
+ sst->addr.lpe_base = pdata->lpe_base;
+ sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size);
+ if (!sst->addr.lpe)
+ return -ENODEV;
+
+ /* ADSP PCI MMIO config space */
+ sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size);
+ if (!sst->addr.pci_cfg) {
+ iounmap(sst->addr.lpe);
+ return -ENODEV;
+ }
+
+ /* SST Shim */
+ sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset;
+ return 0;
+}
+
+struct sst_sram_shift {
+ u32 dev_id; /* SST Device IDs */
+ u32 iram_shift;
+ u32 dram_shift;
+};
+
+static const struct sst_sram_shift sram_shift[] = {
+ {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */
+ {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */
+};
+
+static u32 hsw_block_get_bit(struct sst_mem_block *block)
+{
+ u32 bit = 0, shift = 0, index;
+ struct sst_dsp *sst = block->dsp;
+
+ for (index = 0; index < ARRAY_SIZE(sram_shift); index++) {
+ if (sram_shift[index].dev_id == sst->id)
+ break;
+ }
+
+ if (index < ARRAY_SIZE(sram_shift)) {
+ switch (block->type) {
+ case SST_MEM_DRAM:
+ shift = sram_shift[index].dram_shift;
+ break;
+ case SST_MEM_IRAM:
+ shift = sram_shift[index].iram_shift;
+ break;
+ default:
+ shift = 0;
+ }
+ } else
+ shift = 0;
+
+ bit = 1 << (block->index + shift);
+
+ return bit;
+}
+
+/*dummy read a SRAM block.*/
+static void sst_mem_block_dummy_read(struct sst_mem_block *block)
+{
+ u32 size;
+ u8 tmp_buf[4];
+ struct sst_dsp *sst = block->dsp;
+
+ size = block->size > 4 ? 4 : block->size;
+ memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size);
+}
+
+/* enable 32kB memory block - locks held by caller */
+static int hsw_block_enable(struct sst_mem_block *block)
+{
+ struct sst_dsp *sst = block->dsp;
+ u32 bit, val;
+
+ if (block->users++ > 0)
+ return 0;
+
+ dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+
+ /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ val &= ~SST_VDRTCL2_DCLCGE;
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ bit = hsw_block_get_bit(block);
+ writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ /* wait 18 DSP clock ticks */
+ udelay(10);
+
+ /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ val |= SST_VDRTCL2_DCLCGE;
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ udelay(50);
+
+ /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/
+ sst_mem_block_dummy_read(block);
+ return 0;
+}
+
+/* disable 32kB memory block - locks held by caller */
+static int hsw_block_disable(struct sst_mem_block *block)
+{
+ struct sst_dsp *sst = block->dsp;
+ u32 bit, val;
+
+ if (--block->users > 0)
+ return 0;
+
+ dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+
+ /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ val &= ~SST_VDRTCL2_DCLCGE;
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ bit = hsw_block_get_bit(block);
+ /* don't disable DSRAM[0], keep it always enable for FW dump*/
+ if (bit != (1 << SST_VDRTCL0_DSRAMPGE_SHIFT))
+ writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ /* wait 18 DSP clock ticks */
+ udelay(10);
+
+ /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL2);
+ val |= SST_VDRTCL2_DCLCGE;
+ writel(val, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ udelay(50);
+
+ return 0;
+}
+
+static struct sst_block_ops sst_hsw_ops = {
+ .enable = hsw_block_enable,
+ .disable = hsw_block_disable,
+};
+
+static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ const struct sst_adsp_memregion *region;
+ struct device *dev;
+ int ret = -ENODEV, i, j, region_count;
+ u32 offset, size, fw_dump_bit;
+
+ dev = sst->dma_dev;
+
+ switch (sst->id) {
+ case SST_DEV_ID_LYNX_POINT:
+ region = lp_region;
+ region_count = ARRAY_SIZE(lp_region);
+ sst->addr.iram_offset = SST_LP_IRAM_OFFSET;
+ sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET;
+ sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET;
+ sst->addr.shim_offset = SST_LP_SHIM_OFFSET;
+ break;
+ case SST_DEV_ID_WILDCAT_POINT:
+ region = wpt_region;
+ region_count = ARRAY_SIZE(wpt_region);
+ sst->addr.iram_offset = SST_WPT_IRAM_OFFSET;
+ sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET;
+ sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET;
+ sst->addr.shim_offset = SST_WPT_SHIM_OFFSET;
+ break;
+ default:
+ dev_err(dev, "error: failed to get mem resources\n");
+ return ret;
+ }
+
+ ret = hsw_acpi_resource_map(sst, pdata);
+ if (ret < 0) {
+ dev_err(dev, "error: failed to map resources\n");
+ return ret;
+ }
+
+ /* enable the DSP SHIM */
+ ret = hsw_set_dsp_D0(sst);
+ if (ret < 0) {
+ dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n");
+ return ret;
+ }
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31));
+ if (ret)
+ return ret;
+
+
+ /* register DSP memory blocks - ideally we should get this from ACPI */
+ for (i = 0; i < region_count; i++) {
+ offset = region[i].start;
+ size = (region[i].end - region[i].start) / region[i].blocks;
+
+ /* register individual memory blocks */
+ for (j = 0; j < region[i].blocks; j++) {
+ sst_mem_block_register(sst, offset, size,
+ region[i].type, &sst_hsw_ops, j, sst);
+ offset += size;
+ }
+ }
+
+ /* always enable the block(DSRAM[0]) used for FW dump */
+ fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT;
+ /* set default power gating control, enable power gating control for all blocks. that is,
+ can't be accessed, please enable each block before accessing. */
+ writel(0xffffffff & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ return 0;
+}
+
+static void hsw_free(struct sst_dsp *sst)
+{
+ sst_mem_block_unregister_all(sst);
+ iounmap(sst->addr.lpe);
+ iounmap(sst->addr.pci_cfg);
+}
+
+struct sst_ops haswell_ops = {
+ .reset = hsw_reset,
+ .boot = hsw_boot,
+ .stall = hsw_stall,
+ .wake = hsw_wake,
+ .sleep = hsw_sleep,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .write64 = sst_shim32_write64,
+ .read64 = sst_shim32_read64,
+ .ram_read = sst_memcpy_fromio_32,
+ .ram_write = sst_memcpy_toio_32,
+ .irq_handler = hsw_irq,
+ .init = hsw_init,
+ .free = hsw_free,
+ .parse_fw = hsw_parse_fw_image,
+};
diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c
new file mode 100644
index 000000000..27957a215
--- /dev/null
+++ b/sound/soc/intel/haswell/sst-haswell-ipc.c
@@ -0,0 +1,2219 @@
+/*
+ * Intel SST Haswell/Broadwell IPC Support
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+#include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+#include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
+#include <sound/asound.h>
+
+#include "sst-haswell-ipc.h"
+#include "../common/sst-dsp.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-ipc.h"
+
+/* Global Message - Generic */
+#define IPC_GLB_TYPE_SHIFT 24
+#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT)
+#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT)
+
+/* Global Message - Reply */
+#define IPC_GLB_REPLY_SHIFT 0
+#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT)
+#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT)
+
+/* Stream Message - Generic */
+#define IPC_STR_TYPE_SHIFT 20
+#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT)
+#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT)
+#define IPC_STR_ID_SHIFT 16
+#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT)
+#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT)
+
+/* Stream Message - Reply */
+#define IPC_STR_REPLY_SHIFT 0
+#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT)
+
+/* Stream Stage Message - Generic */
+#define IPC_STG_TYPE_SHIFT 12
+#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT)
+#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT)
+#define IPC_STG_ID_SHIFT 10
+#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT)
+#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT)
+
+/* Stream Stage Message - Reply */
+#define IPC_STG_REPLY_SHIFT 0
+#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT)
+
+/* Debug Log Message - Generic */
+#define IPC_LOG_OP_SHIFT 20
+#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT)
+#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT)
+#define IPC_LOG_ID_SHIFT 16
+#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT)
+#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT)
+
+/* Module Message */
+#define IPC_MODULE_OPERATION_SHIFT 20
+#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT)
+#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT)
+
+#define IPC_MODULE_ID_SHIFT 16
+#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT)
+#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT)
+
+/* IPC message timeout (msecs) */
+#define IPC_TIMEOUT_MSECS 300
+#define IPC_BOOT_MSECS 200
+#define IPC_MSG_WAIT 0
+#define IPC_MSG_NOWAIT 1
+
+/* Firmware Ready Message */
+#define IPC_FW_READY (0x1 << 29)
+#define IPC_STATUS_MASK (0x3 << 30)
+
+#define IPC_EMPTY_LIST_SIZE 8
+#define IPC_MAX_STREAMS 4
+
+/* Mailbox */
+#define IPC_MAX_MAILBOX_BYTES 256
+
+#define INVALID_STREAM_HW_ID 0xffffffff
+
+/* Global Message - Types and Replies */
+enum ipc_glb_type {
+ IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */
+ IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */
+ IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */
+ IPC_GLB_FREE_STREAM = 4, /* Request to free stream */
+ IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */
+ IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */
+ /* Request to store firmware context during D0->D3 transition */
+ IPC_GLB_REQUEST_DUMP = 7,
+ /* Request to restore firmware context during D3->D0 transition */
+ IPC_GLB_RESTORE_CONTEXT = 8,
+ IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */
+ IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */
+ IPC_GLB_SHORT_REPLY = 11,
+ IPC_GLB_ENTER_DX_STATE = 12,
+ IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */
+ IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */
+ IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */
+ IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */
+ IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */
+};
+
+enum ipc_glb_reply {
+ IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */
+ IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */
+ IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */
+ IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */
+ IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */
+ IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */
+ IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */
+ IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */
+ IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */
+ IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */
+ IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */
+};
+
+enum ipc_module_operation {
+ IPC_MODULE_NOTIFICATION = 0,
+ IPC_MODULE_ENABLE = 1,
+ IPC_MODULE_DISABLE = 2,
+ IPC_MODULE_GET_PARAMETER = 3,
+ IPC_MODULE_SET_PARAMETER = 4,
+ IPC_MODULE_GET_INFO = 5,
+ IPC_MODULE_MAX_MESSAGE
+};
+
+/* Stream Message - Types */
+enum ipc_str_operation {
+ IPC_STR_RESET = 0,
+ IPC_STR_PAUSE = 1,
+ IPC_STR_RESUME = 2,
+ IPC_STR_STAGE_MESSAGE = 3,
+ IPC_STR_NOTIFICATION = 4,
+ IPC_STR_MAX_MESSAGE
+};
+
+/* Stream Stage Message Types */
+enum ipc_stg_operation {
+ IPC_STG_GET_VOLUME = 0,
+ IPC_STG_SET_VOLUME,
+ IPC_STG_SET_WRITE_POSITION,
+ IPC_STG_SET_FX_ENABLE,
+ IPC_STG_SET_FX_DISABLE,
+ IPC_STG_SET_FX_GET_PARAM,
+ IPC_STG_SET_FX_SET_PARAM,
+ IPC_STG_SET_FX_GET_INFO,
+ IPC_STG_MUTE_LOOPBACK,
+ IPC_STG_MAX_MESSAGE
+};
+
+/* Stream Stage Message Types For Notification*/
+enum ipc_stg_operation_notify {
+ IPC_POSITION_CHANGED = 0,
+ IPC_STG_GLITCH,
+ IPC_STG_MAX_NOTIFY
+};
+
+enum ipc_glitch_type {
+ IPC_GLITCH_UNDERRUN = 1,
+ IPC_GLITCH_DECODER_ERROR,
+ IPC_GLITCH_DOUBLED_WRITE_POS,
+ IPC_GLITCH_MAX
+};
+
+/* Debug Control */
+enum ipc_debug_operation {
+ IPC_DEBUG_ENABLE_LOG = 0,
+ IPC_DEBUG_DISABLE_LOG = 1,
+ IPC_DEBUG_REQUEST_LOG_DUMP = 2,
+ IPC_DEBUG_NOTIFY_LOG_DUMP = 3,
+ IPC_DEBUG_MAX_DEBUG_LOG
+};
+
+/* Firmware Ready */
+struct sst_hsw_ipc_fw_ready {
+ u32 inbox_offset;
+ u32 outbox_offset;
+ u32 inbox_size;
+ u32 outbox_size;
+ u32 fw_info_size;
+ u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)];
+} __attribute__((packed));
+
+struct sst_hsw_stream;
+struct sst_hsw;
+
+/* Stream infomation */
+struct sst_hsw_stream {
+ /* configuration */
+ struct sst_hsw_ipc_stream_alloc_req request;
+ struct sst_hsw_ipc_stream_alloc_reply reply;
+ struct sst_hsw_ipc_stream_free_req free_req;
+
+ /* Mixer info */
+ u32 mute_volume[SST_HSW_NO_CHANNELS];
+ u32 mute[SST_HSW_NO_CHANNELS];
+
+ /* runtime info */
+ struct sst_hsw *hsw;
+ int host_id;
+ bool commited;
+ bool running;
+
+ /* Notification work */
+ struct work_struct notify_work;
+ u32 header;
+
+ /* Position info from DSP */
+ struct sst_hsw_ipc_stream_set_position wpos;
+ struct sst_hsw_ipc_stream_get_position rpos;
+ struct sst_hsw_ipc_stream_glitch_position glitch;
+
+ /* Volume info */
+ struct sst_hsw_ipc_volume_req vol_req;
+
+ /* driver callback */
+ u32 (*notify_position)(struct sst_hsw_stream *stream, void *data);
+ void *pdata;
+
+ /* record the fw read position when playback */
+ snd_pcm_uframes_t old_position;
+ bool play_silence;
+ struct list_head node;
+};
+
+/* FW log ring information */
+struct sst_hsw_log_stream {
+ dma_addr_t dma_addr;
+ unsigned char *dma_area;
+ unsigned char *ring_descr;
+ int pages;
+ int size;
+
+ /* Notification work */
+ struct work_struct notify_work;
+ wait_queue_head_t readers_wait_q;
+ struct mutex rw_mutex;
+
+ u32 last_pos;
+ u32 curr_pos;
+ u32 reader_pos;
+
+ /* fw log config */
+ u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS];
+
+ struct sst_hsw *hsw;
+};
+
+/* SST Haswell IPC data */
+struct sst_hsw {
+ struct device *dev;
+ struct sst_dsp *dsp;
+ struct platform_device *pdev_pcm;
+
+ /* FW config */
+ struct sst_hsw_ipc_fw_ready fw_ready;
+ struct sst_hsw_ipc_fw_version version;
+ bool fw_done;
+ struct sst_fw *sst_fw;
+
+ /* stream */
+ struct list_head stream_list;
+
+ /* global mixer */
+ struct sst_hsw_ipc_stream_info_reply mixer_info;
+ enum sst_hsw_volume_curve curve_type;
+ u32 curve_duration;
+ u32 mute[SST_HSW_NO_CHANNELS];
+ u32 mute_volume[SST_HSW_NO_CHANNELS];
+
+ /* DX */
+ struct sst_hsw_ipc_dx_reply dx;
+ void *dx_context;
+ dma_addr_t dx_context_paddr;
+
+ /* boot */
+ wait_queue_head_t boot_wait;
+ bool boot_complete;
+ bool shutdown;
+
+ /* IPC messaging */
+ struct sst_generic_ipc ipc;
+
+ /* FW log stream */
+ struct sst_hsw_log_stream log_stream;
+
+ /* flags bit field to track module state when resume from RTD3,
+ * each bit represent state (enabled/disabled) of single module */
+ u32 enabled_modules_rtd3;
+
+ /* buffer to store parameter lines */
+ u32 param_idx_w; /* write index */
+ u32 param_idx_r; /* read index */
+ u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT];
+};
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/hswadsp.h>
+
+static inline u32 msg_get_global_type(u32 msg)
+{
+ return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT;
+}
+
+static inline u32 msg_get_global_reply(u32 msg)
+{
+ return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT;
+}
+
+static inline u32 msg_get_stream_type(u32 msg)
+{
+ return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT;
+}
+
+static inline u32 msg_get_stage_type(u32 msg)
+{
+ return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT;
+}
+
+static inline u32 msg_get_stream_id(u32 msg)
+{
+ return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT;
+}
+
+static inline u32 msg_get_notify_reason(u32 msg)
+{
+ return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT;
+}
+
+static inline u32 msg_get_module_operation(u32 msg)
+{
+ return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT;
+}
+
+static inline u32 msg_get_module_id(u32 msg)
+{
+ return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT;
+}
+
+u32 create_channel_map(enum sst_hsw_channel_config config)
+{
+ switch (config) {
+ case SST_HSW_CHANNEL_CONFIG_MONO:
+ return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER);
+ case SST_HSW_CHANNEL_CONFIG_STEREO:
+ return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_RIGHT << 4));
+ case SST_HSW_CHANNEL_CONFIG_2_POINT_1:
+ return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_RIGHT << 4)
+ | (SST_HSW_CHANNEL_LFE << 8 ));
+ case SST_HSW_CHANNEL_CONFIG_3_POINT_0:
+ return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_CENTER << 4)
+ | (SST_HSW_CHANNEL_RIGHT << 8));
+ case SST_HSW_CHANNEL_CONFIG_3_POINT_1:
+ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_CENTER << 4)
+ | (SST_HSW_CHANNEL_RIGHT << 8)
+ | (SST_HSW_CHANNEL_LFE << 12));
+ case SST_HSW_CHANNEL_CONFIG_QUATRO:
+ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_RIGHT << 4)
+ | (SST_HSW_CHANNEL_LEFT_SURROUND << 8)
+ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12));
+ case SST_HSW_CHANNEL_CONFIG_4_POINT_0:
+ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_CENTER << 4)
+ | (SST_HSW_CHANNEL_RIGHT << 8)
+ | (SST_HSW_CHANNEL_CENTER_SURROUND << 12));
+ case SST_HSW_CHANNEL_CONFIG_5_POINT_0:
+ return (0xFFF00000 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_CENTER << 4)
+ | (SST_HSW_CHANNEL_RIGHT << 8)
+ | (SST_HSW_CHANNEL_LEFT_SURROUND << 12)
+ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16));
+ case SST_HSW_CHANNEL_CONFIG_5_POINT_1:
+ return (0xFF000000 | SST_HSW_CHANNEL_CENTER
+ | (SST_HSW_CHANNEL_LEFT << 4)
+ | (SST_HSW_CHANNEL_RIGHT << 8)
+ | (SST_HSW_CHANNEL_LEFT_SURROUND << 12)
+ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)
+ | (SST_HSW_CHANNEL_LFE << 20));
+ case SST_HSW_CHANNEL_CONFIG_DUAL_MONO:
+ return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT
+ | (SST_HSW_CHANNEL_LEFT << 4));
+ default:
+ return 0xFFFFFFFF;
+ }
+}
+
+static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw,
+ int stream_id)
+{
+ struct sst_hsw_stream *stream;
+
+ list_for_each_entry(stream, &hsw->stream_list, node) {
+ if (stream->reply.stream_hw_id == stream_id)
+ return stream;
+ }
+
+ return NULL;
+}
+
+static void hsw_fw_ready(struct sst_hsw *hsw, u32 header)
+{
+ struct sst_hsw_ipc_fw_ready fw_ready;
+ u32 offset;
+ u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)];
+ char *tmp[5], *pinfo;
+ int i = 0;
+
+ offset = (header & 0x1FFFFFFF) << 3;
+
+ dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n",
+ header, offset);
+
+ /* copy data from the DSP FW ready offset */
+ sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready));
+
+ sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset,
+ fw_ready.inbox_size, fw_ready.outbox_offset,
+ fw_ready.outbox_size);
+
+ hsw->boot_complete = true;
+ wake_up(&hsw->boot_wait);
+
+ dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n",
+ fw_ready.inbox_offset, fw_ready.inbox_size);
+ dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n",
+ fw_ready.outbox_offset, fw_ready.outbox_size);
+ if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) {
+ fw_ready.fw_info[fw_ready.fw_info_size] = 0;
+ dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info);
+
+ /* log the FW version info got from the mailbox here. */
+ memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size);
+ pinfo = &fw_info[0];
+ for (i = 0; i < ARRAY_SIZE(tmp); i++)
+ tmp[i] = strsep(&pinfo, " ");
+ dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - "
+ "version: %s.%s, build %s, source commit id: %s\n",
+ tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]);
+ }
+}
+
+static void hsw_notification_work(struct work_struct *work)
+{
+ struct sst_hsw_stream *stream = container_of(work,
+ struct sst_hsw_stream, notify_work);
+ struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch;
+ struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos;
+ struct sst_hsw *hsw = stream->hsw;
+ u32 reason;
+
+ reason = msg_get_notify_reason(stream->header);
+
+ switch (reason) {
+ case IPC_STG_GLITCH:
+ trace_ipc_notification("DSP stream under/overrun",
+ stream->reply.stream_hw_id);
+ sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch));
+
+ dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n",
+ glitch->glitch_type, glitch->present_pos,
+ glitch->write_pos);
+ break;
+
+ case IPC_POSITION_CHANGED:
+ trace_ipc_notification("DSP stream position changed for",
+ stream->reply.stream_hw_id);
+ sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos));
+
+ if (stream->notify_position)
+ stream->notify_position(stream, stream->pdata);
+
+ break;
+ default:
+ dev_err(hsw->dev, "error: unknown notification 0x%x\n",
+ stream->header);
+ break;
+ }
+
+ /* tell DSP that notification has been handled */
+ sst_dsp_shim_update_bits(hsw->dsp, SST_IPCD,
+ SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE);
+
+ /* unmask busy interrupt */
+ sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0);
+}
+
+static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg)
+{
+ struct sst_hsw_stream *stream;
+ u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK);
+ u32 stream_id = msg_get_stream_id(header);
+ u32 stream_msg = msg_get_stream_type(header);
+
+ stream = get_stream_by_id(hsw, stream_id);
+ if (stream == NULL)
+ return;
+
+ switch (stream_msg) {
+ case IPC_STR_STAGE_MESSAGE:
+ case IPC_STR_NOTIFICATION:
+ break;
+ case IPC_STR_RESET:
+ trace_ipc_notification("stream reset", stream->reply.stream_hw_id);
+ break;
+ case IPC_STR_PAUSE:
+ stream->running = false;
+ trace_ipc_notification("stream paused",
+ stream->reply.stream_hw_id);
+ break;
+ case IPC_STR_RESUME:
+ stream->running = true;
+ trace_ipc_notification("stream running",
+ stream->reply.stream_hw_id);
+ break;
+ }
+}
+
+static int hsw_process_reply(struct sst_hsw *hsw, u32 header)
+{
+ struct ipc_message *msg;
+ u32 reply = msg_get_global_reply(header);
+
+ trace_ipc_reply("processing -->", header);
+
+ msg = sst_ipc_reply_find_msg(&hsw->ipc, header);
+ if (msg == NULL) {
+ trace_ipc_error("error: can't find message header", header);
+ return -EIO;
+ }
+
+ /* first process the header */
+ switch (reply) {
+ case IPC_GLB_REPLY_PENDING:
+ trace_ipc_pending_reply("received", header);
+ msg->pending = true;
+ hsw->ipc.pending = true;
+ return 1;
+ case IPC_GLB_REPLY_SUCCESS:
+ if (msg->pending) {
+ trace_ipc_pending_reply("completed", header);
+ sst_dsp_inbox_read(hsw->dsp, msg->rx_data,
+ msg->rx_size);
+ hsw->ipc.pending = false;
+ } else {
+ /* copy data from the DSP */
+ sst_dsp_outbox_read(hsw->dsp, msg->rx_data,
+ msg->rx_size);
+ }
+ break;
+ /* these will be rare - but useful for debug */
+ case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE:
+ trace_ipc_error("error: unknown message type", header);
+ msg->errno = -EBADMSG;
+ break;
+ case IPC_GLB_REPLY_OUT_OF_RESOURCES:
+ trace_ipc_error("error: out of resources", header);
+ msg->errno = -ENOMEM;
+ break;
+ case IPC_GLB_REPLY_BUSY:
+ trace_ipc_error("error: reply busy", header);
+ msg->errno = -EBUSY;
+ break;
+ case IPC_GLB_REPLY_FAILURE:
+ trace_ipc_error("error: reply failure", header);
+ msg->errno = -EINVAL;
+ break;
+ case IPC_GLB_REPLY_STAGE_UNINITIALIZED:
+ trace_ipc_error("error: stage uninitialized", header);
+ msg->errno = -EINVAL;
+ break;
+ case IPC_GLB_REPLY_NOT_FOUND:
+ trace_ipc_error("error: reply not found", header);
+ msg->errno = -EINVAL;
+ break;
+ case IPC_GLB_REPLY_SOURCE_NOT_STARTED:
+ trace_ipc_error("error: source not started", header);
+ msg->errno = -EINVAL;
+ break;
+ case IPC_GLB_REPLY_INVALID_REQUEST:
+ trace_ipc_error("error: invalid request", header);
+ msg->errno = -EINVAL;
+ break;
+ case IPC_GLB_REPLY_ERROR_INVALID_PARAM:
+ trace_ipc_error("error: invalid parameter", header);
+ msg->errno = -EINVAL;
+ break;
+ default:
+ trace_ipc_error("error: unknown reply", header);
+ msg->errno = -EINVAL;
+ break;
+ }
+
+ /* update any stream states */
+ if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE)
+ hsw_stream_update(hsw, msg);
+
+ /* wake up and return the error if we have waiters on this message ? */
+ list_del(&msg->list);
+ sst_ipc_tx_msg_reply_complete(&hsw->ipc, msg);
+
+ return 1;
+}
+
+static int hsw_module_message(struct sst_hsw *hsw, u32 header)
+{
+ u32 operation, module_id;
+ int handled = 0;
+
+ operation = msg_get_module_operation(header);
+ module_id = msg_get_module_id(header);
+ dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n",
+ header);
+ dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n",
+ operation, module_id);
+
+ switch (operation) {
+ case IPC_MODULE_NOTIFICATION:
+ dev_dbg(hsw->dev, "module notification received");
+ handled = 1;
+ break;
+ default:
+ handled = hsw_process_reply(hsw, header);
+ break;
+ }
+
+ return handled;
+}
+
+static int hsw_stream_message(struct sst_hsw *hsw, u32 header)
+{
+ u32 stream_msg, stream_id, stage_type;
+ struct sst_hsw_stream *stream;
+ int handled = 0;
+
+ stream_msg = msg_get_stream_type(header);
+ stream_id = msg_get_stream_id(header);
+ stage_type = msg_get_stage_type(header);
+
+ stream = get_stream_by_id(hsw, stream_id);
+ if (stream == NULL)
+ return handled;
+
+ stream->header = header;
+
+ switch (stream_msg) {
+ case IPC_STR_STAGE_MESSAGE:
+ dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n",
+ header);
+ break;
+ case IPC_STR_NOTIFICATION:
+ schedule_work(&stream->notify_work);
+ break;
+ default:
+ /* handle pending message complete request */
+ handled = hsw_process_reply(hsw, header);
+ break;
+ }
+
+ return handled;
+}
+
+static int hsw_log_message(struct sst_hsw *hsw, u32 header)
+{
+ u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT;
+ struct sst_hsw_log_stream *stream = &hsw->log_stream;
+ int ret = 1;
+
+ if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) {
+ dev_err(hsw->dev,
+ "error: log msg not implemented 0x%8.8x\n", header);
+ return 0;
+ }
+
+ mutex_lock(&stream->rw_mutex);
+ stream->last_pos = stream->curr_pos;
+ sst_dsp_inbox_read(
+ hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos));
+ mutex_unlock(&stream->rw_mutex);
+
+ schedule_work(&stream->notify_work);
+
+ return ret;
+}
+
+static int hsw_process_notification(struct sst_hsw *hsw)
+{
+ struct sst_dsp *sst = hsw->dsp;
+ u32 type, header;
+ int handled = 1;
+
+ header = sst_dsp_shim_read_unlocked(sst, SST_IPCD);
+ type = msg_get_global_type(header);
+
+ trace_ipc_request("processing -->", header);
+
+ /* FW Ready is a special case */
+ if (!hsw->boot_complete && header & IPC_FW_READY) {
+ hsw_fw_ready(hsw, header);
+ return handled;
+ }
+
+ switch (type) {
+ case IPC_GLB_GET_FW_VERSION:
+ case IPC_GLB_ALLOCATE_STREAM:
+ case IPC_GLB_FREE_STREAM:
+ case IPC_GLB_GET_FW_CAPABILITIES:
+ case IPC_GLB_REQUEST_DUMP:
+ case IPC_GLB_GET_DEVICE_FORMATS:
+ case IPC_GLB_SET_DEVICE_FORMATS:
+ case IPC_GLB_ENTER_DX_STATE:
+ case IPC_GLB_GET_MIXER_STREAM_INFO:
+ case IPC_GLB_MAX_IPC_MESSAGE_TYPE:
+ case IPC_GLB_RESTORE_CONTEXT:
+ case IPC_GLB_SHORT_REPLY:
+ dev_err(hsw->dev, "error: message type %d header 0x%x\n",
+ type, header);
+ break;
+ case IPC_GLB_STREAM_MESSAGE:
+ handled = hsw_stream_message(hsw, header);
+ break;
+ case IPC_GLB_DEBUG_LOG_MESSAGE:
+ handled = hsw_log_message(hsw, header);
+ break;
+ case IPC_GLB_MODULE_OPERATION:
+ handled = hsw_module_message(hsw, header);
+ break;
+ default:
+ dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n",
+ type, header);
+ break;
+ }
+
+ return handled;
+}
+
+static irqreturn_t hsw_irq_thread(int irq, void *context)
+{
+ struct sst_dsp *sst = (struct sst_dsp *) context;
+ struct sst_hsw *hsw = sst_dsp_get_thread_context(sst);
+ struct sst_generic_ipc *ipc = &hsw->ipc;
+ u32 ipcx, ipcd;
+ int handled;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+
+ ipcx = sst_dsp_ipc_msg_rx(hsw->dsp);
+ ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD);
+
+ /* reply message from DSP */
+ if (ipcx & SST_IPCX_DONE) {
+
+ /* Handle Immediate reply from DSP Core */
+ handled = hsw_process_reply(hsw, ipcx);
+
+ if (handled > 0) {
+ /* clear DONE bit - tell DSP we have completed */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX,
+ SST_IPCX_DONE, 0);
+
+ /* unmask Done interrupt */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_DONE, 0);
+ }
+ }
+
+ /* new message from DSP */
+ if (ipcd & SST_IPCD_BUSY) {
+
+ /* Handle Notification and Delayed reply from DSP Core */
+ handled = hsw_process_notification(hsw);
+
+ /* clear BUSY bit and set DONE bit - accept new messages */
+ if (handled > 0) {
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD,
+ SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE);
+
+ /* unmask busy interrupt */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_BUSY, 0);
+ }
+ }
+
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ /* continue to send any remaining messages... */
+ queue_kthread_work(&ipc->kworker, &ipc->kwork);
+
+ return IRQ_HANDLED;
+}
+
+int sst_hsw_fw_get_version(struct sst_hsw *hsw,
+ struct sst_hsw_ipc_fw_version *version)
+{
+ int ret;
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc,
+ IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION),
+ NULL, 0, version, sizeof(*version));
+ if (ret < 0)
+ dev_err(hsw->dev, "error: get version failed\n");
+
+ return ret;
+}
+
+/* Mixer Controls */
+int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ u32 stage_id, u32 channel, u32 *volume)
+{
+ if (channel > 1)
+ return -EINVAL;
+
+ sst_dsp_read(hsw->dsp, volume,
+ stream->reply.volume_register_address[channel],
+ sizeof(*volume));
+
+ return 0;
+}
+
+/* stream volume */
+int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume)
+{
+ struct sst_hsw_ipc_volume_req *req;
+ u32 header;
+ int ret;
+
+ trace_ipc_request("set stream volume", stream->reply.stream_hw_id);
+
+ if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
+ return -EINVAL;
+
+ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) |
+ IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE);
+ header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT);
+ header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT);
+ header |= (stage_id << IPC_STG_ID_SHIFT);
+
+ req = &stream->vol_req;
+ req->target_volume = volume;
+
+ /* set both at same time ? */
+ if (channel == SST_HSW_CHANNELS_ALL) {
+ if (hsw->mute[0] && hsw->mute[1]) {
+ hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
+ return 0;
+ } else if (hsw->mute[0])
+ req->channel = 1;
+ else if (hsw->mute[1])
+ req->channel = 0;
+ else
+ req->channel = SST_HSW_CHANNELS_ALL;
+ } else {
+ /* set only 1 channel */
+ if (hsw->mute[channel]) {
+ hsw->mute_volume[channel] = volume;
+ return 0;
+ }
+ req->channel = channel;
+ }
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, req,
+ sizeof(*req), NULL, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: set stream volume failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
+ u32 *volume)
+{
+ if (channel > 1)
+ return -EINVAL;
+
+ sst_dsp_read(hsw->dsp, volume,
+ hsw->mixer_info.volume_register_address[channel],
+ sizeof(*volume));
+
+ return 0;
+}
+
+/* global mixer volume */
+int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
+ u32 volume)
+{
+ struct sst_hsw_ipc_volume_req req;
+ u32 header;
+ int ret;
+
+ trace_ipc_request("set mixer volume", volume);
+
+ if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL)
+ return -EINVAL;
+
+ /* set both at same time ? */
+ if (channel == SST_HSW_CHANNELS_ALL) {
+ if (hsw->mute[0] && hsw->mute[1]) {
+ hsw->mute_volume[0] = hsw->mute_volume[1] = volume;
+ return 0;
+ } else if (hsw->mute[0])
+ req.channel = 1;
+ else if (hsw->mute[1])
+ req.channel = 0;
+ else
+ req.channel = SST_HSW_CHANNELS_ALL;
+ } else {
+ /* set only 1 channel */
+ if (hsw->mute[channel]) {
+ hsw->mute_volume[channel] = volume;
+ return 0;
+ }
+ req.channel = channel;
+ }
+
+ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) |
+ IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE);
+ header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT);
+ header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT);
+ header |= (stage_id << IPC_STG_ID_SHIFT);
+
+ req.curve_duration = hsw->curve_duration;
+ req.curve_type = hsw->curve_type;
+ req.target_volume = volume;
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &req,
+ sizeof(req), NULL, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: set mixer volume failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Stream API */
+struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id,
+ u32 (*notify_position)(struct sst_hsw_stream *stream, void *data),
+ void *data)
+{
+ struct sst_hsw_stream *stream;
+ struct sst_dsp *sst = hsw->dsp;
+ unsigned long flags;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (stream == NULL)
+ return NULL;
+
+ spin_lock_irqsave(&sst->spinlock, flags);
+ stream->reply.stream_hw_id = INVALID_STREAM_HW_ID;
+ list_add(&stream->node, &hsw->stream_list);
+ stream->notify_position = notify_position;
+ stream->pdata = data;
+ stream->hsw = hsw;
+ stream->host_id = id;
+
+ /* work to process notification messages */
+ INIT_WORK(&stream->notify_work, hsw_notification_work);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return stream;
+}
+
+int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream)
+{
+ u32 header;
+ int ret = 0;
+ struct sst_dsp *sst = hsw->dsp;
+ unsigned long flags;
+
+ if (!stream) {
+ dev_warn(hsw->dev, "warning: stream is NULL, no stream to free, ignore it.\n");
+ return 0;
+ }
+
+ /* dont free DSP streams that are not commited */
+ if (!stream->commited)
+ goto out;
+
+ trace_ipc_request("stream free", stream->host_id);
+
+ stream->free_req.stream_id = stream->reply.stream_hw_id;
+ header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &stream->free_req,
+ sizeof(stream->free_req), NULL, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: free stream %d failed\n",
+ stream->free_req.stream_id);
+ return -EAGAIN;
+ }
+
+ trace_hsw_stream_free_req(stream, &stream->free_req);
+
+out:
+ cancel_work_sync(&stream->notify_work);
+ spin_lock_irqsave(&sst->spinlock, flags);
+ list_del(&stream->node);
+ kfree(stream);
+ spin_unlock_irqrestore(&sst->spinlock, flags);
+
+ return ret;
+}
+
+int sst_hsw_stream_set_bits(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set bits\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.bitdepth = bits;
+ return 0;
+}
+
+int sst_hsw_stream_set_channels(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, int channels)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set channels\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.ch_num = channels;
+ return 0;
+}
+
+int sst_hsw_stream_set_rate(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, int rate)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set rate\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.frequency = rate;
+ return 0;
+}
+
+int sst_hsw_stream_set_map_config(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 map,
+ enum sst_hsw_channel_config config)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set map\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.map = map;
+ stream->request.format.config = config;
+ return 0;
+}
+
+int sst_hsw_stream_set_style(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, enum sst_hsw_interleaving style)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set style\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.style = style;
+ return 0;
+}
+
+int sst_hsw_stream_set_valid(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 bits)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set valid bits\n");
+ return -EINVAL;
+ }
+
+ stream->request.format.valid_bit = bits;
+ return 0;
+}
+
+/* Stream Configuration */
+int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ enum sst_hsw_stream_path_id path_id,
+ enum sst_hsw_stream_type stream_type,
+ enum sst_hsw_stream_format format_id)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set format\n");
+ return -EINVAL;
+ }
+
+ stream->request.path_id = path_id;
+ stream->request.stream_type = stream_type;
+ stream->request.format_id = format_id;
+
+ trace_hsw_stream_alloc_request(stream, &stream->request);
+
+ return 0;
+}
+
+int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ u32 ring_pt_address, u32 num_pages,
+ u32 ring_size, u32 ring_offset, u32 ring_first_pfn)
+{
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for buffer\n");
+ return -EINVAL;
+ }
+
+ stream->request.ringinfo.ring_pt_address = ring_pt_address;
+ stream->request.ringinfo.num_pages = num_pages;
+ stream->request.ringinfo.ring_size = ring_size;
+ stream->request.ringinfo.ring_offset = ring_offset;
+ stream->request.ringinfo.ring_first_pfn = ring_first_pfn;
+
+ trace_hsw_stream_buffer(stream);
+
+ return 0;
+}
+
+int sst_hsw_stream_set_module_info(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, struct sst_module_runtime *runtime)
+{
+ struct sst_hsw_module_map *map = &stream->request.map;
+ struct sst_dsp *dsp = sst_hsw_get_dsp(hsw);
+ struct sst_module *module = runtime->module;
+
+ if (stream->commited) {
+ dev_err(hsw->dev, "error: stream committed for set module\n");
+ return -EINVAL;
+ }
+
+ /* only support initial module atm */
+ map->module_entries_count = 1;
+ map->module_entries[0].module_id = module->id;
+ map->module_entries[0].entry_point = module->entry;
+
+ stream->request.persistent_mem.offset =
+ sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM);
+ stream->request.persistent_mem.size = module->persistent_size;
+
+ stream->request.scratch_mem.offset =
+ sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM);
+ stream->request.scratch_mem.size = dsp->scratch_size;
+
+ dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id,
+ runtime->id);
+ dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n",
+ stream->request.persistent_mem.offset,
+ stream->request.persistent_mem.size);
+ dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n",
+ stream->request.scratch_mem.offset,
+ stream->request.scratch_mem.size);
+
+ return 0;
+}
+
+int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream)
+{
+ struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request;
+ struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply;
+ u32 header;
+ int ret;
+
+ if (!stream) {
+ dev_warn(hsw->dev, "warning: stream is NULL, no stream to commit, ignore it.\n");
+ return 0;
+ }
+
+ if (stream->commited) {
+ dev_warn(hsw->dev, "warning: stream is already committed, ignore it.\n");
+ return 0;
+ }
+
+ trace_ipc_request("stream alloc", stream->host_id);
+
+ header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, str_req,
+ sizeof(*str_req), reply, sizeof(*reply));
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: stream commit failed\n");
+ return ret;
+ }
+
+ stream->commited = 1;
+ trace_hsw_stream_alloc_reply(stream);
+
+ return 0;
+}
+
+snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream)
+{
+ return stream->old_position;
+}
+
+void sst_hsw_stream_set_old_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, snd_pcm_uframes_t val)
+{
+ stream->old_position = val;
+}
+
+bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream)
+{
+ return stream->play_silence;
+}
+
+void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, bool val)
+{
+ stream->play_silence = val;
+}
+
+/* Stream Information - these calls could be inline but we want the IPC
+ ABI to be opaque to client PCM drivers to cope with any future ABI changes */
+int sst_hsw_mixer_get_info(struct sst_hsw *hsw)
+{
+ struct sst_hsw_ipc_stream_info_reply *reply;
+ u32 header;
+ int ret;
+
+ reply = &hsw->mixer_info;
+ header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO);
+
+ trace_ipc_request("get global mixer info", 0);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0,
+ reply, sizeof(*reply));
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: get stream info failed\n");
+ return ret;
+ }
+
+ trace_hsw_mixer_info_reply(reply);
+
+ return 0;
+}
+
+/* Send stream command */
+static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type,
+ int stream_id, int wait)
+{
+ u32 header;
+
+ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type);
+ header |= (stream_id << IPC_STR_ID_SHIFT);
+
+ if (wait)
+ return sst_ipc_tx_message_wait(&hsw->ipc, header,
+ NULL, 0, NULL, 0);
+ else
+ return sst_ipc_tx_message_nowait(&hsw->ipc, header, NULL, 0);
+}
+
+/* Stream ALSA trigger operations */
+int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ int wait)
+{
+ int ret;
+
+ if (!stream) {
+ dev_warn(hsw->dev, "warning: stream is NULL, no stream to pause, ignore it.\n");
+ return 0;
+ }
+
+ trace_ipc_request("stream pause", stream->reply.stream_hw_id);
+
+ ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE,
+ stream->reply.stream_hw_id, wait);
+ if (ret < 0)
+ dev_err(hsw->dev, "error: failed to pause stream %d\n",
+ stream->reply.stream_hw_id);
+
+ return ret;
+}
+
+int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ int wait)
+{
+ int ret;
+
+ if (!stream) {
+ dev_warn(hsw->dev, "warning: stream is NULL, no stream to resume, ignore it.\n");
+ return 0;
+ }
+
+ trace_ipc_request("stream resume", stream->reply.stream_hw_id);
+
+ ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME,
+ stream->reply.stream_hw_id, wait);
+ if (ret < 0)
+ dev_err(hsw->dev, "error: failed to resume stream %d\n",
+ stream->reply.stream_hw_id);
+
+ return ret;
+}
+
+int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream)
+{
+ int ret, tries = 10;
+
+ if (!stream) {
+ dev_warn(hsw->dev, "warning: stream is NULL, no stream to reset, ignore it.\n");
+ return 0;
+ }
+
+ /* dont reset streams that are not commited */
+ if (!stream->commited)
+ return 0;
+
+ /* wait for pause to complete before we reset the stream */
+ while (stream->running && tries--)
+ msleep(1);
+ if (!tries) {
+ dev_err(hsw->dev, "error: reset stream %d still running\n",
+ stream->reply.stream_hw_id);
+ return -EINVAL;
+ }
+
+ trace_ipc_request("stream reset", stream->reply.stream_hw_id);
+
+ ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET,
+ stream->reply.stream_hw_id, 1);
+ if (ret < 0)
+ dev_err(hsw->dev, "error: failed to reset stream %d\n",
+ stream->reply.stream_hw_id);
+ return ret;
+}
+
+/* Stream pointer positions */
+u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream)
+{
+ u32 rpos;
+
+ sst_dsp_read(hsw->dsp, &rpos,
+ stream->reply.read_position_register_address, sizeof(rpos));
+
+ return rpos;
+}
+
+/* Stream presentation (monotonic) positions */
+u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream)
+{
+ u64 ppos;
+
+ sst_dsp_read(hsw->dsp, &ppos,
+ stream->reply.presentation_position_register_address,
+ sizeof(ppos));
+
+ return ppos;
+}
+
+/* physical BE config */
+int sst_hsw_device_set_config(struct sst_hsw *hsw,
+ enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk,
+ enum sst_hsw_device_mode mode, u32 clock_divider)
+{
+ struct sst_hsw_ipc_device_config_req config;
+ u32 header;
+ int ret;
+
+ trace_ipc_request("set device config", dev);
+
+ config.ssp_interface = dev;
+ config.clock_frequency = mclk;
+ config.mode = mode;
+ config.clock_divider = clock_divider;
+ if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER)
+ config.channels = 4;
+ else
+ config.channels = 2;
+
+ trace_hsw_device_config_req(&config);
+
+ header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &config,
+ sizeof(config), NULL, 0);
+ if (ret < 0)
+ dev_err(hsw->dev, "error: set device formats failed\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_hsw_device_set_config);
+
+/* DX Config */
+int sst_hsw_dx_set_state(struct sst_hsw *hsw,
+ enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx)
+{
+ u32 header, state_;
+ int ret, item;
+
+ header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE);
+ state_ = state;
+
+ trace_ipc_request("PM enter Dx state", state);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &state_,
+ sizeof(state_), dx, sizeof(*dx));
+ if (ret < 0) {
+ dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state);
+ return ret;
+ }
+
+ for (item = 0; item < dx->entries_no; item++) {
+ dev_dbg(hsw->dev,
+ "Item[%d] offset[%x] - size[%x] - source[%x]\n",
+ item, dx->mem_info[item].offset,
+ dx->mem_info[item].size,
+ dx->mem_info[item].source);
+ }
+ dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n",
+ dx->entries_no, state);
+
+ return ret;
+}
+
+struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw,
+ int mod_id, int offset)
+{
+ struct sst_dsp *dsp = hsw->dsp;
+ struct sst_module *module;
+ struct sst_module_runtime *runtime;
+ int err;
+
+ module = sst_module_get_from_id(dsp, mod_id);
+ if (module == NULL) {
+ dev_err(dsp->dev, "error: failed to get module %d for pcm\n",
+ mod_id);
+ return NULL;
+ }
+
+ runtime = sst_module_runtime_new(module, mod_id, NULL);
+ if (runtime == NULL) {
+ dev_err(dsp->dev, "error: failed to create module %d runtime\n",
+ mod_id);
+ return NULL;
+ }
+
+ err = sst_module_runtime_alloc_blocks(runtime, offset);
+ if (err < 0) {
+ dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n",
+ mod_id);
+ sst_module_runtime_free(runtime);
+ return NULL;
+ }
+
+ dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id,
+ mod_id);
+ return runtime;
+}
+
+void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime)
+{
+ sst_module_runtime_free_blocks(runtime);
+ sst_module_runtime_free(runtime);
+}
+
+#ifdef CONFIG_PM
+static int sst_hsw_dx_state_dump(struct sst_hsw *hsw)
+{
+ struct sst_dsp *sst = hsw->dsp;
+ u32 item, offset, size;
+ int ret = 0;
+
+ trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS);
+
+ if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) {
+ dev_err(hsw->dev,
+ "error: number of FW context regions greater than %d\n",
+ SST_HSW_MAX_DX_REGIONS);
+ memset(&hsw->dx, 0, sizeof(hsw->dx));
+ return -EINVAL;
+ }
+
+ ret = sst_dsp_dma_get_channel(sst, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+ return ret;
+ }
+
+ /* set on-demond mode on engine 0 channel 3 */
+ sst_dsp_shim_update_bits(sst, SST_HMDC,
+ SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH,
+ SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH);
+
+ for (item = 0; item < hsw->dx.entries_no; item++) {
+ if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
+ && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
+ && hsw->dx.mem_info[item].offset <
+ DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
+
+ offset = hsw->dx.mem_info[item].offset
+ - DSP_DRAM_ADDR_OFFSET;
+ size = (hsw->dx.mem_info[item].size + 3) & (~3);
+
+ ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset,
+ sst->addr.lpe_base + offset, size);
+ if (ret < 0) {
+ dev_err(hsw->dev,
+ "error: FW context dump failed\n");
+ memset(&hsw->dx, 0, sizeof(hsw->dx));
+ goto out;
+ }
+ }
+ }
+
+out:
+ sst_dsp_dma_put_channel(sst);
+ return ret;
+}
+
+static int sst_hsw_dx_state_restore(struct sst_hsw *hsw)
+{
+ struct sst_dsp *sst = hsw->dsp;
+ u32 item, offset, size;
+ int ret;
+
+ for (item = 0; item < hsw->dx.entries_no; item++) {
+ if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP
+ && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET
+ && hsw->dx.mem_info[item].offset <
+ DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) {
+
+ offset = hsw->dx.mem_info[item].offset
+ - DSP_DRAM_ADDR_OFFSET;
+ size = (hsw->dx.mem_info[item].size + 3) & (~3);
+
+ ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset,
+ hsw->dx_context_paddr + offset, size);
+ if (ret < 0) {
+ dev_err(hsw->dev,
+ "error: FW context restore failed\n");
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int sst_hsw_dsp_load(struct sst_hsw *hsw)
+{
+ struct sst_dsp *dsp = hsw->dsp;
+ struct sst_fw *sst_fw, *t;
+ int ret;
+
+ dev_dbg(hsw->dev, "loading audio DSP....");
+
+ ret = sst_dsp_wake(dsp);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: failed to wake audio DSP\n");
+ return -ENODEV;
+ }
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+ return ret;
+ }
+
+ list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) {
+ ret = sst_fw_reload(sst_fw);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: SST FW reload failed\n");
+ sst_dsp_dma_put_channel(dsp);
+ return -ENOMEM;
+ }
+ }
+ ret = sst_block_alloc_scratch(hsw->dsp);
+ if (ret < 0)
+ return -EINVAL;
+
+ sst_dsp_dma_put_channel(dsp);
+ return 0;
+}
+
+static int sst_hsw_dsp_restore(struct sst_hsw *hsw)
+{
+ struct sst_dsp *dsp = hsw->dsp;
+ int ret;
+
+ dev_dbg(hsw->dev, "restoring audio DSP....");
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret);
+ return ret;
+ }
+
+ ret = sst_hsw_dx_state_restore(hsw);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: SST FW context restore failed\n");
+ sst_dsp_dma_put_channel(dsp);
+ return -ENOMEM;
+ }
+ sst_dsp_dma_put_channel(dsp);
+
+ /* wait for DSP boot completion */
+ sst_dsp_boot(dsp);
+
+ return ret;
+}
+
+int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw)
+{
+ int ret;
+
+ dev_dbg(hsw->dev, "audio dsp runtime suspend\n");
+
+ ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx);
+ if (ret < 0)
+ return ret;
+
+ sst_dsp_stall(hsw->dsp);
+
+ ret = sst_hsw_dx_state_dump(hsw);
+ if (ret < 0)
+ return ret;
+
+ sst_ipc_drop_all(&hsw->ipc);
+
+ return 0;
+}
+
+int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw)
+{
+ struct sst_fw *sst_fw, *t;
+ struct sst_dsp *dsp = hsw->dsp;
+
+ list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) {
+ sst_fw_unload(sst_fw);
+ }
+ sst_block_free_scratch(dsp);
+
+ hsw->boot_complete = false;
+
+ sst_dsp_sleep(dsp);
+
+ return 0;
+}
+
+int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw)
+{
+ struct device *dev = hsw->dev;
+ int ret;
+
+ dev_dbg(dev, "audio dsp runtime resume\n");
+
+ if (hsw->boot_complete)
+ return 1; /* tell caller no action is required */
+
+ ret = sst_hsw_dsp_restore(hsw);
+ if (ret < 0)
+ dev_err(dev, "error: audio DSP boot failure\n");
+
+ sst_hsw_init_module_state(hsw);
+
+ ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete,
+ msecs_to_jiffies(IPC_BOOT_MSECS));
+ if (ret == 0) {
+ dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
+ sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
+ sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
+ return -EIO;
+ }
+
+ /* Set ADSP SSP port settings */
+ ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0,
+ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ,
+ SST_HSW_DEVICE_CLOCK_MASTER, 9);
+ if (ret < 0)
+ dev_err(dev, "error: SSP re-initialization failed\n");
+
+ return ret;
+}
+#endif
+
+struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw)
+{
+ return hsw->dsp;
+}
+
+void sst_hsw_init_module_state(struct sst_hsw *hsw)
+{
+ struct sst_module *module;
+ enum sst_hsw_module_id id;
+
+ /* the base fw contains several modules */
+ for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) {
+ module = sst_module_get_from_id(hsw->dsp, id);
+ if (module) {
+ /* module waves is active only after being enabled */
+ if (id == SST_HSW_MODULE_WAVES)
+ module->state = SST_MODULE_STATE_INITIALIZED;
+ else
+ module->state = SST_MODULE_STATE_ACTIVE;
+ }
+ }
+}
+
+bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id)
+{
+ struct sst_module *module;
+
+ module = sst_module_get_from_id(hsw->dsp, module_id);
+ if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED)
+ return false;
+ else
+ return true;
+}
+
+bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id)
+{
+ struct sst_module *module;
+
+ module = sst_module_get_from_id(hsw->dsp, module_id);
+ if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE)
+ return true;
+ else
+ return false;
+}
+
+void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id)
+{
+ hsw->enabled_modules_rtd3 |= (1 << module_id);
+}
+
+void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id)
+{
+ hsw->enabled_modules_rtd3 &= ~(1 << module_id);
+}
+
+bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id)
+{
+ return hsw->enabled_modules_rtd3 & (1 << module_id);
+}
+
+void sst_hsw_reset_param_buf(struct sst_hsw *hsw)
+{
+ hsw->param_idx_w = 0;
+ hsw->param_idx_r = 0;
+ memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf));
+}
+
+int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf)
+{
+ /* save line to the first available position of param buffer */
+ if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) {
+ dev_warn(hsw->dev, "warning: param buffer overflow!\n");
+ return -EPERM;
+ }
+ memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT);
+ hsw->param_idx_w++;
+ return 0;
+}
+
+int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf)
+{
+ u8 id = 0;
+
+ /* read the first matching line from param buffer */
+ while (hsw->param_idx_r < WAVES_PARAM_LINES) {
+ id = hsw->param_buf[hsw->param_idx_r][0];
+ hsw->param_idx_r++;
+ if (buf[0] == id) {
+ memcpy(buf, hsw->param_buf[hsw->param_idx_r],
+ WAVES_PARAM_COUNT);
+ break;
+ }
+ }
+ if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) {
+ dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n");
+ hsw->param_idx_r = 0;
+ return 0;
+ }
+ return 0;
+}
+
+int sst_hsw_launch_param_buf(struct sst_hsw *hsw)
+{
+ int ret, idx;
+
+ if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) {
+ dev_dbg(hsw->dev, "module waves is not active\n");
+ return 0;
+ }
+
+ /* put all param lines to DSP through ipc */
+ for (idx = 0; idx < hsw->param_idx_w; idx++) {
+ ret = sst_hsw_module_set_param(hsw,
+ SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0],
+ WAVES_PARAM_COUNT, hsw->param_buf[idx]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+int sst_hsw_module_load(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id, char *name)
+{
+ int ret = 0;
+ const struct firmware *fw = NULL;
+ struct sst_fw *hsw_sst_fw;
+ struct sst_module *module;
+ struct device *dev = hsw->dev;
+ struct sst_dsp *dsp = hsw->dsp;
+
+ dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name);
+
+ module = sst_module_get_from_id(dsp, module_id);
+ if (module == NULL) {
+ /* loading for the first time */
+ if (module_id == SST_HSW_MODULE_BASE_FW) {
+ /* for base module: use fw requested in acpi probe */
+ fw = dsp->pdata->fw;
+ if (!fw) {
+ dev_err(dev, "request Base fw failed\n");
+ return -ENODEV;
+ }
+ } else {
+ /* try and load any other optional modules if they are
+ * available. Use dev_info instead of dev_err in case
+ * request firmware failed */
+ ret = reject_firmware(&fw, name, dev);
+ if (ret) {
+ dev_info(dev, "fw image %s not available(%d)\n",
+ name, ret);
+ return ret;
+ }
+ }
+ hsw_sst_fw = sst_fw_new(dsp, fw, hsw);
+ if (hsw_sst_fw == NULL) {
+ dev_err(dev, "error: failed to load firmware\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ module = sst_module_get_from_id(dsp, module_id);
+ if (module == NULL) {
+ dev_err(dev, "error: no module %d in firmware %s\n",
+ module_id, name);
+ }
+ } else
+ dev_info(dev, "module %d (%s) already loaded\n",
+ module_id, name);
+out:
+ /* release fw, but base fw should be released by acpi driver */
+ if (fw && module_id != SST_HSW_MODULE_BASE_FW)
+ release_firmware(fw);
+
+ return ret;
+}
+
+int sst_hsw_module_enable(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id)
+{
+ int ret;
+ u32 header = 0;
+ struct sst_hsw_ipc_module_config config;
+ struct sst_module *module;
+ struct sst_module_runtime *runtime;
+ struct device *dev = hsw->dev;
+ struct sst_dsp *dsp = hsw->dsp;
+
+ if (!sst_hsw_is_module_loaded(hsw, module_id)) {
+ dev_dbg(dev, "module %d not loaded\n", module_id);
+ return 0;
+ }
+
+ if (sst_hsw_is_module_active(hsw, module_id)) {
+ dev_info(dev, "module %d already enabled\n", module_id);
+ return 0;
+ }
+
+ module = sst_module_get_from_id(dsp, module_id);
+ if (module == NULL) {
+ dev_err(dev, "module %d not valid\n", module_id);
+ return -ENXIO;
+ }
+
+ runtime = sst_module_runtime_get_from_id(module, module_id);
+ if (runtime == NULL) {
+ dev_err(dev, "runtime %d not valid", module_id);
+ return -ENXIO;
+ }
+
+ header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) |
+ IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) |
+ IPC_MODULE_ID(module_id);
+ dev_dbg(dev, "module enable header: %x\n", header);
+
+ config.map.module_entries_count = 1;
+ config.map.module_entries[0].module_id = module->id;
+ config.map.module_entries[0].entry_point = module->entry;
+
+ config.persistent_mem.offset =
+ sst_dsp_get_offset(dsp,
+ runtime->persistent_offset, SST_MEM_DRAM);
+ config.persistent_mem.size = module->persistent_size;
+
+ config.scratch_mem.offset =
+ sst_dsp_get_offset(dsp,
+ dsp->scratch_offset, SST_MEM_DRAM);
+ config.scratch_mem.size = module->scratch_size;
+ dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x",
+ config.map.module_entries[0].module_id,
+ config.persistent_mem.size,
+ config.persistent_mem.offset,
+ config.scratch_mem.size, config.scratch_mem.offset,
+ config.map.module_entries[0].entry_point);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header,
+ &config, sizeof(config), NULL, 0);
+ if (ret < 0)
+ dev_err(dev, "ipc: module enable failed - %d\n", ret);
+ else
+ module->state = SST_MODULE_STATE_ACTIVE;
+
+ return ret;
+}
+
+int sst_hsw_module_disable(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id)
+{
+ int ret;
+ u32 header;
+ struct sst_module *module;
+ struct device *dev = hsw->dev;
+ struct sst_dsp *dsp = hsw->dsp;
+
+ if (!sst_hsw_is_module_loaded(hsw, module_id)) {
+ dev_dbg(dev, "module %d not loaded\n", module_id);
+ return 0;
+ }
+
+ if (!sst_hsw_is_module_active(hsw, module_id)) {
+ dev_info(dev, "module %d already disabled\n", module_id);
+ return 0;
+ }
+
+ module = sst_module_get_from_id(dsp, module_id);
+ if (module == NULL) {
+ dev_err(dev, "module %d not valid\n", module_id);
+ return -ENXIO;
+ }
+
+ header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) |
+ IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) |
+ IPC_MODULE_ID(module_id);
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0, NULL, 0);
+ if (ret < 0)
+ dev_err(dev, "module disable failed - %d\n", ret);
+ else
+ module->state = SST_MODULE_STATE_INITIALIZED;
+
+ return ret;
+}
+
+int sst_hsw_module_set_param(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id, u32 parameter_id,
+ u32 param_size, char *param)
+{
+ int ret;
+ unsigned char *data = NULL;
+ u32 header = 0;
+ u32 payload_size = 0, transfer_parameter_size = 0;
+ dma_addr_t dma_addr = 0;
+ struct sst_hsw_transfer_parameter *parameter;
+ struct device *dev = hsw->dev;
+
+ header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) |
+ IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) |
+ IPC_MODULE_ID(module_id);
+ dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", header);
+
+ payload_size = param_size +
+ sizeof(struct sst_hsw_transfer_parameter) -
+ sizeof(struct sst_hsw_transfer_list);
+ dev_dbg(dev, "parameter size : %d\n", param_size);
+ dev_dbg(dev, "payload size : %d\n", payload_size);
+
+ if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) {
+ /* short parameter, mailbox can contain data */
+ dev_dbg(dev, "transfer parameter size : %d\n",
+ transfer_parameter_size);
+
+ transfer_parameter_size = ALIGN(payload_size, 4);
+ dev_dbg(dev, "transfer parameter aligned size : %d\n",
+ transfer_parameter_size);
+
+ parameter = kzalloc(transfer_parameter_size, GFP_KERNEL);
+ if (parameter == NULL)
+ return -ENOMEM;
+
+ memcpy(parameter->data, param, param_size);
+ } else {
+ dev_warn(dev, "transfer parameter size too large!");
+ return 0;
+ }
+
+ parameter->parameter_id = parameter_id;
+ parameter->data_size = param_size;
+
+ ret = sst_ipc_tx_message_wait(&hsw->ipc, header,
+ parameter, transfer_parameter_size , NULL, 0);
+ if (ret < 0)
+ dev_err(dev, "ipc: module set parameter failed - %d\n", ret);
+
+ kfree(parameter);
+
+ if (data)
+ dma_free_coherent(hsw->dsp->dma_dev,
+ param_size, (void *)data, dma_addr);
+
+ return ret;
+}
+
+static struct sst_dsp_device hsw_dev = {
+ .thread = hsw_irq_thread,
+ .ops = &haswell_ops,
+};
+
+static void hsw_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg)
+{
+ /* send the message */
+ sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size);
+ sst_dsp_ipc_msg_tx(ipc->dsp, msg->header);
+}
+
+static void hsw_shim_dbg(struct sst_generic_ipc *ipc, const char *text)
+{
+ struct sst_dsp *sst = ipc->dsp;
+ u32 isr, ipcd, imrx, ipcx;
+
+ ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX);
+ isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX);
+ ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD);
+ imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX);
+
+ dev_err(ipc->dev,
+ "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n",
+ text, ipcx, isr, ipcd, imrx);
+}
+
+static void hsw_tx_data_copy(struct ipc_message *msg, char *tx_data,
+ size_t tx_size)
+{
+ memcpy(msg->tx_data, tx_data, tx_size);
+}
+
+static u64 hsw_reply_msg_match(u64 header, u64 *mask)
+{
+ /* clear reply bits & status bits */
+ header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK);
+ *mask = (u64)-1;
+
+ return header;
+}
+
+int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_hsw_ipc_fw_version version;
+ struct sst_hsw *hsw;
+ struct sst_generic_ipc *ipc;
+ int ret;
+
+ dev_dbg(dev, "initialising Audio DSP IPC\n");
+
+ hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL);
+ if (hsw == NULL)
+ return -ENOMEM;
+
+ ipc = &hsw->ipc;
+ ipc->dev = dev;
+ ipc->ops.tx_msg = hsw_tx_msg;
+ ipc->ops.shim_dbg = hsw_shim_dbg;
+ ipc->ops.tx_data_copy = hsw_tx_data_copy;
+ ipc->ops.reply_msg_match = hsw_reply_msg_match;
+
+ ret = sst_ipc_init(ipc);
+ if (ret != 0)
+ goto ipc_init_err;
+
+ INIT_LIST_HEAD(&hsw->stream_list);
+ init_waitqueue_head(&hsw->boot_wait);
+ hsw_dev.thread_context = hsw;
+
+ /* init SST shim */
+ hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata);
+ if (hsw->dsp == NULL) {
+ ret = -ENODEV;
+ goto dsp_new_err;
+ }
+
+ ipc->dsp = hsw->dsp;
+
+ /* allocate DMA buffer for context storage */
+ hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev,
+ SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL);
+ if (hsw->dx_context == NULL) {
+ ret = -ENOMEM;
+ goto dma_err;
+ }
+
+ /* keep the DSP in reset state for base FW loading */
+ sst_dsp_reset(hsw->dsp);
+
+ /* load base module and other modules in base firmware image */
+ ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base");
+ if (ret < 0)
+ goto fw_err;
+
+ /* try to load module waves */
+ sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "/*(DEBLOBBED)*/");
+
+ /* allocate scratch mem regions */
+ ret = sst_block_alloc_scratch(hsw->dsp);
+ if (ret < 0)
+ goto boot_err;
+
+ /* init param buffer */
+ sst_hsw_reset_param_buf(hsw);
+
+ /* wait for DSP boot completion */
+ sst_dsp_boot(hsw->dsp);
+ ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete,
+ msecs_to_jiffies(IPC_BOOT_MSECS));
+ if (ret == 0) {
+ ret = -EIO;
+ dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n",
+ sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD),
+ sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX));
+ goto boot_err;
+ }
+
+ /* init module state after boot */
+ sst_hsw_init_module_state(hsw);
+
+ /* get the FW version */
+ sst_hsw_fw_get_version(hsw, &version);
+
+ /* get the globalmixer */
+ ret = sst_hsw_mixer_get_info(hsw);
+ if (ret < 0) {
+ dev_err(hsw->dev, "error: failed to get stream info\n");
+ goto boot_err;
+ }
+
+ pdata->dsp = hsw;
+ return 0;
+
+boot_err:
+ sst_dsp_reset(hsw->dsp);
+ sst_fw_free_all(hsw->dsp);
+fw_err:
+ dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
+ hsw->dx_context, hsw->dx_context_paddr);
+dma_err:
+ sst_dsp_free(hsw->dsp);
+dsp_new_err:
+ sst_ipc_fini(ipc);
+ipc_init_err:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sst_hsw_dsp_init);
+
+void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata)
+{
+ struct sst_hsw *hsw = pdata->dsp;
+
+ sst_dsp_reset(hsw->dsp);
+ sst_fw_free_all(hsw->dsp);
+ dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE,
+ hsw->dx_context, hsw->dx_context_paddr);
+ sst_dsp_free(hsw->dsp);
+ sst_ipc_fini(&hsw->ipc);
+}
+EXPORT_SYMBOL_GPL(sst_hsw_dsp_free);
diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.h b/sound/soc/intel/haswell/sst-haswell-ipc.h
new file mode 100644
index 000000000..06d71aefa
--- /dev/null
+++ b/sound/soc/intel/haswell/sst-haswell-ipc.h
@@ -0,0 +1,534 @@
+/*
+ * Intel SST Haswell/Broadwell IPC Support
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __SST_HASWELL_IPC_H
+#define __SST_HASWELL_IPC_H
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <sound/asound.h>
+
+#define SST_HSW_NO_CHANNELS 4
+#define SST_HSW_MAX_DX_REGIONS 14
+#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024)
+#define SST_HSW_CHANNELS_ALL 0xffffffff
+
+#define SST_HSW_FW_LOG_CONFIG_DWORDS 12
+#define SST_HSW_GLOBAL_LOG 15
+
+/**
+ * Upfront defined maximum message size that is
+ * expected by the in/out communication pipes in FW.
+ */
+#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400
+#define SST_HSW_MAX_INFO_SIZE 64
+#define SST_HSW_BUILD_HASH_LENGTH 40
+#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500
+#define WAVES_PARAM_COUNT 128
+#define WAVES_PARAM_LINES 160
+
+struct sst_hsw;
+struct sst_hsw_stream;
+struct sst_hsw_log_stream;
+struct sst_pdata;
+struct sst_module;
+struct sst_module_runtime;
+extern struct sst_ops haswell_ops;
+
+/* Stream Allocate Path ID */
+enum sst_hsw_stream_path_id {
+ SST_HSW_STREAM_PATH_SSP0_OUT = 0,
+ SST_HSW_STREAM_PATH_SSP0_IN = 1,
+ SST_HSW_STREAM_PATH_MAX_PATH_ID = 2,
+};
+
+/* Stream Allocate Stream Type */
+enum sst_hsw_stream_type {
+ SST_HSW_STREAM_TYPE_RENDER = 0,
+ SST_HSW_STREAM_TYPE_SYSTEM = 1,
+ SST_HSW_STREAM_TYPE_CAPTURE = 2,
+ SST_HSW_STREAM_TYPE_LOOPBACK = 3,
+ SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4,
+};
+
+/* Stream Allocate Stream Format */
+enum sst_hsw_stream_format {
+ SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0,
+ SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1,
+ SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2,
+ SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3,
+};
+
+/* Device ID */
+enum sst_hsw_device_id {
+ SST_HSW_DEVICE_SSP_0 = 0,
+ SST_HSW_DEVICE_SSP_1 = 1,
+};
+
+/* Device Master Clock Frequency */
+enum sst_hsw_device_mclk {
+ SST_HSW_DEVICE_MCLK_OFF = 0,
+ SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1,
+ SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2,
+ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3,
+};
+
+/* Device Clock Master */
+enum sst_hsw_device_mode {
+ SST_HSW_DEVICE_CLOCK_SLAVE = 0,
+ SST_HSW_DEVICE_CLOCK_MASTER = 1,
+ SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2,
+};
+
+/* DX Power State */
+enum sst_hsw_dx_state {
+ SST_HSW_DX_STATE_D0 = 0,
+ SST_HSW_DX_STATE_D1 = 1,
+ SST_HSW_DX_STATE_D3 = 3,
+ SST_HSW_DX_STATE_MAX = 3,
+};
+
+/* Audio stream stage IDs */
+enum sst_hsw_fx_stage_id {
+ SST_HSW_STAGE_ID_WAVES = 0,
+ SST_HSW_STAGE_ID_DTS = 1,
+ SST_HSW_STAGE_ID_DOLBY = 2,
+ SST_HSW_STAGE_ID_BOOST = 3,
+ SST_HSW_STAGE_ID_MAX_FX_ID
+};
+
+/* DX State Type */
+enum sst_hsw_dx_type {
+ SST_HSW_DX_TYPE_FW_IMAGE = 0,
+ SST_HSW_DX_TYPE_MEMORY_DUMP = 1
+};
+
+/* Volume Curve Type*/
+enum sst_hsw_volume_curve {
+ SST_HSW_VOLUME_CURVE_NONE = 0,
+ SST_HSW_VOLUME_CURVE_FADE = 1
+};
+
+/* Sample ordering */
+enum sst_hsw_interleaving {
+ SST_HSW_INTERLEAVING_PER_CHANNEL = 0,
+ SST_HSW_INTERLEAVING_PER_SAMPLE = 1,
+};
+
+/* Channel indices */
+enum sst_hsw_channel_index {
+ SST_HSW_CHANNEL_LEFT = 0,
+ SST_HSW_CHANNEL_CENTER = 1,
+ SST_HSW_CHANNEL_RIGHT = 2,
+ SST_HSW_CHANNEL_LEFT_SURROUND = 3,
+ SST_HSW_CHANNEL_CENTER_SURROUND = 3,
+ SST_HSW_CHANNEL_RIGHT_SURROUND = 4,
+ SST_HSW_CHANNEL_LFE = 7,
+ SST_HSW_CHANNEL_INVALID = 0xF,
+};
+
+/* List of supported channel maps. */
+enum sst_hsw_channel_config {
+ SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */
+ SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */
+ SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */
+ SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */
+ SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */
+ SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */
+ SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */
+ SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */
+ SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */
+ SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */
+ SST_HSW_CHANNEL_CONFIG_INVALID,
+};
+
+/* List of supported bit depths. */
+enum sst_hsw_bitdepth {
+ SST_HSW_DEPTH_8BIT = 8,
+ SST_HSW_DEPTH_16BIT = 16,
+ SST_HSW_DEPTH_24BIT = 24, /* Default. */
+ SST_HSW_DEPTH_32BIT = 32,
+ SST_HSW_DEPTH_INVALID = 33,
+};
+
+enum sst_hsw_module_id {
+ SST_HSW_MODULE_BASE_FW = 0x0,
+ SST_HSW_MODULE_MP3 = 0x1,
+ SST_HSW_MODULE_AAC_5_1 = 0x2,
+ SST_HSW_MODULE_AAC_2_0 = 0x3,
+ SST_HSW_MODULE_SRC = 0x4,
+ SST_HSW_MODULE_WAVES = 0x5,
+ SST_HSW_MODULE_DOLBY = 0x6,
+ SST_HSW_MODULE_BOOST = 0x7,
+ SST_HSW_MODULE_LPAL = 0x8,
+ SST_HSW_MODULE_DTS = 0x9,
+ SST_HSW_MODULE_PCM_CAPTURE = 0xA,
+ SST_HSW_MODULE_PCM_SYSTEM = 0xB,
+ SST_HSW_MODULE_PCM_REFERENCE = 0xC,
+ SST_HSW_MODULE_PCM = 0xD,
+ SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE,
+ SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF,
+ SST_HSW_MAX_MODULE_ID,
+};
+
+enum sst_hsw_performance_action {
+ SST_HSW_PERF_START = 0,
+ SST_HSW_PERF_STOP = 1,
+};
+
+struct sst_hsw_transfer_info {
+ uint32_t destination; /* destination address */
+ uint32_t reverse:1; /* if 1 data flows from destination */
+ uint32_t size:31; /* transfer size in bytes.*/
+ uint16_t first_page_offset; /* offset to data in the first page. */
+ uint8_t packed_pages; /* page addresses. Each occupies 20 bits */
+} __attribute__((packed));
+
+struct sst_hsw_transfer_list {
+ uint32_t transfers_count;
+ struct sst_hsw_transfer_info transfers;
+} __attribute__((packed));
+
+struct sst_hsw_transfer_parameter {
+ uint32_t parameter_id;
+ uint32_t data_size;
+ union {
+ uint8_t data[1];
+ struct sst_hsw_transfer_list transfer_list;
+ };
+} __attribute__((packed));
+
+/* SST firmware module info */
+struct sst_hsw_module_info {
+ u8 name[SST_HSW_MAX_INFO_SIZE];
+ u8 version[SST_HSW_MAX_INFO_SIZE];
+} __attribute__((packed));
+
+/* Module entry point */
+struct sst_hsw_module_entry {
+ enum sst_hsw_module_id module_id;
+ u32 entry_point;
+} __attribute__((packed));
+
+/* Module map - alignement matches DSP */
+struct sst_hsw_module_map {
+ u8 module_entries_count;
+ struct sst_hsw_module_entry module_entries[1];
+} __attribute__((packed));
+
+struct sst_hsw_memory_info {
+ u32 offset;
+ u32 size;
+} __attribute__((packed));
+
+struct sst_hsw_fx_enable {
+ struct sst_hsw_module_map module_map;
+ struct sst_hsw_memory_info persistent_mem;
+} __attribute__((packed));
+
+struct sst_hsw_ipc_module_config {
+ struct sst_hsw_module_map map;
+ struct sst_hsw_memory_info persistent_mem;
+ struct sst_hsw_memory_info scratch_mem;
+} __attribute__((packed));
+
+struct sst_hsw_get_fx_param {
+ u32 parameter_id;
+ u32 param_size;
+} __attribute__((packed));
+
+struct sst_hsw_perf_action {
+ u32 action;
+} __attribute__((packed));
+
+struct sst_hsw_perf_data {
+ u64 timestamp;
+ u64 cycles;
+ u64 datatime;
+} __attribute__((packed));
+
+/* FW version */
+struct sst_hsw_ipc_fw_version {
+ u8 build;
+ u8 minor;
+ u8 major;
+ u8 type;
+ u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH];
+ u32 fw_log_providers_hash;
+} __attribute__((packed));
+
+/* Stream ring info */
+struct sst_hsw_ipc_stream_ring {
+ u32 ring_pt_address;
+ u32 num_pages;
+ u32 ring_size;
+ u32 ring_offset;
+ u32 ring_first_pfn;
+} __attribute__((packed));
+
+/* Debug Dump Log Enable Request */
+struct sst_hsw_ipc_debug_log_enable_req {
+ struct sst_hsw_ipc_stream_ring ringinfo;
+ u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS];
+} __attribute__((packed));
+
+/* Debug Dump Log Reply */
+struct sst_hsw_ipc_debug_log_reply {
+ u32 log_buffer_begining;
+ u32 log_buffer_size;
+} __attribute__((packed));
+
+/* Stream glitch position */
+struct sst_hsw_ipc_stream_glitch_position {
+ u32 glitch_type;
+ u32 present_pos;
+ u32 write_pos;
+} __attribute__((packed));
+
+/* Stream get position */
+struct sst_hsw_ipc_stream_get_position {
+ u32 position;
+ u32 fw_cycle_count;
+} __attribute__((packed));
+
+/* Stream set position */
+struct sst_hsw_ipc_stream_set_position {
+ u32 position;
+ u32 end_of_buffer;
+} __attribute__((packed));
+
+/* Stream Free Request */
+struct sst_hsw_ipc_stream_free_req {
+ u8 stream_id;
+ u8 reserved[3];
+} __attribute__((packed));
+
+/* Set Volume Request */
+struct sst_hsw_ipc_volume_req {
+ u32 channel;
+ u32 target_volume;
+ u64 curve_duration;
+ u32 curve_type;
+} __attribute__((packed));
+
+/* Device Configuration Request */
+struct sst_hsw_ipc_device_config_req {
+ u32 ssp_interface;
+ u32 clock_frequency;
+ u32 mode;
+ u16 clock_divider;
+ u8 channels;
+ u8 reserved;
+} __attribute__((packed));
+
+/* Audio Data formats */
+struct sst_hsw_audio_data_format_ipc {
+ u32 frequency;
+ u32 bitdepth;
+ u32 map;
+ u32 config;
+ u32 style;
+ u8 ch_num;
+ u8 valid_bit;
+ u8 reserved[2];
+} __attribute__((packed));
+
+/* Stream Allocate Request */
+struct sst_hsw_ipc_stream_alloc_req {
+ u8 path_id;
+ u8 stream_type;
+ u8 format_id;
+ u8 reserved;
+ struct sst_hsw_audio_data_format_ipc format;
+ struct sst_hsw_ipc_stream_ring ringinfo;
+ struct sst_hsw_module_map map;
+ struct sst_hsw_memory_info persistent_mem;
+ struct sst_hsw_memory_info scratch_mem;
+ u32 number_of_notifications;
+} __attribute__((packed));
+
+/* Stream Allocate Reply */
+struct sst_hsw_ipc_stream_alloc_reply {
+ u32 stream_hw_id;
+ u32 mixer_hw_id; // returns rate ????
+ u32 read_position_register_address;
+ u32 presentation_position_register_address;
+ u32 peak_meter_register_address[SST_HSW_NO_CHANNELS];
+ u32 volume_register_address[SST_HSW_NO_CHANNELS];
+} __attribute__((packed));
+
+/* Get Mixer Stream Info */
+struct sst_hsw_ipc_stream_info_reply {
+ u32 mixer_hw_id;
+ u32 peak_meter_register_address[SST_HSW_NO_CHANNELS];
+ u32 volume_register_address[SST_HSW_NO_CHANNELS];
+} __attribute__((packed));
+
+/* DX State Request */
+struct sst_hsw_ipc_dx_req {
+ u8 state;
+ u8 reserved[3];
+} __attribute__((packed));
+
+/* DX State Reply Memory Info Item */
+struct sst_hsw_ipc_dx_memory_item {
+ u32 offset;
+ u32 size;
+ u32 source;
+} __attribute__((packed));
+
+/* DX State Reply */
+struct sst_hsw_ipc_dx_reply {
+ u32 entries_no;
+ struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS];
+} __attribute__((packed));
+
+struct sst_hsw_ipc_fw_version;
+
+/* SST Init & Free */
+struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length,
+ u32 fw_offset);
+void sst_hsw_free(struct sst_hsw *hsw);
+int sst_hsw_fw_get_version(struct sst_hsw *hsw,
+ struct sst_hsw_ipc_fw_version *version);
+u32 create_channel_map(enum sst_hsw_channel_config config);
+
+/* Stream Mixer Controls - */
+int sst_hsw_stream_set_volume(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume);
+int sst_hsw_stream_get_volume(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume);
+
+/* Global Mixer Controls - */
+int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
+ u32 volume);
+int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel,
+ u32 *volume);
+
+/* Stream API */
+struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id,
+ u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data),
+ void *data);
+
+int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream);
+
+/* Stream Configuration */
+int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ enum sst_hsw_stream_path_id path_id,
+ enum sst_hsw_stream_type stream_type,
+ enum sst_hsw_stream_format format_id);
+
+int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ u32 ring_pt_address, u32 num_pages,
+ u32 ring_size, u32 ring_offset, u32 ring_first_pfn);
+
+int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream);
+
+int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ u32 bits);
+int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ int rate);
+int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ enum sst_hsw_bitdepth bits);
+int sst_hsw_stream_set_channels(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, int channels);
+int sst_hsw_stream_set_map_config(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 map,
+ enum sst_hsw_channel_config config);
+int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ enum sst_hsw_interleaving style);
+int sst_hsw_stream_set_module_info(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, struct sst_module_runtime *runtime);
+int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 offset, u32 size);
+int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 offset, u32 size);
+snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream);
+void sst_hsw_stream_set_old_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, snd_pcm_uframes_t val);
+bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream);
+void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, bool val);
+int sst_hsw_mixer_get_info(struct sst_hsw *hsw);
+
+/* Stream ALSA trigger operations */
+int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ int wait);
+int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream,
+ int wait);
+int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream);
+
+/* Stream pointer positions */
+int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 *position);
+int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream, u32 *position);
+u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream);
+u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw,
+ struct sst_hsw_stream *stream);
+
+/* HW port config */
+int sst_hsw_device_set_config(struct sst_hsw *hsw,
+ enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk,
+ enum sst_hsw_device_mode mode, u32 clock_divider);
+
+/* DX Config */
+int sst_hsw_dx_set_state(struct sst_hsw *hsw,
+ enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx);
+
+/* init */
+int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata);
+void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata);
+struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw);
+
+/* fw module function */
+void sst_hsw_init_module_state(struct sst_hsw *hsw);
+bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id);
+bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id);
+void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id);
+void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id);
+bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id);
+void sst_hsw_reset_param_buf(struct sst_hsw *hsw);
+int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf);
+int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf);
+int sst_hsw_launch_param_buf(struct sst_hsw *hsw);
+
+int sst_hsw_module_load(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id, char *name);
+int sst_hsw_module_enable(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id);
+int sst_hsw_module_disable(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id);
+int sst_hsw_module_set_param(struct sst_hsw *hsw,
+ u32 module_id, u32 instance_id, u32 parameter_id,
+ u32 param_size, char *param);
+
+/* runtime module management */
+struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw,
+ int mod_id, int offset);
+void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime);
+
+/* PM */
+int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw);
+int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw);
+int sst_hsw_dsp_load(struct sst_hsw *hsw);
+int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw);
+
+#endif
diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c
new file mode 100644
index 000000000..23ae0400d
--- /dev/null
+++ b/sound/soc/intel/haswell/sst-haswell-pcm.c
@@ -0,0 +1,1405 @@
+/*
+ * Intel SST Haswell/Broadwell PCM Support
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/compress_driver.h>
+
+#include "../haswell/sst-haswell-ipc.h"
+#include "../common/sst-dsp-priv.h"
+#include "../common/sst-dsp.h"
+
+#define HSW_PCM_COUNT 6
+#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */
+
+#define SST_OLD_POSITION(d, r, o) ((d) + \
+ frames_to_bytes(r, o))
+#define SST_SAMPLES(r, x) (bytes_to_samples(r, \
+ frames_to_bytes(r, (x))))
+
+/* simple volume table */
+static const u32 volume_map[] = {
+ HSW_VOLUME_MAX >> 30,
+ HSW_VOLUME_MAX >> 29,
+ HSW_VOLUME_MAX >> 28,
+ HSW_VOLUME_MAX >> 27,
+ HSW_VOLUME_MAX >> 26,
+ HSW_VOLUME_MAX >> 25,
+ HSW_VOLUME_MAX >> 24,
+ HSW_VOLUME_MAX >> 23,
+ HSW_VOLUME_MAX >> 22,
+ HSW_VOLUME_MAX >> 21,
+ HSW_VOLUME_MAX >> 20,
+ HSW_VOLUME_MAX >> 19,
+ HSW_VOLUME_MAX >> 18,
+ HSW_VOLUME_MAX >> 17,
+ HSW_VOLUME_MAX >> 16,
+ HSW_VOLUME_MAX >> 15,
+ HSW_VOLUME_MAX >> 14,
+ HSW_VOLUME_MAX >> 13,
+ HSW_VOLUME_MAX >> 12,
+ HSW_VOLUME_MAX >> 11,
+ HSW_VOLUME_MAX >> 10,
+ HSW_VOLUME_MAX >> 9,
+ HSW_VOLUME_MAX >> 8,
+ HSW_VOLUME_MAX >> 7,
+ HSW_VOLUME_MAX >> 6,
+ HSW_VOLUME_MAX >> 5,
+ HSW_VOLUME_MAX >> 4,
+ HSW_VOLUME_MAX >> 3,
+ HSW_VOLUME_MAX >> 2,
+ HSW_VOLUME_MAX >> 1,
+ HSW_VOLUME_MAX >> 0,
+};
+
+#define HSW_PCM_PERIODS_MAX 64
+#define HSW_PCM_PERIODS_MIN 2
+
+#define HSW_PCM_DAI_ID_SYSTEM 0
+#define HSW_PCM_DAI_ID_OFFLOAD0 1
+#define HSW_PCM_DAI_ID_OFFLOAD1 2
+#define HSW_PCM_DAI_ID_LOOPBACK 3
+
+
+static const struct snd_pcm_hardware hsw_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 |
+ SNDRV_PCM_INFO_DRAIN_TRIGGER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE,
+ .periods_min = HSW_PCM_PERIODS_MIN,
+ .periods_max = HSW_PCM_PERIODS_MAX,
+ .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE,
+};
+
+struct hsw_pcm_module_map {
+ int dai_id;
+ int stream;
+ enum sst_hsw_module_id mod_id;
+};
+
+/* private data for each PCM DSP stream */
+struct hsw_pcm_data {
+ int dai_id;
+ struct sst_hsw_stream *stream;
+ struct sst_module_runtime *runtime;
+ struct sst_module_runtime_context context;
+ struct snd_pcm *hsw_pcm;
+ u32 volume[2];
+ struct snd_pcm_substream *substream;
+ struct snd_compr_stream *cstream;
+ unsigned int wpos;
+ struct mutex mutex;
+ bool allocated;
+ int persistent_offset;
+};
+
+enum hsw_pm_state {
+ HSW_PM_STATE_D0 = 0,
+ HSW_PM_STATE_RTD3 = 1,
+ HSW_PM_STATE_D3 = 2,
+};
+
+/* private data for the driver */
+struct hsw_priv_data {
+ /* runtime DSP */
+ struct sst_hsw *hsw;
+ struct device *dev;
+ enum hsw_pm_state pm_state;
+ struct snd_soc_card *soc_card;
+ struct sst_module_runtime *runtime_waves; /* sound effect module */
+
+ /* page tables */
+ struct snd_dma_buffer dmab[HSW_PCM_COUNT][2];
+
+ /* DAI data */
+ struct hsw_pcm_data pcm[HSW_PCM_COUNT][2];
+};
+
+
+/* static mappings between PCMs and modules - may be dynamic in future */
+static struct hsw_pcm_module_map mod_map[] = {
+ {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM},
+ {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM},
+ {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM},
+ {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE},
+ {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE},
+};
+
+static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data);
+
+static inline u32 hsw_mixer_to_ipc(unsigned int value)
+{
+ if (value >= ARRAY_SIZE(volume_map))
+ return volume_map[0];
+ else
+ return volume_map[value];
+}
+
+static inline unsigned int hsw_ipc_to_mixer(u32 value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(volume_map); i++) {
+ if (volume_map[i] >= value)
+ return i;
+ }
+
+ return i - 1;
+}
+
+static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ u32 volume;
+ int dai, stream;
+
+ dai = mod_map[mc->reg].dai_id;
+ stream = mod_map[mc->reg].stream;
+ pcm_data = &pdata->pcm[dai][stream];
+
+ mutex_lock(&pcm_data->mutex);
+ pm_runtime_get_sync(pdata->dev);
+
+ if (!pcm_data->stream) {
+ pcm_data->volume[0] =
+ hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
+ pcm_data->volume[1] =
+ hsw_mixer_to_ipc(ucontrol->value.integer.value[1]);
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+ return 0;
+ }
+
+ if (ucontrol->value.integer.value[0] ==
+ ucontrol->value.integer.value[1]) {
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
+ /* apply volume value to all channels */
+ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume);
+ } else {
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
+ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume);
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]);
+ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume);
+ }
+
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+ return 0;
+}
+
+static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ u32 volume;
+ int dai, stream;
+
+ dai = mod_map[mc->reg].dai_id;
+ stream = mod_map[mc->reg].stream;
+ pcm_data = &pdata->pcm[dai][stream];
+
+ mutex_lock(&pcm_data->mutex);
+ pm_runtime_get_sync(pdata->dev);
+
+ if (!pcm_data->stream) {
+ ucontrol->value.integer.value[0] =
+ hsw_ipc_to_mixer(pcm_data->volume[0]);
+ ucontrol->value.integer.value[1] =
+ hsw_ipc_to_mixer(pcm_data->volume[1]);
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+ return 0;
+ }
+
+ sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume);
+ ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume);
+ sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume);
+ ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume);
+
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+
+ return 0;
+}
+
+static int hsw_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ u32 volume;
+
+ pm_runtime_get_sync(pdata->dev);
+
+ if (ucontrol->value.integer.value[0] ==
+ ucontrol->value.integer.value[1]) {
+
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
+ sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume);
+
+ } else {
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]);
+ sst_hsw_mixer_set_volume(hsw, 0, 0, volume);
+
+ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]);
+ sst_hsw_mixer_set_volume(hsw, 0, 1, volume);
+ }
+
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ return 0;
+}
+
+static int hsw_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ unsigned int volume = 0;
+
+ pm_runtime_get_sync(pdata->dev);
+ sst_hsw_mixer_get_volume(hsw, 0, 0, &volume);
+ ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume);
+
+ sst_hsw_mixer_get_volume(hsw, 0, 1, &volume);
+ ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume);
+
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ return 0;
+}
+
+static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES;
+
+ ucontrol->value.integer.value[0] =
+ (sst_hsw_is_module_active(hsw, id) ||
+ sst_hsw_is_module_enabled_rtd3(hsw, id));
+ return 0;
+}
+
+static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ int ret = 0;
+ enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES;
+ bool switch_on = (bool)ucontrol->value.integer.value[0];
+
+ /* if module is in RAM on the DSP, apply user settings to module through
+ * ipc. If module is not in RAM on the DSP, store user setting for
+ * track */
+ if (sst_hsw_is_module_loaded(hsw, id)) {
+ if (switch_on == sst_hsw_is_module_active(hsw, id))
+ return 0;
+
+ if (switch_on)
+ ret = sst_hsw_module_enable(hsw, id, 0);
+ else
+ ret = sst_hsw_module_disable(hsw, id, 0);
+ } else {
+ if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id))
+ return 0;
+
+ if (switch_on)
+ sst_hsw_set_module_enabled_rtd3(hsw, id);
+ else
+ sst_hsw_set_module_disabled_rtd3(hsw, id);
+ }
+
+ return ret;
+}
+
+static int hsw_waves_param_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+
+ /* return a matching line from param buffer */
+ return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data);
+}
+
+static int hsw_waves_param_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol);
+ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ int ret;
+ enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES;
+ int param_id = ucontrol->value.bytes.data[0];
+ int param_size = WAVES_PARAM_COUNT;
+
+ /* clear param buffer and reset buffer index */
+ if (param_id == 0xFF) {
+ sst_hsw_reset_param_buf(hsw);
+ return 0;
+ }
+
+ /* store params into buffer */
+ ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data);
+ if (ret < 0)
+ return ret;
+
+ if (sst_hsw_is_module_active(hsw, id))
+ ret = sst_hsw_module_set_param(hsw, id, 0, param_id,
+ param_size, ucontrol->value.bytes.data);
+ return ret;
+}
+
+/* TLV used by both global and stream volumes */
+static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1);
+
+/* System Pin has no volume control */
+static const struct snd_kcontrol_new hsw_volume_controls[] = {
+ /* Global DSP volume */
+ SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8,
+ ARRAY_SIZE(volume_map) - 1, 0,
+ hsw_volume_get, hsw_volume_put, hsw_vol_tlv),
+ /* Offload 0 volume */
+ SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8,
+ ARRAY_SIZE(volume_map) - 1, 0,
+ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
+ /* Offload 1 volume */
+ SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8,
+ ARRAY_SIZE(volume_map) - 1, 0,
+ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
+ /* Mic Capture volume */
+ SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8,
+ ARRAY_SIZE(volume_map) - 1, 0,
+ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv),
+ /* enable/disable module waves */
+ SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
+ hsw_waves_switch_get, hsw_waves_switch_put),
+ /* set parameters to module waves */
+ SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT,
+ hsw_waves_param_get, hsw_waves_param_put),
+};
+
+/* Create DMA buffer page table for DSP */
+static int create_adsp_page_table(struct snd_pcm_substream *substream,
+ struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd,
+ unsigned char *dma_area, size_t size, int pcm)
+{
+ struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream);
+ int i, pages, stream = substream->stream;
+
+ pages = snd_sgbuf_aligned_pages(size);
+
+ dev_dbg(rtd->dev, "generating page table for %p size 0x%zu pages %d\n",
+ dma_area, size, pages);
+
+ for (i = 0; i < pages; i++) {
+ u32 idx = (((i << 2) + i)) >> 1;
+ u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
+ u32 *pg_table;
+
+ dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
+
+ pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx);
+
+ if (i & 1)
+ *pg_table |= (pfn << 4);
+ else
+ *pg_table |= pfn;
+ }
+
+ return 0;
+}
+
+/* this may get called several times by oss emulation */
+static int hsw_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ struct sst_module *module_data;
+ struct sst_dsp *dsp;
+ struct snd_dma_buffer *dmab;
+ enum sst_hsw_stream_type stream_type;
+ enum sst_hsw_stream_path_id path_id;
+ u32 rate, bits, map, pages, module_id;
+ u8 channels;
+ int ret, dai;
+
+ dai = mod_map[rtd->cpu_dai->id].dai_id;
+ pcm_data = &pdata->pcm[dai][substream->stream];
+
+ /* check if we are being called a subsequent time */
+ if (pcm_data->allocated) {
+ ret = sst_hsw_stream_reset(hsw, pcm_data->stream);
+ if (ret < 0)
+ dev_dbg(rtd->dev, "error: reset stream failed %d\n",
+ ret);
+
+ ret = sst_hsw_stream_free(hsw, pcm_data->stream);
+ if (ret < 0) {
+ dev_dbg(rtd->dev, "error: free stream failed %d\n",
+ ret);
+ return ret;
+ }
+ pcm_data->allocated = false;
+
+ pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id,
+ hsw_notify_pointer, pcm_data);
+ if (pcm_data->stream == NULL) {
+ dev_err(rtd->dev, "error: failed to create stream\n");
+ return -EINVAL;
+ }
+ }
+
+ /* stream direction */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ path_id = SST_HSW_STREAM_PATH_SSP0_OUT;
+ else
+ path_id = SST_HSW_STREAM_PATH_SSP0_IN;
+
+ /* DSP stream type depends on DAI ID */
+ switch (rtd->cpu_dai->id) {
+ case 0:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ stream_type = SST_HSW_STREAM_TYPE_SYSTEM;
+ module_id = SST_HSW_MODULE_PCM_SYSTEM;
+ }
+ else {
+ stream_type = SST_HSW_STREAM_TYPE_CAPTURE;
+ module_id = SST_HSW_MODULE_PCM_CAPTURE;
+ }
+ break;
+ case 1:
+ case 2:
+ stream_type = SST_HSW_STREAM_TYPE_RENDER;
+ module_id = SST_HSW_MODULE_PCM;
+ break;
+ case 3:
+ /* path ID needs to be OUT for loopback */
+ stream_type = SST_HSW_STREAM_TYPE_LOOPBACK;
+ path_id = SST_HSW_STREAM_PATH_SSP0_OUT;
+ module_id = SST_HSW_MODULE_PCM_REFERENCE;
+ break;
+ default:
+ dev_err(rtd->dev, "error: invalid DAI ID %d\n",
+ rtd->cpu_dai->id);
+ return -EINVAL;
+ };
+
+ ret = sst_hsw_stream_format(hsw, pcm_data->stream,
+ path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: failed to set format %d\n", ret);
+ return ret;
+ }
+
+ rate = params_rate(params);
+ ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: could not set rate %d\n", rate);
+ return ret;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bits = SST_HSW_DEPTH_16BIT;
+ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bits = SST_HSW_DEPTH_32BIT;
+ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24);
+ break;
+ case SNDRV_PCM_FORMAT_S8:
+ bits = SST_HSW_DEPTH_8BIT;
+ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bits = SST_HSW_DEPTH_32BIT;
+ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32);
+ break;
+ default:
+ dev_err(rtd->dev, "error: invalid format %d\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: could not set bits %d\n", bits);
+ return ret;
+ }
+
+ channels = params_channels(params);
+ map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO);
+ sst_hsw_stream_set_map_config(hsw, pcm_data->stream,
+ map, SST_HSW_CHANNEL_CONFIG_STEREO);
+
+ ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: could not set channels %d\n",
+ channels);
+ return ret;
+ }
+
+ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n",
+ params_buffer_bytes(params), ret);
+ return ret;
+ }
+
+ dmab = snd_pcm_get_dma_buf(substream);
+
+ ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area,
+ runtime->dma_bytes, rtd->cpu_dai->id);
+ if (ret < 0)
+ return ret;
+
+ sst_hsw_stream_set_style(hsw, pcm_data->stream,
+ SST_HSW_INTERLEAVING_PER_CHANNEL);
+
+ if (runtime->dma_bytes % PAGE_SIZE)
+ pages = (runtime->dma_bytes / PAGE_SIZE) + 1;
+ else
+ pages = runtime->dma_bytes / PAGE_SIZE;
+
+ ret = sst_hsw_stream_buffer(hsw, pcm_data->stream,
+ pdata->dmab[rtd->cpu_dai->id][substream->stream].addr,
+ pages, runtime->dma_bytes, 0,
+ snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret);
+ return ret;
+ }
+
+ dsp = sst_hsw_get_dsp(hsw);
+
+ module_data = sst_module_get_from_id(dsp, module_id);
+ if (module_data == NULL) {
+ dev_err(rtd->dev, "error: failed to get module config\n");
+ return -EINVAL;
+ }
+
+ sst_hsw_stream_set_module_info(hsw, pcm_data->stream,
+ pcm_data->runtime);
+
+ ret = sst_hsw_stream_commit(hsw, pcm_data->stream);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error: failed to commit stream %d\n", ret);
+ return ret;
+ }
+
+ if (!pcm_data->allocated) {
+ /* Set previous saved volume */
+ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0,
+ 0, pcm_data->volume[0]);
+ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0,
+ 1, pcm_data->volume[1]);
+ pcm_data->allocated = true;
+ }
+
+ ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1);
+ if (ret < 0)
+ dev_err(rtd->dev, "error: failed to pause %d\n", ret);
+
+ return 0;
+}
+
+static int hsw_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw_stream *sst_stream;
+ struct sst_hsw *hsw = pdata->hsw;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_uframes_t pos;
+ int dai;
+
+ dai = mod_map[rtd->cpu_dai->id].dai_id;
+ pcm_data = &pdata->pcm[dai][substream->stream];
+ sst_stream = pcm_data->stream;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ sst_hsw_stream_set_silence_start(hsw, sst_stream, false);
+ sst_hsw_stream_resume(hsw, pcm_data->stream, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ sst_hsw_stream_set_silence_start(hsw, sst_stream, false);
+ sst_hsw_stream_pause(hsw, pcm_data->stream, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_DRAIN:
+ pos = runtime->control->appl_ptr % runtime->buffer_size;
+ sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos);
+ sst_hsw_stream_set_silence_start(hsw, sst_stream, true);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data)
+{
+ struct hsw_pcm_data *pcm_data = data;
+ struct snd_pcm_substream *substream = pcm_data->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct sst_hsw *hsw = pdata->hsw;
+ u32 pos;
+ snd_pcm_uframes_t position = bytes_to_frames(runtime,
+ sst_hsw_get_dsp_position(hsw, pcm_data->stream));
+ unsigned char *dma_area = runtime->dma_area;
+ snd_pcm_uframes_t dma_frames =
+ bytes_to_frames(runtime, runtime->dma_bytes);
+ snd_pcm_uframes_t old_position;
+ ssize_t samples;
+
+ pos = frames_to_bytes(runtime,
+ (runtime->control->appl_ptr % runtime->buffer_size));
+
+ dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos);
+
+ /* SST fw don't know where to stop dma
+ * So, SST driver need to clean the data which has been consumed
+ */
+ if (dma_area == NULL || dma_frames <= 0
+ || (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ || !sst_hsw_stream_get_silence_start(hsw, stream)) {
+ snd_pcm_period_elapsed(substream);
+ return pos;
+ }
+
+ old_position = sst_hsw_stream_get_old_position(hsw, stream);
+ if (position > old_position) {
+ if (position < dma_frames) {
+ samples = SST_SAMPLES(runtime, position - old_position);
+ snd_pcm_format_set_silence(runtime->format,
+ SST_OLD_POSITION(dma_area,
+ runtime, old_position),
+ samples);
+ } else
+ dev_err(rtd->dev, "PCM: position is wrong\n");
+ } else {
+ if (old_position < dma_frames) {
+ samples = SST_SAMPLES(runtime,
+ dma_frames - old_position);
+ snd_pcm_format_set_silence(runtime->format,
+ SST_OLD_POSITION(dma_area,
+ runtime, old_position),
+ samples);
+ } else
+ dev_err(rtd->dev, "PCM: dma_bytes is wrong\n");
+ if (position < dma_frames) {
+ samples = SST_SAMPLES(runtime, position);
+ snd_pcm_format_set_silence(runtime->format,
+ dma_area, samples);
+ } else
+ dev_err(rtd->dev, "PCM: position is wrong\n");
+ }
+ sst_hsw_stream_set_old_position(hsw, stream, position);
+
+ /* let alsa know we have play a period */
+ snd_pcm_period_elapsed(substream);
+ return pos;
+}
+
+static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ snd_pcm_uframes_t offset;
+ uint64_t ppos;
+ u32 position;
+ int dai;
+
+ dai = mod_map[rtd->cpu_dai->id].dai_id;
+ pcm_data = &pdata->pcm[dai][substream->stream];
+ position = sst_hsw_get_dsp_position(hsw, pcm_data->stream);
+
+ offset = bytes_to_frames(runtime, position);
+ ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream);
+
+ dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n",
+ position, ppos);
+ return offset;
+}
+
+static int hsw_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ int dai;
+
+ dai = mod_map[rtd->cpu_dai->id].dai_id;
+ pcm_data = &pdata->pcm[dai][substream->stream];
+
+ mutex_lock(&pcm_data->mutex);
+ pm_runtime_get_sync(pdata->dev);
+
+ snd_soc_pcm_set_drvdata(rtd, pcm_data);
+ pcm_data->substream = substream;
+
+ snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware);
+
+ pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id,
+ hsw_notify_pointer, pcm_data);
+ if (pcm_data->stream == NULL) {
+ dev_err(rtd->dev, "error: failed to create stream\n");
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&pcm_data->mutex);
+ return 0;
+}
+
+static int hsw_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hsw_priv_data *pdata =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct hsw_pcm_data *pcm_data;
+ struct sst_hsw *hsw = pdata->hsw;
+ int ret, dai;
+
+ dai = mod_map[rtd->cpu_dai->id].dai_id;
+ pcm_data = &pdata->pcm[dai][substream->stream];
+
+ mutex_lock(&pcm_data->mutex);
+ ret = sst_hsw_stream_reset(hsw, pcm_data->stream);
+ if (ret < 0) {
+ dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret);
+ goto out;
+ }
+
+ ret = sst_hsw_stream_free(hsw, pcm_data->stream);
+ if (ret < 0) {
+ dev_dbg(rtd->dev, "error: free stream failed %d\n", ret);
+ goto out;
+ }
+ pcm_data->allocated = 0;
+ pcm_data->stream = NULL;
+
+out:
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+ mutex_unlock(&pcm_data->mutex);
+ return ret;
+}
+
+static struct snd_pcm_ops hsw_pcm_ops = {
+ .open = hsw_pcm_open,
+ .close = hsw_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hsw_pcm_hw_params,
+ .hw_free = hsw_pcm_hw_free,
+ .trigger = hsw_pcm_trigger,
+ .pointer = hsw_pcm_pointer,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+static int hsw_pcm_create_modules(struct hsw_priv_data *pdata)
+{
+ struct sst_hsw *hsw = pdata->hsw;
+ struct hsw_pcm_data *pcm_data;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+
+ /* create new runtime module, use same offset if recreated */
+ pcm_data->runtime = sst_hsw_runtime_module_create(hsw,
+ mod_map[i].mod_id, pcm_data->persistent_offset);
+ if (pcm_data->runtime == NULL)
+ goto err;
+ pcm_data->persistent_offset =
+ pcm_data->runtime->persistent_offset;
+ }
+
+ /* create runtime blocks for module waves */
+ if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) {
+ pdata->runtime_waves = sst_hsw_runtime_module_create(hsw,
+ SST_HSW_MODULE_WAVES, 0);
+ if (pdata->runtime_waves == NULL)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ for (--i; i >= 0; i--) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+ sst_hsw_runtime_module_free(pcm_data->runtime);
+ }
+
+ return -ENODEV;
+}
+
+static void hsw_pcm_free_modules(struct hsw_priv_data *pdata)
+{
+ struct sst_hsw *hsw = pdata->hsw;
+ struct hsw_pcm_data *pcm_data;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+ sst_hsw_runtime_module_free(pcm_data->runtime);
+ }
+ if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) {
+ sst_hsw_runtime_module_free(pdata->runtime_waves);
+ }
+}
+
+static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct sst_pdata *pdata = dev_get_platdata(platform->dev);
+ struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev);
+ struct device *dev = pdata->dma_dev;
+ int ret = 0;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream ||
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV_SG,
+ dev,
+ hsw_pcm_hardware.buffer_bytes_max,
+ hsw_pcm_hardware.buffer_bytes_max);
+ if (ret) {
+ dev_err(rtd->dev, "dma buffer allocation failed %d\n",
+ ret);
+ return ret;
+ }
+ }
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+ priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm;
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+ priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm;
+
+ return ret;
+}
+
+#define HSW_FORMATS \
+ (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_driver hsw_dais[] = {
+ {
+ .name = "System Pin",
+ .id = HSW_PCM_DAI_ID_SYSTEM,
+ .playback = {
+ .stream_name = "System Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Analog Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ /* PCM */
+ .name = "Offload0 Pin",
+ .id = HSW_PCM_DAI_ID_OFFLOAD0,
+ .playback = {
+ .stream_name = "Offload0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = HSW_FORMATS,
+ },
+ },
+ {
+ /* PCM */
+ .name = "Offload1 Pin",
+ .id = HSW_PCM_DAI_ID_OFFLOAD1,
+ .playback = {
+ .stream_name = "Offload1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = HSW_FORMATS,
+ },
+ },
+ {
+ .name = "Loopback Pin",
+ .id = HSW_PCM_DAI_ID_LOOPBACK,
+ .capture = {
+ .stream_name = "Loopback Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+static const struct snd_soc_dapm_widget widgets[] = {
+
+ /* Backend DAIs */
+ 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),
+
+ /* Global Playback Mixer */
+ SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route graph[] = {
+
+ /* Playback Mixer */
+ {"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"},
+};
+
+static int hsw_pcm_probe(struct snd_soc_platform *platform)
+{
+ struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform);
+ struct sst_pdata *pdata = dev_get_platdata(platform->dev);
+ struct device *dma_dev, *dev;
+ int i, ret = 0;
+
+ if (!pdata)
+ return -ENODEV;
+
+ dev = platform->dev;
+ dma_dev = pdata->dma_dev;
+
+ priv_data->hsw = pdata->dsp;
+ priv_data->dev = platform->dev;
+ priv_data->pm_state = HSW_PM_STATE_D0;
+ priv_data->soc_card = platform->component.card;
+
+ /* allocate DSP buffer page tables */
+ for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) {
+
+ /* playback */
+ if (hsw_dais[i].playback.channels_min) {
+ mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex);
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev,
+ PAGE_SIZE, &priv_data->dmab[i][0]);
+ if (ret < 0)
+ goto err;
+ }
+
+ /* capture */
+ if (hsw_dais[i].capture.channels_min) {
+ mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex);
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev,
+ PAGE_SIZE, &priv_data->dmab[i][1]);
+ if (ret < 0)
+ goto err;
+ }
+ }
+
+ /* allocate runtime modules */
+ ret = hsw_pcm_create_modules(priv_data);
+ if (ret < 0)
+ goto err;
+
+ /* enable runtime PM with auto suspend */
+ pm_runtime_set_autosuspend_delay(platform->dev,
+ SST_RUNTIME_SUSPEND_DELAY);
+ pm_runtime_use_autosuspend(platform->dev);
+ pm_runtime_enable(platform->dev);
+ pm_runtime_idle(platform->dev);
+
+ return 0;
+
+err:
+ for (--i; i >= 0; i--) {
+ if (hsw_dais[i].playback.channels_min)
+ snd_dma_free_pages(&priv_data->dmab[i][0]);
+ if (hsw_dais[i].capture.channels_min)
+ snd_dma_free_pages(&priv_data->dmab[i][1]);
+ }
+ return ret;
+}
+
+static int hsw_pcm_remove(struct snd_soc_platform *platform)
+{
+ struct hsw_priv_data *priv_data =
+ snd_soc_platform_get_drvdata(platform);
+ int i;
+
+ pm_runtime_disable(platform->dev);
+ hsw_pcm_free_modules(priv_data);
+
+ for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) {
+ if (hsw_dais[i].playback.channels_min)
+ snd_dma_free_pages(&priv_data->dmab[i][0]);
+ if (hsw_dais[i].capture.channels_min)
+ snd_dma_free_pages(&priv_data->dmab[i][1]);
+ }
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver hsw_soc_platform = {
+ .probe = hsw_pcm_probe,
+ .remove = hsw_pcm_remove,
+ .ops = &hsw_pcm_ops,
+ .pcm_new = hsw_pcm_new,
+};
+
+static const struct snd_soc_component_driver hsw_dai_component = {
+ .name = "haswell-dai",
+ .controls = hsw_volume_controls,
+ .num_controls = ARRAY_SIZE(hsw_volume_controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = graph,
+ .num_dapm_routes = ARRAY_SIZE(graph),
+};
+
+static int hsw_pcm_dev_probe(struct platform_device *pdev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev);
+ struct hsw_priv_data *priv_data;
+ int ret;
+
+ if (!sst_pdata)
+ return -EINVAL;
+
+ priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL);
+ if (!priv_data)
+ return -ENOMEM;
+
+ ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata);
+ if (ret < 0)
+ return -ENODEV;
+
+ priv_data->hsw = sst_pdata->dsp;
+ platform_set_drvdata(pdev, priv_data);
+
+ ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform);
+ if (ret < 0)
+ goto err_plat;
+
+ ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component,
+ hsw_dais, ARRAY_SIZE(hsw_dais));
+ if (ret < 0)
+ goto err_comp;
+
+ return 0;
+
+err_comp:
+ snd_soc_unregister_platform(&pdev->dev);
+err_plat:
+ sst_hsw_dsp_free(&pdev->dev, sst_pdata);
+ return 0;
+}
+
+static int hsw_pcm_dev_remove(struct platform_device *pdev)
+{
+ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev);
+
+ snd_soc_unregister_platform(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+ sst_hsw_dsp_free(&pdev->dev, sst_pdata);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int hsw_pcm_runtime_idle(struct device *dev)
+{
+ return 0;
+}
+
+static int hsw_pcm_runtime_suspend(struct device *dev)
+{
+ struct hsw_priv_data *pdata = dev_get_drvdata(dev);
+ struct sst_hsw *hsw = pdata->hsw;
+ int ret;
+
+ if (pdata->pm_state >= HSW_PM_STATE_RTD3)
+ return 0;
+
+ /* fw modules will be unloaded on RTD3, set flag to track */
+ if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) {
+ ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0);
+ if (ret < 0)
+ return ret;
+ sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES);
+ }
+ sst_hsw_dsp_runtime_suspend(hsw);
+ sst_hsw_dsp_runtime_sleep(hsw);
+ pdata->pm_state = HSW_PM_STATE_RTD3;
+
+ return 0;
+}
+
+static int hsw_pcm_runtime_resume(struct device *dev)
+{
+ struct hsw_priv_data *pdata = dev_get_drvdata(dev);
+ struct sst_hsw *hsw = pdata->hsw;
+ int ret;
+
+ if (pdata->pm_state != HSW_PM_STATE_RTD3)
+ return 0;
+
+ ret = sst_hsw_dsp_load(hsw);
+ if (ret < 0) {
+ dev_err(dev, "failed to reload %d\n", ret);
+ return ret;
+ }
+
+ ret = hsw_pcm_create_modules(pdata);
+ if (ret < 0) {
+ dev_err(dev, "failed to create modules %d\n", ret);
+ return ret;
+ }
+
+ ret = sst_hsw_dsp_runtime_resume(hsw);
+ if (ret < 0)
+ return ret;
+ else if (ret == 1) /* no action required */
+ return 0;
+
+ /* check flag when resume */
+ if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) {
+ ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0);
+ if (ret < 0)
+ return ret;
+ /* put parameters from buffer to dsp */
+ ret = sst_hsw_launch_param_buf(hsw);
+ if (ret < 0)
+ return ret;
+ /* unset flag */
+ sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES);
+ }
+
+ pdata->pm_state = HSW_PM_STATE_D0;
+ return ret;
+}
+
+#else
+#define hsw_pcm_runtime_idle NULL
+#define hsw_pcm_runtime_suspend NULL
+#define hsw_pcm_runtime_resume NULL
+#endif
+
+#ifdef CONFIG_PM
+
+static void hsw_pcm_complete(struct device *dev)
+{
+ struct hsw_priv_data *pdata = dev_get_drvdata(dev);
+ struct sst_hsw *hsw = pdata->hsw;
+ struct hsw_pcm_data *pcm_data;
+ int i, err;
+
+ if (pdata->pm_state != HSW_PM_STATE_D3)
+ return;
+
+ err = sst_hsw_dsp_load(hsw);
+ if (err < 0) {
+ dev_err(dev, "failed to reload %d\n", err);
+ return;
+ }
+
+ err = hsw_pcm_create_modules(pdata);
+ if (err < 0) {
+ dev_err(dev, "failed to create modules %d\n", err);
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+
+ if (!pcm_data->substream)
+ continue;
+
+ err = sst_module_runtime_restore(pcm_data->runtime,
+ &pcm_data->context);
+ if (err < 0)
+ dev_err(dev, "failed to restore context for PCM %d\n", i);
+ }
+
+ snd_soc_resume(pdata->soc_card->dev);
+
+ err = sst_hsw_dsp_runtime_resume(hsw);
+ if (err < 0)
+ return;
+ else if (err == 1) /* no action required */
+ return;
+
+ pdata->pm_state = HSW_PM_STATE_D0;
+ return;
+}
+
+static int hsw_pcm_prepare(struct device *dev)
+{
+ struct hsw_priv_data *pdata = dev_get_drvdata(dev);
+ struct sst_hsw *hsw = pdata->hsw;
+ struct hsw_pcm_data *pcm_data;
+ int i, err;
+
+ if (pdata->pm_state == HSW_PM_STATE_D3)
+ return 0;
+ else if (pdata->pm_state == HSW_PM_STATE_D0) {
+ /* suspend all active streams */
+ for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+
+ if (!pcm_data->substream)
+ continue;
+ dev_dbg(dev, "suspending pcm %d\n", i);
+ snd_pcm_suspend_all(pcm_data->hsw_pcm);
+
+ /* We need to wait until the DSP FW stops the streams */
+ msleep(2);
+ }
+
+ /* preserve persistent memory */
+ for (i = 0; i < ARRAY_SIZE(mod_map); i++) {
+ pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream];
+
+ if (!pcm_data->substream)
+ continue;
+
+ dev_dbg(dev, "saving context pcm %d\n", i);
+ err = sst_module_runtime_save(pcm_data->runtime,
+ &pcm_data->context);
+ if (err < 0)
+ dev_err(dev, "failed to save context for PCM %d\n", i);
+ }
+ /* enter D3 state and stall */
+ sst_hsw_dsp_runtime_suspend(hsw);
+ /* put the DSP to sleep */
+ sst_hsw_dsp_runtime_sleep(hsw);
+ }
+
+ snd_soc_suspend(pdata->soc_card->dev);
+ snd_soc_poweroff(pdata->soc_card->dev);
+
+ pdata->pm_state = HSW_PM_STATE_D3;
+
+ return 0;
+}
+
+#else
+#define hsw_pcm_prepare NULL
+#define hsw_pcm_complete NULL
+#endif
+
+static const struct dev_pm_ops hsw_pcm_pm = {
+ .runtime_idle = hsw_pcm_runtime_idle,
+ .runtime_suspend = hsw_pcm_runtime_suspend,
+ .runtime_resume = hsw_pcm_runtime_resume,
+ .prepare = hsw_pcm_prepare,
+ .complete = hsw_pcm_complete,
+};
+
+static struct platform_driver hsw_pcm_driver = {
+ .driver = {
+ .name = "haswell-pcm-audio",
+ .pm = &hsw_pcm_pm,
+ },
+
+ .probe = hsw_pcm_dev_probe,
+ .remove = hsw_pcm_dev_remove,
+};
+module_platform_driver(hsw_pcm_driver);
+
+MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
+MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:haswell-pcm-audio");