diff options
Diffstat (limited to 'sound/soc/sof')
43 files changed, 4649 insertions, 2438 deletions
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index fb01f0ca6027..827b0ec92522 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -14,8 +14,6 @@ config SND_SOC_SOF_PCI depends on PCI select SND_SOC_SOF select SND_SOC_ACPI if ACPI - select SND_SOC_SOF_OPTIONS - select SND_SOC_SOF_INTEL_PCI if SND_SOC_SOF_INTEL_TOPLEVEL help This adds support for PCI enumeration. This option is required to enable Intel Skylake+ devices @@ -27,22 +25,34 @@ config SND_SOC_SOF_ACPI depends on ACPI || COMPILE_TEST select SND_SOC_SOF select SND_SOC_ACPI if ACPI - select SND_SOC_SOF_OPTIONS - select SND_SOC_SOF_INTEL_ACPI if SND_SOC_SOF_INTEL_TOPLEVEL select IOSF_MBI if X86 && PCI help This adds support for ACPI enumeration. This option is required - to enable Intel Haswell/Broadwell/Baytrail/Cherrytrail devices + to enable Intel Broadwell/Baytrail/Cherrytrail devices Say Y if you need this option If unsure select "N". -config SND_SOC_SOF_OPTIONS - tristate +config SND_SOC_SOF_OF + tristate "SOF OF enumeration support" + depends on OF || COMPILE_TEST + select SND_SOC_SOF help - This option is not user-selectable but automagically handled by - 'select' statements at a higher level + This adds support for Device Tree enumeration. This option is + required to enable i.MX8 devices. + Say Y if you need this option. If unsure select "N". + +config SND_SOC_SOF_DEVELOPER_SUPPORT + bool "SOF developer options support" + depends on EXPERT + help + This option unlock SOF developer options for debug/performance/ + code hardening. + Distributions should not select this option, only SOF development + teams should select it. + Say Y if you are involved in SOF development and need this option + If not, select N -if SND_SOC_SOF_OPTIONS +if SND_SOC_SOF_DEVELOPER_SUPPORT config SND_SOC_SOF_NOCODEC tristate @@ -54,6 +64,11 @@ config SND_SOC_SOF_NOCODEC_SUPPORT option if no known codec is detected. This is typically only enabled for developers or devices where the sound card is controlled externally + This option is mutually exclusive with the Intel HDaudio support, + selecting it may have negative impacts and prevent e.g. microphone + functionality from being enabled on Intel CoffeeLake and later + platforms. + Distributions should not select this option! Say Y if you need this nocodec fallback option If unsure select "N". @@ -132,6 +147,14 @@ config SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE Say Y if you want to enable caching the memory windows. If unsure, select "N". +config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE + bool "SOF enable firmware trace" + help + The firmware trace can be enabled either at build-time with + this option, or dynamically by setting flags in the SOF core + module parameter (similar to dynamic debug) + If unsure, select "N". + config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST bool "SOF enable IPC flood test" help @@ -140,9 +163,17 @@ config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST Say Y if you want to enable IPC flood test. If unsure, select "N". +config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT + bool "SOF retain DSP context on any FW exceptions" + help + This option keeps the DSP in D0 state so that firmware debug + information can be retained and dumped to userspace. + Say Y if you want to retain DSP context for FW exceptions. + If unsure, select "N". + endif ## SND_SOC_SOF_DEBUG -endif ## SND_SOC_SOF_OPTIONS +endif ## SND_SOC_SOF_DEVELOPER_SUPPORT config SND_SOC_SOF tristate @@ -163,6 +194,7 @@ config SND_SOC_SOF_PROBE_WORK_QUEUE When selected, the probe is handled in two steps, for example to avoid lockdeps if request_module is used in the probe. +source "sound/soc/sof/imx/Kconfig" source "sound/soc/sof/intel/Kconfig" source "sound/soc/sof/xtensa/Kconfig" diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 8f14c9d2950b..0a8bc72c28a5 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,18 +1,22 @@ # SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o utils.o + control.o trace.o utils.o sof-audio.o snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o +snd-sof-of-objs := sof-of-dev.o + snd-sof-nocodec-objs := nocodec.o obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o -obj-$(CONFIG_SND_SOC_SOF_ACPI) += sof-acpi-dev.o -obj-$(CONFIG_SND_SOC_SOF_PCI) += sof-pci-dev.o +obj-$(CONFIG_SND_SOC_SOF_ACPI) += snd-sof-acpi.o +obj-$(CONFIG_SND_SOC_SOF_OF) += snd-sof-of.o +obj-$(CONFIG_SND_SOC_SOF_PCI) += snd-sof-pci.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ +obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/ diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index a4983f90ff5b..dfc412e2d956 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -11,7 +11,39 @@ /* Mixer Controls */ #include <linux/pm_runtime.h> +#include <linux/leds.h> #include "sof-priv.h" +#include "sof-audio.h" + +static void update_mute_led(struct snd_sof_control *scontrol, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int temp = 0; + unsigned int mask; + int i; + + mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + for (i = 0; i < scontrol->num_channels; i++) { + if (ucontrol->value.integer.value[i]) { + temp |= mask; + break; + } + } + + if (temp == scontrol->led_ctl.led_value) + return; + + scontrol->led_ctl.led_value = temp; + +#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO) + if (!scontrol->led_ctl.direction) + ledtrig_audio_set(LED_AUDIO_MUTE, temp ? LED_OFF : LED_ON); + else + ledtrig_audio_set(LED_AUDIO_MICMUTE, temp ? LED_OFF : LED_ON); +#endif +} static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) { @@ -57,27 +89,29 @@ int snd_sof_volume_put(struct snd_kcontrol *kcontrol, struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + bool change = false; + u32 value; /* update each channel */ for (i = 0; i < channels; i++) { - cdata->chanv[i].value = - mixer_to_ipc(ucontrol->value.integer.value[i], + value = mixer_to_ipc(ucontrol->value.integer.value[i], scontrol->volume_table, sm->max + 1); + change = change || (value != cdata->chanv[i].value); cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; } /* notify DSP of mixer updates */ - if (pm_runtime_active(sdev->dev)) - snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + if (pm_runtime_active(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, SOF_CTRL_TYPE_VALUE_CHAN_GET, SOF_CTRL_CMD_VOLUME, true); - - return 0; + return change; } int snd_sof_switch_get(struct snd_kcontrol *kcontrol, @@ -102,25 +136,32 @@ int snd_sof_switch_put(struct snd_kcontrol *kcontrol, struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + bool change = false; + u32 value; /* update each channel */ for (i = 0; i < channels; i++) { - cdata->chanv[i].value = ucontrol->value.integer.value[i]; + value = ucontrol->value.integer.value[i]; + change = change || (value != cdata->chanv[i].value); cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; } + if (scontrol->led_ctl.use_led) + update_mute_led(scontrol, kcontrol, ucontrol); + /* notify DSP of mixer updates */ - if (pm_runtime_active(sdev->dev)) - snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + if (pm_runtime_active(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, SOF_CTRL_TYPE_VALUE_CHAN_GET, SOF_CTRL_CMD_SWITCH, true); - return 0; + return change; } int snd_sof_enum_get(struct snd_kcontrol *kcontrol, @@ -145,25 +186,29 @@ int snd_sof_enum_put(struct snd_kcontrol *kcontrol, struct soc_enum *se = (struct soc_enum *)kcontrol->private_value; struct snd_sof_control *scontrol = se->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + bool change = false; + u32 value; /* update each channel */ for (i = 0; i < channels; i++) { - cdata->chanv[i].value = ucontrol->value.enumerated.item[i]; + value = ucontrol->value.enumerated.item[i]; + change = change || (value != cdata->chanv[i].value); cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; } /* notify DSP of enum updates */ - if (pm_runtime_active(sdev->dev)) - snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + if (pm_runtime_active(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, SOF_CTRL_TYPE_VALUE_CHAN_GET, SOF_CTRL_CMD_ENUM, true); - return 0; + return change; } int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, @@ -172,14 +217,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; struct sof_abi_hdr *data = cdata->data; size_t size; int ret = 0; if (be->max > sizeof(ucontrol->value.bytes.data)) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: data max %d exceeds ucontrol data array size\n", be->max); return -EINVAL; @@ -187,7 +232,7 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, size = data->size + sizeof(*data); if (size > be->max) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: DSP sent %zu bytes max is %d\n", size, be->max); ret = -EINVAL; @@ -207,20 +252,20 @@ int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; struct sof_abi_hdr *data = cdata->data; size_t size = data->size + sizeof(*data); if (be->max > sizeof(ucontrol->value.bytes.data)) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: data max %d exceeds ucontrol data array size\n", be->max); return -EINVAL; } if (size > be->max) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: size too big %zu bytes max is %d\n", size, be->max); return -EINVAL; @@ -230,8 +275,8 @@ int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, memcpy(data, ucontrol->value.bytes.data, size); /* notify DSP of byte control updates */ - if (pm_runtime_active(sdev->dev)) - snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + if (pm_runtime_active(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_DATA, SOF_CTRL_TYPE_DATA_SET, scontrol->cmd, @@ -247,7 +292,7 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; struct snd_ctl_tlv header; const struct snd_ctl_tlv __user *tlvd = @@ -263,14 +308,14 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, /* be->max is coming from topology */ if (header.length > be->max) { - dev_err_ratelimited(sdev->dev, "error: Bytes data size %d exceeds max %d.\n", + dev_err_ratelimited(scomp->dev, "error: Bytes data size %d exceeds max %d.\n", header.length, be->max); return -EINVAL; } /* Check that header id matches the command */ if (header.numid != scontrol->cmd) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: incorrect numid %d\n", header.numid); return -EINVAL; @@ -280,26 +325,26 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, return -EFAULT; if (cdata->data->magic != SOF_ABI_MAGIC) { - dev_err_ratelimited(sdev->dev, + dev_err_ratelimited(scomp->dev, "error: Wrong ABI magic 0x%08x.\n", cdata->data->magic); return -EINVAL; } if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { - dev_err_ratelimited(sdev->dev, "error: Incompatible ABI version 0x%08x.\n", + dev_err_ratelimited(scomp->dev, "error: Incompatible ABI version 0x%08x.\n", cdata->data->abi); return -EINVAL; } if (cdata->data->size + sizeof(const struct sof_abi_hdr) > be->max) { - dev_err_ratelimited(sdev->dev, "error: Mismatch in ABI data size (truncated?).\n"); + dev_err_ratelimited(scomp->dev, "error: Mismatch in ABI data size (truncated?).\n"); return -EINVAL; } /* notify DSP of byte control updates */ - if (pm_runtime_active(sdev->dev)) - snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + if (pm_runtime_active(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_DATA, SOF_CTRL_TYPE_DATA_SET, scontrol->cmd, @@ -315,7 +360,7 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; - struct snd_sof_dev *sdev = scontrol->sdev; + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; struct snd_ctl_tlv header; struct snd_ctl_tlv __user *tlvd = @@ -338,7 +383,7 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, /* check data size doesn't exceed max coming from topology */ if (data_size > be->max) { - dev_err_ratelimited(sdev->dev, "error: user data size %d exceeds max size %d.\n", + dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %d.\n", data_size, be->max); ret = -EINVAL; goto out; diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 5beda47cdf9f..34cefbaf2d2a 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -10,122 +10,19 @@ #include <linux/firmware.h> #include <linux/module.h> -#include <asm/unaligned.h> #include <sound/soc.h> #include <sound/sof.h> #include "sof-priv.h" #include "ops.h" -/* SOF defaults if not provided by the platform in ms */ -#define TIMEOUT_DEFAULT_IPC_MS 5 -#define TIMEOUT_DEFAULT_BOOT_MS 100 - -/* - * Generic object lookup APIs. - */ - -struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, - const char *name) -{ - struct snd_sof_pcm *spcm; - - list_for_each_entry(spcm, &sdev->pcm_list, list) { - /* match with PCM dai name */ - if (strcmp(spcm->pcm.dai_name, name) == 0) - return spcm; - - /* match with playback caps name if set */ - if (*spcm->pcm.caps[0].name && - !strcmp(spcm->pcm.caps[0].name, name)) - return spcm; - - /* match with capture caps name if set */ - if (*spcm->pcm.caps[1].name && - !strcmp(spcm->pcm.caps[1].name, name)) - return spcm; - } - - return NULL; -} - -struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, - unsigned int comp_id, - int *direction) -{ - struct snd_sof_pcm *spcm; - - list_for_each_entry(spcm, &sdev->pcm_list, list) { - if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) { - *direction = SNDRV_PCM_STREAM_PLAYBACK; - return spcm; - } - if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { - *direction = SNDRV_PCM_STREAM_CAPTURE; - return spcm; - } - } - - return NULL; -} - -struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, - unsigned int pcm_id) -{ - struct snd_sof_pcm *spcm; - - list_for_each_entry(spcm, &sdev->pcm_list, list) { - if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) - return spcm; - } - - return NULL; -} - -struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, - const char *name) -{ - struct snd_sof_widget *swidget; - - list_for_each_entry(swidget, &sdev->widget_list, list) { - if (strcmp(name, swidget->widget->name) == 0) - return swidget; - } +/* see SOF_DBG_ flags */ +int sof_core_debug; +module_param_named(sof_debug, sof_core_debug, int, 0444); +MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); - return NULL; -} - -/* find widget by stream name and direction */ -struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, - const char *pcm_name, int dir) -{ - struct snd_sof_widget *swidget; - enum snd_soc_dapm_type type; - - if (dir == SNDRV_PCM_STREAM_PLAYBACK) - type = snd_soc_dapm_aif_in; - else - type = snd_soc_dapm_aif_out; - - list_for_each_entry(swidget, &sdev->widget_list, list) { - if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type) - return swidget; - } - - return NULL; -} - -struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, - const char *name) -{ - struct snd_sof_dai *dai; - - list_for_each_entry(dai, &sdev->dai_list, list) { - if (dai->name && (strcmp(name, dai->name) == 0)) - return dai; - } - - return NULL; -} +/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC_MS 500 +#define TIMEOUT_DEFAULT_BOOT_MS 2000 /* * FW Panic/fault handling. @@ -196,104 +93,48 @@ out: EXPORT_SYMBOL(snd_sof_get_status); /* - * Generic buffer page table creation. - * Take the each physical page address and drop the least significant unused - * bits from each (based on PAGE_SIZE). Then pack valid page address bits - * into compressed page table. - */ - -int snd_sof_create_page_table(struct snd_sof_dev *sdev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) -{ - int i, pages; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", - dmab->area, size, pages); - - for (i = 0; i < pages; i++) { - /* - * The number of valid address bits for each page is 20. - * idx determines the byte position within page_table - * where the current page's address is stored - * in the compressed page_table. - * This can be calculated by multiplying the page number by 2.5. - */ - u32 idx = (5 * i) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u8 *pg_table; - - dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u8 *)(page_table + idx); - - /* - * pagetable compression: - * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 - * ___________pfn 0__________ __________pfn 1___________ _pfn 2... - * .... .... .... .... .... .... .... .... .... .... .... - * It is created by: - * 1. set current location to 0, PFN index i to 0 - * 2. put pfn[i] at current location in Little Endian byte order - * 3. calculate an intermediate value as - * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) - * 4. put x at offset (current location + 2) in LE byte order - * 5. increment current location by 5 bytes, increment i by 2 - * 6. continue to (2) - */ - if (i & 1) - put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, - pg_table); - else - put_unaligned_le32(pfn, pg_table); - } - - return pages; -} - -/* - * SOF Driver enumeration. + * FW Boot State Transition Diagram + * + * +-----------------------------------------------------------------------+ + * | | + * ------------------ ------------------ | + * | | | | | + * | BOOT_FAILED | | READY_FAILED |-------------------------+ | + * | | | | | | + * ------------------ ------------------ | | + * ^ ^ | | + * | | | | + * (FW Boot Timeout) (FW_READY FAIL) | | + * | | | | + * | | | | + * ------------------ | ------------------ | | + * | | | | | | | + * | IN_PROGRESS |---------------+------------->| COMPLETE | | | + * | | (FW Boot OK) (FW_READY OK) | | | | + * ------------------ ------------------ | | + * ^ | | | + * | | | | + * (FW Loading OK) (System Suspend/Runtime Suspend) + * | | | | + * | | | | + * ------------------ ------------------ | | | + * | | | |<-----+ | | + * | PREPARE | | NOT_STARTED |<---------------------+ | + * | | | |<---------------------------+ + * ------------------ ------------------ + * | ^ | ^ + * | | | | + * | +-----------------------+ | + * | (DSP Probe OK) | + * | | + * | | + * +------------------------------------+ + * (System Suspend/Runtime Suspend) */ -static int sof_machine_check(struct snd_sof_dev *sdev) -{ - struct snd_sof_pdata *plat_data = sdev->pdata; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) - struct snd_soc_acpi_mach *machine; - int ret; -#endif - - if (plat_data->machine) - return 0; - -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) - dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); - return -ENODEV; -#else - /* fallback to nocodec mode */ - dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n"); - machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL); - if (!machine) - return -ENOMEM; - - ret = sof_nocodec_setup(sdev->dev, plat_data, machine, - plat_data->desc, plat_data->desc->ops); - if (ret < 0) - return ret; - - plat_data->machine = machine; - - return 0; -#endif -} static int sof_probe_continue(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; - const char *drv_name; - const void *mach; - int size; int ret; /* probe the DSP hardware */ @@ -303,6 +144,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) return ret; } + sdev->fw_state = SOF_FW_BOOT_PREPARE; + /* check machine info */ ret = sof_machine_check(sdev); if (ret < 0) { @@ -342,7 +185,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_load_err; } - /* boot the firmware */ + sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + + /* + * Boot the firmware. The FW boot status will be modified + * in snd_sof_run_firmware() depending on the outcome. + */ ret = snd_sof_run_firmware(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", @@ -350,12 +198,20 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_run_err; } - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", ret); + if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || + (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { + sdev->dtrace_is_supported = true; + + /* init DMA trace */ + ret = snd_sof_init_trace(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to initialize trace %d\n", + ret); + } + } else { + dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); } /* hereafter all FW boot flows are for PM reasons */ @@ -368,32 +224,28 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: failed to register DSP DAI driver %d\n", ret); - goto fw_run_err; + goto fw_trace_err; } - drv_name = plat_data->machine->drv_name; - mach = (const void *)plat_data->machine; - size = sizeof(*plat_data->machine); - - /* register machine driver, pass machine info as pdata */ - plat_data->pdev_mach = - platform_device_register_data(sdev->dev, drv_name, - PLATFORM_DEVID_NONE, mach, size); - - if (IS_ERR(plat_data->pdev_mach)) { - ret = PTR_ERR(plat_data->pdev_mach); - goto fw_run_err; - } + ret = snd_sof_machine_register(sdev, plat_data); + if (ret < 0) + goto fw_trace_err; - dev_dbg(sdev->dev, "created machine %s\n", - dev_name(&plat_data->pdev_mach->dev)); + /* + * Some platforms in SOF, ex: BYT, may not have their platform PM + * callbacks set. Increment the usage count so as to + * prevent the device from entering runtime suspend. + */ + if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) + pm_runtime_get_noresume(sdev->dev); if (plat_data->sof_probe_complete) plat_data->sof_probe_complete(sdev->dev); return 0; -#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) +fw_trace_err: + snd_sof_free_trace(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: @@ -402,21 +254,10 @@ ipc_err: snd_sof_free_debug(sdev); dbg_err: snd_sof_remove(sdev); -#else - - /* - * when the probe_continue is handled in a work queue, the - * probe does not fail so we don't release resources here. - * They will be released with an explicit call to - * snd_sof_device_remove() when the PCI/ACPI device is removed - */ -fw_run_err: -fw_load_err: -ipc_err: -dbg_err: - -#endif + /* all resources freed, update state to match */ + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sdev->first_boot = true; return ret; } @@ -445,15 +286,20 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) /* initialize sof device */ sdev->dev = dev; + /* initialize default D0 sub-state */ + sdev->d0_substate = SOF_DSP_D0I0; + sdev->pdata = plat_data; sdev->first_boot = true; + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; dev_set_drvdata(dev, sdev); /* check all mandatory ops */ if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || - !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params) + !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params || + !sof_ops(sdev)->fw_ready) return -EINVAL; INIT_LIST_HEAD(&sdev->pcm_list); @@ -494,18 +340,19 @@ int snd_sof_device_remove(struct device *dev) if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); - snd_sof_fw_unload(sdev); - snd_sof_ipc_free(sdev); - snd_sof_free_debug(sdev); - snd_sof_free_trace(sdev); + if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { + snd_sof_fw_unload(sdev); + snd_sof_ipc_free(sdev); + snd_sof_free_debug(sdev); + snd_sof_free_trace(sdev); + } /* * Unregister machine driver. This will unbind the snd_card which * will remove the component driver and unload the topology * before freeing the snd_card. */ - if (!IS_ERR_OR_NULL(pdata->pdev_mach)) - platform_device_unregister(pdata->pdev_mach); + snd_sof_machine_unregister(sdev, pdata); /* * Unregistering the machine driver results in unloading the topology. @@ -513,7 +360,8 @@ int snd_sof_device_remove(struct device *dev) * scheduled on, when they are unloaded. Therefore, the DSP must be * removed only after the topology has been unloaded. */ - snd_sof_remove(sdev); + if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) + snd_sof_remove(sdev); /* release firmware */ release_firmware(pdata->fw); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 2388477a965e..d2b3b99d3a20 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -128,6 +128,7 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, unsigned long ipc_duration_ms = 0; bool flood_duration_test = false; unsigned long ipc_count = 0; + struct dentry *dentry; int err; #endif size_t size; @@ -149,11 +150,14 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, * ipc_duration_ms test floods the DSP for the time specified * in the debugfs entry. */ - if (strcmp(dfse->dfsentry->d_name.name, "ipc_flood_count") && - strcmp(dfse->dfsentry->d_name.name, "ipc_flood_duration_ms")) - return -EINVAL; + dentry = file->f_path.dentry; + if (strcmp(dentry->d_name.name, "ipc_flood_count") && + strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) { + ret = -EINVAL; + goto out; + } - if (!strcmp(dfse->dfsentry->d_name.name, "ipc_flood_duration_ms")) + if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) flood_duration_test = true; /* test completion criterion */ @@ -226,8 +230,11 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, u8 *buf; #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - if ((!strcmp(dfse->dfsentry->d_name.name, "ipc_flood_count") || - !strcmp(dfse->dfsentry->d_name.name, "ipc_flood_duration_ms")) && + struct dentry *dentry; + + dentry = file->f_path.dentry; + if ((!strcmp(dentry->d_name.name, "ipc_flood_count") || + !strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) && dfse->cache_buf) { if (*ppos) return 0; @@ -290,8 +297,7 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, if (!pm_runtime_active(sdev->dev) && dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { dev_err(sdev->dev, - "error: debugfs entry %s cannot be read in DSP D3\n", - dfse->dfsentry->d_name.name); + "error: debugfs entry cannot be read in DSP D3\n"); kfree(buf); return -EINVAL; } @@ -356,17 +362,11 @@ int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, } #endif - dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, - dfse, &sof_dfs_fops); - if (!dfse->dfsentry) { - /* can't rely on debugfs, only log error and keep going */ - dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", - name); - } else { - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); + debugfs_create_file(name, 0444, sdev->debugfs_root, dfse, + &sof_dfs_fops); - } + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); return 0; } @@ -402,16 +402,10 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, return -ENOMEM; #endif - dfse->dfsentry = debugfs_create_file(name, mode, sdev->debugfs_root, - dfse, &sof_dfs_fops); - if (!dfse->dfsentry) { - /* can't rely on debugfs, only log error and keep going */ - dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", - name); - } else { - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - } + debugfs_create_file(name, mode, sdev->debugfs_root, dfse, + &sof_dfs_fops); + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); return 0; } @@ -426,10 +420,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) /* use "sof" as top level debugFS dir */ sdev->debugfs_root = debugfs_create_dir("sof", NULL); - if (IS_ERR_OR_NULL(sdev->debugfs_root)) { - dev_err(sdev->dev, "error: failed to create debugfs directory\n"); - return 0; - } /* init dfsentry list */ INIT_LIST_HEAD(&sdev->dfsentry_list); @@ -473,3 +463,19 @@ void snd_sof_free_debug(struct snd_sof_dev *sdev) debugfs_remove_recursive(sdev->debugfs_root); } EXPORT_SYMBOL_GPL(snd_sof_free_debug); + +void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) +{ + if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || + (sof_core_debug & SOF_DBG_RETAIN_CTX)) { + /* should we prevent DSP entering D3 ? */ + dev_info(sdev->dev, "info: preventing DSP entering D3 state to preserve context\n"); + pm_runtime_get_noresume(sdev->dev); + } + + /* dump vital information to the logs */ + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_ipc_dump(sdev); + snd_sof_trace_notify_for_error(sdev); +} +EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig new file mode 100644 index 000000000000..bae4f7bf5f75 --- /dev/null +++ b/sound/soc/sof/imx/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) + +config SND_SOC_SOF_IMX_TOPLEVEL + bool "SOF support for NXP i.MX audio DSPs" + depends on ARM64|| COMPILE_TEST + depends on SND_SOC_SOF_OF + help + This adds support for Sound Open Firmware for NXP i.MX platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_IMX_TOPLEVEL + +config SND_SOC_SOF_IMX8_SUPPORT + bool "SOF support for i.MX8" + depends on IMX_SCU + depends on IMX_DSP + help + This adds support for Sound Open Firmware for NXP i.MX8 platforms + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_IMX8 + def_tristate SND_SOC_SOF_OF + depends on SND_SOC_SOF_IMX8_SUPPORT + +endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile new file mode 100644 index 000000000000..6ef908e8c807 --- /dev/null +++ b/sound/soc/sof/imx/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +snd-sof-imx8-objs := imx8.o + +obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c new file mode 100644 index 000000000000..b2556f5e2871 --- /dev/null +++ b/sound/soc/sof/imx/imx8.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// Copyright 2019 NXP +// +// Author: Daniel Baluta <daniel.baluta@nxp.com> +// +// Hardware interface for audio DSP on i.MX8 + +#include <linux/firmware.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/pm_domain.h> + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include <linux/firmware/imx/ipc.h> +#include <linux/firmware/imx/dsp.h> + +#include <linux/firmware/imx/svc/misc.h> +#include <dt-bindings/firmware/imx/rsrc.h> +#include "../ops.h" + +/* DSP memories */ +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE (2 * 1024) +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE (32 * 1024) +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE (32 * 1024) +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE (256 * 1024) +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE (192 * 1024) + +#define RESET_VECTOR_VADDR 0x596f8000 + +#define MBOX_OFFSET 0x800000 +#define MBOX_SIZE 0x1000 + +struct imx8_priv { + struct device *dev; + struct snd_sof_dev *sdev; + + /* DSP IPC handler */ + struct imx_dsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; + + /* System Controller IPC handler */ + struct imx_sc_ipc *sc_ipc; + + /* Power domain handling */ + int num_domains; + struct device **pd_dev; + struct device_link **link; + +}; + +static void imx8_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply has correct size? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; +} + +static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int imx8_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc) +{ + struct imx8_priv *priv = imx_dsp_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + imx8_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc) +{ + struct imx8_priv *priv = imx_dsp_get_data(ipc); + + snd_sof_ipc_msgs_rx(priv->sdev); +} + +struct imx_dsp_ops dsp_ops = { + .handle_reply = imx8_dsp_handle_reply, + .handle_request = imx8_dsp_handle_request, +}; + +static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + imx_dsp_ring_doorbell(priv->dsp_ipc, 0); + + return 0; +} + +/* + * DSP control. + */ +static int imx8_run(struct snd_sof_dev *sdev) +{ + struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private; + int ret; + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 1); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset source select\n"); + return ret; + } + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_AUDIO, 0x80); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset of AUDIO\n"); + return ret; + } + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_PERIPH, 0x5A); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset of PERIPH %d\n", + ret); + return ret; + } + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_IRQ, 0x51); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset of IRQ\n"); + return ret; + } + + imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + + return 0; +} + +static int imx8_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct device_node *np = pdev->dev.of_node; + struct device_node *res_node; + struct resource *mmio; + struct imx8_priv *priv; + struct resource res; + u32 base, size; + int ret = 0; + int i; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->private = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + /* power up device associated power domains */ + priv->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + if (priv->num_domains < 0) { + dev_err(sdev->dev, "no power-domains property in %pOF\n", np); + return priv->num_domains; + } + + priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains, + sizeof(*priv->pd_dev), GFP_KERNEL); + if (!priv->pd_dev) + return -ENOMEM; + + priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains, + sizeof(*priv->link), GFP_KERNEL); + if (!priv->link) + return -ENOMEM; + + for (i = 0; i < priv->num_domains; i++) { + priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i); + if (IS_ERR(priv->pd_dev[i])) { + ret = PTR_ERR(priv->pd_dev[i]); + goto exit_unroll_pm; + } + priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!priv->link[i]) { + ret = -ENOMEM; + dev_pm_domain_detach(priv->pd_dev[i], false); + goto exit_unroll_pm; + } + } + + ret = imx_scu_get_handle(&priv->sc_ipc); + if (ret) { + dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n", + ret); + goto exit_unroll_pm; + } + + priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + goto exit_unroll_pm; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + /* DSP IPC driver not probed yet, try later */ + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "Failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + imx_dsp_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + /* DSP base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); + ret = -EINVAL; + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", + base, size); + ret = -ENODEV; + goto exit_pdev_unregister; + } + sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; + + res_node = of_parse_phandle(np, "memory-region", 0); + if (!res_node) { + dev_err(&pdev->dev, "failed to get memory region node\n"); + ret = -ENODEV; + goto exit_pdev_unregister; + } + + ret = of_address_to_resource(res_node, 0, &res); + if (ret) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, + resource_size(&res)); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", + base, size); + ret = -ENOMEM; + goto exit_pdev_unregister; + } + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); +exit_unroll_pm: + while (--i >= 0) { + device_link_del(priv->link[i]); + dev_pm_domain_detach(priv->pd_dev[i], false); + } + + return ret; +} + +static int imx8_remove(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + int i; + + platform_device_unregister(priv->ipc_dev); + + for (i = 0; i < priv->num_domains; i++) { + device_link_del(priv->link[i]); + dev_pm_domain_detach(priv->pd_dev[i], false); + } + + return 0; +} + +/* on i.MX8 there is 1 to 1 match between type and BAR idx */ +static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static void imx8_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); +} + +static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return 0; +} + +static struct snd_soc_dai_driver imx8_dai[] = { +{ + .name = "esai-port", +}, +}; + +/* i.MX8 ops */ +struct snd_sof_dsp_ops sof_imx8_ops = { + /* probe and remove */ + .probe = imx8_probe, + .remove = imx8_remove, + /* DSP core boot */ + .run = imx8_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* ipc */ + .send_msg = imx8_send_msg, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = imx8_get_mailbox_offset, + .get_window_offset = imx8_get_window_offset, + + .ipc_msg_data = imx8_ipc_msg_data, + .ipc_pcm_params = imx8_ipc_pcm_params, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + .get_bar_index = imx8_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = imx8_dai, + .num_drv = 1, /* we have only 1 ESAI interface on i.MX8 */ + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP +}; +EXPORT_SYMBOL(sof_imx8_ops); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index dd14ce92fe10..56a837d2cb95 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -10,7 +10,7 @@ config SND_SOC_SOF_INTEL_TOPLEVEL if SND_SOC_SOF_INTEL_TOPLEVEL config SND_SOC_SOF_INTEL_ACPI - tristate + def_tristate SND_SOC_SOF_ACPI select SND_SOC_SOF_BAYTRAIL if SND_SOC_SOF_BAYTRAIL_SUPPORT select SND_SOC_SOF_BROADWELL if SND_SOC_SOF_BROADWELL_SUPPORT help @@ -18,7 +18,7 @@ config SND_SOC_SOF_INTEL_ACPI 'select' statements at a higher level config SND_SOC_SOF_INTEL_PCI - tristate + def_tristate SND_SOC_SOF_PCI select SND_SOC_SOF_MERRIFIELD if SND_SOC_SOF_MERRIFIELD_SUPPORT select SND_SOC_SOF_APOLLOLAKE if SND_SOC_SOF_APOLLOLAKE_SUPPORT select SND_SOC_SOF_GEMINILAKE if SND_SOC_SOF_GEMINILAKE_SUPPORT @@ -27,6 +27,9 @@ config SND_SOC_SOF_INTEL_PCI select SND_SOC_SOF_ICELAKE if SND_SOC_SOF_ICELAKE_SUPPORT select SND_SOC_SOF_COMETLAKE_LP if SND_SOC_SOF_COMETLAKE_LP_SUPPORT select SND_SOC_SOF_COMETLAKE_H if SND_SOC_SOF_COMETLAKE_H_SUPPORT + select SND_SOC_SOF_TIGERLAKE if SND_SOC_SOF_TIGERLAKE_SUPPORT + select SND_SOC_SOF_ELKHARTLAKE if SND_SOC_SOF_ELKHARTLAKE_SUPPORT + select SND_SOC_SOF_JASPERLAKE if SND_SOC_SOF_JASPERLAKE_SUPPORT help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -34,7 +37,7 @@ config SND_SOC_SOF_INTEL_PCI config SND_SOC_SOF_INTEL_HIFI_EP_IPC tristate help - This option is not user-selectable but automagically handled by + This option is not user-selectable but automagically handled by 'select' statements at a higher level config SND_SOC_SOF_INTEL_ATOM_HIFI_EP @@ -59,10 +62,18 @@ if SND_SOC_SOF_INTEL_ACPI config SND_SOC_SOF_BAYTRAIL_SUPPORT bool "SOF support for Baytrail, Braswell and Cherrytrail" + depends on SND_SST_ATOM_HIFI2_PLATFORM_ACPI=n help This adds support for Sound Open Firmware for Intel(R) platforms using the Baytrail, Braswell or Cherrytrail processors. - Say Y if you have such a device. + This option is mutually exclusive with the Atom/SST and Baytrail + legacy drivers. If you want to enable SOF on Baytrail/Cherrytrail, + you need to deselect those options first. + SOF does not support Baytrail-CR for now, so this option is not + recommended for distros. At some point all legacy drivers will be + deprecated but not before all userspace firmware/topology/UCM files + are made available to downstream distros. + Say Y if you want to enable SOF on Baytrail/Cherrytrail If unsure select "N". config SND_SOC_SOF_BAYTRAIL @@ -74,10 +85,18 @@ config SND_SOC_SOF_BAYTRAIL config SND_SOC_SOF_BROADWELL_SUPPORT bool "SOF support for Broadwell" + depends on SND_SOC_INTEL_HASWELL=n help This adds support for Sound Open Firmware for Intel(R) platforms using the Broadwell processors. - Say Y if you have such a device. + This option is mutually exclusive with the Haswell/Broadwell legacy + driver. If you want to enable SOF on Broadwell you need to deselect + the legacy driver first. + SOF does fully support Broadwell yet, so this option is not + recommended for distros. At some point all legacy drivers will be + deprecated but not before all userspace firmware/topology/UCM files + are made available to downstream distros. + Say Y if you want to enable SOF on Broadwell If unsure select "N". config SND_SOC_SOF_BROADWELL @@ -212,6 +231,51 @@ config SND_SOC_SOF_COMETLAKE_H_SUPPORT Say Y if you have such a device. If unsure select "N". +config SND_SOC_SOF_TIGERLAKE_SUPPORT + bool "SOF support for Tigerlake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Tigerlake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_TIGERLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_ELKHARTLAKE_SUPPORT + bool "SOF support for ElkhartLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the ElkhartLake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_ELKHARTLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_JASPERLAKE_SUPPORT + bool "SOF support for JasperLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the JasperLake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_JASPERLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + config SND_SOC_SOF_HDA_COMMON tristate select SND_SOC_SOF_INTEL_COMMON @@ -241,6 +305,27 @@ config SND_SOC_SOF_HDA_AUDIO_CODEC Say Y if you want to enable HDAudio codecs with SOF. If unsure select "N". +config SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1 + bool "SOF enable DMI Link L1" + help + This option enables DMI L1 for both playback and capture + and disables known workarounds for specific HDaudio platforms. + Only use to look into power optimizations on platforms not + affected by DMI L1 issues. This option is not recommended. + Say Y if you want to enable DMI Link L1 + If unsure, select "N". + +config SND_SOC_SOF_HDA_COMMON_HDMI_CODEC + bool "SOF common HDA HDMI codec driver" + depends on SND_SOC_SOF_HDA_LINK + depends on SND_HDA_CODEC_HDMI + default SND_HDA_CODEC_HDMI + help + This adds support for HDMI audio by using the common HDA + HDMI/DisplayPort codec driver. + Say Y if you want to use the common codec driver with SOF. + If unsure select "Y". + endif ## SND_SOC_SOF_HDA_COMMON config SND_SOC_SOF_HDA_LINK_BASELINE @@ -254,6 +339,7 @@ config SND_SOC_SOF_HDA tristate select SND_HDA_EXT_CORE if SND_SOC_SOF_HDA_LINK select SND_SOC_HDAC_HDA if SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_INTEL_DSP_CONFIG help This option is not user-selectable but automagically handled by 'select' statements at a higher level diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index fd2e26d79796..2483b15699e7 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -17,6 +17,7 @@ #include "../sof-priv.h" #include "hda.h" +#include "../sof-audio.h" static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, @@ -41,16 +42,23 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .block_write = sof_block_write, /* doorbell */ - .irq_handler = hda_dsp_ipc_irq_handler, .irq_thread = hda_dsp_ipc_irq_thread, /* ipc */ .send_msg = hda_dsp_ipc_send_msg, - .fw_ready = hda_dsp_ipc_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, + .get_window_offset = hda_dsp_ipc_get_window_offset, .ipc_msg_data = hda_ipc_msg_data, .ipc_pcm_params = hda_ipc_pcm_params, + /* machine driver */ + .machine_select = hda_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = hda_set_mach_params, + /* debug */ .debug_map = apl_dsp_debugfs, .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), @@ -95,8 +103,18 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + .set_power_state = hda_dsp_set_power_state, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_apl_ops); +EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc apl_chip_info = { /* Apollolake */ @@ -112,4 +130,4 @@ const struct sof_intel_dsp_desc apl_chip_info = { .ssp_count = APL_SSP_COUNT, .ssp_base_offset = APL_SSP_BASE_OFFSET, }; -EXPORT_SYMBOL(apl_chip_info); +EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 70d524ef9bc0..6c23c5769330 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -17,6 +17,7 @@ #include <sound/sof/xtensa.h> #include "../ops.h" #include "shim.h" +#include "../sof-audio.h" /* BARs */ #define BDW_DSP_BAR 0 @@ -37,6 +38,7 @@ #define MBOX_SIZE 0x1000 #define MBOX_DUMP_SIZE 0x30 #define EXCEPT_OFFSET 0x800 +#define EXCEPT_MAX_HDR_SIZE 0x400 /* DSP peripherals */ #define DMAC0_OFFSET 0xFE000 @@ -228,6 +230,11 @@ static void bdw_get_registers(struct snd_sof_dev *sdev, /* note: variable AR register array is not read */ /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n", + xoops->arch_hdr.totalsize); + return; + } offset += xoops->arch_hdr.totalsize; sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info)); @@ -241,7 +248,7 @@ static void bdw_dump(struct snd_sof_dev *sdev, u32 flags) struct sof_ipc_dsp_oops_xtensa xoops; struct sof_ipc_panic_info panic_info; u32 stack[BDW_STACK_DUMP_SIZE]; - u32 status, panic; + u32 status, panic, imrx, imrd; /* now try generic SOF status messages */ status = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD); @@ -250,6 +257,26 @@ static void bdw_dump(struct snd_sof_dev *sdev, u32 flags) BDW_STACK_DUMP_SIZE); snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, BDW_STACK_DUMP_SIZE); + + /* provide some context for firmware debug */ + imrx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRX); + imrd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRD); + dev_err(sdev->dev, + "error: ipc host -> DSP: pending %s complete %s raw 0x%8.8x\n", + (panic & SHIM_IPCX_BUSY) ? "yes" : "no", + (panic & SHIM_IPCX_DONE) ? "yes" : "no", panic); + dev_err(sdev->dev, + "error: mask host: pending %s complete %s raw 0x%8.8x\n", + (imrx & SHIM_IMRX_BUSY) ? "yes" : "no", + (imrx & SHIM_IMRX_DONE) ? "yes" : "no", imrx); + dev_err(sdev->dev, + "error: ipc DSP -> host: pending %s complete %s raw 0x%8.8x\n", + (status & SHIM_IPCD_BUSY) ? "yes" : "no", + (status & SHIM_IPCD_DONE) ? "yes" : "no", status); + dev_err(sdev->dev, + "error: mask DSP: pending %s complete %s raw 0x%8.8x\n", + (imrd & SHIM_IMRD_BUSY) ? "yes" : "no", + (imrd & SHIM_IMRD_DONE) ? "yes" : "no", imrd); } /* @@ -328,153 +355,6 @@ static irqreturn_t bdw_irq_thread(int irq, void *context) } /* - * IPC Firmware ready. - */ -static void bdw_get_windows(struct snd_sof_dev *sdev) -{ - struct sof_ipc_window_elem *elem; - u32 outbox_offset = 0; - u32 stream_offset = 0; - u32 inbox_offset = 0; - u32 outbox_size = 0; - u32 stream_size = 0; - u32 inbox_size = 0; - int i; - - if (!sdev->info_window) { - dev_err(sdev->dev, "error: have no window info\n"); - return; - } - - for (i = 0; i < sdev->info_window->num_windows; i++) { - elem = &sdev->info_window->window[i]; - - switch (elem->type) { - case SOF_IPC_REGION_UPBOX: - inbox_offset = elem->offset + MBOX_OFFSET; - inbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DOWNBOX: - outbox_offset = elem->offset + MBOX_OFFSET; - outbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DEBUG: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_STREAM: - stream_offset = elem->offset + MBOX_OFFSET; - stream_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - stream_offset, - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_REGS: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_EXCEPTION: - sdev->dsp_oops_offset = elem->offset + MBOX_OFFSET; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BDW_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - default: - dev_err(sdev->dev, "error: get illegal window info\n"); - return; - } - } - - if (outbox_size == 0 || inbox_size == 0) { - dev_err(sdev->dev, "error: get illegal mailbox window\n"); - return; - } - - snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, - outbox_offset, outbox_size); - sdev->stream_box.offset = stream_offset; - sdev->stream_box.size = stream_size; - - dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", - inbox_offset, inbox_size); - dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", - outbox_offset, outbox_size); - dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", - stream_offset, stream_size); -} - -/* check for ABI compatibility and create memory windows on first boot */ -static int bdw_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; - u32 offset; - int ret; - - /* mailbox must be on 4k boundary */ - offset = MBOX_OFFSET; - - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", - msg_id, offset); - - /* no need to re-check version/ABI for subsequent boots */ - if (!sdev->first_boot) - return 0; - - /* copy data from the DSP FW ready offset */ - sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, - sizeof(*fw_ready)); - - snd_sof_dsp_mailbox_init(sdev, fw_ready->dspbox_offset, - fw_ready->dspbox_size, - fw_ready->hostbox_offset, - fw_ready->hostbox_size); - - /* make sure ABI version is compatible */ - ret = snd_sof_ipc_valid(sdev); - if (ret < 0) - return ret; - - /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, MBOX_OFFSET + - sizeof(struct sof_ipc_fw_ready)); - - bdw_get_windows(sdev); - - return 0; -} - -/* * IPC Mailbox IO */ @@ -527,6 +407,16 @@ static void bdw_get_reply(struct snd_sof_dev *sdev) msg->reply_error = ret; } +static int bdw_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int bdw_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + static void bdw_host_done(struct snd_sof_dev *sdev) { /* clear BUSY bit and set DONE bit - accept new messages */ @@ -588,6 +478,7 @@ static int bdw_probe(struct snd_sof_dev *sdev) /* TODO: add offsets */ sdev->mmio_bar = BDW_DSP_BAR; sdev->mailbox_bar = BDW_DSP_BAR; + sdev->dsp_oops_offset = MBOX_OFFSET; /* PCI base */ mmio = platform_get_resource(pdev, IORESOURCE_MEM, @@ -613,11 +504,8 @@ static int bdw_probe(struct snd_sof_dev *sdev) /* register our IRQ */ sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - if (sdev->ipc_irq < 0) { - dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", - desc->irqindex_host_ipc); + if (sdev->ipc_irq < 0) return sdev->ipc_irq; - } dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, @@ -649,6 +537,32 @@ static int bdw_probe(struct snd_sof_dev *sdev) return ret; } +static void bdw_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach *mach; + + mach = snd_soc_acpi_find_machine(desc->machines); + if (!mach) { + dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); + return; + } + + sof_pdata->tplg_filename = mach->sof_tplg_filename; + mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; + sof_pdata->machine = mach; +} + +static void bdw_set_mach_params(const struct snd_soc_acpi_mach *mach, + struct device *dev) +{ + struct snd_soc_acpi_mach_params *mach_params; + + mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params->platform = dev_name(dev); +} + /* Broadwell DAIs */ static struct snd_soc_dai_driver bdw_dai[] = { { @@ -680,11 +594,19 @@ const struct snd_sof_dsp_ops sof_bdw_ops = { /* ipc */ .send_msg = bdw_send_msg, - .fw_ready = bdw_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = bdw_get_mailbox_offset, + .get_window_offset = bdw_get_window_offset, .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_select = bdw_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = bdw_set_mach_params, + /* debug */ .debug_map = bdw_debugfs, .debug_map_count = ARRAY_SIZE(bdw_debugfs), @@ -702,14 +624,25 @@ const struct snd_sof_dsp_ops sof_bdw_ops = { /* DAI drivers */ .drv = bdw_dai, - .num_drv = ARRAY_SIZE(bdw_dai) + .num_drv = ARRAY_SIZE(bdw_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_BATCH, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_bdw_ops); +EXPORT_SYMBOL_NS(sof_bdw_ops, SND_SOC_SOF_BROADWELL); const struct sof_intel_dsp_desc bdw_chip_info = { .cores_num = 1, .cores_mask = 1, }; -EXPORT_SYMBOL(bdw_chip_info); +EXPORT_SYMBOL_NS(bdw_chip_info, SND_SOC_SOF_BROADWELL); MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC); +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index 107d711efc3f..f84391294f12 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -17,6 +17,8 @@ #include <sound/sof/xtensa.h> #include "../ops.h" #include "shim.h" +#include "../sof-audio.h" +#include "../../intel/common/soc-intel-quirks.h" /* DSP memories */ #define IRAM_OFFSET 0x0C0000 @@ -24,10 +26,12 @@ #define DRAM_OFFSET 0x100000 #define DRAM_SIZE (160 * 1024) #define SHIM_OFFSET 0x140000 -#define SHIM_SIZE 0x100 +#define SHIM_SIZE_BYT 0x100 +#define SHIM_SIZE_CHT 0x118 #define MBOX_OFFSET 0x144000 #define MBOX_SIZE 0x1000 #define EXCEPT_OFFSET 0x800 +#define EXCEPT_MAX_HDR_SIZE 0x400 /* DSP peripherals */ #define DMAC0_OFFSET 0x098000 @@ -74,7 +78,7 @@ static const struct snd_sof_debugfs_map byt_debugfs[] = { SOF_DEBUGFS_ACCESS_D0_ONLY}, {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE, SOF_DEBUGFS_ACCESS_D0_ONLY}, - {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT, SOF_DEBUGFS_ACCESS_ALWAYS}, }; @@ -101,7 +105,7 @@ static const struct snd_sof_debugfs_map cht_debugfs[] = { SOF_DEBUGFS_ACCESS_D0_ONLY}, {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE, SOF_DEBUGFS_ACCESS_D0_ONLY}, - {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE_CHT, SOF_DEBUGFS_ACCESS_ALWAYS}, }; @@ -110,153 +114,6 @@ static void byt_dsp_done(struct snd_sof_dev *sdev); static void byt_get_reply(struct snd_sof_dev *sdev); /* - * IPC Firmware ready. - */ -static void byt_get_windows(struct snd_sof_dev *sdev) -{ - struct sof_ipc_window_elem *elem; - u32 outbox_offset = 0; - u32 stream_offset = 0; - u32 inbox_offset = 0; - u32 outbox_size = 0; - u32 stream_size = 0; - u32 inbox_size = 0; - int i; - - if (!sdev->info_window) { - dev_err(sdev->dev, "error: have no window info\n"); - return; - } - - for (i = 0; i < sdev->info_window->num_windows; i++) { - elem = &sdev->info_window->window[i]; - - switch (elem->type) { - case SOF_IPC_REGION_UPBOX: - inbox_offset = elem->offset + MBOX_OFFSET; - inbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DOWNBOX: - outbox_offset = elem->offset + MBOX_OFFSET; - outbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DEBUG: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_STREAM: - stream_offset = elem->offset + MBOX_OFFSET; - stream_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - stream_offset, - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_REGS: - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_EXCEPTION: - sdev->dsp_oops_offset = elem->offset + MBOX_OFFSET; - snd_sof_debugfs_io_item(sdev, - sdev->bar[BYT_DSP_BAR] + - elem->offset + - MBOX_OFFSET, - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - default: - dev_err(sdev->dev, "error: get illegal window info\n"); - return; - } - } - - if (outbox_size == 0 || inbox_size == 0) { - dev_err(sdev->dev, "error: get illegal mailbox window\n"); - return; - } - - snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, - outbox_offset, outbox_size); - sdev->stream_box.offset = stream_offset; - sdev->stream_box.size = stream_size; - - dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", - inbox_offset, inbox_size); - dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", - outbox_offset, outbox_size); - dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", - stream_offset, stream_size); -} - -/* check for ABI compatibility and create memory windows on first boot */ -static int byt_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; - u32 offset; - int ret; - - /* mailbox must be on 4k boundary */ - offset = MBOX_OFFSET; - - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", - msg_id, offset); - - /* no need to re-check version/ABI for subsequent boots */ - if (!sdev->first_boot) - return 0; - - /* copy data from the DSP FW ready offset */ - sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, - sizeof(*fw_ready)); - - snd_sof_dsp_mailbox_init(sdev, fw_ready->dspbox_offset, - fw_ready->dspbox_size, - fw_ready->hostbox_offset, - fw_ready->hostbox_size); - - /* make sure ABI version is compatible */ - ret = snd_sof_ipc_valid(sdev); - if (ret < 0) - return ret; - - /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, MBOX_OFFSET + - sizeof(struct sof_ipc_fw_ready)); - - byt_get_windows(sdev); - - return 0; -} - -/* * Debug */ @@ -273,6 +130,11 @@ static void byt_get_registers(struct snd_sof_dev *sdev, /* note: variable AR register array is not read */ /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n", + xoops->arch_hdr.totalsize); + return; + } offset += xoops->arch_hdr.totalsize; sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info)); @@ -286,15 +148,36 @@ static void byt_dump(struct snd_sof_dev *sdev, u32 flags) struct sof_ipc_dsp_oops_xtensa xoops; struct sof_ipc_panic_info panic_info; u32 stack[BYT_STACK_DUMP_SIZE]; - u32 status, panic; + u64 status, panic, imrd, imrx; /* now try generic SOF status messages */ - status = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCD); - panic = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCX); + status = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD); + panic = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX); byt_get_registers(sdev, &xoops, &panic_info, stack, BYT_STACK_DUMP_SIZE); snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, BYT_STACK_DUMP_SIZE); + + /* provide some context for firmware debug */ + imrx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRX); + imrd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRD); + dev_err(sdev->dev, + "error: ipc host -> DSP: pending %s complete %s raw 0x%llx\n", + (panic & SHIM_IPCX_BUSY) ? "yes" : "no", + (panic & SHIM_IPCX_DONE) ? "yes" : "no", panic); + dev_err(sdev->dev, + "error: mask host: pending %s complete %s raw 0x%llx\n", + (imrx & SHIM_IMRX_BUSY) ? "yes" : "no", + (imrx & SHIM_IMRX_DONE) ? "yes" : "no", imrx); + dev_err(sdev->dev, + "error: ipc DSP -> host: pending %s complete %s raw 0x%llx\n", + (status & SHIM_IPCD_BUSY) ? "yes" : "no", + (status & SHIM_IPCD_DONE) ? "yes" : "no", status); + dev_err(sdev->dev, + "error: mask DSP: pending %s complete %s raw 0x%llx\n", + (imrd & SHIM_IMRD_BUSY) ? "yes" : "no", + (imrd & SHIM_IMRD_DONE) ? "yes" : "no", imrd); + } /* @@ -423,6 +306,16 @@ static void byt_get_reply(struct snd_sof_dev *sdev) msg->reply_error = ret; } +static int byt_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int byt_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + static void byt_host_done(struct snd_sof_dev *sdev) { /* clear BUSY bit and set DONE bit - accept new messages */ @@ -492,6 +385,76 @@ static int byt_reset(struct snd_sof_dev *sdev) return 0; } +static const char *fixup_tplg_name(struct snd_sof_dev *sdev, + const char *sof_tplg_filename, + const char *ssp_str) +{ + const char *tplg_filename = NULL; + char *filename; + char *split_ext; + + filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL); + if (!filename) + return NULL; + + /* this assumes a .tplg extension */ + split_ext = strsep(&filename, "."); + if (split_ext) { + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s-%s.tplg", + split_ext, ssp_str); + if (!tplg_filename) + return NULL; + } + return tplg_filename; +} + +static void byt_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach *mach; + struct platform_device *pdev; + const char *tplg_filename; + + mach = snd_soc_acpi_find_machine(desc->machines); + if (!mach) { + dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); + return; + } + + pdev = to_platform_device(sdev->dev); + if (soc_intel_is_byt_cr(pdev)) { + dev_dbg(sdev->dev, + "BYT-CR detected, SSP0 used instead of SSP2\n"); + + tplg_filename = fixup_tplg_name(sdev, + mach->sof_tplg_filename, + "ssp0"); + } else { + tplg_filename = mach->sof_tplg_filename; + } + + if (!tplg_filename) { + dev_dbg(sdev->dev, + "error: no topology filename\n"); + return; + } + + sof_pdata->tplg_filename = tplg_filename; + mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; + sof_pdata->machine = mach; +} + +static void byt_set_mach_params(const struct snd_soc_acpi_mach *mach, + struct device *dev) +{ + struct snd_soc_acpi_mach_params *mach_params; + + mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params->platform = dev_name(dev); +} + /* Baytrail DAIs */ static struct snd_soc_dai_driver byt_dai[] = { { @@ -617,11 +580,19 @@ const struct snd_sof_dsp_ops sof_tng_ops = { /* ipc */ .send_msg = byt_send_msg, - .fw_ready = byt_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = byt_get_mailbox_offset, + .get_window_offset = byt_get_window_offset, .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_select = byt_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = byt_set_mach_params, + /* debug */ .debug_map = byt_debugfs, .debug_map_count = ARRAY_SIZE(byt_debugfs), @@ -640,14 +611,23 @@ const struct snd_sof_dsp_ops sof_tng_ops = { /* DAI drivers */ .drv = byt_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_BATCH, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_tng_ops); +EXPORT_SYMBOL_NS(sof_tng_ops, SND_SOC_SOF_MERRIFIELD); const struct sof_intel_dsp_desc tng_chip_info = { .cores_num = 1, .cores_mask = 1, }; -EXPORT_SYMBOL(tng_chip_info); +EXPORT_SYMBOL_NS(tng_chip_info, SND_SOC_SOF_MERRIFIELD); #endif /* CONFIG_SND_SOC_SOF_MERRIFIELD */ @@ -728,11 +708,8 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev) irq: /* register our IRQ */ sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - if (sdev->ipc_irq < 0) { - dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", - desc->irqindex_host_ipc); + if (sdev->ipc_irq < 0) return sdev->ipc_irq; - } dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, @@ -779,11 +756,19 @@ const struct snd_sof_dsp_ops sof_byt_ops = { /* ipc */ .send_msg = byt_send_msg, - .fw_ready = byt_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = byt_get_mailbox_offset, + .get_window_offset = byt_get_window_offset, .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_select = byt_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = byt_set_mach_params, + /* debug */ .debug_map = byt_debugfs, .debug_map_count = ARRAY_SIZE(byt_debugfs), @@ -802,14 +787,23 @@ const struct snd_sof_dsp_ops sof_byt_ops = { /* DAI drivers */ .drv = byt_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_BATCH, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_byt_ops); +EXPORT_SYMBOL_NS(sof_byt_ops, SND_SOC_SOF_BAYTRAIL); const struct sof_intel_dsp_desc byt_chip_info = { .cores_num = 1, .cores_mask = 1, }; -EXPORT_SYMBOL(byt_chip_info); +EXPORT_SYMBOL_NS(byt_chip_info, SND_SOC_SOF_BAYTRAIL); /* cherrytrail and braswell ops */ const struct snd_sof_dsp_ops sof_cht_ops = { @@ -836,11 +830,19 @@ const struct snd_sof_dsp_ops sof_cht_ops = { /* ipc */ .send_msg = byt_send_msg, - .fw_ready = byt_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = byt_get_mailbox_offset, + .get_window_offset = byt_get_window_offset, .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_select = byt_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = byt_set_mach_params, + /* debug */ .debug_map = cht_debugfs, .debug_map_count = ARRAY_SIZE(cht_debugfs), @@ -860,15 +862,26 @@ const struct snd_sof_dsp_ops sof_cht_ops = { .drv = byt_dai, /* all 6 SSPs may be available for cherrytrail */ .num_drv = ARRAY_SIZE(byt_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_BATCH, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_cht_ops); +EXPORT_SYMBOL_NS(sof_cht_ops, SND_SOC_SOF_BAYTRAIL); const struct sof_intel_dsp_desc cht_chip_info = { .cores_num = 1, .cores_mask = 1, }; -EXPORT_SYMBOL(cht_chip_info); +EXPORT_SYMBOL_NS(cht_chip_info, SND_SOC_SOF_BAYTRAIL); #endif /* CONFIG_SND_SOC_SOF_BAYTRAIL */ MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC); +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index ffd8d4394537..9e2d8afe0535 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -17,6 +17,8 @@ #include "../ops.h" #include "hda.h" +#include "hda-ipc.h" +#include "../sof-audio.h" static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = { {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, @@ -105,10 +107,6 @@ static irqreturn_t cnl_ipc_irq_thread(int irq, void *context) "nothing to do in IPC IRQ thread\n"); } - /* re-enable IPC interrupt */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, - HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); - return IRQ_HANDLED; } @@ -150,14 +148,45 @@ static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev) CNL_DSP_REG_HIPCCTL_DONE); } +static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg, + u32 *dr, u32 *dd) +{ + struct sof_ipc_pm_gate *pm_gate; + + if (msg->header == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { + pm_gate = msg->msg_data; + + /* send the compact message via the primary register */ + *dr = HDA_IPC_MSG_COMPACT | HDA_IPC_PM_GATE; + + /* send payload via the extended data register */ + *dd = pm_gate->flags; + + return true; + } + + return false; +} + static int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { - /* send the message */ - sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, - msg->msg_size); - snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, - CNL_DSP_REG_HIPCIDR_BUSY); + u32 dr = 0; + u32 dd = 0; + + if (cnl_compact_ipc_compress(msg, &dr, &dd)) { + /* send the message via IPC registers */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD, + dd); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, + CNL_DSP_REG_HIPCIDR_BUSY | dr); + } else { + /* send the message via mailbox */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, + CNL_DSP_REG_HIPCIDR_BUSY); + } return 0; } @@ -199,16 +228,23 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .block_write = sof_block_write, /* doorbell */ - .irq_handler = hda_dsp_ipc_irq_handler, .irq_thread = cnl_ipc_irq_thread, /* ipc */ .send_msg = cnl_ipc_send_msg, - .fw_ready = hda_dsp_ipc_fw_ready, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, + .get_window_offset = hda_dsp_ipc_get_window_offset, .ipc_msg_data = hda_ipc_msg_data, .ipc_pcm_params = hda_ipc_pcm_params, + /* machine driver */ + .machine_select = hda_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = hda_set_mach_params, + /* debug */ .debug_map = cnl_dsp_debugfs, .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), @@ -253,8 +289,18 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + .set_power_state = hda_dsp_set_power_state, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + .arch_ops = &sof_xtensa_arch_ops, }; -EXPORT_SYMBOL(sof_cnl_ops); +EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc cnl_chip_info = { /* Cannonlake */ @@ -273,7 +319,7 @@ const struct sof_intel_dsp_desc cnl_chip_info = { .ssp_count = CNL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, }; -EXPORT_SYMBOL(cnl_chip_info); +EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc icl_chip_info = { /* Icelake */ @@ -292,4 +338,53 @@ const struct sof_intel_dsp_desc icl_chip_info = { .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, }; -EXPORT_SYMBOL(icl_chip_info); +EXPORT_SYMBOL_NS(icl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc tgl_chip_info = { + /* Tigerlake */ + .cores_num = 4, + .init_core_mask = 1, + .cores_mask = HDA_DSP_CORE_MASK(0), + .ipc_req = CNL_DSP_REG_HIPCIDR, + .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, + .ipc_ack = CNL_DSP_REG_HIPCIDA, + .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, + .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_init_timeout = 300, + .ssp_count = ICL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc ehl_chip_info = { + /* Elkhartlake */ + .cores_num = 4, + .init_core_mask = 1, + .cores_mask = HDA_DSP_CORE_MASK(0), + .ipc_req = CNL_DSP_REG_HIPCIDR, + .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, + .ipc_ack = CNL_DSP_REG_HIPCIDA, + .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, + .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_init_timeout = 300, + .ssp_count = ICL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc jsl_chip_info = { + /* Jasperlake */ + .cores_num = 2, + .init_core_mask = 1, + .cores_mask = HDA_DSP_CORE_MASK(0) | + HDA_DSP_CORE_MASK(1), + .ipc_req = CNL_DSP_REG_HIPCIDR, + .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, + .ipc_ack = CNL_DSP_REG_HIPCIDA, + .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, + .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_init_timeout = 300, + .ssp_count = ICL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/hda-bus.c b/sound/soc/sof/intel/hda-bus.c index a7e6d8227df6..1d2babdda9dd 100644 --- a/sound/soc/sof/intel/hda-bus.c +++ b/sound/soc/sof/intel/hda-bus.c @@ -12,82 +12,27 @@ #include "../sof-priv.h" #include "hda.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - -static const struct hdac_bus_ops bus_ops = { - .command = snd_hdac_bus_send_cmd, - .get_response = snd_hdac_bus_get_response, -}; - +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#include "../../codecs/hdac_hda.h" +#define sof_hda_ext_ops snd_soc_hdac_hda_get_ops() +#else +#define sof_hda_ext_ops NULL #endif -static void sof_hda_writel(u32 value, u32 __iomem *addr) -{ - writel(value, addr); -} - -static u32 sof_hda_readl(u32 __iomem *addr) -{ - return readl(addr); -} - -static void sof_hda_writew(u16 value, u16 __iomem *addr) -{ - writew(value, addr); -} - -static u16 sof_hda_readw(u16 __iomem *addr) -{ - return readw(addr); -} - -static void sof_hda_writeb(u8 value, u8 __iomem *addr) -{ - writeb(value, addr); -} - -static u8 sof_hda_readb(u8 __iomem *addr) -{ - return readb(addr); -} - -static int sof_hda_dma_alloc_pages(struct hdac_bus *bus, int type, - size_t size, struct snd_dma_buffer *buf) -{ - return snd_dma_alloc_pages(type, bus->dev, size, buf); -} - -static void sof_hda_dma_free_pages(struct hdac_bus *bus, - struct snd_dma_buffer *buf) -{ - snd_dma_free_pages(buf); -} - -static const struct hdac_io_ops io_ops = { - .reg_writel = sof_hda_writel, - .reg_readl = sof_hda_readl, - .reg_writew = sof_hda_writew, - .reg_readw = sof_hda_readw, - .reg_writeb = sof_hda_writeb, - .reg_readb = sof_hda_readb, - .dma_alloc_pages = sof_hda_dma_alloc_pages, - .dma_free_pages = sof_hda_dma_free_pages, -}; - /* * This can be used for both with/without hda link support. */ -void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev, - const struct hdac_ext_bus_ops *ext_ops) +void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev) { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_ext_bus_init(bus, dev, NULL, sof_hda_ext_ops); +#else /* CONFIG_SND_SOC_SOF_HDA */ memset(bus, 0, sizeof(*bus)); bus->dev = dev; - bus->io_ops = &io_ops; INIT_LIST_HEAD(&bus->stream_list); bus->irq = -1; - bus->ext_ops = ext_ops; /* * There is only one HDA bus atm. keep the index as 0. @@ -96,16 +41,5 @@ void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev, bus->idx = 0; spin_lock_init(&bus->reg_lock); - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - INIT_LIST_HEAD(&bus->codec_list); - INIT_LIST_HEAD(&bus->hlink_list); - - mutex_init(&bus->cmd_mutex); - mutex_init(&bus->lock); - bus->ops = &bus_ops; - INIT_WORK(&bus->unsol_work, snd_hdac_bus_process_unsol_events); - bus->cmd_dma_state = true; -#endif - +#endif /* CONFIG_SND_SOC_SOF_HDA */ } diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index b8b37f082309..9106ab8dac6f 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -10,6 +10,7 @@ #include <linux/module.h> #include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> #include <sound/hda_codec.h> #include <sound/hda_i915.h> #include <sound/sof.h> @@ -23,30 +24,69 @@ #define IDISP_VID_INTEL 0x80860000 /* load the legacy HDA codec driver */ -#ifdef MODULE -static void hda_codec_load_module(struct hda_codec *codec) +static int hda_codec_load_module(struct hda_codec *codec) { +#ifdef MODULE char alias[MODULE_NAME_LEN]; const char *module = alias; snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias)); dev_dbg(&codec->core.dev, "loading codec module: %s\n", module); request_module(module); -} -#else -static void hda_codec_load_module(struct hda_codec *codec) {} #endif + return device_attach(hda_codec_dev(codec)); +} + +/* enable controller wake up event for all codecs with jack connectors */ +void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev) +{ + struct hda_bus *hbus = sof_to_hbus(sdev); + struct hdac_bus *bus = sof_to_bus(sdev); + struct hda_codec *codec; + unsigned int mask = 0; + + list_for_each_codec(codec, hbus) + if (codec->jacktbl.used) + mask |= BIT(codec->core.addr); + + snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask); +} +/* check jack status after resuming from suspend mode */ +void hda_codec_jack_check(struct snd_sof_dev *sdev) +{ + struct hda_bus *hbus = sof_to_hbus(sdev); + struct hdac_bus *bus = sof_to_bus(sdev); + struct hda_codec *codec; + + /* disable controller Wake Up event*/ + snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0); + + list_for_each_codec(codec, hbus) + /* + * Wake up all jack-detecting codecs regardless whether an event + * has been recorded in STATESTS + */ + if (codec->jacktbl.used) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); +} +#else +void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev) {} +void hda_codec_jack_check(struct snd_sof_dev *sdev) {} #endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ +EXPORT_SYMBOL_NS(hda_codec_jack_wake_enable, SND_SOC_SOF_HDA_AUDIO_CODEC); +EXPORT_SYMBOL_NS(hda_codec_jack_check, SND_SOC_SOF_HDA_AUDIO_CODEC); /* probe individual codec */ -static int hda_codec_probe(struct snd_sof_dev *sdev, int address) +static int hda_codec_probe(struct snd_sof_dev *sdev, int address, + bool hda_codec_use_common_hdmi) { - struct hda_bus *hbus = sof_to_hbus(sdev); - struct hdac_device *hdev; #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) struct hdac_hda_priv *hda_priv; #endif + struct hda_bus *hbus = sof_to_hbus(sdev); + struct hdac_device *hdev; u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) | (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; u32 resp = -1; @@ -62,8 +102,7 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address) address, resp); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) - /* snd_hdac_ext_bus_device_exit will use kfree to free hdev */ - hda_priv = kzalloc(sizeof(*hda_priv), GFP_KERNEL); + hda_priv = devm_kzalloc(sdev->dev, sizeof(*hda_priv), GFP_KERNEL); if (!hda_priv) return -ENOMEM; @@ -74,16 +113,28 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address) if (ret < 0) return ret; - /* use legacy bus only for HDA codecs, idisp uses ext bus */ - if ((resp & 0xFFFF0000) != IDISP_VID_INTEL) { + if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) + hda_priv->need_display_power = true; + + /* + * if common HDMI codec driver is not used, codec load + * is skipped here and hdac_hdmi is used instead + */ + if (hda_codec_use_common_hdmi || + (resp & 0xFFFF0000) != IDISP_VID_INTEL) { hdev->type = HDA_DEV_LEGACY; - hda_codec_load_module(&hda_priv->codec); + ret = hda_codec_load_module(&hda_priv->codec); + /* + * handle ret==0 (no driver bound) as an error, but pass + * other return codes without modification + */ + if (ret == 0) + ret = -ENOENT; } - return 0; + return ret; #else - /* snd_hdac_ext_bus_device_exit will use kfree to free hdev */ - hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL); if (!hdev) return -ENOMEM; @@ -94,7 +145,8 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address) } /* Codec initialization */ -int hda_codec_probe_bus(struct snd_sof_dev *sdev) +void hda_codec_probe_bus(struct snd_sof_dev *sdev, + bool hda_codec_use_common_hdmi) { struct hdac_bus *bus = sof_to_bus(sdev); int i, ret; @@ -105,37 +157,27 @@ int hda_codec_probe_bus(struct snd_sof_dev *sdev) if (!(bus->codec_mask & (1 << i))) continue; - ret = hda_codec_probe(sdev, i); + ret = hda_codec_probe(sdev, i, hda_codec_use_common_hdmi); if (ret < 0) { - dev_err(bus->dev, "error: codec #%d probe error, ret: %d\n", - i, ret); - return ret; + dev_warn(bus->dev, "codec #%d probe error, ret: %d\n", + i, ret); + bus->codec_mask &= ~BIT(i); } } - - return 0; } -EXPORT_SYMBOL(hda_codec_probe_bus); - -#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) - -void hda_codec_i915_get(struct snd_sof_dev *sdev) -{ - struct hdac_bus *bus = sof_to_bus(sdev); +EXPORT_SYMBOL_NS(hda_codec_probe_bus, SND_SOC_SOF_HDA_AUDIO_CODEC); - dev_dbg(bus->dev, "Turning i915 HDAC power on\n"); - snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); -} -EXPORT_SYMBOL(hda_codec_i915_get); +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \ + IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) -void hda_codec_i915_put(struct snd_sof_dev *sdev) +void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable) { struct hdac_bus *bus = sof_to_bus(sdev); - dev_dbg(bus->dev, "Turning i915 HDAC power off\n"); - snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + dev_dbg(bus->dev, "Turning i915 HDAC power %d\n", enable); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, enable); } -EXPORT_SYMBOL(hda_codec_i915_put); +EXPORT_SYMBOL_NS(hda_codec_i915_display_power, SND_SOC_SOF_HDA_AUDIO_CODEC_I915); int hda_codec_i915_init(struct snd_sof_dev *sdev) { @@ -147,25 +189,25 @@ int hda_codec_i915_init(struct snd_sof_dev *sdev) if (ret < 0) return ret; - hda_codec_i915_get(sdev); + hda_codec_i915_display_power(sdev, true); return 0; } -EXPORT_SYMBOL(hda_codec_i915_init); +EXPORT_SYMBOL_NS(hda_codec_i915_init, SND_SOC_SOF_HDA_AUDIO_CODEC_I915); int hda_codec_i915_exit(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); int ret; - hda_codec_i915_put(sdev); + hda_codec_i915_display_power(sdev, false); ret = snd_hdac_i915_exit(bus); return ret; } -EXPORT_SYMBOL(hda_codec_i915_exit); +EXPORT_SYMBOL_NS(hda_codec_i915_exit, SND_SOC_SOF_HDA_AUDIO_CODEC_I915); -#endif /* CONFIG_SND_SOC_HDAC_HDMI */ +#endif MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index ea63f83a509b..871b71a15a63 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -15,11 +15,18 @@ * Hardware interface for generic Intel audio DSP HDA IP */ +#include <linux/module.h> #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> #include "../ops.h" #include "hda.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +static int hda_codec_mask = -1; +module_param_named(codec_mask, hda_codec_mask, int, 0444); +MODULE_PARM_DESC(codec_mask, "SOF HDA codec mask for probing"); +#endif + /* * HDA Operations. */ @@ -139,20 +146,16 @@ void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) */ int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - struct hdac_bus *bus = sof_to_bus(sdev); -#endif u32 val; /* enable/disable audio dsp clock gating */ val = enable ? PCI_CGCTL_ADSPDCGE : 0; snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - /* enable/disable L1 support */ - val = enable ? SOF_HDA_VS_EM2_L1SEN : 0; - snd_hdac_chip_updatel(bus, VS_EM2, SOF_HDA_VS_EM2_L1SEN, val); -#endif + /* enable/disable DMI Link L1 support */ + val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, val); /* enable/disable audio dsp power gating */ val = enable ? 0 : PCI_PGCTL_ADSPPGD; @@ -164,6 +167,9 @@ int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset) { struct hdac_bus *bus = sof_to_bus(sdev); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_ext_link *hlink; +#endif struct hdac_stream *stream; int sd_offset, ret = 0; @@ -173,11 +179,6 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset) hda_dsp_ctrl_misc_clock_gating(sdev, false); if (full_reset) { - /* clear WAKESTS */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS, - SOF_HDA_WAKESTS_INT_MASK, - SOF_HDA_WAKESTS_INT_MASK); - /* reset HDA controller */ ret = hda_dsp_ctrl_link_reset(sdev, true); if (ret < 0) { @@ -212,6 +213,12 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset) bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS); dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask); } + + if (hda_codec_mask != -1) { + bus->codec_mask &= hda_codec_mask; + dev_dbg(bus->dev, "filtered codec_mask = 0x%lx\n", + bus->codec_mask); + } #endif /* clear stream status */ @@ -245,13 +252,18 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset) SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) /* program the position buffer */ if (bus->use_posbuf && bus->posbuf.addr) { - snd_hdac_chip_writel(bus, DPLBASE, (u32)bus->posbuf.addr); - snd_hdac_chip_writel(bus, DPUBASE, - upper_32_bits(bus->posbuf.addr)); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE, + (u32)bus->posbuf.addr); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE, + upper_32_bits(bus->posbuf.addr)); } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* Reset stream-to-link mapping */ + list_for_each_entry(hlink, &bus->hlink_list, list) + writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); #endif bus->chip_init = true; diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index a514f9cf5c9a..9c6e3f990ee3 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -11,6 +11,7 @@ #include <sound/pcm_params.h> #include <sound/hdaudio_ext.h> #include "../sof-priv.h" +#include "../sof-audio.h" #include "hda.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) @@ -210,9 +211,15 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, int stream_tag; int ret; - link_dev = hda_link_stream_assign(bus, substream); - if (!link_dev) - return -EBUSY; + /* get stored dma data if resuming from system suspend */ + link_dev = snd_soc_dai_get_dma_data(dai, substream); + if (!link_dev) { + link_dev = hda_link_stream_assign(bus, substream); + if (!link_dev) + return -EBUSY; + + snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); + } stream_tag = hdac_stream(link_dev)->stream_tag; @@ -224,10 +231,6 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; - snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); - - hda_stream->hw_params_upon_resume = 0; - link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); if (!link) return -EINVAL; @@ -259,16 +262,12 @@ static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, { struct hdac_ext_stream *link_dev = snd_soc_dai_get_dma_data(dai, substream); - struct sof_intel_hda_stream *hda_stream; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); int stream = substream->stream; - hda_stream = hstream_to_sof_hda_stream(link_dev); - - /* setup hw_params again only if resuming from system suspend */ - if (!hda_stream->hw_params_upon_resume) + if (link_dev->link_prepared) return 0; dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); @@ -317,22 +316,25 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, snd_hdac_ext_link_stream_start(link_dev); break; case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: /* - * clear and release link DMA channel. It will be assigned when + * clear link DMA channel. It will be assigned when * hw_params is set up again after resume. */ ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID, substream->stream); if (ret < 0) return ret; - stream_tag = hdac_stream(link_dev)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - snd_hdac_ext_stream_release(link_dev, - HDAC_EXT_STREAM_TYPE_LINK); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + + link_dev->link_prepared = 0; /* fallthrough */ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_STOP: snd_hdac_ext_link_stream_clear(link_dev); break; default: @@ -357,6 +359,13 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream, bus = hstream->bus; rtd = snd_pcm_substream_chip(substream); link_dev = snd_soc_dai_get_dma_data(dai, substream); + + if (!link_dev) { + dev_dbg(dai->dev, + "%s: link_dev is not assigned\n", __func__); + return -EINVAL; + } + hda_stream = hstream_to_sof_hda_stream(link_dev); /* free the link DMA channel in the FW */ @@ -369,8 +378,12 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream, if (!link) return -EINVAL; - stream_tag = hdac_stream(link_dev)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + + snd_soc_dai_set_dma_data(dai, substream, NULL); snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); link_dev->link_prepared = 0; @@ -432,6 +445,10 @@ struct snd_soc_dai_driver skl_dai[] = { .ops = &hda_link_dai_ops, }, { + .name = "iDisp4 Pin", + .ops = &hda_link_dai_ops, +}, +{ .name = "Analog CPU DAI", .ops = &hda_link_dai_ops, }, diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 91de4785b6a3..4a4d318f97ff 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -19,6 +19,7 @@ #include <sound/hda_register.h> #include "../ops.h" #include "hda.h" +#include "hda-ipc.h" /* * DSP Core control. @@ -42,6 +43,12 @@ int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) ((adspcs & reset) == reset), HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n", + __func__); + return ret; + } /* has core entered reset ? */ adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, @@ -77,6 +84,13 @@ int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n", + __func__); + return ret; + } + /* has core left reset ? */ adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS); @@ -151,8 +165,12 @@ int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) (adspcs & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); - if (ret < 0) - dev_err(sdev->dev, "error: timeout on core powerup\n"); + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n", + __func__); + return ret; + } /* did core power up ? */ adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, @@ -171,17 +189,24 @@ int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) { u32 adspcs; + int ret; /* update bits */ snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, HDA_DSP_ADSPCS_SPA_MASK(core_mask), 0); - return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, adspcs, !(adspcs & HDA_DSP_ADSPCS_SPA_MASK(core_mask)), HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC); + if (ret < 0) + dev_err(sdev->dev, + "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n", + __func__); + + return ret; } bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, @@ -282,7 +307,81 @@ void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev) HDA_DSP_REG_HIPCCTL_BUSY | HDA_DSP_REG_HIPCCTL_DONE, 0); } -static int hda_suspend(struct snd_sof_dev *sdev, int state) +static int hda_dsp_wait_d0i3c_done(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int retry = HDA_DSP_REG_POLL_RETRY_COUNT; + + while (snd_hdac_chip_readb(bus, VS_D0I3C) & SOF_HDA_VS_D0I3C_CIP) { + if (!retry--) + return -ETIMEDOUT; + usleep_range(10, 15); + } + + return 0; +} + +static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_pm_gate pm_gate; + struct sof_ipc_reply reply; + + memset(&pm_gate, 0, sizeof(pm_gate)); + + /* configure pm_gate ipc message */ + pm_gate.hdr.size = sizeof(pm_gate); + pm_gate.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE; + pm_gate.flags = flags; + + /* send pm_gate ipc to dsp */ + return sof_ipc_tx_message(sdev->ipc, pm_gate.hdr.cmd, &pm_gate, + sizeof(pm_gate), &reply, sizeof(reply)); +} + +int hda_dsp_set_power_state(struct snd_sof_dev *sdev, + enum sof_d0_substate d0_substate) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + u32 flags; + int ret; + u8 value; + + /* Write to D0I3C after Command-In-Progress bit is cleared */ + ret = hda_dsp_wait_d0i3c_done(sdev); + if (ret < 0) { + dev_err(bus->dev, "CIP timeout before D0I3C update!\n"); + return ret; + } + + /* Update D0I3C register */ + value = d0_substate == SOF_DSP_D0I3 ? SOF_HDA_VS_D0I3C_I3 : 0; + snd_hdac_chip_updateb(bus, VS_D0I3C, SOF_HDA_VS_D0I3C_I3, value); + + /* Wait for cmd in progress to be cleared before exiting the function */ + ret = hda_dsp_wait_d0i3c_done(sdev); + if (ret < 0) { + dev_err(bus->dev, "CIP timeout after D0I3C update!\n"); + return ret; + } + + dev_vdbg(bus->dev, "D0I3C updated, register = 0x%x\n", + snd_hdac_chip_readb(bus, VS_D0I3C)); + + if (d0_substate == SOF_DSP_D0I0) + flags = HDA_PM_PPG;/* prevent power gating in D0 */ + else + flags = HDA_PM_NO_DMA_TRACE;/* disable DMA trace in D0I3*/ + + /* sending pm_gate IPC */ + ret = hda_dsp_send_pm_gate_ipc(sdev, flags); + if (ret < 0) + dev_err(sdev->dev, + "error: PM_GATE ipc error %d\n", ret); + + return ret; +} + +static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; @@ -295,6 +394,9 @@ static int hda_suspend(struct snd_sof_dev *sdev, int state) hda_dsp_ipc_int_disable(sdev); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + if (runtime_suspend) + hda_codec_jack_wake_enable(sdev); + /* power down all hda link */ snd_hdac_ext_bus_link_power_down_all(bus); #endif @@ -329,7 +431,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, int state) return 0; } -static int hda_resume(struct snd_sof_dev *sdev) +static int hda_resume(struct snd_sof_dev *sdev, bool runtime_resume) { #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) struct hdac_bus *bus = sof_to_bus(sdev); @@ -343,7 +445,6 @@ static int hda_resume(struct snd_sof_dev *sdev) */ snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) /* reset and start hda controller */ ret = hda_dsp_ctrl_init_chip(sdev, true); if (ret < 0) { @@ -352,46 +453,11 @@ static int hda_resume(struct snd_sof_dev *sdev) return ret; } - hda_dsp_ctrl_misc_clock_gating(sdev, false); - - /* Reset stream-to-link mapping */ - list_for_each_entry(hlink, &bus->hlink_list, list) - bus->io_ops->reg_writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); - - hda_dsp_ctrl_misc_clock_gating(sdev, true); -#else - - hda_dsp_ctrl_misc_clock_gating(sdev, false); - - /* reset controller */ - ret = hda_dsp_ctrl_link_reset(sdev, true); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to reset controller during resume\n"); - return ret; - } - - /* take controller out of reset */ - ret = hda_dsp_ctrl_link_reset(sdev, false); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to ready controller during resume\n"); - return ret; - } - - /* enable hda bus irq */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, - SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, - SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN); - - hda_dsp_ctrl_misc_clock_gating(sdev, true); -#endif - - /* enable ppcap interrupt */ - hda_dsp_ctrl_ppcap_enable(sdev, true); - hda_dsp_ctrl_ppcap_int_enable(sdev, true); - #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* check jack status */ + if (runtime_resume) + hda_codec_jack_check(sdev); + /* turn off the links that were off before suspend */ list_for_each_entry(hlink, &bus->hlink_list, list) { if (!hlink->ref_count) @@ -403,19 +469,39 @@ static int hda_resume(struct snd_sof_dev *sdev) snd_hdac_bus_stop_cmd_io(bus); #endif + /* enable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, true); + hda_dsp_ctrl_ppcap_int_enable(sdev, true); + return 0; } int hda_dsp_resume(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct pci_dev *pci = to_pci_dev(sdev->dev); + + if (sdev->s0_suspend) { + /* restore L1SEN bit */ + if (hda->l1_support_changed) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, 0); + + /* restore and disable the system wakeup */ + pci_restore_state(pci); + disable_irq_wake(pci->irq); + return 0; + } + /* init hda controller. DSP cores will be powered up during fw boot */ - return hda_resume(sdev); + return hda_resume(sdev, false); } int hda_dsp_runtime_resume(struct snd_sof_dev *sdev) { /* init hda controller. DSP cores will be powered up during fw boot */ - return hda_resume(sdev); + return hda_resume(sdev, true); } int hda_dsp_runtime_idle(struct snd_sof_dev *sdev) @@ -431,19 +517,35 @@ int hda_dsp_runtime_idle(struct snd_sof_dev *sdev) return 0; } -int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev, int state) +int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) { /* stop hda controller and power dsp off */ - return hda_suspend(sdev, state); + return hda_suspend(sdev, true); } -int hda_dsp_suspend(struct snd_sof_dev *sdev, int state) +int hda_dsp_suspend(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_bus *bus = sof_to_bus(sdev); + struct pci_dev *pci = to_pci_dev(sdev->dev); int ret; + if (sdev->s0_suspend) { + /* enable L1SEN to make sure the system can enter S0Ix */ + hda->l1_support_changed = + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, + HDA_VS_INTEL_EM2_L1SEN); + + /* enable the system waking up via IPC IRQ */ + enable_irq_wake(pci->irq); + pci_save_state(pci); + return 0; + } + /* stop hda controller and power dsp off */ - ret = hda_suspend(sdev, state); + ret = hda_suspend(sdev, false); if (ret < 0) { dev_err(bus->dev, "error: suspending dsp\n"); return ret; @@ -454,30 +556,24 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, int state) int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) { - struct hdac_bus *bus = sof_to_bus(sdev); - struct sof_intel_hda_stream *hda_stream; - struct hdac_ext_stream *stream; - struct hdac_stream *s; - #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_stream *stream; struct hdac_ext_link *link; + struct hdac_stream *s; const char *name; int stream_tag; -#endif /* set internal flag for BE */ list_for_each_entry(s, &bus->stream_list, list) { stream = stream_to_hdac_ext_stream(s); - hda_stream = container_of(stream, struct sof_intel_hda_stream, - hda_stream); - hda_stream->hw_params_upon_resume = 1; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* - * clear and release stream. This should already be taken care - * for running streams when the SUSPEND trigger is called. - * But paused streams do not get suspended, so this needs to be - * done explicitly during suspend. + * clear stream. This should already be taken care for running + * streams when the SUSPEND trigger is called. But paused + * streams do not get suspended, so this needs to be done + * explicitly during suspend. */ if (stream->link_substream) { rtd = snd_pcm_substream_chip(stream->link_substream); @@ -485,12 +581,17 @@ int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) link = snd_hdac_ext_bus_get_link(bus, name); if (!link) return -EINVAL; + + stream->link_prepared = 0; + + if (hdac_stream(stream)->direction == + SNDRV_PCM_STREAM_CAPTURE) + continue; + stream_tag = hdac_stream(stream)->stream_tag; snd_hdac_ext_link_clear_stream_id(link, stream_tag); - snd_hdac_ext_stream_release(stream, - HDAC_EXT_STREAM_TYPE_LINK); } -#endif } +#endif return 0; } diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index 2ecba91f5219..1837f66e361f 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -83,10 +83,12 @@ void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) } hdr = msg->msg_data; - if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE)) { + if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) || + hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { /* * memory windows are powered off before sending IPC reply, - * so we can't read the mailbox for CTX_SAVE reply. + * so we can't read the mailbox for CTX_SAVE and PM_GATE + * replies. */ reply.error = 0; reply.hdr.cmd = SOF_IPC_GLB_REPLY; @@ -228,22 +230,15 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) "nothing to do in IPC IRQ thread\n"); } - /* re-enable IPC interrupt */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, - HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); - return IRQ_HANDLED; } -/* is this IRQ for ADSP ? - we only care about IPC here */ -irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context) +/* Check if an IPC IRQ occurred */ +bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) { - struct snd_sof_dev *sdev = context; - int ret = IRQ_NONE; + bool ret = false; u32 irq_status; - spin_lock(&sdev->hw_lock); - /* store status */ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); @@ -253,169 +248,21 @@ irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context) goto out; /* IPC message ? */ - if (irq_status & HDA_DSP_ADSPIS_IPC) { - /* disable IPC interrupt */ - snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, - HDA_DSP_REG_ADSPIC, - HDA_DSP_ADSPIC_IPC, 0); - ret = IRQ_WAKE_THREAD; - } + if (irq_status & HDA_DSP_ADSPIS_IPC) + ret = true; out: - spin_unlock(&sdev->hw_lock); return ret; } -/* IPC Firmware ready */ - -static void ipc_get_windows(struct snd_sof_dev *sdev) +int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) { - struct sof_ipc_window_elem *elem; - u32 outbox_offset = 0; - u32 stream_offset = 0; - u32 inbox_offset = 0; - u32 outbox_size = 0; - u32 stream_size = 0; - u32 inbox_size = 0; - int i; - - if (!sdev->info_window) { - dev_err(sdev->dev, "error: have no window info\n"); - return; - } - - for (i = 0; i < sdev->info_window->num_windows; i++) { - elem = &sdev->info_window->window[i]; - - switch (elem->type) { - case SOF_IPC_REGION_UPBOX: - inbox_offset = - elem->offset + SRAM_WINDOW_OFFSET(elem->id); - inbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DOWNBOX: - outbox_offset = - elem->offset + SRAM_WINDOW_OFFSET(elem->id); - outbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - elem->offset + - SRAM_WINDOW_OFFSET - (elem->id), - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DEBUG: - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - elem->offset + - SRAM_WINDOW_OFFSET - (elem->id), - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_STREAM: - stream_offset = - elem->offset + SRAM_WINDOW_OFFSET(elem->id); - stream_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - elem->offset + - SRAM_WINDOW_OFFSET - (elem->id), - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_REGS: - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - elem->offset + - SRAM_WINDOW_OFFSET - (elem->id), - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_EXCEPTION: - sdev->dsp_oops_offset = elem->offset + - SRAM_WINDOW_OFFSET(elem->id); - snd_sof_debugfs_io_item(sdev, - sdev->bar[HDA_DSP_BAR] + - elem->offset + - SRAM_WINDOW_OFFSET - (elem->id), - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - default: - dev_err(sdev->dev, "error: get illegal window info\n"); - return; - } - } - - if (outbox_size == 0 || inbox_size == 0) { - dev_err(sdev->dev, "error: get illegal mailbox window\n"); - return; - } - - snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, - outbox_offset, outbox_size); - sdev->stream_box.offset = stream_offset; - sdev->stream_box.size = stream_size; - - dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", - inbox_offset, inbox_size); - dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", - outbox_offset, outbox_size); - dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", - stream_offset, stream_size); + return HDA_DSP_MBOX_UPLINK_OFFSET; } -/* check for ABI compatibility and create memory windows on first boot */ -int hda_dsp_ipc_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) { - struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; - u32 offset; - int ret; - - /* mailbox must be on 4k boundary */ - offset = HDA_DSP_MBOX_UPLINK_OFFSET; - - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", - msg_id, offset); - - /* no need to re-check version/ABI for subsequent boots */ - if (!sdev->first_boot) - return 0; - - /* copy data from the DSP FW ready offset */ - sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, - sizeof(*fw_ready)); - - /* make sure ABI version is compatible */ - ret = snd_sof_ipc_valid(sdev); - if (ret < 0) - return ret; - - /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, - HDA_DSP_MBOX_UPLINK_OFFSET + - sizeof(struct sof_ipc_fw_ready)); - - ipc_get_windows(sdev); - - return 0; + return SRAM_WINDOW_OFFSET(id); } void hda_ipc_msg_data(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/intel/hda-ipc.h b/sound/soc/sof/intel/hda-ipc.h new file mode 100644 index 000000000000..aef0ceac9803 --- /dev/null +++ b/sound/soc/sof/intel/hda-ipc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2019 Intel Corporation. All rights reserved. + * + * Author: Keyon Jie <yang.jie@linux.intel.com> + */ + +#ifndef __SOF_INTEL_HDA_IPC_H +#define __SOF_INTEL_HDA_IPC_H + +/* + * Primary register, mapped to + * - DIPCTDR (HIPCIDR) in sideband IPC (cAVS 1.8+) + * - DIPCT in cAVS 1.5 IPC + * + * Secondary register, mapped to: + * - DIPCTDD (HIPCIDD) in sideband IPC (cAVS 1.8+) + * - DIPCTE in cAVS 1.5 IPC + */ + +/* Common bits in primary register */ + +/* Reserved for doorbell */ +#define HDA_IPC_RSVD_31 BIT(31) +/* Target, 0 - normal message, 1 - compact message(cAVS compatible) */ +#define HDA_IPC_MSG_COMPACT BIT(30) +/* Direction, 0 - request, 1 - response */ +#define HDA_IPC_RSP BIT(29) + +#define HDA_IPC_TYPE_SHIFT 24 +#define HDA_IPC_TYPE_MASK GENMASK(28, 24) +#define HDA_IPC_TYPE(x) ((x) << HDA_IPC_TYPE_SHIFT) + +#define HDA_IPC_PM_GATE HDA_IPC_TYPE(0x8U) + +/* Command specific payload bits in secondary register */ + +/* Disable DMA tracing (0 - keep tracing, 1 - to disable DMA trace) */ +#define HDA_PM_NO_DMA_TRACE BIT(4) +/* Prevent clock gating (0 - cg allowed, 1 - DSP clock always on) */ +#define HDA_PM_PCG BIT(3) +/* Prevent power gating (0 - deep power state transitions allowed) */ +#define HDA_PM_PPG BIT(2) +/* Indicates whether streaming is active */ +#define HDA_PM_PG_STREAMING BIT(1) +#define HDA_PM_PG_RSVD BIT(0) + +#endif diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 6427f0b3a2f1..8852184a2569 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -44,6 +44,7 @@ static int cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, return -ENODEV; } hstream = &dsp_stream->hstream; + hstream->substream = NULL; /* allocate DMA buffer */ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, &pci->dev, size, dmab); @@ -125,7 +126,8 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata, HDA_DSP_INIT_TIMEOUT_US); if (ret < 0) { - dev_err(sdev->dev, "error: waiting for HIPCIE done\n"); + dev_err(sdev->dev, "error: %s: timeout for HIPCIE done\n", + __func__); goto err; } @@ -151,6 +153,10 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata, if (!ret) return 0; + dev_err(sdev->dev, + "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", + __func__); + err: hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX); hda_dsp_core_reset_power_down(sdev, chip->cores_mask); @@ -252,10 +258,22 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream) HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_BASEFW_TIMEOUT_US); + /* + * even in case of errors we still need to stop the DMAs, + * but we return the initial error should the DMA stop also fail + */ + + if (status < 0) { + dev_err(sdev->dev, + "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", + __func__); + } + ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_STOP); if (ret < 0) { dev_err(sdev->dev, "error: DMA trigger stop failed\n"); - return ret; + if (!status) + status = ret; } return status; @@ -277,7 +295,6 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) /* init for booting wait */ init_waitqueue_head(&sdev->boot_wait); - sdev->boot_complete = false; /* prepare DMA for code loader stream */ tag = cl_stream_prepare(sdev, 0x40, stripped_firmware.size, @@ -311,13 +328,13 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) if (!ret) break; - dev_err(sdev->dev, "error: Error code=0x%x: FW status=0x%x\n", + dev_dbg(sdev->dev, "iteration %d of Core En/ROM load failed: %d\n", + i, ret); + dev_dbg(sdev->dev, "Error code=0x%x: FW status=0x%x\n", snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR), snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_STATUS)); - dev_err(sdev->dev, "error: iteration %d of Core En/ROM load failed: %d\n", - i, ret); } if (i == HDA_FW_BOOT_ATTEMPTS) { @@ -340,13 +357,15 @@ cleanup: /* * Perform codeloader stream cleanup. * This should be done even if firmware loading fails. + * If the cleanup also fails, we return the initial error */ ret1 = cl_cleanup(sdev, &sdev->dmab, stream); if (ret1 < 0) { dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); /* set return value to indicate cleanup failure */ - ret = ret1; + if (!ret) + ret = ret1; } /* diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index 9b730f183529..23872f6e708d 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -17,6 +17,7 @@ #include <sound/hda_register.h> #include <sound/pcm_params.h> +#include "../sof-audio.h" #include "../ops.h" #include "hda.h" @@ -89,6 +90,7 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct snd_dma_buffer *dmab; + struct sof_ipc_fw_version *v = &sdev->fw_ready.version; int ret; u32 size, rate, bits; @@ -116,9 +118,17 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, /* disable SPIB, to enable buffer wrap for stream */ hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); - /* set host_period_bytes to 0 if no IPC position */ - if (hda && hda->no_ipc_position) - ipc_params->host_period_bytes = 0; + /* update no_stream_position flag for ipc params */ + if (hda && hda->no_ipc_position) { + /* For older ABIs set host_period_bytes to zero to inform + * FW we don't want position updates. Newer versions use + * no_stream_position for this purpose. + */ + if (v->abi_version < SOF_ABI_VER(3, 10, 0)) + ipc_params->host_period_bytes = 0; + else + ipc_params->no_stream_position = 1; + } ipc_params->stream_tag = hstream->stream_tag; @@ -138,12 +148,13 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *scomp = sdev->component; struct hdac_stream *hstream = substream->runtime->private_data; struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct snd_sof_pcm *spcm; snd_pcm_uframes_t pos; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(scomp, rtd); if (!spcm) { dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", rtd->dai_link->id); diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index ad8d41f22e92..c0ab9bb2a797 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -20,6 +20,7 @@ #include <sound/hda_register.h> #include <sound/sof.h> #include "../ops.h" +#include "../sof-audio.h" #include "hda.h" /* @@ -185,6 +186,17 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) direction == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + /* + * Disable DMI Link L1 entry when capture stream is opened. + * Workaround to address a known issue with host DMA that results + * in xruns during pause/release in capture scenarios. + */ + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1)) + if (stream && direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, 0); + return stream; } @@ -193,23 +205,43 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) { struct hdac_bus *bus = sof_to_bus(sdev); struct hdac_stream *s; + bool active_capture_stream = false; + bool found = false; spin_lock_irq(&bus->reg_lock); - /* find used stream */ + /* + * close stream matching the stream tag + * and check if there are any open capture streams. + */ list_for_each_entry(s, &bus->stream_list, list) { - if (s->direction == direction && - s->opened && s->stream_tag == stream_tag) { + if (!s->opened) + continue; + + if (s->direction == direction && s->stream_tag == stream_tag) { s->opened = false; - spin_unlock_irq(&bus->reg_lock); - return 0; + found = true; + } else if (s->direction == SNDRV_PCM_STREAM_CAPTURE) { + active_capture_stream = true; } } spin_unlock_irq(&bus->reg_lock); - dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag); - return -ENODEV; + /* Enable DMI L1 entry if there are no capture streams open */ + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1)) + if (!active_capture_stream) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, + HDA_VS_INTEL_EM2_L1SEN); + + if (!found) { + dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag); + return -ENODEV; + } + + return 0; } int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, @@ -244,8 +276,12 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret) + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n", + __func__, cmd); return ret; + } hstream->running = true; break; @@ -263,8 +299,12 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret) + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n", + __func__, cmd); return ret; + } snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, @@ -325,8 +365,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret) + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: timeout on STREAM_SD_OFFSET read1\n", + __func__); return ret; + } snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, @@ -387,8 +431,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret) + if (ret < 0) { + dev_err(sdev->dev, + "error: %s: timeout on STREAM_SD_OFFSET read2\n", + __func__); return ret; + } snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, @@ -502,22 +550,23 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, return 0; } -irqreturn_t hda_dsp_stream_interrupt(int irq, void *context) +bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) { - struct hdac_bus *bus = context; - int ret = IRQ_WAKE_THREAD; + struct hdac_bus *bus = sof_to_bus(sdev); + bool ret = false; u32 status; - spin_lock(&bus->reg_lock); + /* The function can be called at irq thread, so use spin_lock_irq */ + spin_lock_irq(&bus->reg_lock); status = snd_hdac_chip_readl(bus, INTSTS); dev_vdbg(bus->dev, "stream irq, INTSTS status: 0x%x\n", status); - /* Register inaccessible, ignore it.*/ - if (status == 0xffffffff) - ret = IRQ_NONE; + /* if Register inaccessible, ignore it.*/ + if (status != 0xffffffff) + ret = true; - spin_unlock(&bus->reg_lock); + spin_unlock_irq(&bus->reg_lock); return ret; } @@ -555,7 +604,8 @@ static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status) irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context) { - struct hdac_bus *bus = context; + struct snd_sof_dev *sdev = context; + struct hdac_bus *bus = sof_to_bus(sdev); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) u32 rirb_status; #endif diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 7f665392618f..65b86dd044f1 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -19,13 +19,11 @@ #include <sound/hda_register.h> #include <linux/module.h> +#include <sound/intel-nhlt.h> #include <sound/sof.h> #include <sound/sof/xtensa.h> #include "../ops.h" #include "hda.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) -#include "../../codecs/hdac_hda.h" -#endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) #include <sound/soc-acpi-intel-match.h> @@ -34,8 +32,7 @@ /* platform specific devices */ #include "shim.h" -#define IS_CFL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa348) -#define IS_CNL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9dc8) +#define EXCEPT_MAX_HDR_SIZE 0x400 /* * Debug @@ -46,6 +43,23 @@ struct hda_dsp_msg_code { const char *msg; }; +static bool hda_use_msi = IS_ENABLED(CONFIG_PCI); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG) +module_param_named(use_msi, hda_use_msi, bool, 0444); +MODULE_PARM_DESC(use_msi, "SOF HDA use PCI MSI mode"); +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +static int hda_dmic_num = -1; +module_param_named(dmic_num, hda_dmic_num, int, 0444); +MODULE_PARM_DESC(dmic_num, "SOF HDA DMIC number"); + +static bool hda_codec_use_common_hdmi = + IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_COMMON_HDMI_CODEC); +module_param_named(use_common_hdmi, hda_codec_use_common_hdmi, bool, 0444); +MODULE_PARM_DESC(use_common_hdmi, "SOF HDA use common HDMI codec driver"); +#endif + static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = { {HDA_DSP_ROM_FW_MANIFEST_LOADED, "status: manifest loaded"}, {HDA_DSP_ROM_FW_FW_LOADED, "status: fw loaded"}, @@ -121,6 +135,11 @@ static void hda_dsp_get_registers(struct snd_sof_dev *sdev, /* note: variable AR register array is not read */ /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n", + xoops->arch_hdr.totalsize); + return; + } offset += xoops->arch_hdr.totalsize; sof_block_read(sdev, sdev->mmio_bar, offset, panic_info, sizeof(*panic_info)); @@ -149,7 +168,7 @@ void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags) panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_ADSP_ERROR_CODE_SKL + 0x4); - if (sdev->boot_complete) { + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, HDA_DSP_STACK_DUMP_SIZE); snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, @@ -176,7 +195,7 @@ void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) HDA_DSP_SRAM_REG_FW_STATUS); panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP); - if (sdev->boot_complete) { + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, HDA_DSP_STACK_DUMP_SIZE); snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, @@ -236,7 +255,6 @@ static int hda_init(struct snd_sof_dev *sdev) { struct hda_bus *hbus; struct hdac_bus *bus; - struct hdac_ext_bus_ops *ext_ops = NULL; struct pci_dev *pci = to_pci_dev(sdev->dev); int ret; @@ -244,17 +262,11 @@ static int hda_init(struct snd_sof_dev *sdev) bus = sof_to_bus(sdev); /* HDA bus init */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) - ext_ops = snd_soc_hdac_hda_get_ops(); -#endif - sof_hda_bus_init(bus, &pci->dev, ext_ops); - - /* Workaround for a communication error on CFL (bko#199007) and CNL */ - if (IS_CFL(pci) || IS_CNL(pci)) - bus->polling_mode = 1; + sof_hda_bus_init(bus, &pci->dev); bus->use_posbuf = 1; bus->bdl_pos_adj = 0; + bus->sync_write = 1; mutex_init(&hbus->prepare_mutex); hbus->pci = pci; @@ -284,8 +296,26 @@ static int hda_init(struct snd_sof_dev *sdev) #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +static int check_nhlt_dmic(struct snd_sof_dev *sdev) +{ + struct nhlt_acpi_table *nhlt; + int dmic_num; + + nhlt = intel_nhlt_init(sdev->dev); + if (nhlt) { + dmic_num = intel_nhlt_get_dmic_geo(sdev->dev, nhlt); + intel_nhlt_free(nhlt); + if (dmic_num == 2 || dmic_num == 4) + return dmic_num; + } + + return 0; +} + static const char *fixup_tplg_name(struct snd_sof_dev *sdev, - const char *sof_tplg_filename) + const char *sof_tplg_filename, + const char *idisp_str, + const char *dmic_str) { const char *tplg_filename = NULL; char *filename; @@ -299,7 +329,8 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev, split_ext = strsep(&filename, "."); if (split_ext) { tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, - "%s-idisp.tplg", split_ext); + "%s%s%s.tplg", + split_ext, idisp_str, dmic_str); if (!tplg_filename) return NULL; } @@ -313,13 +344,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) struct hdac_bus *bus = sof_to_bus(sdev); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) struct hdac_ext_link *hlink; - struct snd_soc_acpi_mach_params *mach_params; - struct snd_soc_acpi_mach *hda_mach; - struct snd_sof_pdata *pdata = sdev->pdata; - struct snd_soc_acpi_mach *mach; - const char *tplg_filename; - int codec_num = 0; - int i; #endif int ret = 0; @@ -329,10 +353,23 @@ static int hda_init_caps(struct snd_sof_dev *sdev) if (bus->ppcap) dev_dbg(sdev->dev, "PP capability, will probe DSP later.\n"); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* init i915 and HDMI codecs */ + ret = hda_codec_i915_init(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: init i915 and HDMI codec failed\n"); + return ret; + } +#endif + + /* Init HDA controller after i915 init */ ret = hda_dsp_ctrl_init_chip(sdev, true); if (ret < 0) { dev_err(bus->dev, "error: init chip failed with ret: %d\n", ret); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + hda_codec_i915_exit(sdev); +#endif return ret; } @@ -340,74 +377,11 @@ static int hda_init_caps(struct snd_sof_dev *sdev) if (bus->mlcap) snd_hdac_ext_bus_get_ml_capabilities(bus); - /* init i915 and HDMI codecs */ - ret = hda_codec_i915_init(sdev); - if (ret < 0) { - dev_err(sdev->dev, "error: no HDMI audio devices found\n"); - return ret; - } - - /* codec detection */ - if (!bus->codec_mask) { - dev_info(bus->dev, "no hda codecs found!\n"); - } else { - dev_info(bus->dev, "hda codecs found, mask %lx\n", - bus->codec_mask); - - for (i = 0; i < HDA_MAX_CODECS; i++) { - if (bus->codec_mask & (1 << i)) - codec_num++; - } - - /* - * If no machine driver is found, then: - * - * hda machine driver is used if : - * 1. there is one HDMI codec and one external HDAudio codec - * 2. only HDMI codec - */ - if (!pdata->machine && codec_num <= 2 && - HDA_IDISP_CODEC(bus->codec_mask)) { - hda_mach = snd_soc_acpi_intel_hda_machines; - pdata->machine = hda_mach; - - /* topology: use the info from hda_machines */ - pdata->tplg_filename = - hda_mach->sof_tplg_filename; - - /* firmware: pick the first in machine list */ - mach = pdata->desc->machines; - pdata->fw_filename = mach->sof_fw_filename; - - dev_info(bus->dev, "using HDA machine driver %s now\n", - hda_mach->drv_name); - - /* fixup topology file for HDMI only platforms */ - if (codec_num == 1) { - /* use local variable for readability */ - tplg_filename = pdata->tplg_filename; - tplg_filename = fixup_tplg_name(sdev, tplg_filename); - if (!tplg_filename) { - hda_codec_i915_exit(sdev); - return ret; - } - pdata->tplg_filename = tplg_filename; - } - } - } - - /* used by hda machine driver to create dai links */ - if (pdata->machine) { - mach_params = (struct snd_soc_acpi_mach_params *) - &pdata->machine->mach_params; - mach_params->codec_mask = bus->codec_mask; - mach_params->platform = dev_name(sdev->dev); - } - /* create codec instances */ - hda_codec_probe_bus(sdev); + hda_codec_probe_bus(sdev, hda_codec_use_common_hdmi); - hda_codec_i915_put(sdev); + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); /* * we are done probing so decrement link counts @@ -429,6 +403,49 @@ static const struct sof_intel_dsp_desc return chip_info; } +static irqreturn_t hda_dsp_interrupt_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + + /* + * Get global interrupt status. It includes all hardware interrupt + * sources in the Intel HD Audio controller. + */ + if (snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS) & + SOF_HDA_INTSTS_GIS) { + + /* disable GIE interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + SOF_HDA_INTCTL, + SOF_HDA_INT_GLOBAL_EN, + 0); + + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + + /* deal with streams and controller first */ + if (hda_dsp_check_stream_irq(sdev)) + hda_dsp_stream_threaded_handler(irq, sdev); + + if (hda_dsp_check_ipc_irq(sdev)) + sof_ops(sdev)->irq_thread(irq, sdev); + + /* enable GIE interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + SOF_HDA_INTCTL, + SOF_HDA_INT_GLOBAL_EN, + SOF_HDA_INT_GLOBAL_EN); + + return IRQ_HANDLED; +} + int hda_dsp_probe(struct snd_sof_dev *sdev) { struct pci_dev *pci = to_pci_dev(sdev->dev); @@ -529,45 +546,32 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) * register our IRQ * let's try to enable msi firstly * if it fails, use legacy interrupt mode - * TODO: support interrupt mode selection with kernel parameter - * support msi multiple vectors + * TODO: support msi multiple vectors */ - ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI); - if (ret < 0) { + if (hda_use_msi && pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI) > 0) { + dev_info(sdev->dev, "use msi interrupt mode\n"); + sdev->ipc_irq = pci_irq_vector(pci, 0); + /* initialised to "false" by kzalloc() */ + sdev->msi_enabled = true; + } + + if (!sdev->msi_enabled) { dev_info(sdev->dev, "use legacy interrupt mode\n"); /* * in IO-APIC mode, hda->irq and ipc_irq are using the same * irq number of pci->irq */ - hdev->irq = pci->irq; sdev->ipc_irq = pci->irq; - sdev->msi_enabled = 0; - } else { - dev_info(sdev->dev, "use msi interrupt mode\n"); - hdev->irq = pci_irq_vector(pci, 0); - /* ipc irq number is the same of hda irq */ - sdev->ipc_irq = hdev->irq; - sdev->msi_enabled = 1; - } - - dev_dbg(sdev->dev, "using HDA IRQ %d\n", hdev->irq); - ret = request_threaded_irq(hdev->irq, hda_dsp_stream_interrupt, - hda_dsp_stream_threaded_handler, - IRQF_SHARED, "AudioHDA", bus); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to register HDA IRQ %d\n", - hdev->irq); - goto free_irq_vector; } dev_dbg(sdev->dev, "using IPC IRQ %d\n", sdev->ipc_irq); - ret = request_threaded_irq(sdev->ipc_irq, hda_dsp_ipc_irq_handler, - sof_ops(sdev)->irq_thread, IRQF_SHARED, - "AudioDSP", sdev); + ret = request_threaded_irq(sdev->ipc_irq, hda_dsp_interrupt_handler, + hda_dsp_interrupt_thread, + IRQF_SHARED, "AudioDSP", sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to register IPC IRQ %d\n", sdev->ipc_irq); - goto free_hda_irq; + goto free_irq_vector; } pci_set_master(pci); @@ -598,8 +602,6 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); -free_hda_irq: - free_irq(hdev->irq, bus); free_irq_vector: if (sdev->msi_enabled) pci_free_irq_vectors(pci); @@ -645,7 +647,6 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) SOF_HDA_PPCTL_GPROCEN, 0); free_irq(sdev->ipc_irq, sdev); - free_irq(hda->irq, bus); if (sdev->msi_enabled) pci_free_irq_vectors(pci); @@ -665,4 +666,136 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) return 0; } +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +static int hda_generic_machine_select(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct snd_soc_acpi_mach_params *mach_params; + struct snd_soc_acpi_mach *hda_mach; + struct snd_sof_pdata *pdata = sdev->pdata; + const char *tplg_filename; + const char *idisp_str; + const char *dmic_str; + int dmic_num = 0; + int codec_num = 0; + int i; + + /* codec detection */ + if (!bus->codec_mask) { + dev_info(bus->dev, "no hda codecs found!\n"); + } else { + dev_info(bus->dev, "hda codecs found, mask %lx\n", + bus->codec_mask); + + for (i = 0; i < HDA_MAX_CODECS; i++) { + if (bus->codec_mask & (1 << i)) + codec_num++; + } + + /* + * If no machine driver is found, then: + * + * hda machine driver is used if : + * 1. there is one HDMI codec and one external HDAudio codec + * 2. only HDMI codec + */ + if (!pdata->machine && codec_num <= 2 && + HDA_IDISP_CODEC(bus->codec_mask)) { + hda_mach = snd_soc_acpi_intel_hda_machines; + + /* topology: use the info from hda_machines */ + pdata->tplg_filename = + hda_mach->sof_tplg_filename; + + dev_info(bus->dev, "using HDA machine driver %s now\n", + hda_mach->drv_name); + + if (codec_num == 1) + idisp_str = "-idisp"; + else + idisp_str = ""; + + /* first check NHLT for DMICs */ + dmic_num = check_nhlt_dmic(sdev); + + /* allow for module parameter override */ + if (hda_dmic_num != -1) + dmic_num = hda_dmic_num; + + switch (dmic_num) { + case 2: + dmic_str = "-2ch"; + break; + case 4: + dmic_str = "-4ch"; + break; + default: + dmic_num = 0; + dmic_str = ""; + break; + } + + tplg_filename = pdata->tplg_filename; + tplg_filename = fixup_tplg_name(sdev, tplg_filename, + idisp_str, dmic_str); + if (!tplg_filename) + return -EINVAL; + + pdata->machine = hda_mach; + pdata->tplg_filename = tplg_filename; + } + } + + /* used by hda machine driver to create dai links */ + if (pdata->machine) { + mach_params = (struct snd_soc_acpi_mach_params *) + &pdata->machine->mach_params; + mach_params->codec_mask = bus->codec_mask; + mach_params->common_hdmi_codec_drv = hda_codec_use_common_hdmi; + mach_params->dmic_num = dmic_num; + } + + return 0; +} +#else +static int hda_generic_machine_select(struct snd_sof_dev *sdev) +{ + return 0; +} +#endif + +void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, + struct device *dev) +{ + struct snd_soc_acpi_mach_params *mach_params; + + mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params->platform = dev_name(dev); +} + +void hda_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach *mach; + + mach = snd_soc_acpi_find_machine(desc->machines); + if (mach) { + sof_pdata->tplg_filename = mach->sof_tplg_filename; + sof_pdata->machine = mach; + } + + /* + * Choose HDA generic machine driver if mach is NULL. + * Otherwise, set certain mach params. + */ + hda_generic_machine_select(sdev); + + if (!sof_pdata->machine) + dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); +} + MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC); +MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC_I915); +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index d9c17146200b..6191d9192fae 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -39,16 +39,18 @@ #define SOF_HDA_WAKESTS 0x0E #define SOF_HDA_WAKESTS_INT_MASK ((1 << 8) - 1) #define SOF_HDA_RIRBSTS 0x5d -#define SOF_HDA_VS_EM2_L1SEN BIT(13) /* SOF_HDA_GCTL register bist */ #define SOF_HDA_GCTL_RESET BIT(0) -/* SOF_HDA_INCTL and SOF_HDA_INTSTS regs */ +/* SOF_HDA_INCTL regs */ #define SOF_HDA_INT_GLOBAL_EN BIT(31) #define SOF_HDA_INT_CTRL_EN BIT(30) #define SOF_HDA_INT_ALL_STREAM 0xff +/* SOF_HDA_INTSTS regs */ +#define SOF_HDA_INTSTS_GIS BIT(31) + #define SOF_HDA_MAX_CAPS 10 #define SOF_HDA_CAP_ID_OFF 16 #define SOF_HDA_CAP_ID_MASK GENMASK(SOF_HDA_CAP_ID_OFF + 11,\ @@ -65,6 +67,13 @@ #define SOF_HDA_PPCTL_PIE BIT(31) #define SOF_HDA_PPCTL_GPROCEN BIT(30) +/*Vendor Specific Registers*/ +#define SOF_HDA_VS_D0I3C 0x104A + +/* D0I3C Register fields */ +#define SOF_HDA_VS_D0I3C_CIP BIT(0) /* Command-In-Progress */ +#define SOF_HDA_VS_D0I3C_I3 BIT(2) /* D0i3 enable bit */ + /* DPIB entry size: 8 Bytes = 2 DWords */ #define SOF_HDA_DPIB_ENTRY_SIZE 0x8 @@ -175,7 +184,7 @@ #define HDA_DSP_STACK_DUMP_SIZE 32 /* ROM status/error values */ -#define HDA_DSP_ROM_STS_MASK 0xf +#define HDA_DSP_ROM_STS_MASK GENMASK(23, 0) #define HDA_DSP_ROM_INIT 0x1 #define HDA_DSP_ROM_FW_MANIFEST_LOADED 0x3 #define HDA_DSP_ROM_FW_FW_LOADED 0x4 @@ -208,6 +217,7 @@ #define HDA_DSP_CTRL_RESET_TIMEOUT 100 #define HDA_DSP_WAIT_TIMEOUT 500 /* 500 msec */ #define HDA_DSP_REG_POLL_INTERVAL_US 500 /* 0.5 msec */ +#define HDA_DSP_REG_POLL_RETRY_COUNT 50 #define HDA_DSP_ADSPIC_IPC 1 #define HDA_DSP_ADSPIS_IPC 1 @@ -228,6 +238,10 @@ #define HDA_DSP_REG_HIPCIE (HDA_DSP_IPC_BASE + 0x0C) #define HDA_DSP_REG_HIPCCTL (HDA_DSP_IPC_BASE + 0x10) +/* Intel Vendor Specific Registers */ +#define HDA_VS_INTEL_EM2 0x1030 +#define HDA_VS_INTEL_EM2_L1SEN BIT(13) + /* HIPCI */ #define HDA_DSP_REG_HIPCI_BUSY BIT(31) #define HDA_DSP_REG_HIPCI_MSG_MASK 0x7FFFFFFF @@ -301,6 +315,7 @@ #define CNL_DSP_REG_HIPCTDD (CNL_DSP_IPC_BASE + 0x08) #define CNL_DSP_REG_HIPCIDR (CNL_DSP_IPC_BASE + 0x10) #define CNL_DSP_REG_HIPCIDA (CNL_DSP_IPC_BASE + 0x14) +#define CNL_DSP_REG_HIPCIDD (CNL_DSP_IPC_BASE + 0x18) #define CNL_DSP_REG_HIPCCTL (CNL_DSP_IPC_BASE + 0x28) /* HIPCI */ @@ -333,7 +348,7 @@ /* Number of DAIs */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) -#define SOF_SKL_NUM_DAIS 14 +#define SOF_SKL_NUM_DAIS 15 #else #define SOF_SKL_NUM_DAIS 8 #endif @@ -394,7 +409,8 @@ struct sof_intel_hda_dev { /* the maximum number of streams (playback + capture) supported */ u32 stream_max; - int irq; + /* PM related */ + bool l1_support_changed;/* during suspend, is L1SEN changed or not */ /* DMIC device */ struct platform_device *dmic_dev; @@ -418,7 +434,6 @@ struct sof_intel_hda_stream { struct snd_sof_dev *sdev; struct hdac_ext_stream hda_stream; struct sof_intel_stream stream; - int hw_params_upon_resume; /* set up hw_params upon resume */ int host_reserved; /* reserve host DMA channel */ }; @@ -453,9 +468,12 @@ int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev); void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev); -int hda_dsp_suspend(struct snd_sof_dev *sdev, int state); +int hda_dsp_set_power_state(struct snd_sof_dev *sdev, + enum sof_d0_substate d0_substate); + +int hda_dsp_suspend(struct snd_sof_dev *sdev); int hda_dsp_resume(struct snd_sof_dev *sdev); -int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev, int state); +int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev); int hda_dsp_runtime_resume(struct snd_sof_dev *sdev); int hda_dsp_runtime_idle(struct snd_sof_dev *sdev); int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev); @@ -494,11 +512,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params); int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, int cmd); -irqreturn_t hda_dsp_stream_interrupt(int irq, void *context); irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context); int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct hdac_stream *stream); +bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev); +bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev); struct hdac_ext_stream * hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction); @@ -520,8 +539,9 @@ int hda_ipc_pcm_params(struct snd_sof_dev *sdev, int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev); -int hda_dsp_ipc_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); -irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context); +int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev); +int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id); + irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context); int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir); @@ -549,32 +569,35 @@ void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev); /* * HDA bus operations. */ -void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev, - const struct hdac_ext_bus_ops *ext_ops); +void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) /* * HDA Codec operations. */ -int hda_codec_probe_bus(struct snd_sof_dev *sdev); +void hda_codec_probe_bus(struct snd_sof_dev *sdev, + bool hda_codec_use_common_hdmi); +void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev); +void hda_codec_jack_check(struct snd_sof_dev *sdev); #endif /* CONFIG_SND_SOC_SOF_HDA */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && \ + (IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \ + IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) -void hda_codec_i915_get(struct snd_sof_dev *sdev); -void hda_codec_i915_put(struct snd_sof_dev *sdev); +void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable); int hda_codec_i915_init(struct snd_sof_dev *sdev); int hda_codec_i915_exit(struct snd_sof_dev *sdev); #else -static inline void hda_codec_i915_get(struct snd_sof_dev *sdev) { } -static inline void hda_codec_i915_put(struct snd_sof_dev *sdev) { } +static inline void hda_codec_i915_display_power(struct snd_sof_dev *sdev, + bool enable) { } static inline int hda_codec_i915_init(struct snd_sof_dev *sdev) { return 0; } static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } -#endif /* CONFIG_SND_SOC_SOF_HDA && CONFIG_SND_SOC_HDAC_HDMI */ +#endif /* * Trace Control. @@ -591,11 +614,18 @@ extern struct snd_soc_dai_driver skl_dai[]; */ extern const struct snd_sof_dsp_ops sof_apl_ops; extern const struct snd_sof_dsp_ops sof_cnl_ops; -extern const struct snd_sof_dsp_ops sof_skl_ops; extern const struct sof_intel_dsp_desc apl_chip_info; extern const struct sof_intel_dsp_desc cnl_chip_info; extern const struct sof_intel_dsp_desc skl_chip_info; extern const struct sof_intel_dsp_desc icl_chip_info; +extern const struct sof_intel_dsp_desc tgl_chip_info; +extern const struct sof_intel_dsp_desc ehl_chip_info; +extern const struct sof_intel_dsp_desc jsl_chip_info; + +/* machine driver select */ +void hda_machine_select(struct snd_sof_dev *sdev); +void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, + struct device *dev); #endif diff --git a/sound/soc/sof/intel/intel-ipc.c b/sound/soc/sof/intel/intel-ipc.c index 4edd92151fd5..e935f70d611b 100644 --- a/sound/soc/sof/intel/intel-ipc.c +++ b/sound/soc/sof/intel/intel-ipc.c @@ -39,7 +39,7 @@ void intel_ipc_msg_data(struct snd_sof_dev *sdev, sof_mailbox_read(sdev, stream->posn_offset, p, sz); } } -EXPORT_SYMBOL(intel_ipc_msg_data); +EXPORT_SYMBOL_NS(intel_ipc_msg_data, SND_SOC_SOF_INTEL_HIFI_EP_IPC); int intel_ipc_pcm_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, @@ -60,7 +60,7 @@ int intel_ipc_pcm_params(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL(intel_ipc_pcm_params); +EXPORT_SYMBOL_NS(intel_ipc_pcm_params, SND_SOC_SOF_INTEL_HIFI_EP_IPC); int intel_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) @@ -75,7 +75,7 @@ int intel_pcm_open(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL(intel_pcm_open); +EXPORT_SYMBOL_NS(intel_pcm_open, SND_SOC_SOF_INTEL_HIFI_EP_IPC); int intel_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) @@ -87,6 +87,6 @@ int intel_pcm_close(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL(intel_pcm_close); +EXPORT_SYMBOL_NS(intel_pcm_close, SND_SOC_SOF_INTEL_HIFI_EP_IPC); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h index f7a3f62e45d4..daaf3364c177 100644 --- a/sound/soc/sof/intel/shim.h +++ b/sound/soc/sof/intel/shim.h @@ -12,7 +12,7 @@ #define __SOF_INTEL_SHIM_H /* - * SHIM registers for BYT, BSW, CHT, HSW, BDW + * SHIM registers for BYT, BSW, CHT, BDW */ #define SHIM_CSR (SHIM_OFFSET + 0x00) @@ -38,7 +38,7 @@ #define SHIM_PWMCTRL 0x1000 /* - * SST SHIM register bits for BYT, BSW, CHT HSW, BDW + * SST SHIM register bits for BYT, BSW, CHT, BDW * Register bit naming and functionaility can differ between devices. */ @@ -169,13 +169,11 @@ struct sof_intel_dsp_desc { extern const struct snd_sof_dsp_ops sof_tng_ops; extern const struct snd_sof_dsp_ops sof_byt_ops; extern const struct snd_sof_dsp_ops sof_cht_ops; -extern const struct snd_sof_dsp_ops sof_hsw_ops; extern const struct snd_sof_dsp_ops sof_bdw_ops; extern const struct sof_intel_dsp_desc byt_chip_info; extern const struct sof_intel_dsp_desc cht_chip_info; extern const struct sof_intel_dsp_desc bdw_chip_info; -extern const struct sof_intel_dsp_desc hsw_chip_info; extern const struct sof_intel_dsp_desc tng_chip_info; struct sof_intel_stream { diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 20dfca9c93b7..b63fc529b456 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -15,14 +15,9 @@ #include <linux/types.h> #include "sof-priv.h" +#include "sof-audio.h" #include "ops.h" -/* - * IPC message default size and timeout (ms). - * TODO: allow platforms to set size and timeout. - */ -#define IPC_TIMEOUT_MS 300 - static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id); static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); @@ -211,14 +206,12 @@ static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, /* wait for DSP IPC completion */ ret = wait_event_timeout(msg->waitq, msg->ipc_complete, - msecs_to_jiffies(IPC_TIMEOUT_MS)); + msecs_to_jiffies(sdev->ipc_timeout)); if (ret == 0) { dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n", hdr->cmd, hdr->size); - snd_sof_dsp_dbg_dump(ipc->sdev, SOF_DBG_REGS | SOF_DBG_MBOX); - snd_sof_ipc_dump(ipc->sdev); - snd_sof_trace_notify_for_error(ipc->sdev); + snd_sof_handle_fw_exception(ipc->sdev); ret = -ETIMEDOUT; } else { /* copy the data returned from DSP */ @@ -354,19 +347,12 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) break; case SOF_IPC_FW_READY: /* check for FW boot completion */ - if (!sdev->boot_complete) { + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { err = sof_ops(sdev)->fw_ready(sdev, cmd); - if (err < 0) { - /* - * this indicates a mismatch in ABI - * between the driver and fw - */ - dev_err(sdev->dev, "error: ABI mismatch %d\n", - err); - } else { - /* firmware boot completed OK */ - sdev->boot_complete = true; - } + if (err < 0) + sdev->fw_state = SOF_FW_BOOT_READY_FAILED; + else + sdev->fw_state = SOF_FW_BOOT_COMPLETE; /* wake up firmware loader */ wake_up(&sdev->boot_wait); @@ -420,12 +406,13 @@ static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id) static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) { + struct snd_soc_component *scomp = sdev->component; struct snd_sof_pcm_stream *stream; struct sof_ipc_stream_posn posn; struct snd_sof_pcm *spcm; int direction; - spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); if (!spcm) { dev_err(sdev->dev, "error: period elapsed for unknown stream, msg_id %d\n", @@ -449,12 +436,13 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) /* DSP notifies host of an XRUN within FW */ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) { + struct snd_soc_component *scomp = sdev->component; struct snd_sof_pcm_stream *stream; struct sof_ipc_stream_posn posn; struct snd_sof_pcm *spcm; int direction; - spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); if (!spcm) { dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", msg_id); @@ -496,10 +484,11 @@ static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) } /* get stream position IPC - use faster MMIO method if available on platform */ -int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, +int snd_sof_ipc_stream_posn(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, int direction, struct sof_ipc_stream_posn *posn) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc_stream stream; int err; @@ -578,8 +567,10 @@ static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, else err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata, sparams); - if (err < 0) + if (err < 0) { + kfree(partdata); return err; + } msg_bytes = sparams->msg_bytes; pl_size = sparams->pl_size; @@ -626,15 +617,15 @@ static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, /* * IPC get()/set() for kcontrols. */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc, - struct snd_sof_control *scontrol, +int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, u32 ipc_cmd, enum sof_ipc_ctrl_type ctrl_type, enum sof_ipc_ctrl_cmd ctrl_cmd, bool send) { + struct snd_soc_component *scomp = scontrol->scomp; struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc_fw_ready *ready = &sdev->fw_ready; struct sof_ipc_fw_version *v = &ready->version; struct sof_ipc_ctrl_data_params sparams; @@ -800,12 +791,6 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) struct snd_sof_ipc *ipc; struct snd_sof_ipc_msg *msg; - /* check if mandatory ops required for ipc are defined */ - if (!sof_ops(sdev)->fw_ready) { - dev_err(sdev->dev, "error: ipc mandatory ops not defined\n"); - return NULL; - } - ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); if (!ipc) return NULL; @@ -838,6 +823,9 @@ void snd_sof_ipc_free(struct snd_sof_dev *sdev) { struct snd_sof_ipc *ipc = sdev->ipc; + if (!ipc) + return; + /* disable sending of ipc's */ mutex_lock(&ipc->tx_mutex); ipc->disable_ipc_tx = true; diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 952a19091c58..fc4ab51bacf4 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -32,6 +32,42 @@ static int get_ext_windows(struct snd_sof_dev *sdev, return 0; } +static int get_cc_info(struct snd_sof_dev *sdev, + struct sof_ipc_ext_data_hdr *ext_hdr) +{ + int ret; + + struct sof_ipc_cc_version *cc = + container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); + + dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n", + cc->name, cc->major, cc->minor, cc->micro, cc->desc, + cc->optim); + + /* create read-only cc_version debugfs to store compiler version info */ + /* use local copy of the cc_version to prevent data corruption */ + if (sdev->first_boot) { + sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size, + GFP_KERNEL); + + if (!sdev->cc_version) + return -ENOMEM; + + memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size); + ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version, + cc->ext_hdr.hdr.size, + "cc_version", 0444); + + /* errors are only due to memory allocation, not debugfs */ + if (ret < 0) { + dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); + return ret; + } + } + + return 0; +} + /* parse the extended FW boot data structures from FW boot message */ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) { @@ -50,8 +86,7 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { /* read in ext structure */ - offset += sizeof(*ext_hdr); - snd_sof_dsp_block_read(sdev, bar, offset, + snd_sof_dsp_block_read(sdev, bar, offset + sizeof(*ext_hdr), (void *)((u8 *)ext_data + sizeof(*ext_hdr)), ext_hdr->hdr.size - sizeof(*ext_hdr)); @@ -61,11 +96,18 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) /* process structure data */ switch (ext_hdr->type) { case SOF_IPC_EXT_DMA_BUFFER: + ret = 0; break; case SOF_IPC_EXT_WINDOW: ret = get_ext_windows(sdev, ext_hdr); break; + case SOF_IPC_EXT_CC_INFO: + ret = get_cc_info(sdev, ext_hdr); + break; default: + dev_warn(sdev->dev, "warning: unknown ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + ret = 0; break; } @@ -87,12 +129,180 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) } EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); +/* + * IPC Firmware ready. + */ +static void sof_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + int window_offset; + int bar; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "error: have no window info\n"); + return; + } + + bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); + if (bar < 0) { + dev_err(sdev->dev, "error: have no bar mapping\n"); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); + if (window_offset < 0) { + dev_warn(sdev->dev, "warn: no offset for window %d\n", + elem->id); + continue; + } + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = window_offset + elem->offset; + inbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = window_offset + elem->offset; + outbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + window_offset + + elem->offset, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + window_offset + + elem->offset, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = window_offset + elem->offset; + stream_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + window_offset + + elem->offset, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = window_offset + elem->offset; + snd_sof_debugfs_io_item(sdev, + sdev->bar[bar] + + window_offset + + elem->offset, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "error: get illegal window info\n"); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "error: get illegal mailbox window\n"); + return; + } + + snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, + outbox_offset, outbox_size); + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); +} + +/* check for ABI compatibility and create memory windows on first boot */ +int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + int offset; + int bar; + int ret; + + /* mailbox must be on 4k boundary */ + offset = snd_sof_dsp_get_mailbox_offset(sdev); + if (offset < 0) { + dev_err(sdev->dev, "error: have no mailbox offset\n"); + return offset; + } + + bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); + if (bar < 0) { + dev_err(sdev->dev, "error: have no bar mapping\n"); + return -EINVAL; + } + + dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", + msg_id, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* copy data from the DSP FW ready offset */ + sof_block_read(sdev, bar, offset, fw_ready, sizeof(*fw_ready)); + + /* make sure ABI version is compatible */ + ret = snd_sof_ipc_valid(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + snd_sof_fw_parse_ext_data(sdev, bar, offset + + sizeof(struct sof_ipc_fw_ready)); + + sof_get_windows(sdev); + + return 0; +} +EXPORT_SYMBOL(sof_fw_ready); + /* generic module parser for mmaped DSPs */ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, struct snd_sof_mod_hdr *module) { struct snd_sof_blk_hdr *block; - int count; + int count, bar; u32 offset; size_t remaining; @@ -123,11 +333,19 @@ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, switch (block->type) { case SOF_FW_BLK_TYPE_RSRVD0: - case SOF_FW_BLK_TYPE_SRAM...SOF_FW_BLK_TYPE_RSRVD14: + case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: continue; /* not handled atm */ case SOF_FW_BLK_TYPE_IRAM: case SOF_FW_BLK_TYPE_DRAM: + case SOF_FW_BLK_TYPE_SRAM: offset = block->offset; + bar = snd_sof_dsp_get_bar_index(sdev, block->type); + if (bar < 0) { + dev_err(sdev->dev, + "error: no BAR mapping for block type 0x%x\n", + block->type); + return bar; + } break; default: dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", @@ -145,7 +363,7 @@ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, block->size); return -EINVAL; } - snd_sof_dsp_block_write(sdev, sdev->mmio_bar, offset, + snd_sof_dsp_block_write(sdev, bar, offset, block + 1, block->size); if (remaining < block->size) { @@ -269,6 +487,9 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: request firmware %s failed err: %d\n", fw_filename, ret); + } else { + dev_dbg(sdev->dev, "request_firmware %s successful\n", + fw_filename); } kfree(fw_filename); @@ -333,7 +554,6 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) int init_core_mask; init_waitqueue_head(&sdev->boot_wait); - sdev->boot_complete = false; /* create read-only fw_version debugfs to store boot version info */ if (sdev->first_boot) { @@ -365,19 +585,27 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) init_core_mask = ret; - /* now wait for the DSP to boot */ - ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete, + /* + * now wait for the DSP to boot. There are 3 possible outcomes: + * 1. Boot wait times out indicating FW boot failure. + * 2. FW boots successfully and fw_ready op succeeds. + * 3. FW boots but fw_ready op fails. + */ + ret = wait_event_timeout(sdev->boot_wait, + sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS, msecs_to_jiffies(sdev->boot_timeout)); if (ret == 0) { dev_err(sdev->dev, "error: firmware boot failure\n"); - /* after this point FW_READY msg should be ignored */ - sdev->boot_complete = true; snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX | SOF_DBG_TEXT | SOF_DBG_PCI); + sdev->fw_state = SOF_FW_BOOT_FAILED; return -EIO; } - dev_info(sdev->dev, "firmware boot complete\n"); + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) + dev_info(sdev->dev, "firmware boot complete\n"); + else + return -EIO; /* FW boots but fw_ready op failed */ /* perform post fw run operations */ ret = snd_sof_dsp_post_fw_run(sdev); diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c index 3d128e5a132c..2233146386cc 100644 --- a/sound/soc/sof/nocodec.c +++ b/sound/soc/sof/nocodec.c @@ -63,23 +63,11 @@ static int sof_nocodec_bes_setup(struct device *dev, } int sof_nocodec_setup(struct device *dev, - struct snd_sof_pdata *sof_pdata, - struct snd_soc_acpi_mach *mach, - const struct sof_dev_desc *desc, const struct snd_sof_dsp_ops *ops) { struct snd_soc_dai_link *links; int ret; - if (!mach) - return -EINVAL; - - sof_pdata->drv_name = "sof-nocodec"; - - mach->drv_name = "sof-nocodec"; - sof_pdata->fw_filename = desc->nocodec_fw_filename; - sof_pdata->tplg_filename = desc->nocodec_tplg_filename; - /* create dummy BE dai_links */ links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * ops->num_drv, GFP_KERNEL); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index b1c27615b805..e929a6e0058f 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -100,6 +100,43 @@ static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev) return 0; } +/* misc */ + +/** + * snd_sof_dsp_get_bar_index - Maps a section type with a BAR index + * + * @sdev: sof device + * @type: section type as described by snd_sof_fw_blk_type + * + * Returns the corresponding BAR index (a positive integer) or -EINVAL + * in case there is no mapping + */ +static inline int snd_sof_dsp_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + if (sof_ops(sdev)->get_bar_index) + return sof_ops(sdev)->get_bar_index(sdev, type); + + return sdev->mmio_bar; +} + +static inline int snd_sof_dsp_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->get_mailbox_offset) + return sof_ops(sdev)->get_mailbox_offset(sdev); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +static inline int snd_sof_dsp_get_window_offset(struct snd_sof_dev *sdev, + u32 id) +{ + if (sof_ops(sdev)->get_window_offset) + return sof_ops(sdev)->get_window_offset(sdev, id); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} /* power management */ static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev) { @@ -109,10 +146,10 @@ static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev) return 0; } -static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev, int state) +static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev) { if (sof_ops(sdev)->suspend) - return sof_ops(sdev)->suspend(sdev, state); + return sof_ops(sdev)->suspend(sdev); return 0; } @@ -125,11 +162,10 @@ static inline int snd_sof_dsp_runtime_resume(struct snd_sof_dev *sdev) return 0; } -static inline int snd_sof_dsp_runtime_suspend(struct snd_sof_dev *sdev, - int state) +static inline int snd_sof_dsp_runtime_suspend(struct snd_sof_dev *sdev) { if (sof_ops(sdev)->runtime_suspend) - return sof_ops(sdev)->runtime_suspend(sdev, state); + return sof_ops(sdev)->runtime_suspend(sdev); return 0; } @@ -157,6 +193,16 @@ static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq) return 0; } +static inline int snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev, + enum sof_d0_substate substate) +{ + if (sof_ops(sdev)->set_power_state) + return sof_ops(sdev)->set_power_state(sdev, substate); + + /* D0 substate is not supported */ + return -ENOTSUPP; +} + /* debug */ static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) { @@ -345,6 +391,40 @@ snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, return 0; } +/* machine driver */ +static inline int +snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_register) + return sof_ops(sdev)->machine_register(sdev, pdata); + + return 0; +} + +static inline void +snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_unregister) + sof_ops(sdev)->machine_unregister(sdev, pdata); +} + +static inline void +snd_sof_machine_select(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_select) + sof_ops(sdev)->machine_select(sdev); +} + +static inline void +snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach, + struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + if (sof_ops(sdev) && sof_ops(sdev)->set_mach_params) + sof_ops(sdev)->set_mach_params(mach, dev); +} + static inline const struct snd_sof_dsp_ops *sof_get_ops(const struct sof_dev_desc *d, const struct sof_ops_table mach_ops[], int asize) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 334e9d59b1ba..29435ba2d329 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -14,39 +14,38 @@ #include <sound/pcm_params.h> #include <sound/sof.h> #include "sof-priv.h" +#include "sof-audio.h" #include "ops.h" -#define DRV_NAME "sof-audio-component" - /* Create DMA buffer page table for DSP */ -static int create_page_table(struct snd_pcm_substream *substream, +static int create_page_table(struct snd_soc_component *component, + struct snd_pcm_substream *substream, unsigned char *dma_area, size_t size) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); int stream = substream->stream; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - return snd_sof_create_page_table(sdev, dmab, + return snd_sof_create_page_table(component->dev, dmab, spcm->stream[stream].page_table.area, size); } static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream *substream, const struct sof_ipc_pcm_params_reply *reply) { - struct snd_sof_dev *sdev = spcm->sdev; + struct snd_soc_component *scomp = spcm->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + /* validate offset */ int ret = snd_sof_ipc_pcm_params(sdev, substream, reply); if (ret < 0) - dev_err(sdev->dev, "error: got wrong reply for PCM %d\n", + dev_err(scomp->dev, "error: got wrong reply for PCM %d\n", spcm->pcm.pcm_id); return ret; @@ -71,13 +70,12 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_pcm *spcm; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) { - dev_err(sdev->dev, + dev_err(component->dev, "error: period elapsed for unknown stream!\n"); return; } @@ -94,14 +92,33 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); -/* this may get called several times by oss emulation */ -static int sof_pcm_hw_params(struct snd_pcm_substream *substream, +static int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, + struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm) +{ + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + if (!ret) + spcm->prepared[substream->stream] = false; + + return ret; +} + +static int sof_pcm_hw_params(struct snd_soc_component *component, + 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 snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; struct sof_ipc_pcm_params pcm; @@ -112,30 +129,28 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream, if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - dev_dbg(sdev->dev, "pcm: hw params stream %d dir %d\n", + /* + * Handle repeated calls to hw_params() without free_pcm() in + * between. At least ALSA OSS emulation depends on this. + */ + if (spcm->prepared[substream->stream]) { + ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); + if (ret < 0) + return ret; + } + + dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); memset(&pcm, 0, sizeof(pcm)); - /* allocate audio buffer pages */ - ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - if (ret < 0) { - dev_err(sdev->dev, "error: could not allocate %d bytes for PCM %d\n", - params_buffer_bytes(params), spcm->pcm.pcm_id); - return ret; - } - if (ret) { - /* - * ret == 1 means the buffer is changed - * create compressed page table for audio firmware - * ret == 0 means the buffer is not changed - * so no need to regenerate the page table - */ - ret = create_page_table(substream, runtime->dma_area, + /* create compressed page table for audio firmware */ + if (runtime->buffer_changed) { + ret = create_page_table(component, substream, runtime->dma_area, runtime->dma_bytes); if (ret < 0) return ret; @@ -189,17 +204,17 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream, params, &pcm.params); if (ret < 0) { - dev_err(sdev->dev, "error: platform hw params failed\n"); + dev_err(component->dev, "error: platform hw params failed\n"); return ret; } - dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag); + dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag); /* send IPC to the DSP */ ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) { - dev_err(sdev->dev, "error: hw params ipc failed for stream %d\n", + dev_err(component->dev, "error: hw params ipc failed for stream %d\n", pcm.params.stream_tag); return ret; } @@ -208,65 +223,54 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; + spcm->prepared[substream->stream] = true; + /* save pcm hw_params */ memcpy(&spcm->params[substream->stream], params, sizeof(*params)); - /* clear hw_params_upon_resume flag */ - spcm->hw_params_upon_resume[substream->stream] = 0; - return ret; } -static int sof_pcm_hw_free(struct snd_pcm_substream *substream) +static int sof_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; - struct sof_ipc_stream stream; - struct sof_ipc_reply reply; - int ret; + int ret, err = 0; /* nothing to do for BE */ if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, - substream->stream); - - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; - stream.comp_id = spcm->stream[substream->stream].comp_id; - - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + dev_dbg(component->dev, "pcm: free stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); - snd_pcm_lib_free_pages(substream); + if (spcm->prepared[substream->stream]) { + ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); + if (ret < 0) + err = ret; + } cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); - if (ret < 0) - return ret; - ret = snd_sof_pcm_platform_hw_free(sdev, substream); - if (ret < 0) - dev_err(sdev->dev, "error: platform hw free failed\n"); + if (ret < 0) { + dev_err(component->dev, "error: platform hw free failed\n"); + err = ret; + } - return ret; + return err; } -static int sof_pcm_prepare(struct snd_pcm_substream *substream) +static int sof_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; int ret; @@ -274,24 +278,22 @@ static int sof_pcm_prepare(struct snd_pcm_substream *substream) if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - /* - * check if hw_params needs to be set-up again. - * This is only needed when resuming from system sleep. - */ - if (!spcm->hw_params_upon_resume[substream->stream]) + if (spcm->prepared[substream->stream]) return 0; - dev_dbg(sdev->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id, - substream->stream); + dev_dbg(component->dev, "pcm: prepare stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); /* set hw_params */ - ret = sof_pcm_hw_params(substream, &spcm->params[substream->stream]); + ret = sof_pcm_hw_params(component, + substream, &spcm->params[substream->stream]); if (ret < 0) { - dev_err(sdev->dev, "error: set pcm hw_params after resume\n"); + dev_err(component->dev, + "error: set pcm hw_params after resume\n"); return ret; } @@ -302,26 +304,27 @@ static int sof_pcm_prepare(struct snd_pcm_substream *substream) * FE dai link trigger actions are always executed in non-atomic context because * they involve IPC's. */ -static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +static int sof_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; struct sof_ipc_stream stream; struct sof_ipc_reply reply; + bool reset_hw_params = false; + bool ipc_first = false; int ret; /* nothing to do for BE */ if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - dev_dbg(sdev->dev, "pcm: trigger stream %d dir %d cmd %d\n", + dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n", spcm->pcm.pcm_id, substream->stream, cmd); stream.hdr.size = sizeof(stream); @@ -331,60 +334,93 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH: stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; + ipc_first = true; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; break; case SNDRV_PCM_TRIGGER_RESUME: + if (spcm->stream[substream->stream].suspend_ignored) { + /* + * this case will be triggered when INFO_RESUME is + * supported, no need to resume streams that remained + * enabled in D0ix. + */ + spcm->stream[substream->stream].suspend_ignored = false; + return 0; + } + /* set up hw_params */ - ret = sof_pcm_prepare(substream); + ret = sof_pcm_prepare(component, substream); if (ret < 0) { - dev_err(sdev->dev, + dev_err(component->dev, "error: failed to set up hw_params upon resume\n"); return ret; } /* fallthrough */ case SNDRV_PCM_TRIGGER_START: + if (spcm->stream[substream->stream].suspend_ignored) { + /* + * This case will be triggered when INFO_RESUME is + * not supported, no need to re-start streams that + * remained enabled in D0ix. + */ + spcm->stream[substream->stream].suspend_ignored = false; + return 0; + } stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; break; case SNDRV_PCM_TRIGGER_SUSPEND: + if (sdev->s0_suspend && + spcm->stream[substream->stream].d0i3_compatible) { + /* + * trap the event, not sending trigger stop to + * prevent the FW pipelines from being stopped, + * and mark the flag to ignore the upcoming DAPM + * PM events. + */ + spcm->stream[substream->stream].suspend_ignored = true; + return 0; + } + /* fallthrough */ case SNDRV_PCM_TRIGGER_STOP: stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + ipc_first = true; + reset_hw_params = true; break; default: - dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd); + dev_err(component->dev, "error: unhandled trigger cmd %d\n", + cmd); return -EINVAL; } - snd_sof_pcm_platform_trigger(sdev, substream, cmd); + /* + * DMA and IPC sequence is different for start and stop. Need to send + * STOP IPC before stop DMA + */ + if (!ipc_first) + snd_sof_pcm_platform_trigger(sdev, substream, cmd); /* send IPC to the DSP */ ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, sizeof(stream), &reply, sizeof(reply)); - if (ret < 0 || cmd != SNDRV_PCM_TRIGGER_SUSPEND) - return ret; + /* need to STOP DMA even if STOP IPC failed */ + if (ipc_first) + snd_sof_pcm_platform_trigger(sdev, substream, cmd); - /* - * The hw_free op is usually called when the pcm stream is closed. - * Since the stream is not closed during suspend, the DSP needs to be - * notified explicitly to free pcm to prevent errors upon resume. - */ - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; - stream.comp_id = spcm->stream[substream->stream].comp_id; + /* free PCM if reset_hw_params is set and the STOP IPC is successful */ + if (!ret && reset_hw_params) + ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); - /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + return ret; } -static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; snd_pcm_uframes_t host, dai; @@ -397,7 +433,7 @@ static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) if (sof_ops(sdev)->pcm_pointer) return sof_ops(sdev)->pcm_pointer(sdev, substream); - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; @@ -407,19 +443,20 @@ static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) dai = bytes_to_frames(substream->runtime, spcm->stream[substream->stream].posn.dai_posn); - dev_dbg(sdev->dev, "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", + dev_dbg(component->dev, + "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", spcm->pcm.pcm_id, substream->stream, host, dai); return host; } -static int sof_pcm_open(struct snd_pcm_substream *substream) +static int sof_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct snd_sof_dsp_ops *ops = sof_ops(sdev); struct snd_sof_pcm *spcm; struct snd_soc_tplg_stream_caps *caps; int ret; @@ -428,12 +465,12 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - dev_dbg(sdev->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id, - substream->stream); + dev_dbg(component->dev, "pcm: open stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); INIT_WORK(&spcm->stream[substream->stream].period_elapsed_work, sof_pcm_period_elapsed_work); @@ -449,11 +486,8 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) le32_to_cpu(caps->period_size_min)); /* set runtime config */ - runtime->hw.info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + runtime->hw.info = ops->hw_info; /* platform-specific */ + runtime->hw.formats = le64_to_cpu(caps->formats); runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min); runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max); @@ -466,13 +500,13 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) */ runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max); - dev_dbg(sdev->dev, "period min %zd max %zd bytes\n", + dev_dbg(component->dev, "period min %zd max %zd bytes\n", runtime->hw.period_bytes_min, runtime->hw.period_bytes_max); - dev_dbg(sdev->dev, "period count %d max %d\n", + dev_dbg(component->dev, "period count %d max %d\n", runtime->hw.periods_min, runtime->hw.periods_max); - dev_dbg(sdev->dev, "buffer max %zd bytes\n", + dev_dbg(component->dev, "buffer max %zd bytes\n", runtime->hw.buffer_bytes_max); /* set wait time - TODO: come from topology */ @@ -481,19 +515,19 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) spcm->stream[substream->stream].posn.host_posn = 0; spcm->stream[substream->stream].posn.dai_posn = 0; spcm->stream[substream->stream].substream = substream; + spcm->prepared[substream->stream] = false; ret = snd_sof_pcm_platform_open(sdev, substream); if (ret < 0) - dev_err(sdev->dev, "error: pcm open failed %d\n", ret); + dev_err(component->dev, "error: pcm open failed %d\n", ret); return ret; } -static int sof_pcm_close(struct snd_pcm_substream *substream) +static int sof_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; int err; @@ -502,16 +536,16 @@ static int sof_pcm_close(struct snd_pcm_substream *substream) if (rtd->dai_link->no_pcm) return 0; - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) return -EINVAL; - dev_dbg(sdev->dev, "pcm: close stream %d dir %d\n", spcm->pcm.pcm_id, - substream->stream); + dev_dbg(component->dev, "pcm: close stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); err = snd_sof_pcm_platform_close(sdev, substream); if (err < 0) { - dev_err(sdev->dev, "error: pcm close failed %d\n", + dev_err(component->dev, "error: pcm close failed %d\n", err); /* * keep going, no point in preventing the close @@ -522,27 +556,14 @@ static int sof_pcm_close(struct snd_pcm_substream *substream) return 0; } -static struct snd_pcm_ops sof_pcm_ops = { - .open = sof_pcm_open, - .close = sof_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = sof_pcm_hw_params, - .prepare = sof_pcm_prepare, - .hw_free = sof_pcm_hw_free, - .trigger = sof_pcm_trigger, - .pointer = sof_pcm_pointer, - .page = snd_pcm_sgbuf_ops_page, -}; - /* * Pre-allocate playback/capture audio buffer pages. * no need to explicitly release memory preallocated by sof_pcm_new in pcm_free * snd_pcm_lib_preallocate_free_for_all() is called by the core. */ -static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) +static int sof_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; struct snd_pcm *pcm = rtd->pcm; @@ -550,14 +571,14 @@ static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) int stream = SNDRV_PCM_STREAM_PLAYBACK; /* find SOF PCM for this RTD */ - spcm = snd_sof_find_spcm_dai(sdev, rtd); + spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) { - dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + dev_warn(component->dev, "warn: can't find PCM with DAI ID %d\n", rtd->dai_link->id); return 0; } - dev_dbg(sdev->dev, "creating new PCM %s\n", spcm->pcm.pcm_name); + dev_dbg(component->dev, "creating new PCM %s\n", spcm->pcm.pcm_name); /* do we need to pre-allocate playback audio buffer pages */ if (!spcm->pcm.playback) @@ -566,13 +587,19 @@ static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) caps = &spcm->pcm.caps[stream]; /* pre-allocate playback audio buffer pages */ - dev_dbg(sdev->dev, "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n", + dev_dbg(component->dev, + "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n", caps->name, caps->buffer_size_min, caps->buffer_size_max); - snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, - SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - le32_to_cpu(caps->buffer_size_min), - le32_to_cpu(caps->buffer_size_max)); + if (!pcm->streams[stream].substream) { + dev_err(component->dev, "error: NULL playback substream!\n"); + return -EINVAL; + } + + snd_pcm_set_managed_buffer(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); capture: stream = SNDRV_PCM_STREAM_CAPTURE; @@ -583,13 +610,19 @@ capture: caps = &spcm->pcm.caps[stream]; /* pre-allocate capture audio buffer pages */ - dev_dbg(sdev->dev, "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n", + dev_dbg(component->dev, + "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n", caps->name, caps->buffer_size_min, caps->buffer_size_max); - snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, - SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - le32_to_cpu(caps->buffer_size_min), - le32_to_cpu(caps->buffer_size_max)); + if (!pcm->streams[stream].substream) { + dev_err(component->dev, "error: NULL capture substream!\n"); + return -EINVAL; + } + + snd_pcm_set_managed_buffer(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); return 0; } @@ -604,14 +637,14 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_soc_component *component = - snd_soc_rtdcom_lookup(rtd, DRV_NAME); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_dai *dai = - snd_sof_find_dai(sdev, (char *)rtd->dai_link->name); + snd_sof_find_dai(component, (char *)rtd->dai_link->name); /* no topology exists for this BE, try a common configuration */ if (!dai) { - dev_warn(sdev->dev, "warning: no topology found for BE DAI %s config\n", + dev_warn(component->dev, + "warning: no topology found for BE DAI %s config\n", rtd->dai_link->name); /* set 48k, stereo, 16bits by default */ @@ -641,7 +674,7 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); break; default: - dev_err(sdev->dev, "error: No available DAI format!\n"); + dev_err(component->dev, "error: No available DAI format!\n"); return -EINVAL; } @@ -653,9 +686,9 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, channels->min = dai->dai_config->ssp.tdm_slots; channels->max = dai->dai_config->ssp.tdm_slots; - dev_dbg(sdev->dev, + dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max); - dev_dbg(sdev->dev, + dev_dbg(component->dev, "channels_min: %d channels_max: %d\n", channels->min, channels->max); @@ -663,7 +696,7 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, case SOF_DAI_INTEL_DMIC: /* DMIC only supports 16 or 32 bit formats */ if (dai->comp_dai.config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { - dev_err(sdev->dev, + dev_err(component->dev, "error: invalid fmt %d for DAI type %d\n", dai->comp_dai.config.frame_fmt, dai->dai_config->type); @@ -672,8 +705,27 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, case SOF_DAI_INTEL_HDA: /* do nothing for HDA dai_link */ break; + case SOF_DAI_INTEL_ALH: + /* do nothing for ALH dai_link */ + break; + case SOF_DAI_IMX_ESAI: + channels->min = dai->dai_config->esai.tdm_slots; + channels->max = dai->dai_config->esai.tdm_slots; + + dev_dbg(component->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_IMX_SAI: + channels->min = dai->dai_config->sai.tdm_slots; + channels->max = dai->dai_config->sai.tdm_slots; + + dev_dbg(component->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; default: - dev_err(sdev->dev, "error: invalid DAI type %d\n", + dev_err(component->dev, "error: invalid DAI type %d\n", dai->dai_config->type); break; } @@ -698,21 +750,13 @@ static int sof_pcm_probe(struct snd_soc_component *component) if (!tplg_filename) return -ENOMEM; - ret = snd_sof_load_topology(sdev, tplg_filename); + ret = snd_sof_load_topology(component, tplg_filename); if (ret < 0) { - dev_err(sdev->dev, "error: failed to load DSP topology %d\n", + dev_err(component->dev, "error: failed to load DSP topology %d\n", ret); return ret; } - /* - * Some platforms in SOF, ex: BYT, may not have their platform PM - * callbacks set. Increment the usage count so as to - * prevent the device from entering runtime suspend. - */ - if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) - pm_runtime_get_noresume(sdev->dev); - return ret; } @@ -733,11 +777,18 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->name = "sof-audio-component"; pd->probe = sof_pcm_probe; pd->remove = sof_pcm_remove; - pd->ops = &sof_pcm_ops; + pd->open = sof_pcm_open; + pd->close = sof_pcm_close; + pd->hw_params = sof_pcm_hw_params; + pd->prepare = sof_pcm_prepare; + pd->hw_free = sof_pcm_hw_free; + pd->trigger = sof_pcm_trigger; + pd->pointer = sof_pcm_pointer; + #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) pd->compr_ops = &sof_compressed_ops; #endif - pd->pcm_new = sof_pcm_new; + pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; pd->be_pcm_base = SOF_BE_PCM_BASE; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 278abfd10490..a0cde053b61a 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -10,194 +10,9 @@ #include "ops.h" #include "sof-priv.h" +#include "sof-audio.h" -static int sof_restore_kcontrols(struct snd_sof_dev *sdev) -{ - struct snd_sof_control *scontrol; - int ipc_cmd, ctrl_type; - int ret = 0; - - /* restore kcontrol values */ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - /* reset readback offset for scontrol after resuming */ - scontrol->readback_offset = 0; - - /* notify DSP of kcontrol values */ - switch (scontrol->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_ENUM: - case SOF_CTRL_CMD_SWITCH: - ipc_cmd = SOF_IPC_COMP_SET_VALUE; - ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; - ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, - ipc_cmd, ctrl_type, - scontrol->cmd, - true); - break; - case SOF_CTRL_CMD_BINARY: - ipc_cmd = SOF_IPC_COMP_SET_DATA; - ctrl_type = SOF_CTRL_TYPE_DATA_SET; - ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, - ipc_cmd, ctrl_type, - scontrol->cmd, - true); - break; - - default: - break; - } - - if (ret < 0) { - dev_err(sdev->dev, - "error: failed kcontrol value set for widget: %d\n", - scontrol->comp_id); - - return ret; - } - } - - return 0; -} - -static int sof_restore_pipelines(struct snd_sof_dev *sdev) -{ - struct snd_sof_widget *swidget; - struct snd_sof_route *sroute; - struct sof_ipc_pipe_new *pipeline; - struct snd_sof_dai *dai; - struct sof_ipc_comp_dai *comp_dai; - struct sof_ipc_cmd_hdr *hdr; - int ret; - - /* restore pipeline components */ - list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { - struct sof_ipc_comp_reply r; - - /* skip if there is no private data */ - if (!swidget->private) - continue; - - switch (swidget->id) { - case snd_soc_dapm_dai_in: - case snd_soc_dapm_dai_out: - dai = swidget->private; - comp_dai = &dai->comp_dai; - ret = sof_ipc_tx_message(sdev->ipc, - comp_dai->comp.hdr.cmd, - comp_dai, sizeof(*comp_dai), - &r, sizeof(r)); - break; - case snd_soc_dapm_scheduler: - - /* - * During suspend, all DSP cores are powered off. - * Therefore upon resume, create the pipeline comp - * and power up the core that the pipeline is - * scheduled on. - */ - pipeline = swidget->private; - ret = sof_load_pipeline_ipc(sdev, pipeline, &r); - break; - default: - hdr = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, - swidget->private, hdr->size, - &r, sizeof(r)); - break; - } - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to load widget type %d with ID: %d\n", - swidget->widget->id, swidget->comp_id); - - return ret; - } - } - - /* restore pipeline connections */ - list_for_each_entry_reverse(sroute, &sdev->route_list, list) { - struct sof_ipc_pipe_comp_connect *connect; - struct sof_ipc_reply reply; - - /* skip if there's no private data */ - if (!sroute->private) - continue; - - connect = sroute->private; - - /* send ipc */ - ret = sof_ipc_tx_message(sdev->ipc, - connect->hdr.cmd, - connect, sizeof(*connect), - &reply, sizeof(reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to load route sink %s control %s source %s\n", - sroute->route->sink, - sroute->route->control ? sroute->route->control - : "none", - sroute->route->source); - - return ret; - } - } - - /* restore dai links */ - list_for_each_entry_reverse(dai, &sdev->dai_list, list) { - struct sof_ipc_reply reply; - struct sof_ipc_dai_config *config = dai->dai_config; - - if (!config) { - dev_err(sdev->dev, "error: no config for DAI %s\n", - dai->name); - continue; - } - - /* - * The link DMA channel would be invalidated for running - * streams but not for streams that were in the PAUSED - * state during suspend. So invalidate it here before setting - * the dai config in the DSP. - */ - if (config->type == SOF_DAI_INTEL_HDA) - config->hda.link_dma_ch = DMA_CHAN_INVALID; - - ret = sof_ipc_tx_message(sdev->ipc, - config->hdr.cmd, config, - config->hdr.size, - &reply, sizeof(reply)); - - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to set dai config for %s\n", - dai->name); - - return ret; - } - } - - /* complete pipeline */ - list_for_each_entry(swidget, &sdev->widget_list, list) { - switch (swidget->id) { - case snd_soc_dapm_scheduler: - swidget->complete = - snd_sof_complete_pipeline(sdev, swidget); - break; - default: - break; - } - } - - /* restore pipeline kcontrols */ - ret = sof_restore_kcontrols(sdev); - if (ret < 0) - dev_err(sdev->dev, - "error: restoring kcontrols after resume\n"); - - return ret; -} - -static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) +static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd) { struct sof_ipc_pm_ctx pm_ctx; struct sof_ipc_reply reply; @@ -213,34 +28,6 @@ static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) sizeof(pm_ctx), &reply, sizeof(reply)); } -static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev) -{ - struct snd_pcm_substream *substream; - struct snd_sof_pcm *spcm; - snd_pcm_state_t state; - int dir; - - /* - * SOF requires hw_params to be set-up internally upon resume. - * So, set the flag to indicate this for those streams that - * have been suspended. - */ - list_for_each_entry(spcm, &sdev->pcm_list, list) { - for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { - substream = spcm->stream[dir].substream; - if (!substream || !substream->runtime) - continue; - - state = substream->runtime->status->state; - if (state == SNDRV_PCM_STATE_SUSPENDED) - spcm->hw_params_upon_resume[dir] = 1; - } - } - - /* set internal flag for BE */ - return snd_sof_dsp_hw_params_upon_resume(sdev); -} - #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) static void sof_cache_debugfs(struct snd_sof_dev *sdev) { @@ -269,6 +56,10 @@ static int sof_resume(struct device *dev, bool runtime_resume) if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) return 0; + /* DSP was never successfully started, nothing to resume */ + if (sdev->first_boot) + return 0; + /* * if the runtime_resume flag is set, call the runtime_resume routine * or else call the system resume routine @@ -283,6 +74,8 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } + sdev->fw_state = SOF_FW_BOOT_PREPARE; + /* load the firmware */ ret = snd_sof_load_firmware(sdev); if (ret < 0) { @@ -292,7 +85,12 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } - /* boot the firmware */ + sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + + /* + * Boot the firmware. The FW boot status will be modified + * in snd_sof_run_firmware() depending on the outcome. + */ ret = snd_sof_run_firmware(sdev); if (ret < 0) { dev_err(sdev->dev, @@ -311,7 +109,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) } /* restore pipelines */ - ret = sof_restore_pipelines(sdev); + ret = sof_restore_pipelines(sdev->dev); if (ret < 0) { dev_err(sdev->dev, "error: failed to restore pipeline after resume %d\n", @@ -320,12 +118,15 @@ static int sof_resume(struct device *dev, bool runtime_resume) } /* notify DSP of system resume */ - ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); + ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); if (ret < 0) dev_err(sdev->dev, "error: ctx_restore ipc error during resume %d\n", ret); + /* initialize default D0 sub-state */ + sdev->d0_substate = SOF_DSP_D0I0; + return ret; } @@ -338,12 +139,15 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) if (!sof_ops(sdev)->suspend) return 0; + if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) + goto power_down; + /* release trace */ snd_sof_release_trace(sdev); /* set restore_stream for all streams during system suspend */ if (!runtime_suspend) { - ret = sof_set_hw_params_upon_resume(sdev); + ret = sof_set_hw_params_upon_resume(sdev->dev); if (ret < 0) { dev_err(sdev->dev, "error: setting hw_params flag during suspend %d\n", @@ -358,7 +162,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) sof_cache_debugfs(sdev); #endif /* notify DSP of upcoming power down */ - ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); if (ret == -EBUSY || ret == -EAGAIN) { /* * runtime PM has logic to handle -EBUSY/-EAGAIN so @@ -375,16 +179,25 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) ret); } +power_down: + + /* return if the DSP was not probed successfully */ + if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) + return 0; + /* power down all DSP cores */ if (runtime_suspend) - ret = snd_sof_dsp_runtime_suspend(sdev, 0); + ret = snd_sof_dsp_runtime_suspend(sdev); else - ret = snd_sof_dsp_suspend(sdev, 0); + ret = snd_sof_dsp_suspend(sdev); if (ret < 0) dev_err(sdev->dev, "error: failed to power down DSP during suspend %d\n", ret); + /* reset FW state */ + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + return ret; } @@ -408,14 +221,135 @@ int snd_sof_runtime_resume(struct device *dev) } EXPORT_SYMBOL(snd_sof_runtime_resume); +int snd_sof_set_d0_substate(struct snd_sof_dev *sdev, + enum sof_d0_substate d0_substate) +{ + int ret; + + if (sdev->d0_substate == d0_substate) + return 0; + + /* do platform specific set_state */ + ret = snd_sof_dsp_set_power_state(sdev, d0_substate); + if (ret < 0) + return ret; + + /* update dsp D0 sub-state */ + sdev->d0_substate = d0_substate; + + return 0; +} +EXPORT_SYMBOL(snd_sof_set_d0_substate); + +/* + * Audio DSP states may transform as below:- + * + * D0I3 compatible stream + * Runtime +---------------------+ opened only, timeout + * suspend | +--------------------+ + * +------------+ D0(active) | | + * | | <---------------+ | + * | +--------> | | | + * | |Runtime +--^--+---------^--+--+ The last | | + * | |resume | | | | opened D0I3 | | + * | | | | | | compatible | | + * | | resume| | | | stream closed | | + * | | from | | D3 | | | | + * | | D3 | |suspend | | d0i3 | | + * | | | | | |suspend | | + * | | | | | | | | + * | | | | | | | | + * +-v---+-----------+--v-------+ | | +------+----v----+ + * | | | +-----------> | + * | D3 (suspended) | | | D0I3 +-----+ + * | | +--------------+ | | + * | | resume from | | | + * +-------------------^--------+ d0i3 suspend +----------------+ | + * | | + * | D3 suspend | + * +------------------------------------------------+ + * + * d0i3_suspend = s0_suspend && D0I3 stream opened, + * D3 suspend = !d0i3_suspend, + */ + int snd_sof_resume(struct device *dev) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + int ret; + + if (snd_sof_dsp_d0i3_on_suspend(sdev)) { + /* resume from D0I3 */ + dev_dbg(sdev->dev, "DSP will exit from D0i3...\n"); + ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I0); + if (ret == -ENOTSUPP) { + /* fallback to resume from D3 */ + dev_dbg(sdev->dev, "D0i3 not supported, fall back to resume from D3...\n"); + goto d3_resume; + } else if (ret < 0) { + dev_err(sdev->dev, "error: failed to exit from D0I3 %d\n", + ret); + return ret; + } + + /* platform-specific resume from D0i3 */ + return snd_sof_dsp_resume(sdev); + } + +d3_resume: + /* resume from D3 */ return sof_resume(dev, false); } EXPORT_SYMBOL(snd_sof_resume); int snd_sof_suspend(struct device *dev) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + int ret; + + if (snd_sof_dsp_d0i3_on_suspend(sdev)) { + /* suspend to D0i3 */ + dev_dbg(sdev->dev, "DSP is trying to enter D0i3...\n"); + ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I3); + if (ret == -ENOTSUPP) { + /* fallback to D3 suspend */ + dev_dbg(sdev->dev, "D0i3 not supported, fall back to D3...\n"); + goto d3_suspend; + } else if (ret < 0) { + dev_err(sdev->dev, "error: failed to enter D0I3, %d\n", + ret); + return ret; + } + + /* platform-specific suspend to D0i3 */ + return snd_sof_dsp_suspend(sdev); + } + +d3_suspend: + /* suspend to D3 */ return sof_suspend(dev, false); } EXPORT_SYMBOL(snd_sof_suspend); + +int snd_sof_prepare(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + +#if defined(CONFIG_ACPI) + sdev->s0_suspend = acpi_target_system_state() == ACPI_STATE_S0; +#else + /* will suspend to S3 by default */ + sdev->s0_suspend = false; +#endif + + return 0; +} +EXPORT_SYMBOL(snd_sof_prepare); + +void snd_sof_complete(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + sdev->s0_suspend = false; +} +EXPORT_SYMBOL(snd_sof_complete); diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c index ea7b8b895412..1278aa95effa 100644 --- a/sound/soc/sof/sof-acpi-dev.c +++ b/sound/soc/sof/sof-acpi-dev.c @@ -29,22 +29,11 @@ static char *tplg_path; module_param(tplg_path, charp, 0444); MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL) -static const struct sof_dev_desc sof_acpi_haswell_desc = { - .machines = snd_soc_acpi_intel_haswell_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = 1, - .resindex_imr_base = -1, - .irqindex_host_ipc = 0, - .chip_info = &hsw_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-hsw.ri", - .nocodec_tplg_filename = "sof-hsw-nocodec.tplg", - .ops = &sof_hsw_ops, - .arch_ops = &sof_xtensa_arch_ops -}; -#endif +static int sof_acpi_debug; +module_param_named(sof_acpi_debug, sof_acpi_debug, int, 0444); +MODULE_PARM_DESC(sof_acpi_debug, "SOF ACPI debug options (0x0 all off)"); + +#define SOF_ACPI_DISABLE_PM_RUNTIME BIT(0) #if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) static const struct sof_dev_desc sof_acpi_broadwell_desc = { @@ -56,10 +45,9 @@ static const struct sof_dev_desc sof_acpi_broadwell_desc = { .chip_info = &bdw_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-bdw.ri", + .default_fw_filename = "sof-bdw.ri", .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", .ops = &sof_bdw_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif @@ -75,10 +63,9 @@ static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { .chip_info = &byt_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-byt.ri", + .default_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, - .arch_ops = &sof_xtensa_arch_ops }; static const struct sof_dev_desc sof_acpi_baytrail_desc = { @@ -90,10 +77,9 @@ static const struct sof_dev_desc sof_acpi_baytrail_desc = { .chip_info = &byt_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-byt.ri", + .default_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, - .arch_ops = &sof_xtensa_arch_ops }; static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { @@ -105,10 +91,9 @@ static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { .chip_info = &cht_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cht.ri", + .default_fw_filename = "sof-cht.ri", .nocodec_tplg_filename = "sof-cht-nocodec.tplg", .ops = &sof_cht_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif @@ -121,6 +106,9 @@ static const struct dev_pm_ops sof_acpi_pm = { static void sof_acpi_probe_complete(struct device *dev) { + if (sof_acpi_debug & SOF_ACPI_DISABLE_PM_RUNTIME) + return; + /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); @@ -131,7 +119,6 @@ static int sof_acpi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct sof_dev_desc *desc; - struct snd_soc_acpi_mach *mach; struct snd_sof_pdata *sof_pdata; const struct snd_sof_dsp_ops *ops; int ret; @@ -158,35 +145,9 @@ static int sof_acpi_probe(struct platform_device *pdev) return -ENODEV; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) - /* force nocodec mode */ - dev_warn(dev, "Force to use nocodec mode\n"); - mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); - if (!mach) - return -ENOMEM; - ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); - if (ret < 0) - return ret; -#else - /* find machine */ - mach = snd_soc_acpi_find_machine(desc->machines); - if (!mach) { - dev_warn(dev, "warning: No matching ASoC machine driver found\n"); - } else { - sof_pdata->fw_filename = mach->sof_fw_filename; - sof_pdata->tplg_filename = mach->sof_tplg_filename; - } -#endif - - if (mach) { - mach->mach_params.platform = dev_name(dev); - mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; - } - - sof_pdata->machine = mach; sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->platform = dev_name(dev); + sof_pdata->fw_filename = desc->default_fw_filename; /* alternate fw and tplg filenames ? */ if (fw_path) @@ -221,7 +182,8 @@ static int sof_acpi_probe(struct platform_device *pdev) static int sof_acpi_remove(struct platform_device *pdev) { - pm_runtime_disable(&pdev->dev); + if (!(sof_acpi_debug & SOF_ACPI_DISABLE_PM_RUNTIME)) + pm_runtime_disable(&pdev->dev); /* call sof helper for DSP hardware remove */ snd_sof_device_remove(&pdev->dev); @@ -230,9 +192,6 @@ static int sof_acpi_remove(struct platform_device *pdev) } static const struct acpi_device_id sof_acpi_match[] = { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL) - { "INT33C8", (unsigned long)&sof_acpi_haswell_desc }, -#endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) { "INT3438", (unsigned long)&sof_acpi_broadwell_desc }, #endif @@ -257,3 +216,5 @@ static struct platform_driver snd_sof_acpi_driver = { module_platform_driver(snd_sof_acpi_driver); MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_BAYTRAIL); +MODULE_IMPORT_NS(SND_SOC_SOF_BROADWELL); diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c new file mode 100644 index 000000000000..0d8f65b9ae25 --- /dev/null +++ b/sound/soc/sof/sof-audio.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2019 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// + +#include "sof-audio.h" +#include "ops.h" + +bool snd_sof_dsp_d0i3_on_suspend(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored || + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored) + return true; + } + + return false; +} + +int sof_set_hw_params_upon_resume(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_pcm_substream *substream; + struct snd_sof_pcm *spcm; + snd_pcm_state_t state; + int dir; + + /* + * SOF requires hw_params to be set-up internally upon resume. + * So, set the flag to indicate this for those streams that + * have been suspended. + */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { + substream = spcm->stream[dir].substream; + if (!substream || !substream->runtime) + continue; + + state = substream->runtime->status->state; + if (state == SNDRV_PCM_STATE_SUSPENDED) + spcm->prepared[dir] = false; + } + } + + /* set internal flag for BE */ + return snd_sof_dsp_hw_params_upon_resume(sdev); +} + +static int sof_restore_kcontrols(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_control *scontrol; + int ipc_cmd, ctrl_type; + int ret = 0; + + /* restore kcontrol values */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + /* reset readback offset for scontrol after resuming */ + scontrol->readback_offset = 0; + + /* notify DSP of kcontrol values */ + switch (scontrol->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_ENUM: + case SOF_CTRL_CMD_SWITCH: + ipc_cmd = SOF_IPC_COMP_SET_VALUE; + ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + ret = snd_sof_ipc_set_get_comp_data(scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd, + true); + break; + case SOF_CTRL_CMD_BINARY: + ipc_cmd = SOF_IPC_COMP_SET_DATA; + ctrl_type = SOF_CTRL_TYPE_DATA_SET; + ret = snd_sof_ipc_set_get_comp_data(scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd, + true); + break; + + default: + break; + } + + if (ret < 0) { + dev_err(dev, + "error: failed kcontrol value set for widget: %d\n", + scontrol->comp_id); + + return ret; + } + } + + return 0; +} + +int sof_restore_pipelines(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_widget *swidget; + struct snd_sof_route *sroute; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_dai *dai; + struct sof_ipc_comp_dai *comp_dai; + struct sof_ipc_cmd_hdr *hdr; + int ret; + + /* restore pipeline components */ + list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + struct sof_ipc_comp_reply r; + + /* skip if there is no private data */ + if (!swidget->private) + continue; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = swidget->private; + comp_dai = &dai->comp_dai; + ret = sof_ipc_tx_message(sdev->ipc, + comp_dai->comp.hdr.cmd, + comp_dai, sizeof(*comp_dai), + &r, sizeof(r)); + break; + case snd_soc_dapm_scheduler: + + /* + * During suspend, all DSP cores are powered off. + * Therefore upon resume, create the pipeline comp + * and power up the core that the pipeline is + * scheduled on. + */ + pipeline = swidget->private; + ret = sof_load_pipeline_ipc(dev, pipeline, &r); + break; + default: + hdr = swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, + swidget->private, hdr->size, + &r, sizeof(r)); + break; + } + if (ret < 0) { + dev_err(dev, + "error: failed to load widget type %d with ID: %d\n", + swidget->widget->id, swidget->comp_id); + + return ret; + } + } + + /* restore pipeline connections */ + list_for_each_entry_reverse(sroute, &sdev->route_list, list) { + struct sof_ipc_pipe_comp_connect *connect; + struct sof_ipc_reply reply; + + /* skip if there's no private data */ + if (!sroute->private) + continue; + + connect = sroute->private; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(dev, + "error: failed to load route sink %s control %s source %s\n", + sroute->route->sink, + sroute->route->control ? sroute->route->control + : "none", + sroute->route->source); + + return ret; + } + } + + /* restore dai links */ + list_for_each_entry_reverse(dai, &sdev->dai_list, list) { + struct sof_ipc_reply reply; + struct sof_ipc_dai_config *config = dai->dai_config; + + if (!config) { + dev_err(dev, "error: no config for DAI %s\n", + dai->name); + continue; + } + + /* + * The link DMA channel would be invalidated for running + * streams but not for streams that were in the PAUSED + * state during suspend. So invalidate it here before setting + * the dai config in the DSP. + */ + if (config->type == SOF_DAI_INTEL_HDA) + config->hda.link_dma_ch = DMA_CHAN_INVALID; + + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, + config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(dev, + "error: failed to set dai config for %s\n", + dai->name); + + return ret; + } + } + + /* complete pipeline */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(dev, swidget); + break; + default: + break; + } + } + + /* restore pipeline kcontrols */ + ret = sof_restore_kcontrols(dev); + if (ret < 0) + dev_err(dev, + "error: restoring kcontrols after resume\n"); + + return ret; +} + +/* + * Generic object lookup APIs. + */ + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, + const char *name) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + /* match with PCM dai name */ + if (strcmp(spcm->pcm.dai_name, name) == 0) + return spcm; + + /* match with playback caps name if set */ + if (*spcm->pcm.caps[0].name && + !strcmp(spcm->pcm.caps[0].name, name)) + return spcm; + + /* match with capture caps name if set */ + if (*spcm->pcm.caps[1].name && + !strcmp(spcm->pcm.caps[1].name, name)) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, + unsigned int comp_id, + int *direction) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pcm *spcm; + int dir; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + dir = SNDRV_PCM_STREAM_PLAYBACK; + if (spcm->stream[dir].comp_id == comp_id) { + *direction = dir; + return spcm; + } + + dir = SNDRV_PCM_STREAM_CAPTURE; + if (spcm->stream[dir].comp_id == comp_id) { + *direction = dir; + return spcm; + } + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, + unsigned int pcm_id) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) + return spcm; + } + + return NULL; +} + +struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, + const char *name) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (strcmp(name, swidget->widget->name) == 0) + return swidget; + } + + return NULL; +} + +/* find widget by stream name and direction */ +struct snd_sof_widget * +snd_sof_find_swidget_sname(struct snd_soc_component *scomp, + const char *pcm_name, int dir) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + enum snd_soc_dapm_type type; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + type = snd_soc_dapm_aif_in; + else + type = snd_soc_dapm_aif_out; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (!strcmp(pcm_name, swidget->widget->sname) && + swidget->id == type) + return swidget; + } + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, + const char *name) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_dai *dai; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (dai->name && (strcmp(name, dai->name) == 0)) + return dai; + } + + return NULL; +} + +/* + * SOF Driver enumeration. + */ +int sof_machine_check(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach *mach; + int ret; + + /* force nocodec mode */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) + dev_warn(sdev->dev, "Force to use nocodec mode\n"); + goto nocodec; +#endif + + /* find machine */ + snd_sof_machine_select(sdev); + if (sof_pdata->machine) { + snd_sof_set_mach_params(sof_pdata->machine, sdev->dev); + return 0; + } + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) + dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); + return -ENODEV; +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) +nocodec: +#endif + /* select nocodec mode */ + dev_warn(sdev->dev, "Using nocodec machine driver\n"); + mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); + if (!mach) + return -ENOMEM; + + mach->drv_name = "sof-nocodec"; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + + ret = sof_nocodec_setup(sdev->dev, desc->ops); + if (ret < 0) + return ret; + + sof_pdata->machine = mach; + snd_sof_set_mach_params(sof_pdata->machine, sdev->dev); + + return 0; +} +EXPORT_SYMBOL(sof_machine_check); + +int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) +{ + struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; + const char *drv_name; + const void *mach; + int size; + + drv_name = plat_data->machine->drv_name; + mach = (const void *)plat_data->machine; + size = sizeof(*plat_data->machine); + + /* register machine driver, pass machine info as pdata */ + plat_data->pdev_mach = + platform_device_register_data(sdev->dev, drv_name, + PLATFORM_DEVID_NONE, mach, size); + if (IS_ERR(plat_data->pdev_mach)) + return PTR_ERR(plat_data->pdev_mach); + + dev_dbg(sdev->dev, "created machine %s\n", + dev_name(&plat_data->pdev_mach->dev)); + + return 0; +} +EXPORT_SYMBOL(sof_machine_register); + +void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) +{ + struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; + + if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) + platform_device_unregister(plat_data->pdev_mach); +} +EXPORT_SYMBOL(sof_machine_unregister); diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h new file mode 100644 index 000000000000..a62fb2da6a6e --- /dev/null +++ b/sound/soc/sof/sof-audio.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2019 Intel Corporation. All rights reserved. + * + * Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> + */ + +#ifndef __SOUND_SOC_SOF_AUDIO_H +#define __SOUND_SOC_SOF_AUDIO_H + +#include <sound/soc.h> +#include <sound/control.h> +#include <sound/sof/stream.h> /* needs to be included before control.h */ +#include <sound/sof/control.h> +#include <sound/sof/dai.h> +#include <sound/sof/topology.h> +#include "sof-priv.h" + +#define SOF_AUDIO_PCM_DRV_NAME "sof-audio-component" + +/* max number of FE PCMs before BEs */ +#define SOF_BE_PCM_BASE 16 + +#define DMA_CHAN_INVALID 0xFFFFFFFF + +/* PCM stream, mapped to FW component */ +struct snd_sof_pcm_stream { + u32 comp_id; + struct snd_dma_buffer page_table; + struct sof_ipc_stream_posn posn; + struct snd_pcm_substream *substream; + struct work_struct period_elapsed_work; + bool d0i3_compatible; /* DSP can be in D0I3 when this pcm is opened */ + /* + * flag to indicate that the DSP pipelines should be kept + * active or not while suspending the stream + */ + bool suspend_ignored; +}; + +/* ALSA SOF PCM device */ +struct snd_sof_pcm { + struct snd_soc_component *scomp; + struct snd_soc_tplg_pcm pcm; + struct snd_sof_pcm_stream stream[2]; + struct list_head list; /* list in sdev pcm list */ + struct snd_pcm_hw_params params[2]; + bool prepared[2]; /* PCM_PARAMS set successfully */ +}; + +struct snd_sof_led_control { + unsigned int use_led; + unsigned int direction; + unsigned int led_value; +}; + +/* ALSA SOF Kcontrol device */ +struct snd_sof_control { + struct snd_soc_component *scomp; + int comp_id; + int min_volume_step; /* min volume step for volume_table */ + int max_volume_step; /* max volume step for volume_table */ + int num_channels; + u32 readback_offset; /* offset to mmapped data if used */ + struct sof_ipc_ctrl_data *control_data; + u32 size; /* cdata size */ + enum sof_ipc_ctrl_cmd cmd; + u32 *volume_table; /* volume table computed from tlv data*/ + + struct list_head list; /* list in sdev control list */ + + struct snd_sof_led_control led_ctl; +}; + +/* ASoC SOF DAPM widget */ +struct snd_sof_widget { + struct snd_soc_component *scomp; + int comp_id; + int pipeline_id; + int complete; + int id; + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ + + void *private; /* core does not touch this */ +}; + +/* ASoC SOF DAPM route */ +struct snd_sof_route { + struct snd_soc_component *scomp; + + struct snd_soc_dapm_route *route; + struct list_head list; /* list in sdev route list */ + + void *private; +}; + +/* ASoC DAI device */ +struct snd_sof_dai { + struct snd_soc_component *scomp; + const char *name; + const char *cpu_dai_name; + + struct sof_ipc_comp_dai comp_dai; + struct sof_ipc_dai_config *dai_config; + struct list_head list; /* list in sdev dai list */ +}; + +/* + * Kcontrols. + */ + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size); +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size); + +/* + * Topology. + * There is no snd_sof_free_topology since topology components will + * be freed by snd_soc_unregister_component, + */ +int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file); +int snd_sof_complete_pipeline(struct device *dev, + struct snd_sof_widget *swidget); + +int sof_load_pipeline_ipc(struct device *dev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r); + +/* + * Stream IPC + */ +int snd_sof_ipc_stream_posn(struct snd_soc_component *scomp, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn); + +struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, + const char *name); +struct snd_sof_widget * +snd_sof_find_swidget_sname(struct snd_soc_component *scomp, + const char *pcm_name, int dir); +struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, + const char *name); + +static inline +struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_soc_component *scomp, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, + const char *name); +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, + unsigned int comp_id, + int *direction); +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, + unsigned int pcm_id); +void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); + +/* + * Mixer IPC + */ +int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, + u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd, + bool send); + +/* PM */ +int sof_restore_pipelines(struct device *dev); +int sof_set_hw_params_upon_resume(struct device *dev); +bool snd_sof_dsp_d0i3_on_suspend(struct snd_sof_dev *sdev); + +/* Machine driver enumeration */ +int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); +void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); + +#endif diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c new file mode 100644 index 000000000000..39ea8af6213f --- /dev/null +++ b/sound/soc/sof/sof-of-dev.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// Copyright 2019 NXP +// +// Author: Daniel Baluta <daniel.baluta@nxp.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/sof.h> + +#include "ops.h" + +extern struct snd_sof_dsp_ops sof_imx8_ops; + +/* platform specific devices */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) +static struct sof_dev_desc sof_of_imx8qxp_desc = { + .default_fw_path = "imx/sof", + .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8.ri", + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8_ops, +}; +#endif + +static const struct dev_pm_ops sof_of_pm = { + SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) + SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, + NULL) +}; + +static void sof_of_probe_complete(struct device *dev) +{ + /* allow runtime_pm */ + pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); +} + +static int sof_of_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct sof_dev_desc *desc; + struct snd_sof_pdata *sof_pdata; + const struct snd_sof_dsp_ops *ops; + int ret; + + dev_info(&pdev->dev, "DT DSP detected"); + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + desc = device_get_match_data(dev); + if (!desc) + return -ENODEV; + + /* get ops for platform */ + ops = desc->ops; + if (!ops) { + dev_err(dev, "error: no matching DT descriptor ops\n"); + return -ENODEV; + } + + sof_pdata->desc = desc; + sof_pdata->dev = &pdev->dev; + sof_pdata->fw_filename = desc->default_fw_filename; + + /* TODO: read alternate fw and tplg filenames from DT */ + sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; + sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + /* set callback to enable runtime_pm */ + sof_pdata->sof_probe_complete = sof_of_probe_complete; +#endif + /* call sof helper for DSP hardware probe */ + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret) { + dev_err(dev, "error: failed to probe DSP hardware\n"); + return ret; + } + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + sof_of_probe_complete(dev); +#endif + + return ret; +} + +static int sof_of_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + /* call sof helper for DSP hardware remove */ + snd_sof_device_remove(&pdev->dev); + + return 0; +} + +static const struct of_device_id sof_of_ids[] = { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) + { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc}, +#endif + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of", + .pm = &sof_of_pm, + .of_match_table = sof_of_ids, + }, +}; +module_platform_driver(snd_sof_of_driver); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index 65d1bac4c6b8..cec631a1389b 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -9,9 +9,11 @@ // #include <linux/firmware.h> +#include <linux/dmi.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/pm_runtime.h> +#include <sound/intel-dsp-config.h> #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> #include <sound/sof.h> @@ -29,6 +31,29 @@ static char *tplg_path; module_param(tplg_path, charp, 0444); MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); +static int sof_pci_debug; +module_param_named(sof_pci_debug, sof_pci_debug, int, 0444); +MODULE_PARM_DESC(sof_pci_debug, "SOF PCI debug options (0x0 all off)"); + +#define SOF_PCI_DISABLE_PM_RUNTIME BIT(0) + +static const struct dmi_system_id community_key_platforms[] = { + { + .ident = "Up Squared", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"), + } + }, + { + .ident = "Google Chromebooks", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + } + }, + {}, +}; + #if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) static const struct sof_dev_desc bxt_desc = { .machines = snd_soc_acpi_intel_bxt_machines, @@ -40,10 +65,9 @@ static const struct sof_dev_desc bxt_desc = { .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-apl.ri", + .default_fw_filename = "sof-apl.ri", .nocodec_tplg_filename = "sof-apl-nocodec.tplg", .ops = &sof_apl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif @@ -58,10 +82,9 @@ static const struct sof_dev_desc glk_desc = { .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-glk.ri", + .default_fw_filename = "sof-glk.ri", .nocodec_tplg_filename = "sof-glk-nocodec.tplg", .ops = &sof_apl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif @@ -86,16 +109,16 @@ static const struct sof_dev_desc tng_desc = { .chip_info = &tng_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-byt.ri", + .default_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt.tplg", .ops = &sof_tng_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE) static const struct sof_dev_desc cnl_desc = { .machines = snd_soc_acpi_intel_cnl_machines, + .alt_machines = snd_soc_acpi_intel_cnl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -104,16 +127,16 @@ static const struct sof_dev_desc cnl_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cnl.ri", + .default_fw_filename = "sof-cnl.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) static const struct sof_dev_desc cfl_desc = { - .machines = snd_soc_acpi_intel_cnl_machines, + .machines = snd_soc_acpi_intel_cfl_machines, + .alt_machines = snd_soc_acpi_intel_cfl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -122,10 +145,9 @@ static const struct sof_dev_desc cfl_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cnl.ri", + .default_fw_filename = "sof-cfl.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif @@ -133,7 +155,8 @@ static const struct sof_dev_desc cfl_desc = { IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_H) static const struct sof_dev_desc cml_desc = { - .machines = snd_soc_acpi_intel_cnl_machines, + .machines = snd_soc_acpi_intel_cml_machines, + .alt_machines = snd_soc_acpi_intel_cml_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -142,16 +165,16 @@ static const struct sof_dev_desc cml_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cnl.ri", + .default_fw_filename = "sof-cml.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) static const struct sof_dev_desc icl_desc = { .machines = snd_soc_acpi_intel_icl_machines, + .alt_machines = snd_soc_acpi_intel_icl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -160,50 +183,67 @@ static const struct sof_dev_desc icl_desc = { .chip_info = &icl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-icl.ri", + .default_fw_filename = "sof-icl.ri", .nocodec_tplg_filename = "sof-icl-nocodec.tplg", .ops = &sof_cnl_ops, - .arch_ops = &sof_xtensa_arch_ops }; #endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE) -static const struct sof_dev_desc skl_desc = { - .machines = snd_soc_acpi_intel_skl_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = -1, - .resindex_imr_base = -1, - .irqindex_host_ipc = -1, - .resindex_dma_base = -1, - .chip_info = &skl_chip_info, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) +static const struct sof_dev_desc tgl_desc = { + .machines = snd_soc_acpi_intel_tgl_machines, + .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &tgl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-skl.ri", - .nocodec_tplg_filename = "sof-skl-nocodec.tplg", - .ops = &sof_skl_ops, - .arch_ops = &sof_xtensa_arch_ops + .default_fw_filename = "sof-tgl.ri", + .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", + .ops = &sof_cnl_ops, }; #endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE) -static const struct sof_dev_desc kbl_desc = { - .machines = snd_soc_acpi_intel_kbl_machines, - .resindex_lpe_base = 0, - .resindex_pcicfg_base = -1, - .resindex_imr_base = -1, - .irqindex_host_ipc = -1, - .resindex_dma_base = -1, - .chip_info = &skl_chip_info, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE) +static const struct sof_dev_desc ehl_desc = { + .machines = snd_soc_acpi_intel_ehl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &ehl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-ehl.ri", + .nocodec_tplg_filename = "sof-ehl-nocodec.tplg", + .ops = &sof_cnl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE) +static const struct sof_dev_desc jsl_desc = { + .machines = snd_soc_acpi_intel_jsl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &jsl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-kbl.ri", - .nocodec_tplg_filename = "sof-kbl-nocodec.tplg", - .ops = &sof_skl_ops, - .arch_ops = &sof_xtensa_arch_ops + .default_fw_filename = "sof-jsl.ri", + .nocodec_tplg_filename = "sof-jsl-nocodec.tplg", + .ops = &sof_cnl_ops, }; #endif static const struct dev_pm_ops sof_pci_pm = { + .prepare = snd_sof_prepare, + .complete = snd_sof_complete, SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, snd_sof_runtime_idle) @@ -213,6 +253,9 @@ static void sof_pci_probe_complete(struct device *dev) { dev_dbg(dev, "Completing SOF PCI probe"); + if (sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME) + return; + /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); @@ -223,6 +266,9 @@ static void sof_pci_probe_complete(struct device *dev) */ pm_runtime_allow(dev); + /* mark last_busy for pm_runtime to make sure not suspend immediately */ + pm_runtime_mark_last_busy(dev); + /* follow recommendation in pci-driver.c to decrement usage counter */ pm_runtime_put_noidle(dev); } @@ -233,11 +279,15 @@ static int sof_pci_probe(struct pci_dev *pci, struct device *dev = &pci->dev; const struct sof_dev_desc *desc = (const struct sof_dev_desc *)pci_id->driver_data; - struct snd_soc_acpi_mach *mach; struct snd_sof_pdata *sof_pdata; const struct snd_sof_dsp_ops *ops; int ret; + ret = snd_intel_dsp_driver_probe(pci); + if (ret != SND_INTEL_DSP_DRIVER_ANY && + ret != SND_INTEL_DSP_DRIVER_SOF) + return -ENODEV; + dev_dbg(&pci->dev, "PCI DSP detected"); /* get ops for platform */ @@ -259,42 +309,39 @@ static int sof_pci_probe(struct pci_dev *pci, if (ret < 0) return ret; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) - /* force nocodec mode */ - dev_warn(dev, "Force to use nocodec mode\n"); - mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); - if (!mach) { - ret = -ENOMEM; - goto release_regions; - } - ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); - if (ret < 0) - goto release_regions; - -#else - /* find machine */ - mach = snd_soc_acpi_find_machine(desc->machines); - if (!mach) { - dev_warn(dev, "warning: No matching ASoC machine driver found\n"); - } else { - mach->mach_params.platform = dev_name(dev); - sof_pdata->fw_filename = mach->sof_fw_filename; - sof_pdata->tplg_filename = mach->sof_tplg_filename; - } -#endif /* CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE */ - sof_pdata->name = pci_name(pci); - sof_pdata->machine = mach; sof_pdata->desc = (struct sof_dev_desc *)pci_id->driver_data; sof_pdata->dev = dev; - sof_pdata->platform = dev_name(dev); + sof_pdata->fw_filename = desc->default_fw_filename; + + /* + * for platforms using the SOF community key, change the + * default path automatically to pick the right files from the + * linux-firmware tree. This can be overridden with the + * fw_path kernel parameter, e.g. for developers. + */ /* alternate fw and tplg filenames ? */ - if (fw_path) + if (fw_path) { sof_pdata->fw_filename_prefix = fw_path; - else + + dev_dbg(dev, + "Module parameter used, changed fw path to %s\n", + sof_pdata->fw_filename_prefix); + + } else if (dmi_check_system(community_key_platforms)) { + sof_pdata->fw_filename_prefix = + devm_kasprintf(dev, GFP_KERNEL, "%s/%s", + sof_pdata->desc->default_fw_path, + "community"); + + dev_dbg(dev, + "Platform uses community key, changed fw path to %s\n", + sof_pdata->fw_filename_prefix); + } else { sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; + } if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; @@ -331,7 +378,8 @@ static void sof_pci_remove(struct pci_dev *pci) snd_sof_device_remove(&pci->dev); /* follow recommendation in pci-driver.c to increment usage counter */ - pm_runtime_get_noresume(&pci->dev); + if (!(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME)) + pm_runtime_get_noresume(&pci->dev); /* release pci regions and disable device */ pci_release_regions(pci); @@ -362,18 +410,16 @@ static const struct pci_device_id sof_pci_ids[] = { { PCI_DEVICE(0x8086, 0xa348), .driver_data = (unsigned long)&cfl_desc}, #endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE) - { PCI_DEVICE(0x8086, 0x9d71), - .driver_data = (unsigned long)&kbl_desc}, -#endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE) - { PCI_DEVICE(0x8086, 0x9d70), - .driver_data = (unsigned long)&skl_desc}, -#endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) { PCI_DEVICE(0x8086, 0x34C8), .driver_data = (unsigned long)&icl_desc}, #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE) + { PCI_DEVICE(0x8086, 0x38c8), + .driver_data = (unsigned long)&jsl_desc}, + { PCI_DEVICE(0x8086, 0x4dc8), + .driver_data = (unsigned long)&jsl_desc}, +#endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_LP) { PCI_DEVICE(0x8086, 0x02c8), .driver_data = (unsigned long)&cml_desc}, @@ -382,6 +428,14 @@ static const struct pci_device_id sof_pci_ids[] = { { PCI_DEVICE(0x8086, 0x06c8), .driver_data = (unsigned long)&cml_desc}, #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) + { PCI_DEVICE(0x8086, 0xa0c8), + .driver_data = (unsigned long)&tgl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE) + { PCI_DEVICE(0x8086, 0x4b55), + .driver_data = (unsigned long)&ehl_desc}, +#endif { 0, } }; MODULE_DEVICE_TABLE(pci, sof_pci_ids); @@ -399,3 +453,5 @@ static struct pci_driver snd_sof_pci_driver = { module_pci_driver(snd_sof_pci_driver); MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_MERRIFIELD); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index b8c0b2a22684..bc2337cf1142 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -12,26 +12,23 @@ #define __SOUND_SOC_SOF_PRIV_H #include <linux/device.h> - #include <sound/hdaudio.h> -#include <sound/soc.h> - #include <sound/sof.h> -#include <sound/sof/stream.h> /* needs to be included before control.h */ -#include <sound/sof/control.h> -#include <sound/sof/dai.h> #include <sound/sof/info.h> #include <sound/sof/pm.h> -#include <sound/sof/topology.h> #include <sound/sof/trace.h> - #include <uapi/sound/sof/fw.h> /* debug flags */ -#define SOF_DBG_REGS BIT(1) -#define SOF_DBG_MBOX BIT(2) -#define SOF_DBG_TEXT BIT(3) -#define SOF_DBG_PCI BIT(4) +#define SOF_DBG_ENABLE_TRACE BIT(0) +#define SOF_DBG_REGS BIT(1) +#define SOF_DBG_MBOX BIT(2) +#define SOF_DBG_TEXT BIT(3) +#define SOF_DBG_PCI BIT(4) +#define SOF_DBG_RETAIN_CTX BIT(5) /* prevent DSP D3 on FW exception */ + +/* global debug state set by SOF_DBG_ flags */ +extern int sof_core_debug; /* max BARs mmaped devices can use */ #define SND_SOF_BARS 8 @@ -42,9 +39,6 @@ /* DMA buffer size for trace */ #define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16) -/* max number of FE PCMs before BEs */ -#define SOF_BE_PCM_BASE 16 - #define SOF_IPC_DSP_REPLY 0 #define SOF_IPC_HOST_REPLY 1 @@ -60,7 +54,11 @@ (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) -#define DMA_CHAN_INVALID 0xFFFFFFFF +/* DSP D0ix sub-state */ +enum sof_d0_substate { + SOF_DSP_D0I0 = 0, /* DSP default D0 substate */ + SOF_DSP_D0I3, /* DSP D0i3(low power) substate*/ +}; struct snd_sof_dev; struct snd_sof_ipc_msg; @@ -128,7 +126,7 @@ struct snd_sof_dsp_ops { * FW ready checks for ABI compatibility and creates * memory windows at first boot */ - int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* optional */ + int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* mandatory */ /* connect pcm substream to a host stream */ int (*pcm_open)(struct snd_sof_dev *sdev, @@ -171,13 +169,14 @@ struct snd_sof_dsp_ops { int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ /* DSP PM */ - int (*suspend)(struct snd_sof_dev *sof_dev, int state); /* optional */ + int (*suspend)(struct snd_sof_dev *sof_dev); /* optional */ int (*resume)(struct snd_sof_dev *sof_dev); /* optional */ - int (*runtime_suspend)(struct snd_sof_dev *sof_dev, - int state); /* optional */ + int (*runtime_suspend)(struct snd_sof_dev *sof_dev); /* optional */ int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* optional */ int (*runtime_idle)(struct snd_sof_dev *sof_dev); /* optional */ int (*set_hw_params_upon_resume)(struct snd_sof_dev *sdev); /* optional */ + int (*set_power_state)(struct snd_sof_dev *sdev, + enum sof_d0_substate d0_substate); /* optional */ /* DSP clocking */ int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); /* optional */ @@ -196,9 +195,30 @@ struct snd_sof_dsp_ops { int (*trace_trigger)(struct snd_sof_dev *sdev, int cmd); /* optional */ + /* misc */ + int (*get_bar_index)(struct snd_sof_dev *sdev, + u32 type); /* optional */ + int (*get_mailbox_offset)(struct snd_sof_dev *sdev);/* mandatory for common loader code */ + int (*get_window_offset)(struct snd_sof_dev *sdev, + u32 id);/* mandatory for common loader code */ + + /* machine driver ops */ + int (*machine_register)(struct snd_sof_dev *sdev, + void *pdata); /* optional */ + void (*machine_unregister)(struct snd_sof_dev *sdev, + void *pdata); /* optional */ + void (*machine_select)(struct snd_sof_dev *sdev); /* optional */ + void (*set_mach_params)(const struct snd_soc_acpi_mach *mach, + struct device *dev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; + + /* ALSA HW info flags, will be stored in snd_pcm_runtime.hw.info */ + u32 hw_info; + + const struct sof_arch_ops *arch_ops; }; /* DSP architecture specific callbacks for oops and stack dumps */ @@ -208,7 +228,7 @@ struct sof_arch_ops { u32 *stack, u32 stack_words); }; -#define sof_arch_ops(sdev) ((sdev)->pdata->desc->arch_ops) +#define sof_arch_ops(sdev) ((sdev)->pdata->desc->ops->arch_ops) /* DSP device HW descriptor mapping between bus ID and ops */ struct sof_ops_table { @@ -228,7 +248,6 @@ enum sof_debugfs_access_type { /* FS entry for debug files that can expose DSP memories, registers */ struct snd_sof_dfsentry { - struct dentry *dfsentry; size_t size; enum sof_dfsentry_type type; /* @@ -281,74 +300,13 @@ struct snd_sof_ipc_msg { bool ipc_complete; }; -/* PCM stream, mapped to FW component */ -struct snd_sof_pcm_stream { - u32 comp_id; - struct snd_dma_buffer page_table; - struct sof_ipc_stream_posn posn; - struct snd_pcm_substream *substream; - struct work_struct period_elapsed_work; -}; - -/* ALSA SOF PCM device */ -struct snd_sof_pcm { - struct snd_sof_dev *sdev; - struct snd_soc_tplg_pcm pcm; - struct snd_sof_pcm_stream stream[2]; - struct list_head list; /* list in sdev pcm list */ - struct snd_pcm_hw_params params[2]; - int hw_params_upon_resume[2]; /* set up hw_params upon resume */ -}; - -/* ALSA SOF Kcontrol device */ -struct snd_sof_control { - struct snd_sof_dev *sdev; - int comp_id; - int min_volume_step; /* min volume step for volume_table */ - int max_volume_step; /* max volume step for volume_table */ - int num_channels; - u32 readback_offset; /* offset to mmaped data if used */ - struct sof_ipc_ctrl_data *control_data; - u32 size; /* cdata size */ - enum sof_ipc_ctrl_cmd cmd; - u32 *volume_table; /* volume table computed from tlv data*/ - - struct list_head list; /* list in sdev control list */ -}; - -/* ASoC SOF DAPM widget */ -struct snd_sof_widget { - struct snd_sof_dev *sdev; - int comp_id; - int pipeline_id; - int complete; - int id; - - struct snd_soc_dapm_widget *widget; - struct list_head list; /* list in sdev widget list */ - - void *private; /* core does not touch this */ -}; - -/* ASoC SOF DAPM route */ -struct snd_sof_route { - struct snd_sof_dev *sdev; - - struct snd_soc_dapm_route *route; - struct list_head list; /* list in sdev route list */ - - void *private; -}; - -/* ASoC DAI device */ -struct snd_sof_dai { - struct snd_sof_dev *sdev; - const char *name; - const char *cpu_dai_name; - - struct sof_ipc_comp_dai comp_dai; - struct sof_ipc_dai_config *dai_config; - struct list_head list; /* list in sdev dai list */ +enum snd_sof_fw_state { + SOF_FW_BOOT_NOT_STARTED = 0, + SOF_FW_BOOT_PREPARE, + SOF_FW_BOOT_IN_PROGRESS, + SOF_FW_BOOT_FAILED, + SOF_FW_BOOT_READY_FAILED, /* firmware booted but fw_ready op failed */ + SOF_FW_BOOT_COMPLETE, }; /* @@ -365,9 +323,14 @@ struct snd_sof_dev { */ struct snd_soc_component_driver plat_drv; + /* power states related */ + enum sof_d0_substate d0_substate; + /* flag to track if the intended power target of suspend is S0ix */ + bool s0_suspend; + /* DSP firmware boot */ wait_queue_head_t boot_wait; - u32 boot_complete; + enum snd_sof_fw_state fw_state; u32 first_boot; /* work queue in case the probe is implemented in two steps */ @@ -400,6 +363,7 @@ struct snd_sof_dev { struct snd_dma_buffer dmab_bdl; struct sof_ipc_fw_ready fw_ready; struct sof_ipc_fw_version fw_version; + struct sof_ipc_cc_version *cc_version; /* topology */ struct snd_soc_tplg_ops *tplg_ops; @@ -429,11 +393,12 @@ struct snd_sof_dev { int dma_trace_pages; wait_queue_head_t trace_sleep; u32 host_offset; + u32 dtrace_is_supported; /* set with Kconfig or module parameter */ u32 dtrace_is_enabled; u32 dtrace_error; u32 dtrace_draining; - u32 msi_enabled; + bool msi_enabled; void *private; /* core does not touch this */ }; @@ -450,10 +415,14 @@ int snd_sof_runtime_resume(struct device *dev); int snd_sof_runtime_idle(struct device *dev); int snd_sof_resume(struct device *dev); int snd_sof_suspend(struct device *dev); +int snd_sof_prepare(struct device *dev); +void snd_sof_complete(struct device *dev); +int snd_sof_set_d0_substate(struct snd_sof_dev *sdev, + enum sof_d0_substate d0_substate); void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); -int snd_sof_create_page_table(struct snd_sof_dev *sdev, +int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, unsigned char *page_table, size_t size); @@ -485,67 +454,6 @@ int snd_sof_ipc_valid(struct snd_sof_dev *sdev); int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); -struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, - const char *name); -struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, - const char *pcm_name, - int dir); -struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, - const char *name); - -static inline -struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev, - struct snd_soc_pcm_runtime *rtd) -{ - struct snd_sof_pcm *spcm = NULL; - - list_for_each_entry(spcm, &sdev->pcm_list, list) { - if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) - return spcm; - } - - return NULL; -} - -struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, - const char *name); -struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, - unsigned int comp_id, - int *direction); -struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, - unsigned int pcm_id); -void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); - -/* - * Stream IPC - */ -int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, - struct snd_sof_pcm *spcm, int direction, - struct sof_ipc_stream_posn *posn); - -/* - * Mixer IPC - */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc, - struct snd_sof_control *scontrol, u32 ipc_cmd, - enum sof_ipc_ctrl_type ctrl_type, - enum sof_ipc_ctrl_cmd ctrl_cmd, - bool send); - -/* - * Topology. - * There is no snd_sof_free_topology since topology components will - * be freed by snd_soc_unregister_component, - */ -int snd_sof_init_topology(struct snd_sof_dev *sdev, - struct snd_soc_tplg_ops *ops); -int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file); -int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, - struct snd_sof_widget *swidget); - -int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, - struct sof_ipc_pipe_new *pipeline, - struct sof_ipc_comp_reply *r); /* * Trace/debug @@ -570,6 +478,7 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, struct sof_ipc_panic_info *panic_info, void *stack, size_t stack_words); int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); +void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); /* * Platform specific ops. @@ -577,39 +486,11 @@ int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); extern struct snd_compr_ops sof_compressed_ops; /* - * Kcontrols. - */ - -int snd_sof_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_enum_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_enum_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, - const unsigned int __user *binary_data, - unsigned int size); -int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, - unsigned int __user *binary_data, - unsigned int size); - -/* * DSP Architectures. */ static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, u32 stack_words) { - if (sof_arch_ops(sdev)->dsp_stack) sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); } @@ -637,6 +518,8 @@ void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, size_t size); +int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); + void intel_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, void *p, size_t sz); @@ -649,4 +532,6 @@ int intel_pcm_open(struct snd_sof_dev *sdev, int intel_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int sof_machine_check(struct snd_sof_dev *sdev); + #endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 432ae343f960..9f4f8868b386 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -13,6 +13,7 @@ #include <sound/pcm_params.h> #include <uapi/sound/sof/tokens.h> #include "sof-priv.h" +#include "sof-audio.h" #include "ops.h" #define COMP_ID_UNASSIGNED 0xffffffff @@ -42,11 +43,19 @@ /* size of tplg abi in byte */ #define SOF_TPLG_ABI_SIZE 3 +struct sof_widget_data { + int ctrl_type; + int ipc_cmd; + struct sof_abi_hdr *pdata; + struct snd_sof_control *control; +}; + /* send pcm params ipc */ static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) { struct sof_ipc_pcm_params_reply ipc_params_reply; - struct snd_sof_dev *sdev = swidget->sdev; + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc_pcm_params pcm; struct snd_pcm_hw_params *params; struct snd_sof_pcm *spcm; @@ -55,9 +64,9 @@ static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) memset(&pcm, 0, sizeof(pcm)); /* get runtime PCM params using widget's stream name */ - spcm = snd_sof_find_spcm_name(sdev, swidget->widget->sname); + spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); if (!spcm) { - dev_err(sdev->dev, "error: cannot find PCM for %s\n", + dev_err(scomp->dev, "error: cannot find PCM for %s\n", swidget->widget->name); return -EINVAL; } @@ -95,7 +104,7 @@ static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) - dev_err(sdev->dev, "error: pcm params failed for %s\n", + dev_err(scomp->dev, "error: pcm params failed for %s\n", swidget->widget->name); return ret; @@ -104,7 +113,8 @@ static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) /* send stream trigger ipc */ static int ipc_trigger(struct snd_sof_widget *swidget, int cmd) { - struct snd_sof_dev *sdev = swidget->sdev; + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc_stream stream; struct sof_ipc_reply reply; int ret = 0; @@ -118,7 +128,7 @@ static int ipc_trigger(struct snd_sof_widget *swidget, int cmd) ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, sizeof(stream), &reply, sizeof(reply)); if (ret < 0) - dev_err(sdev->dev, "error: failed to trigger %s\n", + dev_err(scomp->dev, "error: failed to trigger %s\n", swidget->widget->name); return ret; @@ -128,24 +138,39 @@ static int sof_keyword_dapm_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_sof_widget *swidget = w->dobj.private; - struct snd_sof_dev *sdev; + struct snd_soc_component *scomp; + int stream = SNDRV_PCM_STREAM_CAPTURE; + struct snd_sof_pcm *spcm; int ret = 0; if (!swidget) return 0; - sdev = swidget->sdev; + scomp = swidget->scomp; - dev_dbg(sdev->dev, "received event %d for widget %s\n", + dev_dbg(scomp->dev, "received event %d for widget %s\n", event, w->name); + /* get runtime PCM params using widget's stream name */ + spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); + if (!spcm) { + dev_err(scomp->dev, "error: cannot find PCM for %s\n", + swidget->widget->name); + return -EINVAL; + } + /* process events */ switch (event) { case SND_SOC_DAPM_PRE_PMU: + if (spcm->stream[stream].suspend_ignored) { + dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n"); + return 0; + } + /* set pcm params */ - ret = ipc_pcm_params(swidget, SOF_IPC_STREAM_CAPTURE); + ret = ipc_pcm_params(swidget, stream); if (ret < 0) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: failed to set pcm params for widget %s\n", swidget->widget->name); break; @@ -154,22 +179,27 @@ static int sof_keyword_dapm_event(struct snd_soc_dapm_widget *w, /* start trigger */ ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START); if (ret < 0) - dev_err(sdev->dev, + dev_err(scomp->dev, "error: failed to trigger widget %s\n", swidget->widget->name); break; case SND_SOC_DAPM_POST_PMD: + if (spcm->stream[stream].suspend_ignored) { + dev_dbg(scomp->dev, "POST_PMD even ignored, KWD pipeline will remain RUNNING\n"); + return 0; + } + /* stop trigger */ ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP); if (ret < 0) - dev_err(sdev->dev, + dev_err(scomp->dev, "error: failed to trigger widget %s\n", swidget->widget->name); /* pcm free */ ret = ipc_trigger(swidget, SOF_IPC_STREAM_PCM_FREE); if (ret < 0) - dev_err(sdev->dev, + dev_err(scomp->dev, "error: failed to trigger widget %s\n", swidget->widget->name); break; @@ -339,6 +369,9 @@ static const struct sof_dai_types sof_dais[] = { {"SSP", SOF_DAI_INTEL_SSP}, {"HDA", SOF_DAI_INTEL_HDA}, {"DMIC", SOF_DAI_INTEL_DMIC}, + {"ALH", SOF_DAI_INTEL_ALH}, + {"SAI", SOF_DAI_IMX_SAI}, + {"ESAI", SOF_DAI_IMX_ESAI}, }; static enum sof_ipc_dai_type find_dai(const char *name) @@ -423,163 +456,6 @@ static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type) } /* - * Standard Kcontrols. - */ - -static int sof_control_load_volume(struct snd_soc_component *scomp, - struct snd_sof_control *scontrol, - struct snd_kcontrol_new *kc, - struct snd_soc_tplg_ctl_hdr *hdr) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_mixer_control *mc = - container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); - struct sof_ipc_ctrl_data *cdata; - int tlv[TLV_ITEMS]; - unsigned int i; - int ret; - - /* validate topology data */ - if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) - return -EINVAL; - - /* init the volume get/put data */ - scontrol->size = struct_size(scontrol->control_data, chanv, - le32_to_cpu(mc->num_channels)); - scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); - if (!scontrol->control_data) - return -ENOMEM; - - scontrol->comp_id = sdev->next_comp_id; - scontrol->min_volume_step = le32_to_cpu(mc->min); - scontrol->max_volume_step = le32_to_cpu(mc->max); - scontrol->num_channels = le32_to_cpu(mc->num_channels); - - /* set cmd for mixer control */ - if (le32_to_cpu(mc->max) == 1) { - scontrol->cmd = SOF_CTRL_CMD_SWITCH; - goto out; - } - - scontrol->cmd = SOF_CTRL_CMD_VOLUME; - - /* extract tlv data */ - if (get_tlv_data(kc->tlv.p, tlv) < 0) { - dev_err(sdev->dev, "error: invalid TLV data\n"); - return -EINVAL; - } - - /* set up volume table */ - ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); - if (ret < 0) { - dev_err(sdev->dev, "error: setting up volume table\n"); - return ret; - } - - /* set default volume values to 0dB in control */ - cdata = scontrol->control_data; - for (i = 0; i < scontrol->num_channels; i++) { - cdata->chanv[i].channel = i; - cdata->chanv[i].value = VOL_ZERO_DB; - } - -out: - dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", - scontrol->comp_id, scontrol->num_channels); - - return 0; -} - -static int sof_control_load_enum(struct snd_soc_component *scomp, - struct snd_sof_control *scontrol, - struct snd_kcontrol_new *kc, - struct snd_soc_tplg_ctl_hdr *hdr) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_enum_control *ec = - container_of(hdr, struct snd_soc_tplg_enum_control, hdr); - - /* validate topology data */ - if (le32_to_cpu(ec->num_channels) > SND_SOC_TPLG_MAX_CHAN) - return -EINVAL; - - /* init the enum get/put data */ - scontrol->size = struct_size(scontrol->control_data, chanv, - le32_to_cpu(ec->num_channels)); - scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); - if (!scontrol->control_data) - return -ENOMEM; - - scontrol->comp_id = sdev->next_comp_id; - scontrol->num_channels = le32_to_cpu(ec->num_channels); - - scontrol->cmd = SOF_CTRL_CMD_ENUM; - - dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n", - scontrol->comp_id, scontrol->num_channels, scontrol->comp_id); - - return 0; -} - -static int sof_control_load_bytes(struct snd_soc_component *scomp, - struct snd_sof_control *scontrol, - struct snd_kcontrol_new *kc, - struct snd_soc_tplg_ctl_hdr *hdr) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_ctrl_data *cdata; - struct snd_soc_tplg_bytes_control *control = - container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); - struct soc_bytes_ext *sbe = (struct soc_bytes_ext *)kc->private_value; - int max_size = sbe->max; - - if (le32_to_cpu(control->priv.size) > max_size) { - dev_err(sdev->dev, "err: bytes data size %d exceeds max %d.\n", - control->priv.size, max_size); - return -EINVAL; - } - - /* init the get/put bytes data */ - scontrol->size = sizeof(struct sof_ipc_ctrl_data) + - le32_to_cpu(control->priv.size); - scontrol->control_data = kzalloc(max_size, GFP_KERNEL); - cdata = scontrol->control_data; - if (!scontrol->control_data) - return -ENOMEM; - - scontrol->comp_id = sdev->next_comp_id; - scontrol->cmd = SOF_CTRL_CMD_BINARY; - - dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", - scontrol->comp_id, scontrol->num_channels); - - if (le32_to_cpu(control->priv.size) > 0) { - memcpy(cdata->data, control->priv.data, - le32_to_cpu(control->priv.size)); - - if (cdata->data->magic != SOF_ABI_MAGIC) { - dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n", - cdata->data->magic); - return -EINVAL; - } - if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, - cdata->data->abi)) { - dev_err(sdev->dev, - "error: Incompatible ABI version 0x%08x.\n", - cdata->data->abi); - return -EINVAL; - } - if (cdata->data->size + sizeof(const struct sof_abi_hdr) != - le32_to_cpu(control->priv.size)) { - dev_err(sdev->dev, - "error: Conflict in bytes vs. priv size.\n"); - return -EINVAL; - } - } - return 0; -} - -/* * Topology Token Parsing. * New tokens should be added to headers and parsing tables below. */ @@ -697,6 +573,20 @@ static const struct sof_topology_token src_tokens[] = { offsetof(struct sof_ipc_comp_src, sink_rate), 0}, }; +/* ASRC */ +static const struct sof_topology_token asrc_tokens[] = { + {SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, source_rate), 0}, + {SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, sink_rate), 0}, + {SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_comp_asrc, asynchronous_mode), 0}, + {SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_comp_asrc, operation_mode), 0}, +}; + /* Tone */ static const struct sof_topology_token tone_tokens[] = { }; @@ -714,6 +604,16 @@ static const struct sof_topology_token pcm_tokens[] = { offsetof(struct sof_ipc_comp_host, dmac_config), 0}, }; +/* PCM */ +static const struct sof_topology_token stream_tokens[] = { + {SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3, + SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_pcm, stream[0].d0i3_compatible), 0}, + {SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3, + SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_pcm, stream[1].d0i3_compatible), 0}, +}; + /* Generic components */ static const struct sof_topology_token comp_tokens[] = { {SOF_TKN_COMP_PERIOD_SINK_COUNT, @@ -748,6 +648,9 @@ static const struct sof_topology_token ssp_tokens[] = { get_token_u16, offsetof(struct sof_ipc_dai_ssp_params, tdm_per_slot_padding_flag), 0}, + {SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, bclk_delay), 0}, }; @@ -785,6 +688,20 @@ static const struct sof_topology_token dmic_tokens[] = { }; +/* ESAI */ +static const struct sof_topology_token esai_tokens[] = { + {SOF_TKN_IMX_ESAI_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_esai_params, mclk_id), 0}, +}; + +/* SAI */ +static const struct sof_topology_token sai_tokens[] = { + {SOF_TKN_IMX_SAI_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_sai_params, mclk_id), 0}, +}; + /* * DMIC PDM Tokens * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token @@ -826,6 +743,14 @@ static const struct sof_topology_token dmic_pdm_tokens[] = { static const struct sof_topology_token hda_tokens[] = { }; +/* Leds */ +static const struct sof_topology_token led_tokens[] = { + {SOF_TKN_MUTE_LED_USE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct snd_sof_led_control, use_led), 0}, + {SOF_TKN_MUTE_LED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct snd_sof_led_control, direction), 0}, +}; + static void sof_parse_uuid_tokens(struct snd_soc_component *scomp, void *object, const struct sof_topology_token *tokens, @@ -907,7 +832,9 @@ static void sof_parse_word_tokens(struct snd_soc_component *scomp, for (j = 0; j < count; j++) { /* match token type */ if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || - tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT)) + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL)) continue; /* match token id */ @@ -935,7 +862,7 @@ static void sof_parse_word_tokens(struct snd_soc_component *scomp, /* check if array index is valid */ if (!index || *index == 0) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: invalid array offset\n"); continue; } else { @@ -963,7 +890,6 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, struct snd_soc_tplg_vendor_array *array, int priv_size) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); int asize; while (priv_size > 0) { @@ -971,7 +897,7 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, /* validate asize */ if (asize < 0) { /* FIXME: A zero-size array makes no sense */ - dev_err(sdev->dev, "error: invalid array size 0x%x\n", + dev_err(scomp->dev, "error: invalid array size 0x%x\n", asize); return -EINVAL; } @@ -979,7 +905,7 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, /* make sure there is enough data before parsing */ priv_size -= asize; if (priv_size < 0) { - dev_err(sdev->dev, "error: invalid array size 0x%x\n", + dev_err(scomp->dev, "error: invalid array size 0x%x\n", asize); return -EINVAL; } @@ -1002,7 +928,7 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, array); break; default: - dev_err(sdev->dev, "error: unknown token type %d\n", + dev_err(scomp->dev, "error: unknown token type %d\n", array->type); return -EINVAL; } @@ -1017,13 +943,205 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, static void sof_dbg_comp_config(struct snd_soc_component *scomp, struct sof_ipc_comp_config *config) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - - dev_dbg(sdev->dev, " config: periods snk %d src %d fmt %d\n", + dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n", config->periods_sink, config->periods_source, config->frame_fmt); } +/* + * Standard Kcontrols. + */ + +static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + struct sof_ipc_ctrl_data *cdata; + int tlv[TLV_ITEMS]; + unsigned int i; + int ret = 0; + + /* validate topology data */ + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) { + ret = -EINVAL; + goto out; + } + + /* init the volume get/put data */ + scontrol->size = struct_size(scontrol->control_data, chanv, + le32_to_cpu(mc->num_channels)); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) { + ret = -ENOMEM; + goto out; + } + + scontrol->comp_id = sdev->next_comp_id; + scontrol->min_volume_step = le32_to_cpu(mc->min); + scontrol->max_volume_step = le32_to_cpu(mc->max); + scontrol->num_channels = le32_to_cpu(mc->num_channels); + + /* set cmd for mixer control */ + if (le32_to_cpu(mc->max) == 1) { + scontrol->cmd = SOF_CTRL_CMD_SWITCH; + goto skip; + } + + scontrol->cmd = SOF_CTRL_CMD_VOLUME; + + /* extract tlv data */ + if (get_tlv_data(kc->tlv.p, tlv) < 0) { + dev_err(scomp->dev, "error: invalid TLV data\n"); + ret = -EINVAL; + goto out_free; + } + + /* set up volume table */ + ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); + if (ret < 0) { + dev_err(scomp->dev, "error: setting up volume table\n"); + goto out_free; + } + + /* set default volume values to 0dB in control */ + cdata = scontrol->control_data; + for (i = 0; i < scontrol->num_channels; i++) { + cdata->chanv[i].channel = i; + cdata->chanv[i].value = VOL_ZERO_DB; + } + +skip: + /* set up possible led control from mixer private data */ + ret = sof_parse_tokens(scomp, &scontrol->led_ctl, led_tokens, + ARRAY_SIZE(led_tokens), mc->priv.array, + le32_to_cpu(mc->priv.size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse led tokens failed %d\n", + le32_to_cpu(mc->priv.size)); + goto out_free_table; + } + + dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + return ret; + +out_free_table: + if (le32_to_cpu(mc->max) > 1) + kfree(scontrol->volume_table); +out_free: + kfree(scontrol->control_data); +out: + return ret; +} + +static int sof_control_load_enum(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_enum_control *ec = + container_of(hdr, struct snd_soc_tplg_enum_control, hdr); + + /* validate topology data */ + if (le32_to_cpu(ec->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + /* init the enum get/put data */ + scontrol->size = struct_size(scontrol->control_data, chanv, + le32_to_cpu(ec->num_channels)); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(ec->num_channels); + + scontrol->cmd = SOF_CTRL_CMD_ENUM; + + dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n", + scontrol->comp_id, scontrol->num_channels, scontrol->comp_id); + + return 0; +} + +static int sof_control_load_bytes(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_ctrl_data *cdata; + struct snd_soc_tplg_bytes_control *control = + container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); + struct soc_bytes_ext *sbe = (struct soc_bytes_ext *)kc->private_value; + int max_size = sbe->max; + int ret = 0; + + /* init the get/put bytes data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + le32_to_cpu(control->priv.size); + + if (scontrol->size > max_size) { + dev_err(scomp->dev, "err: bytes data size %d exceeds max %d.\n", + scontrol->size, max_size); + ret = -EINVAL; + goto out; + } + + scontrol->control_data = kzalloc(max_size, GFP_KERNEL); + cdata = scontrol->control_data; + if (!scontrol->control_data) { + ret = -ENOMEM; + goto out; + } + + scontrol->comp_id = sdev->next_comp_id; + scontrol->cmd = SOF_CTRL_CMD_BINARY; + + dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + if (le32_to_cpu(control->priv.size) > 0) { + memcpy(cdata->data, control->priv.data, + le32_to_cpu(control->priv.size)); + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(scomp->dev, "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + ret = -EINVAL; + goto out_free; + } + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, + cdata->data->abi)) { + dev_err(scomp->dev, + "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + ret = -EINVAL; + goto out_free; + } + if (cdata->data->size + sizeof(const struct sof_abi_hdr) != + le32_to_cpu(control->priv.size)) { + dev_err(scomp->dev, + "error: Conflict in bytes vs. priv size.\n"); + ret = -EINVAL; + goto out_free; + } + } + + return ret; + +out_free: + kfree(scontrol->control_data); +out: + return ret; +} + /* external kcontrol init - used for any driver specific init */ static int sof_control_load(struct snd_soc_component *scomp, int index, struct snd_kcontrol_new *kc, @@ -1037,14 +1155,14 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, struct snd_sof_control *scontrol; int ret = -EINVAL; - dev_dbg(sdev->dev, "tplg: load control type %d name : %s\n", + dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n", hdr->type, hdr->name); scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); if (!scontrol) return -ENOMEM; - scontrol->sdev = sdev; + scontrol->scomp = scomp; switch (le32_to_cpu(hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: @@ -1073,12 +1191,17 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_PIN: default: - dev_warn(sdev->dev, "control type not supported %d:%d:%d\n", + dev_warn(scomp->dev, "control type not supported %d:%d:%d\n", hdr->ops.get, hdr->ops.put, hdr->ops.info); kfree(scontrol); return 0; } + if (ret < 0) { + kfree(scontrol); + return ret; + } + dobj->private = scontrol; list_add(&scontrol->list, &sdev->kcontrol_list); return ret; @@ -1091,7 +1214,7 @@ static int sof_control_unload(struct snd_soc_component *scomp, struct sof_ipc_free fcomp; struct snd_sof_control *scontrol = dobj->private; - dev_dbg(sdev->dev, "tplg: unload control name : %s\n", scomp->name); + dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scomp->name); fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE; fcomp.hdr.size = sizeof(fcomp); @@ -1115,12 +1238,11 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, struct snd_soc_tplg_dapm_widget *tw, struct snd_sof_dai *dai) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_card *card = scomp->card; struct snd_soc_pcm_runtime *rtd; list_for_each_entry(rtd, &card->rtd_list, list) { - dev_vdbg(sdev->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", + dev_vdbg(scomp->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", w->name, w->sname, rtd->dai_link->stream_name); if (!w->sname || !rtd->dai_link->stream_name) @@ -1134,13 +1256,13 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, case snd_soc_dapm_dai_out: rtd->cpu_dai->capture_widget = w; dai->name = rtd->dai_link->name; - dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n", w->name, rtd->dai_link->name); break; case snd_soc_dapm_dai_in: rtd->cpu_dai->playback_widget = w; dai->name = rtd->dai_link->name; - dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n", w->name, rtd->dai_link->name); break; default: @@ -1150,7 +1272,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, /* check we have a connection */ if (!dai->name) { - dev_err(sdev->dev, "error: can't connect DAI %s stream %s\n", + dev_err(scomp->dev, "error: can't connect DAI %s stream %s\n", w->name, w->sname); return -EINVAL; } @@ -1182,7 +1304,7 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, ARRAY_SIZE(dai_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse dai tokens failed %d\n", + dev_err(scomp->dev, "error: parse dai tokens failed %d\n", le32_to_cpu(private->size)); return ret; } @@ -1191,12 +1313,12 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse dai.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse dai.cfg tokens failed %d\n", private->size); return ret; } - dev_dbg(sdev->dev, "dai %s: type %d index %d\n", + dev_dbg(scomp->dev, "dai %s: type %d index %d\n", swidget->widget->name, comp_dai.type, comp_dai.dai_index); sof_dbg_comp_config(scomp, &comp_dai.config); @@ -1204,7 +1326,7 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, &comp_dai, sizeof(comp_dai), r, sizeof(*r)); if (ret == 0 && dai) { - dai->sdev = sdev; + dai->scomp = scomp; memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai)); } @@ -1240,13 +1362,13 @@ static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, ARRAY_SIZE(buffer_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse buffer tokens failed %d\n", + dev_err(scomp->dev, "error: parse buffer tokens failed %d\n", private->size); kfree(buffer); return ret; } - dev_dbg(sdev->dev, "buffer %s: size %d caps 0x%x\n", + dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n", swidget->widget->name, buffer->size, buffer->caps); swidget->private = buffer; @@ -1254,7 +1376,7 @@ static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, sizeof(*buffer), r, sizeof(*r)); if (ret < 0) { - dev_err(sdev->dev, "error: buffer %s load failed\n", + dev_err(scomp->dev, "error: buffer %s load failed\n", swidget->widget->name); kfree(buffer); } @@ -1263,16 +1385,16 @@ static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, } /* bind PCM ID to host component ID */ -static int spcm_bind(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, +static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, int dir) { struct snd_sof_widget *host_widget; - host_widget = snd_sof_find_swidget_sname(sdev, + host_widget = snd_sof_find_swidget_sname(scomp, spcm->pcm.caps[dir].name, dir); if (!host_widget) { - dev_err(sdev->dev, "can't find host comp to bind pcm\n"); + dev_err(scomp->dev, "can't find host comp to bind pcm\n"); return -EINVAL; } @@ -1313,7 +1435,7 @@ static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, ARRAY_SIZE(pcm_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse host tokens failed %d\n", + dev_err(scomp->dev, "error: parse host tokens failed %d\n", private->size); goto err; } @@ -1322,12 +1444,12 @@ static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse host.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse host.cfg tokens failed %d\n", le32_to_cpu(private->size)); goto err; } - dev_dbg(sdev->dev, "loaded host %s\n", swidget->widget->name); + dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name); sof_dbg_comp_config(scomp, &host->config); swidget->private = host; @@ -1344,24 +1466,25 @@ err: /* * Pipeline Topology */ -int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, +int sof_load_pipeline_ipc(struct device *dev, struct sof_ipc_pipe_new *pipeline, struct sof_ipc_comp_reply *r) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct sof_ipc_pm_core_config pm_core_config; int ret; ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, sizeof(*pipeline), r, sizeof(*r)); if (ret < 0) { - dev_err(sdev->dev, "error: load pipeline ipc failure\n"); + dev_err(dev, "error: load pipeline ipc failure\n"); return ret; } /* power up the core that this pipeline is scheduled on */ ret = snd_sof_dsp_core_power_up(sdev, 1 << pipeline->core); if (ret < 0) { - dev_err(sdev->dev, "error: powering up pipeline schedule core %d\n", + dev_err(dev, "error: powering up pipeline schedule core %d\n", pipeline->core); return ret; } @@ -1385,7 +1508,7 @@ int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, &pm_core_config, sizeof(pm_core_config), &pm_core_config, sizeof(pm_core_config)); if (ret < 0) - dev_err(sdev->dev, "error: core enable ipc failure\n"); + dev_err(dev, "error: core enable ipc failure\n"); return ret; } @@ -1395,7 +1518,6 @@ static int sof_widget_load_pipeline(struct snd_soc_component *scomp, struct snd_soc_tplg_dapm_widget *tw, struct sof_ipc_comp_reply *r) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_pipe_new *pipeline; struct snd_sof_widget *comp_swidget; @@ -1412,9 +1534,9 @@ static int sof_widget_load_pipeline(struct snd_soc_component *scomp, pipeline->comp_id = swidget->comp_id; /* component at start of pipeline is our stream id */ - comp_swidget = snd_sof_find_swidget(sdev, tw->sname); + comp_swidget = snd_sof_find_swidget(scomp, tw->sname); if (!comp_swidget) { - dev_err(sdev->dev, "error: widget %s refers to non existent widget %s\n", + dev_err(scomp->dev, "error: widget %s refers to non existent widget %s\n", tw->name, tw->sname); ret = -EINVAL; goto err; @@ -1422,26 +1544,26 @@ static int sof_widget_load_pipeline(struct snd_soc_component *scomp, pipeline->sched_id = comp_swidget->comp_id; - dev_dbg(sdev->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", + dev_dbg(scomp->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id); ret = sof_parse_tokens(scomp, pipeline, sched_tokens, ARRAY_SIZE(sched_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse pipeline tokens failed %d\n", + dev_err(scomp->dev, "error: parse pipeline tokens failed %d\n", private->size); goto err; } - dev_dbg(sdev->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n", + dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n", swidget->widget->name, pipeline->period, pipeline->priority, pipeline->period_mips, pipeline->core, pipeline->frames_per_sched); swidget->private = pipeline; /* send ipc's to create pipeline comp and power up schedule core */ - ret = sof_load_pipeline_ipc(sdev, pipeline, r); + ret = sof_load_pipeline_ipc(scomp->dev, pipeline, r); if (ret >= 0) return ret; err: @@ -1479,7 +1601,7 @@ static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse mixer.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse mixer.cfg tokens failed %d\n", private->size); kfree(mixer); return ret; @@ -1526,7 +1648,7 @@ static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse mux.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse mux.cfg tokens failed %d\n", private->size); kfree(mux); return ret; @@ -1565,8 +1687,8 @@ static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, if (!volume) return -ENOMEM; - if (le32_to_cpu(tw->num_kcontrols) != 1) { - dev_err(sdev->dev, "error: invalid kcontrol count %d for volume\n", + if (!le32_to_cpu(tw->num_kcontrols)) { + dev_err(scomp->dev, "error: invalid kcontrol count %d for volume\n", tw->num_kcontrols); ret = -EINVAL; goto err; @@ -1584,7 +1706,7 @@ static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, ARRAY_SIZE(volume_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse volume tokens failed %d\n", + dev_err(scomp->dev, "error: parse volume tokens failed %d\n", private->size); goto err; } @@ -1592,7 +1714,7 @@ static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse volume.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse volume.cfg tokens failed %d\n", le32_to_cpu(private->size)); goto err; } @@ -1602,7 +1724,8 @@ static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, swidget->private = volume; list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - if (scontrol->comp_id == swidget->comp_id) { + if (scontrol->comp_id == swidget->comp_id && + scontrol->volume_table) { min_step = scontrol->min_volume_step; max_step = scontrol->max_volume_step; volume->min_value = scontrol->volume_table[min_step]; @@ -1651,7 +1774,7 @@ static int sof_widget_load_src(struct snd_soc_component *scomp, int index, ARRAY_SIZE(src_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse src tokens failed %d\n", + dev_err(scomp->dev, "error: parse src tokens failed %d\n", private->size); goto err; } @@ -1660,12 +1783,12 @@ static int sof_widget_load_src(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse src.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse src.cfg tokens failed %d\n", le32_to_cpu(private->size)); goto err; } - dev_dbg(sdev->dev, "src %s: source rate %d sink rate %d\n", + dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n", swidget->widget->name, src->source_rate, src->sink_rate); sof_dbg_comp_config(scomp, &src->config); @@ -1681,6 +1804,67 @@ err: } /* + * ASRC Topology + */ + +static int sof_widget_load_asrc(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_asrc *asrc; + int ret; + + asrc = kzalloc(sizeof(*asrc), GFP_KERNEL); + if (!asrc) + return -ENOMEM; + + /* configure ASRC IPC message */ + asrc->comp.hdr.size = sizeof(*asrc); + asrc->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + asrc->comp.id = swidget->comp_id; + asrc->comp.type = SOF_COMP_ASRC; + asrc->comp.pipeline_id = index; + asrc->config.hdr.size = sizeof(asrc->config); + + ret = sof_parse_tokens(scomp, asrc, asrc_tokens, + ARRAY_SIZE(asrc_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse asrc tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &asrc->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse asrc.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(scomp->dev, "asrc %s: source rate %d sink rate %d " + "asynch %d operation %d\n", + swidget->widget->name, asrc->source_rate, asrc->sink_rate, + asrc->asynchronous_mode, asrc->operation_mode); + sof_dbg_comp_config(scomp, &asrc->config); + + swidget->private = asrc; + + ret = sof_ipc_tx_message(sdev->ipc, asrc->comp.hdr.cmd, asrc, + sizeof(*asrc), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(asrc); + return ret; +} + +/* * Signal Generator Topology */ @@ -1710,7 +1894,7 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, ARRAY_SIZE(tone_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse tone tokens failed %d\n", + dev_err(scomp->dev, "error: parse tone tokens failed %d\n", le32_to_cpu(private->size)); goto err; } @@ -1719,12 +1903,12 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse tone.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse tone.cfg tokens failed %d\n", le32_to_cpu(private->size)); goto err; } - dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", + dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n", swidget->widget->name, tone->frequency, tone->amplitude); sof_dbg_comp_config(scomp, &tone->config); @@ -1739,84 +1923,133 @@ err: return ret; } -static int sof_process_load(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r, - int type) +static int sof_get_control_data(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *widget, + struct sof_widget_data *wdata, + size_t *size) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &tw->priv; - struct snd_soc_dapm_widget *widget = swidget->widget; const struct snd_kcontrol_new *kc; - struct soc_bytes_ext *sbe; struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; struct soc_enum *se; - struct snd_sof_control *scontrol = NULL; - struct sof_abi_hdr *pdata = NULL; - struct sof_ipc_comp_process *process; - size_t ipc_size, ipc_data_size = 0; - int ret, i, offset = 0; + int i; - if (type == SOF_COMP_NONE) { - dev_err(sdev->dev, "error: invalid process comp type %d\n", - type); - return -EINVAL; - } + *size = 0; - /* - * get possible component controls - get size of all pdata, - * then memcpy with headers - */ for (i = 0; i < widget->num_kcontrols; i++) { - kc = &widget->kcontrol_news[i]; switch (widget->dobj.widget.kcontrol_type) { case SND_SOC_TPLG_TYPE_MIXER: sm = (struct soc_mixer_control *)kc->private_value; - scontrol = sm->dobj.private; + wdata[i].control = sm->dobj.private; break; case SND_SOC_TPLG_TYPE_BYTES: sbe = (struct soc_bytes_ext *)kc->private_value; - scontrol = sbe->dobj.private; + wdata[i].control = sbe->dobj.private; break; case SND_SOC_TPLG_TYPE_ENUM: se = (struct soc_enum *)kc->private_value; - scontrol = se->dobj.private; + wdata[i].control = se->dobj.private; break; default: - dev_err(sdev->dev, "error: unknown kcontrol type %d in widget %s\n", + dev_err(scomp->dev, "error: unknown kcontrol type %d in widget %s\n", widget->dobj.widget.kcontrol_type, widget->name); return -EINVAL; } - if (!scontrol) { - dev_err(sdev->dev, "error: no scontrol for widget %s\n", + if (!wdata[i].control) { + dev_err(scomp->dev, "error: no scontrol for widget %s\n", widget->name); return -EINVAL; } - /* don't include if no private data */ - pdata = scontrol->control_data->data; - if (!pdata) - continue; + wdata[i].pdata = wdata[i].control->control_data->data; + if (!wdata[i].pdata) + return -EINVAL; /* make sure data is valid - data can be updated at runtime */ - if (pdata->magic != SOF_ABI_MAGIC) - continue; + if (wdata[i].pdata->magic != SOF_ABI_MAGIC) + return -EINVAL; + + *size += wdata[i].pdata->size; + + /* get data type */ + switch (wdata[i].control->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_ENUM: + case SOF_CTRL_CMD_SWITCH: + wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE; + wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + break; + case SOF_CTRL_CMD_BINARY: + wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA; + wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET; + break; + default: + break; + } + } + + return 0; +} + +static int sof_process_load(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r, + int type) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dapm_widget *widget = swidget->widget; + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_process *process = NULL; + struct sof_widget_data *wdata = NULL; + size_t ipc_data_size = 0; + size_t ipc_size; + int offset = 0; + int ret = 0; + int i; + + if (type == SOF_COMP_NONE) { + dev_err(scomp->dev, "error: invalid process comp type %d\n", + type); + return -EINVAL; + } + + /* allocate struct for widget control data sizes and types */ + if (widget->num_kcontrols) { + wdata = kcalloc(widget->num_kcontrols, + sizeof(*wdata), + GFP_KERNEL); + + if (!wdata) + return -ENOMEM; + + /* get possible component controls and get size of all pdata */ + ret = sof_get_control_data(scomp, widget, wdata, + &ipc_data_size); - ipc_data_size += pdata->size; + if (ret < 0) + goto out; } ipc_size = sizeof(struct sof_ipc_comp_process) + le32_to_cpu(private->size) + ipc_data_size; + /* we are exceeding max ipc size, config needs to be sent separately */ + if (ipc_size > SOF_IPC_MSG_MAX_SIZE) { + ipc_size -= ipc_data_size; + ipc_data_size = 0; + } + process = kzalloc(ipc_size, GFP_KERNEL); - if (!process) - return -ENOMEM; + if (!process) { + ret = -ENOMEM; + goto out; + } /* configure iir IPC message */ process->comp.hdr.size = ipc_size; @@ -1830,7 +2063,7 @@ static int sof_process_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(comp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse process.cfg tokens failed %d\n", + dev_err(scomp->dev, "error: parse process.cfg tokens failed %d\n", le32_to_cpu(private->size)); goto err; } @@ -1842,40 +2075,13 @@ static int sof_process_load(struct snd_soc_component *scomp, int index, * get possible component controls - get size of all pdata, * then memcpy with headers */ - for (i = 0; i < widget->num_kcontrols; i++) { - kc = &widget->kcontrol_news[i]; - - switch (widget->dobj.widget.kcontrol_type) { - case SND_SOC_TPLG_TYPE_MIXER: - sm = (struct soc_mixer_control *)kc->private_value; - scontrol = sm->dobj.private; - break; - case SND_SOC_TPLG_TYPE_BYTES: - sbe = (struct soc_bytes_ext *)kc->private_value; - scontrol = sbe->dobj.private; - break; - case SND_SOC_TPLG_TYPE_ENUM: - se = (struct soc_enum *)kc->private_value; - scontrol = se->dobj.private; - break; - default: - dev_err(sdev->dev, "error: unknown kcontrol type %d in widget %s\n", - widget->dobj.widget.kcontrol_type, - widget->name); - return -EINVAL; + if (ipc_data_size) { + for (i = 0; i < widget->num_kcontrols; i++) { + memcpy(&process->data + offset, + wdata[i].pdata->data, + wdata[i].pdata->size); + offset += wdata[i].pdata->size; } - - /* don't include if no private data */ - pdata = scontrol->control_data->data; - if (!pdata) - continue; - - /* make sure data is valid - data can be updated at runtime */ - if (pdata->magic != SOF_ABI_MAGIC) - continue; - - memcpy(&process->data + offset, pdata->data, pdata->size); - offset += pdata->size; } process->size = ipc_data_size; @@ -1883,10 +2089,35 @@ static int sof_process_load(struct snd_soc_component *scomp, int index, ret = sof_ipc_tx_message(sdev->ipc, process->comp.hdr.cmd, process, ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + + if (ret < 0) { + dev_err(scomp->dev, "error: create process failed\n"); + goto err; + } + + /* we sent the data in single message so return */ + if (ipc_data_size) + goto out; + + /* send control data with large message supported method */ + for (i = 0; i < widget->num_kcontrols; i++) { + wdata[i].control->readback_offset = 0; + ret = snd_sof_ipc_set_get_comp_data(wdata[i].control, + wdata[i].ipc_cmd, + wdata[i].ctrl_type, + wdata[i].control->cmd, + true); + if (ret != 0) { + dev_err(scomp->dev, "error: send control failed\n"); + break; + } + } + err: - kfree(process); + if (ret < 0) + kfree(process); +out: + kfree(wdata); return ret; } @@ -1900,14 +2131,13 @@ static int sof_widget_load_process(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_dapm_widget *tw, struct sof_ipc_comp_reply *r) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_process config; int ret; /* check we have some tokens - we need at least process type */ if (le32_to_cpu(private->size) == 0) { - dev_err(sdev->dev, "error: process tokens not found\n"); + dev_err(scomp->dev, "error: process tokens not found\n"); return -EINVAL; } @@ -1918,7 +2148,7 @@ static int sof_widget_load_process(struct snd_soc_component *scomp, int index, ARRAY_SIZE(process_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse process tokens failed %d\n", + dev_err(scomp->dev, "error: parse process tokens failed %d\n", le32_to_cpu(private->size)); return ret; } @@ -1927,14 +2157,14 @@ static int sof_widget_load_process(struct snd_soc_component *scomp, int index, ret = sof_process_load(scomp, index, swidget, tw, r, find_process_comp_type(config.type)); if (ret < 0) { - dev_err(sdev->dev, "error: process loading failed\n"); + dev_err(scomp->dev, "error: process loading failed\n"); return ret; } return 0; } -static int sof_widget_bind_event(struct snd_sof_dev *sdev, +static int sof_widget_bind_event(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, u16 event_type) { @@ -1960,7 +2190,7 @@ static int sof_widget_bind_event(struct snd_sof_dev *sdev, break; } - dev_err(sdev->dev, + dev_err(scomp->dev, "error: invalid event type %d for widget %s\n", event_type, swidget->widget->name); return -EINVAL; @@ -1982,7 +2212,7 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, if (!swidget) return -ENOMEM; - swidget->sdev = sdev; + swidget->scomp = scomp; swidget->widget = w; swidget->comp_id = sdev->next_comp_id++; swidget->complete = 0; @@ -1991,7 +2221,7 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, swidget->private = NULL; memset(&reply, 0, sizeof(reply)); - dev_dbg(sdev->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n", + dev_dbg(scomp->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n", swidget->comp_id, index, swidget->id, tw->name, strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 ? tw->sname : "none"); @@ -2047,6 +2277,9 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, case snd_soc_dapm_src: ret = sof_widget_load_src(scomp, index, swidget, tw, &reply); break; + case snd_soc_dapm_asrc: + ret = sof_widget_load_asrc(scomp, index, swidget, tw, &reply); + break; case snd_soc_dapm_siggen: ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply); break; @@ -2062,14 +2295,14 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, case snd_soc_dapm_dai_link: case snd_soc_dapm_kcontrol: default: - dev_warn(sdev->dev, "warning: widget type %d name %s not handled\n", + dev_warn(scomp->dev, "warning: widget type %d name %s not handled\n", swidget->id, tw->name); break; } /* check IPC reply */ if (ret < 0 || reply.rhdr.error < 0) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n", tw->shift, swidget->id, tw->name, strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 @@ -2080,10 +2313,10 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, /* bind widget to external event */ if (tw->event_type) { - ret = sof_widget_bind_event(sdev, swidget, + ret = sof_widget_bind_event(scomp, swidget, le16_to_cpu(tw->event_type)); if (ret) { - dev_err(sdev->dev, "error: widget event binding failed\n"); + dev_err(scomp->dev, "error: widget event binding failed\n"); kfree(swidget->private); kfree(swidget); return ret; @@ -2151,7 +2384,7 @@ static int sof_widget_unload(struct snd_soc_component *scomp, pipeline = swidget->private; ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); if (ret < 0) - dev_err(sdev->dev, "error: powering down pipeline schedule core %d\n", + dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n", pipeline->core); /* update enabled cores mask */ @@ -2179,7 +2412,7 @@ static int sof_widget_unload(struct snd_soc_component *scomp, scontrol = sbe->dobj.private; break; default: - dev_warn(sdev->dev, "unsupported kcontrol_type\n"); + dev_warn(scomp->dev, "unsupported kcontrol_type\n"); goto out; } kfree(scontrol->control_data); @@ -2209,6 +2442,7 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_stream_caps *caps; + struct snd_soc_tplg_private *private = &pcm->priv; struct snd_sof_pcm *spcm; int stream = SNDRV_PCM_STREAM_PLAYBACK; int ret = 0; @@ -2221,37 +2455,48 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, if (!spcm) return -ENOMEM; - spcm->sdev = sdev; + spcm->scomp = scomp; spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED; spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED; - if (pcm) { - spcm->pcm = *pcm; - dev_dbg(sdev->dev, "tplg: load pcm %s\n", pcm->dai_name); - } + spcm->pcm = *pcm; + dev_dbg(scomp->dev, "tplg: load pcm %s\n", pcm->dai_name); + dai_drv->dobj.private = spcm; list_add(&spcm->list, &sdev->pcm_list); + ret = sof_parse_tokens(scomp, spcm, stream_tokens, + ARRAY_SIZE(stream_tokens), private->array, + le32_to_cpu(private->size)); + if (ret) { + dev_err(scomp->dev, "error: parse stream tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + /* do we need to allocate playback PCM DMA pages */ if (!spcm->pcm.playback) goto capture; + dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: playback d0i3:%d\n", + spcm->pcm.pcm_name, spcm->stream[0].d0i3_compatible); + caps = &spcm->pcm.caps[stream]; /* allocate playback page table buffer */ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, PAGE_SIZE, &spcm->stream[stream].page_table); if (ret < 0) { - dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + dev_err(scomp->dev, "error: can't alloc page table for %s %d\n", caps->name, ret); return ret; } /* bind pcm to host comp */ - ret = spcm_bind(sdev, spcm, stream); + ret = spcm_bind(scomp, spcm, stream); if (ret) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: can't bind pcm to host\n"); goto free_playback_tables; } @@ -2263,21 +2508,24 @@ capture: if (!spcm->pcm.capture) return ret; + dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: capture d0i3:%d\n", + spcm->pcm.pcm_name, spcm->stream[1].d0i3_compatible); + caps = &spcm->pcm.caps[stream]; /* allocate capture page table buffer */ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, PAGE_SIZE, &spcm->stream[stream].page_table); if (ret < 0) { - dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + dev_err(scomp->dev, "error: can't alloc page table for %s %d\n", caps->name, ret); goto free_playback_tables; } /* bind pcm to host comp */ - ret = spcm_bind(sdev, spcm, stream); + ret = spcm_bind(scomp, spcm, stream); if (ret) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: can't bind pcm to host\n"); snd_dma_free_pages(&spcm->stream[stream].page_table); goto free_playback_tables; @@ -2403,7 +2651,7 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(ssp_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse ssp tokens failed %d\n", + dev_err(scomp->dev, "error: parse ssp tokens failed %d\n", le32_to_cpu(private->size)); return ret; } @@ -2417,7 +2665,7 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots); config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots); - dev_dbg(sdev->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n", + dev_dbg(scomp->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n", config->dai_index, config->format, config->ssp.mclk_rate, config->ssp.bclk_rate, config->ssp.fsync_rate, config->ssp.sample_valid_bits, @@ -2426,13 +2674,81 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, /* validate SSP fsync rate and channel count */ if (config->ssp.fsync_rate < 8000 || config->ssp.fsync_rate > 192000) { - dev_err(sdev->dev, "error: invalid fsync rate for SSP%d\n", + dev_err(scomp->dev, "error: invalid fsync rate for SSP%d\n", config->dai_index); return -EINVAL; } if (config->ssp.tdm_slots < 1 || config->ssp.tdm_slots > 8) { - dev_err(sdev->dev, "error: invalid channel count for SSP%d\n", + dev_err(scomp->dev, "error: invalid channel count for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(scomp->dev, "error: failed to set DAI config for SSP%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(scomp->dev, "error: failed to save DAI config for SSP%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_sai_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->sai, 0, sizeof(struct sof_ipc_dai_sai_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->sai, sai_tokens, + ARRAY_SIZE(sai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse sai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->sai.mclk_direction = hw_config->mclk_direction; + + config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(scomp->dev, + "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->sai.mclk_rate, config->sai.tdm_slot_width, + config->sai.tdm_slots, config->sai.mclk_id); + + if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) { + dev_err(scomp->dev, "error: invalid channel count for SAI%d\n", config->dai_index); return -EINVAL; } @@ -2443,7 +2759,75 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, sizeof(reply)); if (ret < 0) { - dev_err(sdev->dev, "error: failed to set DAI config for SSP%d\n", + dev_err(scomp->dev, "error: failed to set DAI config for SAI%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(scomp->dev, "error: failed to save DAI config for SAI%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_esai_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->esai, 0, sizeof(struct sof_ipc_dai_esai_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->esai, esai_tokens, + ARRAY_SIZE(esai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse esai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->esai.mclk_direction = hw_config->mclk_direction; + config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(scomp->dev, + "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->esai.mclk_rate, config->esai.tdm_slot_width, + config->esai.tdm_slots, config->esai.mclk_id); + + if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) { + dev_err(scomp->dev, "error: invalid channel count for ESAI%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(scomp->dev, "error: failed to set DAI config for ESAI%d\n", config->dai_index); return ret; } @@ -2451,7 +2835,7 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, /* set config for all DAI's with name matching the link name */ ret = sof_set_dai_config(sdev, size, link, config); if (ret < 0) - dev_err(sdev->dev, "error: failed to save DAI config for SSP%d\n", + dev_err(scomp->dev, "error: failed to save DAI config for ESAI%d\n", config->dai_index); return ret; @@ -2484,7 +2868,7 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(dmic_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse dmic tokens failed %d\n", + dev_err(scomp->dev, "error: parse dmic tokens failed %d\n", le32_to_cpu(private->size)); return ret; } @@ -2519,7 +2903,7 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(dmic_pdm_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse dmic pdm tokens failed %d\n", + dev_err(scomp->dev, "error: parse dmic pdm tokens failed %d\n", le32_to_cpu(private->size)); goto err; } @@ -2528,27 +2912,27 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, ipc_config->hdr.size = size; /* debug messages */ - dev_dbg(sdev->dev, "tplg: config DMIC%d driver version %d\n", + dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n", ipc_config->dai_index, ipc_config->dmic.driver_ipc_version); - dev_dbg(sdev->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", + dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", ipc_config->dmic.pdmclk_min, ipc_config->dmic.pdmclk_max, ipc_config->dmic.duty_min); - dev_dbg(sdev->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", + dev_dbg(scomp->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", ipc_config->dmic.duty_max, ipc_config->dmic.fifo_fs, ipc_config->dmic.num_pdm_active); - dev_dbg(sdev->dev, "fifo word length %hd\n", + dev_dbg(scomp->dev, "fifo word length %hd\n", ipc_config->dmic.fifo_bits); for (j = 0; j < ipc_config->dmic.num_pdm_active; j++) { - dev_dbg(sdev->dev, "pdm %hd mic a %hd mic b %hd\n", + dev_dbg(scomp->dev, "pdm %hd mic a %hd mic b %hd\n", ipc_config->dmic.pdm[j].id, ipc_config->dmic.pdm[j].enable_mic_a, ipc_config->dmic.pdm[j].enable_mic_b); - dev_dbg(sdev->dev, "pdm %hd polarity a %hd polarity b %hd\n", + dev_dbg(scomp->dev, "pdm %hd polarity a %hd polarity b %hd\n", ipc_config->dmic.pdm[j].id, ipc_config->dmic.pdm[j].polarity_mic_a, ipc_config->dmic.pdm[j].polarity_mic_b); - dev_dbg(sdev->dev, "pdm %hd clk_edge %hd skew %hd\n", + dev_dbg(scomp->dev, "pdm %hd clk_edge %hd skew %hd\n", ipc_config->dmic.pdm[j].id, ipc_config->dmic.pdm[j].clk_edge, ipc_config->dmic.pdm[j].skew); @@ -2565,7 +2949,7 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, sizeof(reply)); if (ret < 0) { - dev_err(sdev->dev, + dev_err(scomp->dev, "error: failed to set DAI config for DMIC%d\n", config->dai_index); goto err; @@ -2574,7 +2958,7 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, /* set config for all DAI's with name matching the link name */ ret = sof_set_dai_config(sdev, size, link, ipc_config); if (ret < 0) - dev_err(sdev->dev, "error: failed to save DAI config for DMIC%d\n", + dev_err(scomp->dev, "error: failed to save DAI config for DMIC%d\n", config->dai_index); err: @@ -2665,32 +3049,65 @@ static int sof_link_hda_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(hda_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse hda tokens failed %d\n", + dev_err(scomp->dev, "error: parse hda tokens failed %d\n", le32_to_cpu(private->size)); return ret; } dai = snd_soc_find_dai(link->cpus); if (!dai) { - dev_err(sdev->dev, "error: failed to find dai %s in %s", + dev_err(scomp->dev, "error: failed to find dai %s in %s", link->cpus->dai_name, __func__); return -EINVAL; } ret = sof_link_hda_process(sdev, link, config); if (ret < 0) - dev_err(sdev->dev, "error: failed to process hda dai link %s", + dev_err(scomp->dev, "error: failed to process hda dai link %s", link->name); return ret; } +static int sof_link_alh_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* init IPC */ + config->hdr.size = size; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(scomp->dev, "error: failed to set DAI config for ALH %d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(scomp->dev, "error: failed to save DAI config for ALH %d\n", + config->dai_index); + + return ret; +} + /* DAI link - used for any driver specific init */ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &cfg->priv; struct sof_ipc_dai_config config; struct snd_soc_tplg_hw_config *hw_config; @@ -2699,10 +3116,10 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, int i = 0; if (!link->platforms) { - dev_err(sdev->dev, "error: no platforms\n"); + dev_err(scomp->dev, "error: no platforms\n"); return -EINVAL; } - link->platforms->name = dev_name(sdev->dev); + link->platforms->name = dev_name(scomp->dev); /* * Set nonatomic property for FE dai links as their trigger action @@ -2711,13 +3128,17 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, if (!link->no_pcm) { link->nonatomic = true; + /* set trigger order */ + link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST; + link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST; + /* nothing more to do for FE dai links */ return 0; } /* check we have some tokens - we need at least DAI type */ if (le32_to_cpu(private->size) == 0) { - dev_err(sdev->dev, "error: expected tokens for DAI, none found\n"); + dev_err(scomp->dev, "error: expected tokens for DAI, none found\n"); return -EINVAL; } @@ -2729,7 +3150,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, ARRAY_SIZE(dai_link_tokens), private->array, le32_to_cpu(private->size)); if (ret != 0) { - dev_err(sdev->dev, "error: parse link tokens failed %d\n", + dev_err(scomp->dev, "error: parse link tokens failed %d\n", le32_to_cpu(private->size)); return ret; } @@ -2741,12 +3162,12 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, num_hw_configs = le32_to_cpu(cfg->num_hw_configs); if (!num_hw_configs) { if (config.type != SOF_DAI_INTEL_HDA) { - dev_err(sdev->dev, "error: unexpected DAI config count %d!\n", + dev_err(scomp->dev, "error: unexpected DAI config count %d!\n", le32_to_cpu(cfg->num_hw_configs)); return -EINVAL; } } else { - dev_dbg(sdev->dev, "tplg: %d hw_configs found, default id: %d!\n", + dev_dbg(scomp->dev, "tplg: %d hw_configs found, default id: %d!\n", cfg->num_hw_configs, le32_to_cpu(cfg->default_hw_config_id)); for (i = 0; i < num_hw_configs; i++) { @@ -2755,7 +3176,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, } if (i == num_hw_configs) { - dev_err(sdev->dev, "error: default hw_config id: %d not found!\n", + dev_err(scomp->dev, "error: default hw_config id: %d not found!\n", le32_to_cpu(cfg->default_hw_config_id)); return -EINVAL; } @@ -2781,8 +3202,21 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, ret = sof_link_hda_load(scomp, index, link, cfg, hw_config, &config); break; + case SOF_DAI_INTEL_ALH: + ret = sof_link_alh_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_IMX_SAI: + ret = sof_link_sai_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_IMX_ESAI: + ret = sof_link_esai_load(scomp, index, link, cfg, hw_config, + &config); + break; default: - dev_err(sdev->dev, "error: invalid DAI type %d\n", config.type); + dev_err(scomp->dev, "error: invalid DAI type %d\n", + config.type); ret = -EINVAL; break; } @@ -2830,7 +3264,7 @@ static int sof_link_unload(struct snd_soc_component *scomp, goto found; } - dev_err(sdev->dev, "error: failed to find dai %s in %s", + dev_err(scomp->dev, "error: failed to find dai %s in %s", link->name, __func__); return -EINVAL; found: @@ -2838,13 +3272,16 @@ found: switch (sof_dai->dai_config->type) { case SOF_DAI_INTEL_SSP: case SOF_DAI_INTEL_DMIC: - /* no resource needs to be released for SSP and DMIC */ + case SOF_DAI_INTEL_ALH: + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_ESAI: + /* no resource needs to be released for all cases above */ break; case SOF_DAI_INTEL_HDA: ret = sof_link_hda_unload(sdev, link); break; default: - dev_err(sdev->dev, "error: invalid DAI type %d\n", + dev_err(scomp->dev, "error: invalid DAI type %d\n", sof_dai->dai_config->type); ret = -EINVAL; break; @@ -2870,7 +3307,7 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, if (!sroute) return -ENOMEM; - sroute->sdev = sdev; + sroute->scomp = scomp; connect = kzalloc(sizeof(*connect), GFP_KERNEL); if (!connect) { @@ -2881,14 +3318,14 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, connect->hdr.size = sizeof(*connect); connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; - dev_dbg(sdev->dev, "sink %s control %s source %s\n", + dev_dbg(scomp->dev, "sink %s control %s source %s\n", route->sink, route->control ? route->control : "none", route->source); /* source component */ - source_swidget = snd_sof_find_swidget(sdev, (char *)route->source); + source_swidget = snd_sof_find_swidget(scomp, (char *)route->source); if (!source_swidget) { - dev_err(sdev->dev, "error: source %s not found\n", + dev_err(scomp->dev, "error: source %s not found\n", route->source); ret = -EINVAL; goto err; @@ -2907,9 +3344,9 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, connect->source_id = source_swidget->comp_id; /* sink component */ - sink_swidget = snd_sof_find_swidget(sdev, (char *)route->sink); + sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink); if (!sink_swidget) { - dev_err(sdev->dev, "error: sink %s not found\n", + dev_err(scomp->dev, "error: sink %s not found\n", route->sink); ret = -EINVAL; goto err; @@ -2933,7 +3370,7 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, */ if (source_swidget->id != snd_soc_dapm_buffer && sink_swidget->id != snd_soc_dapm_buffer) { - dev_dbg(sdev->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", + dev_dbg(scomp->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", route->source, route->sink); ret = 0; goto err; @@ -2945,7 +3382,7 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, /* check IPC return value */ if (ret < 0) { - dev_err(sdev->dev, "error: failed to add route sink %s control %s source %s\n", + dev_err(scomp->dev, "error: failed to add route sink %s control %s source %s\n", route->sink, route->control ? route->control : "none", route->source); @@ -2954,7 +3391,7 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, /* check IPC reply */ if (reply.error < 0) { - dev_err(sdev->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n", + dev_err(scomp->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n", route->sink, route->control ? route->control : "none", route->source, reply.error); @@ -2981,8 +3418,9 @@ err: /* Function to set the initial value of SOF kcontrols. * The value will be stored in scontrol->control_data */ -static int snd_sof_cache_kcontrol_val(struct snd_sof_dev *sdev) +static int snd_sof_cache_kcontrol_val(struct snd_soc_component *scomp) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_control *scontrol = NULL; int ipc_cmd, ctrl_type; int ret = 0; @@ -3002,33 +3440,34 @@ static int snd_sof_cache_kcontrol_val(struct snd_sof_dev *sdev) ctrl_type = SOF_CTRL_TYPE_DATA_GET; break; default: - dev_err(sdev->dev, + dev_err(scomp->dev, "error: Invalid scontrol->cmd: %d\n", scontrol->cmd); return -EINVAL; } - ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + ret = snd_sof_ipc_set_get_comp_data(scontrol, ipc_cmd, ctrl_type, scontrol->cmd, false); if (ret < 0) { - dev_warn(sdev->dev, - "error: kcontrol value get for widget: %d\n", - scontrol->comp_id); + dev_warn(scomp->dev, + "error: kcontrol value get for widget: %d\n", + scontrol->comp_id); } } return ret; } -int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, +int snd_sof_complete_pipeline(struct device *dev, struct snd_sof_widget *swidget) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct sof_ipc_pipe_ready ready; struct sof_ipc_reply reply; int ret; - dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", + dev_dbg(dev, "tplg: complete pipeline %s id %d\n", swidget->widget->name, swidget->comp_id); memset(&ready, 0, sizeof(ready)); @@ -3058,7 +3497,7 @@ static void sof_complete(struct snd_soc_component *scomp) switch (swidget->id) { case snd_soc_dapm_scheduler: swidget->complete = - snd_sof_complete_pipeline(sdev, swidget); + snd_sof_complete_pipeline(scomp->dev, swidget); break; default: break; @@ -3068,14 +3507,13 @@ static void sof_complete(struct snd_soc_component *scomp) * cache initial values of SOF kcontrols by reading DSP value over * IPC. It may be overwritten by alsa-mixer after booting up */ - snd_sof_cache_kcontrol_val(sdev); + snd_sof_cache_kcontrol_val(scomp); } /* manifest - optional to inform component of manifest */ static int sof_manifest(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_manifest *man) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); u32 size; u32 abi_version; @@ -3083,16 +3521,16 @@ static int sof_manifest(struct snd_soc_component *scomp, int index, /* backward compatible with tplg without ABI info */ if (!size) { - dev_dbg(sdev->dev, "No topology ABI info\n"); + dev_dbg(scomp->dev, "No topology ABI info\n"); return 0; } if (size != SOF_TPLG_ABI_SIZE) { - dev_err(sdev->dev, "error: invalid topology ABI size\n"); + dev_err(scomp->dev, "error: invalid topology ABI size\n"); return -EINVAL; } - dev_info(sdev->dev, + dev_info(scomp->dev, "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", man->priv.data[0], man->priv.data[1], man->priv.data[2], SOF_ABI_MAJOR, SOF_ABI_MINOR, @@ -3103,15 +3541,15 @@ static int sof_manifest(struct snd_soc_component *scomp, int index, man->priv.data[2]); if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) { - dev_err(sdev->dev, "error: incompatible topology ABI version\n"); + dev_err(scomp->dev, "error: incompatible topology ABI version\n"); return -EINVAL; } if (abi_version > SOF_ABI_VERSION) { if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { - dev_warn(sdev->dev, "warn: topology ABI is more recent than kernel\n"); + dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n"); } else { - dev_err(sdev->dev, "error: topology ABI is more recent than kernel\n"); + dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n"); return -EINVAL; } } @@ -3169,34 +3607,25 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), }; -int snd_sof_init_topology(struct snd_sof_dev *sdev, - struct snd_soc_tplg_ops *ops) -{ - /* TODO: support linked list of topologies */ - sdev->tplg_ops = ops; - return 0; -} -EXPORT_SYMBOL(snd_sof_init_topology); - -int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file) +int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { const struct firmware *fw; int ret; - dev_dbg(sdev->dev, "loading topology:%s\n", file); + dev_dbg(scomp->dev, "loading topology:%s\n", file); - ret = request_firmware(&fw, file, sdev->dev); + ret = request_firmware(&fw, file, scomp->dev); if (ret < 0) { - dev_err(sdev->dev, "error: tplg request firmware %s failed err: %d\n", + dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n", file, ret); return ret; } - ret = snd_soc_tplg_component_load(sdev->component, + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw, SND_SOC_TPLG_INDEX_ALL); if (ret < 0) { - dev_err(sdev->dev, "error: tplg component load failed %d\n", + dev_err(scomp->dev, "error: tplg component load failed %d\n", ret); ret = -EINVAL; } diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index befed975161c..d815090252f8 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -148,13 +148,8 @@ static int trace_debugfs_create(struct snd_sof_dev *sdev) dfse->size = sdev->dmatb.bytes; dfse->sdev = sdev; - dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root, - dfse, &sof_dfs_trace_fops); - if (!dfse->dfsentry) { - /* can't rely on debugfs, only log error and keep going */ - dev_err(sdev->dev, - "error: cannot create debugfs entry for trace\n"); - } + debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, + &sof_dfs_trace_fops); return 0; } @@ -167,6 +162,9 @@ int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) struct sof_ipc_reply ipc_reply; int ret; + if (!sdev->dtrace_is_supported) + return 0; + if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) return -EINVAL; @@ -227,6 +225,9 @@ int snd_sof_init_trace(struct snd_sof_dev *sdev) { int ret; + if (!sdev->dtrace_is_supported) + return 0; + /* set false before start initialization */ sdev->dtrace_is_enabled = false; @@ -249,8 +250,8 @@ int snd_sof_init_trace(struct snd_sof_dev *sdev) } /* create compressed page table for audio firmware */ - ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area, - sdev->dmatb.bytes); + ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, + sdev->dmatp.area, sdev->dmatb.bytes); if (ret < 0) goto table_err; @@ -282,6 +283,9 @@ EXPORT_SYMBOL(snd_sof_init_trace); int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, struct sof_ipc_dma_trace_posn *posn) { + if (!sdev->dtrace_is_supported) + return 0; + if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) { sdev->host_offset = posn->host_offset; wake_up(&sdev->trace_sleep); @@ -298,6 +302,9 @@ int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, /* an error has occurred within the DSP that prevents further trace */ void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) { + if (!sdev->dtrace_is_supported) + return; + if (sdev->dtrace_is_enabled) { dev_err(sdev->dev, "error: waking up any trace sleepers\n"); sdev->dtrace_error = true; @@ -310,7 +317,7 @@ void snd_sof_release_trace(struct snd_sof_dev *sdev) { int ret; - if (!sdev->dtrace_is_enabled) + if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled) return; ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); @@ -331,9 +338,15 @@ EXPORT_SYMBOL(snd_sof_release_trace); void snd_sof_free_trace(struct snd_sof_dev *sdev) { + if (!sdev->dtrace_is_supported) + return; + snd_sof_release_trace(sdev); - snd_dma_free_pages(&sdev->dmatb); - snd_dma_free_pages(&sdev->dmatp); + if (sdev->dma_trace_pages) { + snd_dma_free_pages(&sdev->dmatb); + snd_dma_free_pages(&sdev->dmatp); + sdev->dma_trace_pages = 0; + } } EXPORT_SYMBOL(snd_sof_free_trace); diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c index 2ac4c3da0320..9831eb57df6c 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/utils.c @@ -10,6 +10,7 @@ #include <linux/io-64-nonatomic-lo-hi.h> #include <linux/platform_device.h> +#include <asm/unaligned.h> #include <sound/soc.h> #include <sound/sof.h> #include "sof-priv.h" @@ -110,3 +111,62 @@ void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, memcpy_fromio(dest, src, size); } EXPORT_SYMBOL(sof_block_read); + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} +EXPORT_SYMBOL(snd_sof_create_page_table); diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c index 46a4905a9dce..ea08651f0bb3 100644 --- a/sound/soc/sof/xtensa/core.c +++ b/sound/soc/sof/xtensa/core.c @@ -132,7 +132,7 @@ const struct sof_arch_ops sof_xtensa_arch_ops = { .dsp_oops = xtensa_dsp_oops, .dsp_stack = xtensa_stack, }; -EXPORT_SYMBOL(sof_xtensa_arch_ops); +EXPORT_SYMBOL_NS(sof_xtensa_arch_ops, SND_SOC_SOF_XTENSA); MODULE_DESCRIPTION("SOF Xtensa DSP support"); MODULE_LICENSE("Dual BSD/GPL"); |