diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /sound/soc/sof | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
57 files changed, 23185 insertions, 0 deletions
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig new file mode 100644 index 000000000..8c1f0829d --- /dev/null +++ b/sound/soc/sof/Kconfig @@ -0,0 +1,210 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SOC_SOF_TOPLEVEL + bool "Sound Open Firmware Support" + help + This adds support for Sound Open Firmware (SOF). SOF is a free and + generic open source audio DSP firmware for multiple devices. + Say Y if you have such a device that is supported by SOF. + If unsure select "N". + +if SND_SOC_SOF_TOPLEVEL + +config SND_SOC_SOF_PCI + tristate "SOF PCI enumeration support" + depends on PCI + select SND_SOC_SOF + select SND_SOC_ACPI if ACPI + help + This adds support for PCI enumeration. This option is + required to enable Intel Skylake+ devices + Say Y if you need this option + If unsure select "N". + +config SND_SOC_SOF_ACPI + tristate "SOF ACPI enumeration support" + depends on ACPI || COMPILE_TEST + select SND_SOC_SOF + select SND_SOC_ACPI if ACPI + select IOSF_MBI if X86 && PCI + help + This adds support for ACPI enumeration. This option is required + to enable Intel Broadwell/Baytrail/Cherrytrail devices + Say Y if you need this option + If unsure select "N". + +config SND_SOC_SOF_OF + tristate "SOF OF enumeration support" + depends on OF || COMPILE_TEST + select SND_SOC_SOF + help + 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_DEBUG_PROBES + bool "SOF enable data probing" + select SND_SOC_COMPRESS + help + This option enables the data probing feature that can be used to + gather data directly from specific points of the audio pipeline. + Say Y if you want to enable probes. + 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_DEVELOPER_SUPPORT + +config SND_SOC_SOF_NOCODEC + tristate + +config SND_SOC_SOF_NOCODEC_SUPPORT + bool "SOF nocodec mode support" + help + This adds support for a dummy/nocodec machine driver fallback + 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". + +config SND_SOC_SOF_STRICT_ABI_CHECKS + bool "SOF strict ABI checks" + help + This option enables strict ABI checks for firmware and topology + files. + When these files are more recent than the kernel, the kernel + will handle the functionality it supports and may report errors + during topology creation or run-time usage if new functionality + is invoked. + This option will stop topology creation and firmware load upfront. + It is intended for SOF CI/releases and not for users or distros. + Say Y if you want strict ABI checks for an SOF release + If you are not involved in SOF releases and CI development + select "N". + +config SND_SOC_SOF_DEBUG + bool "SOF debugging features" + help + This option can be used to enable or disable individual SOF firmware + and driver debugging options. + Say Y if you are debugging SOF FW or drivers. + If unsure select "N". + +if SND_SOC_SOF_DEBUG + +config SND_SOC_SOF_FORCE_NOCODEC_MODE + bool "SOF force nocodec Mode" + depends on SND_SOC_SOF_NOCODEC_SUPPORT + help + This forces SOF to use dummy/nocodec as machine driver, even + though there is a codec detected on the real platform. This is + typically only enabled for developers for debug purposes, before + codec/machine driver is ready, or to exclude the impact of those + drivers + Say Y if you need this force nocodec mode option + If unsure select "N". + +config SND_SOC_SOF_DEBUG_XRUN_STOP + bool "SOF stop on XRUN" + help + This option forces PCMs to stop on any XRUN event. This is useful to + preserve any trace data ond pipeline status prior to the XRUN. + Say Y if you are debugging SOF FW pipeline XRUNs. + If unsure select "N". + +config SND_SOC_SOF_DEBUG_VERBOSE_IPC + bool "SOF verbose IPC logs" + help + This option enables more verbose IPC logs, with command types in + human-readable form instead of just 32-bit hex dumps. This is useful + if you are trying to debug IPC with the DSP firmware. + If unsure select "N". + +config SND_SOC_SOF_DEBUG_FORCE_IPC_POSITION + bool "SOF force to use IPC for position update on SKL+" + help + This option force to handle stream position update IPCs and run pcm + elapse to inform ALSA about that, on platforms (e.g. Intel SKL+) that + with other approach (e.g. HDAC DPIB/posbuf) to elapse PCM. + On platforms (e.g. Intel SKL-) where position update IPC is the only + one choice, this setting won't impact anything. + if you are trying to debug pointer update with position IPCs or where + DPIB/posbuf is not ready, select "Y". + If unsure select "N". + +config SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE + bool "SOF enable debugfs caching" + help + This option enables caching of debugfs + memory -> DSP resource (memory, register, etc) + before the audio DSP is suspended. This will increase the suspend + latency and therefore should be used for debug purposes only. + 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 + This option enables the IPC flood test which can be used to flood + the DSP with test IPCs and gather stats about response times. + 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_DEVELOPER_SUPPORT + +config SND_SOC_SOF + tristate + select SND_SOC_TOPOLOGY + select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_SUPPORT + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + The selection is made at the top level and does not exactly follow + module dependencies but since the module or built-in type is decided + at the top level it doesn't matter. + +config SND_SOC_SOF_PROBE_WORK_QUEUE + bool + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + 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" + +endif diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile new file mode 100644 index 000000000..05718dfe6 --- /dev/null +++ b/sound/soc/sof/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: (GPL-2.0-only 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 sof-audio.o +snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.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) += 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/compress.c b/sound/soc/sof/compress.c new file mode 100644 index 000000000..2d4969c70 --- /dev/null +++ b/sound/soc/sof/compress.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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-2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <sound/soc.h> +#include "compress.h" +#include "ops.h" +#include "probe.h" + +struct snd_compress_ops sof_probe_compressed_ops = { + .copy = sof_probe_compr_copy, +}; +EXPORT_SYMBOL(sof_probe_compressed_ops); + +int sof_probe_compr_open(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + int ret; + + ret = snd_sof_probe_compr_assign(sdev, cstream, dai); + if (ret < 0) { + dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); + return ret; + } + + sdev->extractor_stream_tag = ret; + return 0; +} +EXPORT_SYMBOL(sof_probe_compr_open); + +int sof_probe_compr_free(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + struct sof_probe_point_desc *desc; + size_t num_desc; + int i, ret; + + /* disconnect all probe points */ + ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); + if (ret < 0) { + dev_err(dai->dev, "Failed to get probe points: %d\n", ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); + kfree(desc); + +exit: + ret = sof_ipc_probe_deinit(sdev); + if (ret < 0) + dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); + + sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; + snd_compr_free_pages(cstream); + + return snd_sof_probe_compr_free(sdev, cstream, dai); +} +EXPORT_SYMBOL(sof_probe_compr_free); + +int sof_probe_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, struct snd_soc_dai *dai) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + int ret; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sdev->dev; + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + + ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); + if (ret < 0) + return ret; + + ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, + rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "Failed to init probe: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sof_probe_compr_set_params); + +int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + + return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); +} +EXPORT_SYMBOL(sof_probe_compr_trigger); + +int sof_probe_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + + return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); +} +EXPORT_SYMBOL(sof_probe_compr_pointer); + +int sof_probe_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} +EXPORT_SYMBOL(sof_probe_compr_copy); diff --git a/sound/soc/sof/compress.h b/sound/soc/sof/compress.h new file mode 100644 index 000000000..ca8790bd4 --- /dev/null +++ b/sound/soc/sof/compress.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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-2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOF_COMPRESS_H +#define __SOF_COMPRESS_H + +#include <sound/compress_driver.h> + +extern struct snd_compress_ops sof_probe_compressed_ops; + +int sof_probe_compr_open(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int sof_probe_compr_free(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int sof_probe_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, struct snd_soc_dai *dai); +int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai); +int sof_probe_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai); +int sof_probe_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count); + +#endif diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c new file mode 100644 index 000000000..0352d2b61 --- /dev/null +++ b/sound/soc/sof/control.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* 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) +{ + int temp = 0; + 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) +{ + if (value >= size) + return volume_map[size - 1]; + + return volume_map[value]; +} + +static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = + ipc_to_mixer(cdata->chanv[i].value, + scontrol->volume_table, sm->max + 1); + + return 0; +} + +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + 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++) { + 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(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 change; +} + +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + return 0; +} + +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + 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++) { + 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(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 change; +} + +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; + + return 0; +} + +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + 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++) { + 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(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 change; +} + +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + 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; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (data->size > be->max - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "error: %u bytes of control data is invalid, max is %zu\n", + data->size, be->max - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + + return 0; +} + +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + 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; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (data->size > be->max - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "error: data size too big %u bytes max is %zu\n", + data->size, be->max - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, size); + + /* notify DSP of byte control updates */ + 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, + true); + + return 0; +} + +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + 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 = + (const struct snd_ctl_tlv __user *)binary_data; + + /* make sure we have at least a header */ + if (size < sizeof(struct snd_ctl_tlv)) + return -EINVAL; + + /* + * The beginning of bytes data contains a header from where + * the length (as bytes) is needed to know the correct copy + * length of data from tlvd->tlv. + */ + if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv))) + return -EFAULT; + + /* make sure TLV info is consistent */ + if (header.length + sizeof(struct snd_ctl_tlv) > size) { + dev_err_ratelimited(scomp->dev, "error: inconsistent TLV, data %d + header %zu > %d\n", + header.length, sizeof(struct snd_ctl_tlv), size); + return -EINVAL; + } + + /* be->max is coming from topology */ + if (header.length > be->max) { + 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(scomp->dev, + "error: incorrect numid %d\n", + header.numid); + return -EINVAL; + } + + if (copy_from_user(cdata->data, tlvd->tlv, header.length)) + return -EFAULT; + + if (cdata->data->magic != SOF_ABI_MAGIC) { + 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(scomp->dev, "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + + /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) { + 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(scomp->dev)) + snd_sof_ipc_set_get_comp_data(scontrol, + SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, + scontrol->cmd, + true); + + return 0; +} + +int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + 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 = (struct snd_ctl_tlv __user *)binary_data; + size_t data_size; + int ret; + int err; + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + if (size < sizeof(struct snd_ctl_tlv)) + return -ENOSPC; + size -= sizeof(struct snd_ctl_tlv); + + ret = pm_runtime_get_sync(scomp->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to resume %d\n", ret); + pm_runtime_put_noidle(scomp->dev); + return ret; + } + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + /* get all the component data from DSP */ + ret = snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_GET_DATA, SOF_CTRL_TYPE_DATA_GET, + scontrol->cmd, false); + if (ret < 0) + goto out; + + /* check data size doesn't exceed max coming from topology */ + if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) { + dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n", + cdata->data->size, + be->max - sizeof(const struct sof_abi_hdr)); + ret = -EINVAL; + goto out; + } + + data_size = cdata->data->size + sizeof(const struct sof_abi_hdr); + + /* make sure we don't exceed size provided by user space for data */ + if (data_size > size) { + ret = -ENOSPC; + goto out; + } + + header.numid = scontrol->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) { + ret = -EFAULT; + goto out; + } + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + ret = -EFAULT; +out: + pm_runtime_mark_last_busy(scomp->dev); + err = pm_runtime_put_autosuspend(scomp->dev); + if (err < 0) + dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to idle %d\n", err); + + return ret; +} + +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + 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 = + (struct snd_ctl_tlv __user *)binary_data; + size_t data_size; + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + if (size < sizeof(struct snd_ctl_tlv)) + return -ENOSPC; + size -= sizeof(struct snd_ctl_tlv); + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* check data size doesn't exceed max coming from topology */ + if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) { + dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n", + cdata->data->size, + be->max - sizeof(const struct sof_abi_hdr)); + return -EINVAL; + } + + data_size = cdata->data->size + sizeof(const struct sof_abi_hdr); + + /* make sure we don't exceed size provided by user space for data */ + if (data_size > size) + return -ENOSPC; + + header.numid = scontrol->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) + return -EFAULT; + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + return -EFAULT; + + return 0; +} diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c new file mode 100644 index 000000000..feced9077 --- /dev/null +++ b/sound/soc/sof/core.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) +#include "probe.h" +#endif + +/* 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)"); + +/* 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. + */ + +struct sof_panic_msg { + u32 id; + const char *msg; +}; + +/* standard FW panic types */ +static const struct sof_panic_msg panic_msg[] = { + {SOF_IPC_PANIC_MEM, "out of memory"}, + {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, + {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, + {SOF_IPC_PANIC_ARCH, "arch init failed"}, + {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, + {SOF_IPC_PANIC_TASK, "scheduler init failed"}, + {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, + {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, + {SOF_IPC_PANIC_STACK, "stack overflow"}, + {SOF_IPC_PANIC_IDLE, "can't enter idle"}, + {SOF_IPC_PANIC_WFI, "invalid wait state"}, + {SOF_IPC_PANIC_ASSERT, "assertion failed"}, +}; + +/* + * helper to be called from .dbg_dump callbacks. No error code is + * provided, it's left as an exercise for the caller of .dbg_dump + * (typically IPC or loader) + */ +void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words) +{ + u32 code; + int i; + + /* is firmware dead ? */ + if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { + dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", + panic_code, tracep_code); + return; /* no fault ? */ + } + + code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); + + for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { + if (panic_msg[i].id == code) { + dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); + dev_err(sdev->dev, "error: trace point %8.8x\n", + tracep_code); + goto out; + } + } + + /* unknown error */ + dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); + dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); + +out: + dev_err(sdev->dev, "error: panic at %s:%d\n", + panic_info->filename, panic_info->linenum); + sof_oops(sdev, oops); + sof_stack(sdev, oops, stack, stack_words); +} +EXPORT_SYMBOL(snd_sof_get_status); + +/* + * 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_probe_continue(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + int ret; + + /* probe the DSP hardware */ + ret = snd_sof_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); + return ret; + } + + sdev->fw_state = SOF_FW_BOOT_PREPARE; + + /* check machine info */ + ret = sof_machine_check(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get machine info %d\n", + ret); + goto dbg_err; + } + + /* set up platform component driver */ + snd_sof_new_platform_drv(sdev); + + /* register any debug/trace capabilities */ + ret = snd_sof_dbg_init(sdev); + if (ret < 0) { + /* + * debugfs issues are suppressed in snd_sof_dbg_init() since + * we cannot rely on debugfs + * here we trap errors due to memory allocation only. + */ + dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", + ret); + goto dbg_err; + } + + /* init the IPC */ + sdev->ipc = snd_sof_ipc_init(sdev); + if (!sdev->ipc) { + ret = -ENOMEM; + dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); + goto ipc_err; + } + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", + ret); + goto fw_load_err; + } + + 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", + ret); + goto fw_run_err; + } + + 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 */ + sdev->first_boot = false; + + /* now register audio DSP platform driver and dai */ + ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, + sof_ops(sdev)->drv, + sof_ops(sdev)->num_drv); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto fw_trace_err; + } + + ret = snd_sof_machine_register(sdev, plat_data); + if (ret < 0) + goto fw_trace_err; + + /* + * 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; + +fw_trace_err: + snd_sof_free_trace(sdev); +fw_run_err: + snd_sof_fw_unload(sdev); +fw_load_err: + snd_sof_ipc_free(sdev); +ipc_err: + snd_sof_free_debug(sdev); +dbg_err: + snd_sof_remove(sdev); + + /* all resources freed, update state to match */ + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sdev->first_boot = true; + + return ret; +} + +static void sof_probe_work(struct work_struct *work) +{ + struct snd_sof_dev *sdev = + container_of(work, struct snd_sof_dev, probe_work); + int ret; + + ret = sof_probe_continue(sdev); + if (ret < 0) { + /* errors cannot be propagated, log */ + dev_err(sdev->dev, "error: %s failed err: %d\n", __func__, ret); + } +} + +int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) +{ + struct snd_sof_dev *sdev; + + sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + /* initialize sof device */ + sdev->dev = dev; + + /* initialize default DSP power state */ + sdev->dsp_power_state.state = SOF_DSP_PM_D0; + + sdev->pdata = plat_data; + sdev->first_boot = true; + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) + sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; +#endif + 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)->fw_ready) + return -EINVAL; + + INIT_LIST_HEAD(&sdev->pcm_list); + INIT_LIST_HEAD(&sdev->kcontrol_list); + INIT_LIST_HEAD(&sdev->widget_list); + INIT_LIST_HEAD(&sdev->dai_list); + INIT_LIST_HEAD(&sdev->route_list); + spin_lock_init(&sdev->ipc_lock); + spin_lock_init(&sdev->hw_lock); + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) + INIT_WORK(&sdev->probe_work, sof_probe_work); + + /* set default timeouts if none provided */ + if (plat_data->desc->ipc_timeout == 0) + sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; + else + sdev->ipc_timeout = plat_data->desc->ipc_timeout; + if (plat_data->desc->boot_timeout == 0) + sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; + else + sdev->boot_timeout = plat_data->desc->boot_timeout; + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { + schedule_work(&sdev->probe_work); + return 0; + } + + return sof_probe_continue(sdev); +} +EXPORT_SYMBOL(snd_sof_device_probe); + +int snd_sof_device_remove(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_pdata *pdata = sdev->pdata; + int ret; + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) + cancel_work_sync(&sdev->probe_work); + + if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { + ret = snd_sof_dsp_power_down_notify(sdev); + if (ret < 0) + dev_warn(dev, "error: %d failed to prepare DSP for device removal", + ret); + + 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. + */ + snd_sof_machine_unregister(sdev, pdata); + + /* + * Unregistering the machine driver results in unloading the topology. + * Some widgets, ex: scheduler, attempt to power down the core they are + * scheduled on, when they are unloaded. Therefore, the DSP must be + * removed only after the topology has been unloaded. + */ + if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) + snd_sof_remove(sdev); + + /* release firmware */ + snd_sof_fw_unload(sdev); + + return 0; +} +EXPORT_SYMBOL(snd_sof_device_remove); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-audio"); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c new file mode 100644 index 000000000..3ef51b221 --- /dev/null +++ b/sound/soc/sof/debug.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic debug routines used to export DSP MMIO and memories to userspace +// for firmware debugging. +// + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include "sof-priv.h" +#include "ops.h" + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) +#include "probe.h" + +/** + * strsplit_u32 - Split string into sequence of u32 tokens + * @buf: String to split into tokens. + * @delim: String containing delimiter characters. + * @tkns: Returned u32 sequence pointer. + * @num_tkns: Returned number of tokens obtained. + */ +static int +strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) +{ + char *s; + u32 *data, *tmp; + size_t count = 0; + size_t cap = 32; + int ret = 0; + + *tkns = NULL; + *num_tkns = 0; + data = kcalloc(cap, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + while ((s = strsep(buf, delim)) != NULL) { + ret = kstrtouint(s, 0, data + count); + if (ret) + goto exit; + if (++count >= cap) { + cap *= 2; + tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); + if (!tmp) { + ret = -ENOMEM; + goto exit; + } + data = tmp; + } + } + + if (!count) + goto exit; + *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); + if (*tkns == NULL) { + ret = -ENOMEM; + goto exit; + } + *num_tkns = count; + +exit: + kfree(data); + return ret; +} + +static int tokenize_input(const char __user *from, size_t count, + loff_t *ppos, u32 **tkns, size_t *num_tkns) +{ + char *buf; + int ret; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = simple_write_to_buffer(buf, count, ppos, from, count); + if (ret != count) { + ret = ret >= 0 ? -EIO : ret; + goto exit; + } + + buf[count] = '\0'; + ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); +exit: + kfree(buf); + return ret; +} + +static ssize_t probe_points_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_probe_point_desc *desc; + size_t num_desc, len = 0; + char *buf; + int i, ret; + + if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(sdev->dev, "no extractor stream running\n"); + return -ENOENT; + } + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); + if (ret < 0) + goto exit; + + for (i = 0; i < num_desc; i++) { + ret = snprintf(buf + len, PAGE_SIZE - len, + "Id: %#010x Purpose: %d Node id: %#x\n", + desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); + if (ret < 0) + goto free_desc; + len += ret; + } + + ret = simple_read_from_buffer(to, count, ppos, buf, len); +free_desc: + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t probe_points_write(struct file *file, + const char __user *from, size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_probe_point_desc *desc; + size_t num_tkns, bytes; + u32 *tkns; + int ret; + + if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(sdev->dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + bytes = sizeof(*tkns) * num_tkns; + if (!num_tkns || (bytes % sizeof(*desc))) { + ret = -EINVAL; + goto exit; + } + + desc = (struct sof_probe_point_desc *)tkns; + ret = sof_ipc_probe_points_add(sdev, + desc, bytes / sizeof(*desc)); + if (!ret) + ret = count; +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations probe_points_fops = { + .open = simple_open, + .read = probe_points_read, + .write = probe_points_write, + .llseek = default_llseek, +}; + +static ssize_t probe_points_remove_write(struct file *file, + const char __user *from, size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + size_t num_tkns; + u32 *tkns; + int ret; + + if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(sdev->dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + if (!num_tkns) { + ret = -EINVAL; + goto exit; + } + + ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); + if (!ret) + ret = count; +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations probe_points_remove_fops = { + .open = simple_open, + .write = probe_points_remove_write, + .llseek = default_llseek, +}; + +static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, + const char *name, mode_t mode, + const struct file_operations *fops) +{ + struct snd_sof_dfsentry *dfse; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->sdev = sdev; + + debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) +#define MAX_IPC_FLOOD_DURATION_MS 1000 +#define MAX_IPC_FLOOD_COUNT 10000 +#define IPC_FLOOD_TEST_RESULT_LEN 512 + +static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev, + struct snd_sof_dfsentry *dfse, + bool flood_duration_test, + unsigned long ipc_duration_ms, + unsigned long ipc_count) +{ + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; + u64 min_response_time = U64_MAX; + ktime_t start, end, test_end; + u64 avg_response_time = 0; + u64 max_response_time = 0; + u64 ipc_response_time; + int i = 0; + int ret; + + /* configure test IPC */ + hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; + hdr.size = sizeof(hdr); + + /* set test end time for duration flood test */ + if (flood_duration_test) + test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; + + /* send test IPC's */ + while (1) { + start = ktime_get(); + ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, + &reply, sizeof(reply)); + end = ktime_get(); + + if (ret < 0) + break; + + /* compute min and max response times */ + ipc_response_time = ktime_to_ns(ktime_sub(end, start)); + min_response_time = min(min_response_time, ipc_response_time); + max_response_time = max(max_response_time, ipc_response_time); + + /* sum up response times */ + avg_response_time += ipc_response_time; + i++; + + /* test complete? */ + if (flood_duration_test) { + if (ktime_to_ns(end) >= test_end) + break; + } else { + if (i == ipc_count) + break; + } + } + + if (ret < 0) + dev_err(sdev->dev, + "error: ipc flood test failed at %d iterations\n", i); + + /* return if the first IPC fails */ + if (!i) + return ret; + + /* compute average response time */ + do_div(avg_response_time, i); + + /* clear previous test output */ + memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN); + + if (flood_duration_test) { + dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n", + ipc_duration_ms); + snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN, + "IPC Flood test duration: %lums\n", ipc_duration_ms); + } + + dev_dbg(sdev->dev, + "IPC Flood count: %d, Avg response time: %lluns\n", + i, avg_response_time); + dev_dbg(sdev->dev, "Max response time: %lluns\n", + max_response_time); + dev_dbg(sdev->dev, "Min response time: %lluns\n", + min_response_time); + + /* format output string */ + snprintf(dfse->cache_buf + strlen(dfse->cache_buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), + "IPC Flood count: %d\nAvg response time: %lluns\n", + i, avg_response_time); + + snprintf(dfse->cache_buf + strlen(dfse->cache_buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), + "Max response time: %lluns\nMin response time: %lluns\n", + max_response_time, min_response_time); + + return ret; +} +#endif + +static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + 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; + char *string; + int ret; + + string = kzalloc(count+1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + ret = size; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) + /* + * write op is only supported for ipc_flood_count or + * ipc_flood_duration_ms debugfs entries atm. + * ipc_flood_count floods the DSP with the number of IPC's specified. + * ipc_duration_ms test floods the DSP for the time specified + * in the debugfs entry. + */ + 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(dentry->d_name.name, "ipc_flood_duration_ms")) + flood_duration_test = true; + + /* test completion criterion */ + if (flood_duration_test) + ret = kstrtoul(string, 0, &ipc_duration_ms); + else + ret = kstrtoul(string, 0, &ipc_count); + if (ret < 0) + goto out; + + /* limit max duration/ipc count for flood test */ + if (flood_duration_test) { + if (!ipc_duration_ms) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) + ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; + } else { + if (!ipc_count) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_count > MAX_IPC_FLOOD_COUNT) + ipc_count = MAX_IPC_FLOOD_COUNT; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(sdev->dev, + "error: debugfs write failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + goto out; + } + + /* flood test */ + ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test, + ipc_duration_ms, ipc_count); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: debugfs write failed to idle %d\n", + err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; +out: +#endif + kfree(string); + return ret; +} + +static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + loff_t pos = *ppos; + size_t size_ret; + int skip = 0; + int size; + u8 *buf; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) + 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; + + count = strlen(dfse->cache_buf); + size_ret = copy_to_user(buffer, dfse->cache_buf, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + return count; + } +#endif + size = dfse->size; + + /* validate position & count */ + if (pos < 0) + return -EINVAL; + if (pos >= size || !count) + return 0; + /* find the minimum. min() is not used since it adds sparse warnings */ + if (count > size - pos) + count = size - pos; + + /* align io read start to u32 multiple */ + pos = ALIGN_DOWN(pos, 4); + + /* intermediate buffer size must be u32 multiple */ + size = ALIGN(count, 4); + + /* if start position is unaligned, read extra u32 */ + if (unlikely(pos != *ppos)) { + skip = *ppos - pos; + if (pos + size + 4 < dfse->size) + size += 4; + } + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* + * If the DSP is active: copy from IO. + * If the DSP is suspended: + * - Copy from IO if the memory is always accessible. + * - Otherwise, copy from cached buffer. + */ + if (pm_runtime_active(sdev->dev) || + dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) { + memcpy_fromio(buf, dfse->io_mem + pos, size); + } else { + dev_info(sdev->dev, + "Copying cached debugfs data\n"); + memcpy(buf, dfse->cache_buf + pos, size); + } +#else + /* if the DSP is in D3 */ + if (!pm_runtime_active(sdev->dev) && + dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { + dev_err(sdev->dev, + "error: debugfs entry cannot be read in DSP D3\n"); + kfree(buf); + return -EINVAL; + } + + memcpy_fromio(buf, dfse->io_mem + pos, size); +#endif + } else { + memcpy(buf, ((u8 *)(dfse->buf) + pos), size); + } + + /* copy to userspace */ + size_ret = copy_to_user(buffer, buf + skip, count); + + kfree(buf); + + /* update count & position if copy succeeded */ + if (size_ret) + return -EFAULT; + + *ppos = pos + count; + + return count; +} + +static const struct file_operations sof_dfs_fops = { + .open = simple_open, + .read = sof_dfsentry_read, + .llseek = default_llseek, + .write = sof_dfsentry_write, +}; + +/* create FS entry for debug files that can expose DSP memories, registers */ +int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name, + enum sof_debugfs_access_type access_type) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_IOMEM; + dfse->io_mem = base; + dfse->size = size; + dfse->sdev = sdev; + dfse->access_type = access_type; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* + * allocate cache buffer that will be used to save the mem window + * contents prior to suspend + */ + if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { + dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL); + if (!dfse->cache_buf) + return -ENOMEM; + } +#endif + + 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; +} +EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item); + +/* create FS entry for debug files to expose kernel memory */ +int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name, mode_t mode) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = base; + dfse->size = size; + dfse->sdev = sdev; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) + /* + * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries. + * So, use it to save the results of the last IPC flood test. + */ + dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN, + GFP_KERNEL); + if (!dfse->cache_buf) + return -ENOMEM; +#endif + + 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; +} +EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); + +int snd_sof_dbg_init(struct snd_sof_dev *sdev) +{ + const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + const struct snd_sof_debugfs_map *map; + int i; + int err; + + /* use "sof" as top level debugFS dir */ + sdev->debugfs_root = debugfs_create_dir("sof", NULL); + + /* init dfsentry list */ + INIT_LIST_HEAD(&sdev->dfsentry_list); + + /* create debugFS files for platform specific MMIO/DSP memories */ + for (i = 0; i < ops->debug_map_count; i++) { + map = &ops->debug_map[i]; + + err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] + + map->offset, map->size, + map->name, map->access_type); + /* errors are only due to memory allocation, not debugfs */ + if (err < 0) + return err; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) + err = snd_sof_debugfs_probe_item(sdev, "probe_points", + 0644, &probe_points_fops); + if (err < 0) + return err; + err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", + 0200, &probe_points_remove_fops); + if (err < 0) + return err; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) + /* create read-write ipc_flood_count debugfs entry */ + err = snd_sof_debugfs_buf_item(sdev, NULL, 0, + "ipc_flood_count", 0666); + + /* errors are only due to memory allocation, not debugfs */ + if (err < 0) + return err; + + /* create read-write ipc_flood_duration_ms debugfs entry */ + err = snd_sof_debugfs_buf_item(sdev, NULL, 0, + "ipc_flood_duration_ms", 0666); + + /* errors are only due to memory allocation, not debugfs */ + if (err < 0) + return err; +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sof_dbg_init); + +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 000000000..48f998a19 --- /dev/null +++ b/sound/soc/sof/imx/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only 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_IMX_OF + def_tristate SND_SOC_SOF_OF + select SND_SOC_SOF_IMX8 if SND_SOC_SOF_IMX8_SUPPORT + select SND_SOC_SOF_IMX8M if SND_SOC_SOF_IMX8M_SUPPORT + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_IMX_COMMON + tristate + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_IMX8_SUPPORT + bool "SOF support for i.MX8" + depends on IMX_SCU=y || IMX_SCU=SND_SOC_SOF_IMX_OF + depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_IMX_OF + 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 + tristate + select SND_SOC_SOF_IMX_COMMON + select SND_SOC_SOF_XTENSA + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_IMX8M_SUPPORT + bool "SOF support for i.MX8M" + depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_OF + help + This adds support for Sound Open Firmware for NXP i.MX8M platforms + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_IMX8M + tristate + select SND_SOC_SOF_IMX_COMMON + select SND_SOC_SOF_XTENSA + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +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 000000000..dba93c346 --- /dev/null +++ b/sound/soc/sof/imx/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +snd-sof-imx8-objs := imx8.o +snd-sof-imx8m-objs := imx8m.o + +snd-sof-imx-common-objs := imx-common.o + +obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o +obj-$(CONFIG_SND_SOC_SOF_IMX8M) += snd-sof-imx8m.o +obj-$(CONFIG_SND_SOC_SOF_IMX_COMMON) += imx-common.o diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c new file mode 100644 index 000000000..5fee63783 --- /dev/null +++ b/sound/soc/sof/imx/imx-common.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2020 NXP +// +// Common helpers for the audio DSP on i.MX8 + +#include <linux/module.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" + +#include "imx-common.h" + +/** + * imx8_get_registers() - This function is called in case of DSP oops + * in order to gather information about the registers, filename and + * linenumber and stack. + * @sdev: SOF device + * @xoops: Stores information about registers. + * @panic_info: Stores information about filename and line number. + * @stack: Stores the stack dump. + * @stack_words: Size of the stack dump. + */ +void imx8_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* 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)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +/** + * imx8_dump() - This function is called when a panic message is + * received from the firmware. + */ +void imx8_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[IMX8_STACK_DUMP_SIZE]; + u32 status; + + /* Get information about the panic status from the debug box area. + * Compute the trace point based on the status. + */ + sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4); + + /* Get information about the registers, the filename and line + * number and the stack. + */ + imx8_get_registers(sdev, &xoops, &panic_info, stack, + IMX8_STACK_DUMP_SIZE); + + /* Print the information to the console */ + snd_sof_get_status(sdev, status, status, &xoops, &panic_info, stack, + IMX8_STACK_DUMP_SIZE); +} +EXPORT_SYMBOL(imx8_dump); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h new file mode 100644 index 000000000..1cc7d6704 --- /dev/null +++ b/sound/soc/sof/imx/imx-common.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +#ifndef __IMX_COMMON_H__ +#define __IMX_COMMON_H__ + +#define EXCEPT_MAX_HDR_SIZE 0x400 +#define IMX8_STACK_DUMP_SIZE 32 + +void imx8_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words); + +void imx8_dump(struct snd_sof_dev *sdev, u32 flags); + +#endif diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c new file mode 100644 index 000000000..4e7dccadd --- /dev/null +++ b/sound/soc/sof/imx/imx8.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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" +#include "imx-common.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); + u32 p; /* panic code */ + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p)); + + /* Check to see if the message is a panic code (0x0dead***) */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) + snd_sof_dsp_panic(priv->sdev, p); + else + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static 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 = sdev->pdata->hw_pdata; + + 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 imx8x_run(struct snd_sof_dev *sdev) +{ + struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata; + 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_run(struct snd_sof_dev *sdev) +{ + struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata; + int ret; + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 0); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset source select\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->pdata->hw_pdata = 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 = sdev->pdata->hw_pdata; + 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 = "esai0", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "sai1", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, +}, +}; + +/* 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, + + /* Module IO */ + .read64 = sof_io_read64, + + /* 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, + + /* Debug information */ + .dbg_dump = imx8_dump, + + /* Firmware ops */ + .arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = imx8_dai, + .num_drv = ARRAY_SIZE(imx8_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_NO_PERIOD_WAKEUP, +}; +EXPORT_SYMBOL(sof_imx8_ops); + +/* i.MX8X ops */ +struct snd_sof_dsp_ops sof_imx8x_ops = { + /* probe and remove */ + .probe = imx8_probe, + .remove = imx8_remove, + /* DSP core boot */ + .run = imx8x_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Module IO */ + .read64 = sof_io_read64, + + /* 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, + + /* Debug information */ + .dbg_dump = imx8_dump, + + /* Firmware ops */ + .arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = imx8_dai, + .num_drv = ARRAY_SIZE(imx8_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_NO_PERIOD_WAKEUP +}; +EXPORT_SYMBOL(sof_imx8x_ops); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c new file mode 100644 index 000000000..6943c0527 --- /dev/null +++ b/sound/soc/sof/imx/imx8m.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2020 NXP +// +// Author: Daniel Baluta <daniel.baluta@nxp.com> +// +// Hardware interface for audio DSP on i.MX8M + +#include <linux/firmware.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include <linux/firmware/imx/dsp.h> + +#include "../ops.h" +#include "imx-common.h" + +#define MBOX_OFFSET 0x800000 +#define MBOX_SIZE 0x1000 + +struct imx8m_priv { + struct device *dev; + struct snd_sof_dev *sdev; + + /* DSP IPC handler */ + struct imx_dsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; +}; + +static void imx8m_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 imx8m_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int imx8m_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc) +{ + struct imx8m_priv *priv = imx_dsp_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + imx8m_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc) +{ + struct imx8m_priv *priv = imx_dsp_get_data(ipc); + u32 p; /* Panic code */ + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p)); + + /* Check to see if the message is a panic code (0x0dead***) */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) + snd_sof_dsp_panic(priv->sdev, p); + else + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static struct imx_dsp_ops imx8m_dsp_ops = { + .handle_reply = imx8m_dsp_handle_reply, + .handle_request = imx8m_dsp_handle_request, +}; + +static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct imx8m_priv *priv = sdev->pdata->hw_pdata; + + 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 imx8m_run(struct snd_sof_dev *sdev) +{ + /* TODO: start DSP using Audio MIX bits */ + return 0; +} + +static int imx8m_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 imx8m_priv *priv; + struct resource res; + u32 base, size; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) + return PTR_ERR(priv->ipc_dev); + + 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 = &imx8m_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); + of_node_put(res_node); + 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); + return ret; +} + +static int imx8m_remove(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = sdev->pdata->hw_pdata; + + platform_device_unregister(priv->ipc_dev); + + return 0; +} + +/* on i.MX8 there is 1 to 1 match between type and BAR idx */ +static int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static void imx8m_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 imx8m_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 imx8m_dai[] = { +{ + .name = "sai3", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, +}, +}; + +/* i.MX8 ops */ +struct snd_sof_dsp_ops sof_imx8m_ops = { + /* probe and remove */ + .probe = imx8m_probe, + .remove = imx8m_remove, + /* DSP core boot */ + .run = imx8m_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Module IO */ + .read64 = sof_io_read64, + + /* ipc */ + .send_msg = imx8m_send_msg, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = imx8m_get_mailbox_offset, + .get_window_offset = imx8m_get_window_offset, + + .ipc_msg_data = imx8m_ipc_msg_data, + .ipc_pcm_params = imx8m_ipc_pcm_params, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + .get_bar_index = imx8m_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* Debug information */ + .dbg_dump = imx8_dump, + + /* Firmware ops */ + .arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = imx8m_dai, + .num_drv = ARRAY_SIZE(imx8m_dai), + + .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_imx8m_ops); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig new file mode 100644 index 000000000..6708a2c5a --- /dev/null +++ b/sound/soc/sof/intel/Kconfig @@ -0,0 +1,364 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SOC_SOF_INTEL_TOPLEVEL + bool "SOF support for Intel audio DSPs" + depends on X86 || COMPILE_TEST + help + This adds support for Sound Open Firmware for Intel(R) platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_INTEL_TOPLEVEL + +config SND_SOC_SOF_INTEL_ACPI + 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 + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_PCI + 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 + select SND_SOC_SOF_CANNONLAKE if SND_SOC_SOF_CANNONLAKE_SUPPORT + select SND_SOC_SOF_COFFEELAKE if SND_SOC_SOF_COFFEELAKE_SUPPORT + select SND_SOC_SOF_ICELAKE if SND_SOC_SOF_ICELAKE_SUPPORT + select SND_SOC_SOF_COMETLAKE if SND_SOC_SOF_COMETLAKE_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 + +config SND_SOC_SOF_INTEL_HIFI_EP_IPC + tristate + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_ATOM_HIFI_EP + tristate + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_INTEL_HIFI_EP_IPC + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_COMMON + tristate + select SND_SOC_ACPI_INTEL_MATCH + select SND_SOC_SOF_XTENSA + select SND_SOC_INTEL_MACH + select SND_SOC_ACPI if ACPI + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +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. + 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 + tristate + select SND_SOC_SOF_INTEL_ATOM_HIFI_EP + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_BROADWELL_SUPPORT + bool "SOF support for Broadwell" + depends on SND_SOC_INTEL_CATPT=n + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Broadwell processors. + 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 + tristate + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_INTEL_HIFI_EP_IPC + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +endif ## SND_SOC_SOF_INTEL_ACPI + +if SND_SOC_SOF_INTEL_PCI + +config SND_SOC_SOF_MERRIFIELD_SUPPORT + bool "SOF support for Tangier/Merrifield" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Tangier/Merrifield processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_MERRIFIELD + tristate + select SND_SOC_SOF_INTEL_ATOM_HIFI_EP + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_APOLLOLAKE_SUPPORT + bool "SOF support for Apollolake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Apollolake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_APOLLOLAKE + 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_GEMINILAKE_SUPPORT + bool "SOF support for GeminiLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Geminilake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_GEMINILAKE + 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_CANNONLAKE_SUPPORT + bool "SOF support for Cannonlake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Cannonlake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_CANNONLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_COFFEELAKE_SUPPORT + bool "SOF support for CoffeeLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Coffeelake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_COFFEELAKE + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_ICELAKE_SUPPORT + bool "SOF support for Icelake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Icelake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_ICELAKE + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_COMETLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_COMETLAKE_SUPPORT + bool + +config SND_SOC_SOF_COMETLAKE_LP_SUPPORT + bool "SOF support for CometLake" + select SND_SOC_SOF_COMETLAKE_SUPPORT + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Cometlake processors. + 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 + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + 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 + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + 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_INTEL_DSP_CONFIG + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_HDA_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +if SND_SOC_SOF_HDA_COMMON + +config SND_SOC_SOF_HDA_LINK + bool "SOF support for HDA Links(HDA/HDMI)" + depends on SND_SOC_SOF_NOCODEC=n + select SND_SOC_SOF_PROBE_WORK_QUEUE + help + This adds support for HDA links(HDA/HDMI) with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable HDA links with SOF. + If unsure select "N". + +config SND_SOC_SOF_HDA_AUDIO_CODEC + bool "SOF support for HDAudio codecs" + depends on SND_SOC_SOF_HDA_LINK + help + This adds support for HDAudio codecs with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable HDAudio codecs with SOF. + If unsure select "N". + +config SND_SOC_SOF_HDA_PROBES + bool "SOF enable probes over HDA" + depends on SND_SOC_SOF_DEBUG_PROBES + help + This option enables the data probing for Intel(R). + Intel(R) Skylake and newer platforms. + Say Y if you want to enable probes. + 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". + +endif ## SND_SOC_SOF_HDA_COMMON + +config SND_SOC_SOF_HDA_LINK_BASELINE + tristate + select SND_SOC_SOF_HDA if SND_SOC_SOF_HDA_LINK + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +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 + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK + bool "SOF support for SoundWire" + depends on ACPI + help + This adds support for SoundWire with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable SoundWire links with SOF. + If unsure select "N". + +config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + tristate + select SND_SOC_SOF_INTEL_SOUNDWIRE if SND_SOC_SOF_INTEL_SOUNDWIRE_LINK + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_SOUNDWIRE + tristate + select SOUNDWIRE + select SOUNDWIRE_INTEL + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +endif ## SND_SOC_SOF_INTEL_PCI + +endif ## SND_SOC_SOF_INTEL_TOPLEVEL diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile new file mode 100644 index 000000000..72d85b25d --- /dev/null +++ b/sound/soc/sof/intel/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) + +snd-sof-intel-byt-objs := byt.o +snd-sof-intel-bdw-objs := bdw.o + +snd-sof-intel-ipc-objs := intel-ipc.o + +snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ + hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ + hda-dai.o hda-bus.o \ + apl.o cnl.o tgl.o +snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-compress.o + +snd-sof-intel-hda-objs := hda-codec.o + +obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-byt.o +obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-intel-bdw.o +obj-$(CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC) += snd-sof-intel-ipc.o +obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o +obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c new file mode 100644 index 000000000..4eeade2e7 --- /dev/null +++ b/sound/soc/sof/intel/apl.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Apollolake and GeminiLake + */ + +#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}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +/* apollolake ops */ +const struct snd_sof_dsp_ops sof_apl_ops = { + /* probe and remove */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_thread = hda_dsp_ipc_irq_thread, + + /* ipc */ + .send_msg = hda_dsp_ipc_send_msg, + .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), + .dbg_dump = hda_dsp_dump, + .ipc_dump = hda_ipc_dump, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_hw_free = hda_dsp_stream_hw_free, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) + /* probe callbacks */ + .probe_assign = hda_probe_compr_assign, + .probe_free = hda_probe_compr_free, + .probe_set_params = hda_probe_compr_set_params, + .probe_trigger = hda_probe_compr_trigger, + .probe_pointer = hda_probe_compr_pointer, +#endif + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + + /* dsp core power up/down */ + .core_power_up = hda_dsp_enable_core, + .core_power_down = hda_dsp_core_reset_power_down, + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .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_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc apl_chip_info = { + /* Apollolake */ + .cores_num = 2, + .init_core_mask = 1, + .host_managed_cores_mask = GENMASK(1, 0), + .ipc_req = HDA_DSP_REG_HIPCI, + .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY, + .ipc_ack = HDA_DSP_REG_HIPCIE, + .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE, + .ipc_ctl = HDA_DSP_REG_HIPCCTL, + .rom_init_timeout = 150, + .ssp_count = APL_SSP_COUNT, + .ssp_base_offset = APL_SSP_BASE_OFFSET, +}; +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 new file mode 100644 index 000000000..50a4a73e6 --- /dev/null +++ b/sound/soc/sof/intel/bdw.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Broadwell + */ + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "shim.h" +#include "../sof-audio.h" + +/* BARs */ +#define BDW_DSP_BAR 0 +#define BDW_PCI_BAR 1 + +/* + * Debug + */ + +/* DSP memories for BDW */ +#define IRAM_OFFSET 0xA0000 +#define BDW_IRAM_SIZE (10 * 32 * 1024) +#define DRAM_OFFSET 0x00000 +#define BDW_DRAM_SIZE (20 * 32 * 1024) +#define SHIM_OFFSET 0xFB000 +#define SHIM_SIZE 0x100 +#define MBOX_OFFSET 0x9E000 +#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 +#define DMAC1_OFFSET 0xFF000 +#define DMAC_SIZE 0x420 +#define SSP0_OFFSET 0xFC000 +#define SSP1_OFFSET 0xFD000 +#define SSP_SIZE 0x100 + +#define BDW_STACK_DUMP_SIZE 32 + +#define BDW_PANIC_OFFSET(x) ((x) & 0xFFFF) + +static const struct snd_sof_debugfs_map bdw_debugfs[] = { + {"dmac0", BDW_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BDW_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BDW_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BDW_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BDW_DSP_BAR, IRAM_OFFSET, BDW_IRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"dram", BDW_DSP_BAR, DRAM_OFFSET, BDW_DRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"shim", BDW_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void bdw_host_done(struct snd_sof_dev *sdev); +static void bdw_dsp_done(struct snd_sof_dev *sdev); +static void bdw_get_reply(struct snd_sof_dev *sdev); + +/* + * DSP Control. + */ + +static int bdw_run(struct snd_sof_dev *sdev) +{ + /* set opportunistic mode on engine 0,1 for all channels */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH, 0); + + /* set DSP to RUN */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_STALL, 0x0); + + /* return init core mask */ + return 1; +} + +static int bdw_reset(struct snd_sof_dev *sdev) +{ + /* put DSP into reset and stall */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_RST | SHIM_CSR_STALL, + SHIM_CSR_RST | SHIM_CSR_STALL); + + /* keep in reset for 10ms */ + mdelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_RST | SHIM_CSR_STALL, + SHIM_CSR_STALL); + + return 0; +} + +static int bdw_set_dsp_D0(struct snd_sof_dev *sdev) +{ + int tries = 10; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE, 0); + + /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0, + PCI_VDRTCL0_D3PGD, PCI_VDRTCL0_D3PGD); + + /* Set D0 state */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_PMCS, + PCI_PMCS_PS_MASK, 0); + + /* check that ADSP shim is enabled */ + while (tries--) { + reg = readl(sdev->bar[BDW_PCI_BAR] + PCI_PMCS) + & PCI_PMCS_PS_MASK; + if (reg == 0) + goto finish; + + msleep(20); + } + + return -ENODEV; + +finish: + /* + * select SSP1 19.2MHz base clock, SSP clock 0, + * turn off Low Power Clock + */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_S1IOCS | SHIM_CSR_SBCS1 | + SHIM_CSR_LPCS, 0x0); + + /* stall DSP core, set clk to 192/96Mhz */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_CSR, SHIM_CSR_STALL | + SHIM_CSR_DCS_MASK, + SHIM_CSR_STALL | + SHIM_CSR_DCS(4)); + + /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CLKCTL, + SHIM_CLKCTL_MASK | + SHIM_CLKCTL_DCPLCG | + SHIM_CLKCTL_SCOE0, + SHIM_CLKCTL_MASK | + SHIM_CLKCTL_DCPLCG | + SHIM_CLKCTL_SCOE0); + + /* Stall and reset core, set CSR */ + bdw_reset(sdev); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE); + + usleep_range(50, 55); + + /* switch on audio PLL */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_APLLSE_MASK, 0); + + /* + * set default power gating control, enable power gating control for + * all blocks. that is, can't be accessed, please enable each block + * before accessing. + */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0, + 0xfffffffC, 0x0); + + /* disable DMA finish function for SSP0 & SSP1 */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR2, + SHIM_CSR2_SDFD_SSP1, + SHIM_CSR2_SDFD_SSP1); + + /* set on-demond mode on engine 0,1 for all channels */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH); + + /* Enable Interrupt from both sides */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRX, + (SHIM_IMRX_BUSY | SHIM_IMRX_DONE), 0x0); + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRD, + (SHIM_IMRD_DONE | SHIM_IMRD_BUSY | + SHIM_IMRD_SSP0 | SHIM_IMRD_DMAC), 0x0); + + /* clear IPC registers */ + snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCX, 0x0); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCD, 0x0); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0x80, 0x6); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0xe0, 0x300a); + + return 0; +} + +static void bdw_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* 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)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +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, imrx, imrd; + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD); + panic = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX); + bdw_get_registers(sdev, &xoops, &panic_info, stack, + 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); +} + +/* + * IPC Doorbell IRQ handler and thread. + */ + +static irqreturn_t bdw_irq_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 isr; + int ret = IRQ_NONE; + + /* Interrupt arrived, check src */ + isr = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_ISRX); + if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY)) + ret = IRQ_WAKE_THREAD; + + return ret; +} + +static irqreturn_t bdw_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 ipcx, ipcd, imrx; + + imrx = snd_sof_dsp_read64(sdev, BDW_DSP_BAR, SHIM_IMRX); + ipcx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX); + + /* reply message from DSP */ + if (ipcx & SHIM_IPCX_DONE && + !(imrx & SHIM_IMRX_DONE)) { + /* Mask Done interrupt before return */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_IMRX, SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + + spin_lock_irq(&sdev->ipc_lock); + + /* + * handle immediate reply from DSP core. If the msg is + * found, set done bit in cmd_done which is called at the + * end of message processing function, else set it here + * because the done bit can't be set in cmd_done function + * which is triggered by msg + */ + bdw_get_reply(sdev); + snd_sof_ipc_reply(sdev, ipcx); + + bdw_dsp_done(sdev); + + spin_unlock_irq(&sdev->ipc_lock); + } + + ipcd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD); + + /* new message from DSP */ + if (ipcd & SHIM_IPCD_BUSY && + !(imrx & SHIM_IMRX_BUSY)) { + /* Mask Busy interrupt before return */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_IMRX, SHIM_IMRX_BUSY, + SHIM_IMRX_BUSY); + + /* Handle messages from DSP Core */ + if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) + + MBOX_OFFSET); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + bdw_host_done(sdev); + } + + return IRQ_HANDLED; +} + +/* + * IPC Mailbox IO + */ + +static int bdw_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, BDW_DSP_BAR, SHIM_IPCX, SHIM_IPCX_BUSY); + + return 0; +} + +static void bdw_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\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 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 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 */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCD, + SHIM_IPCD_BUSY | SHIM_IPCD_DONE, + SHIM_IPCD_DONE); + + /* unmask busy interrupt */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY, 0); +} + +static void bdw_dsp_done(struct snd_sof_dev *sdev) +{ + /* clear DONE bit - tell DSP we have completed */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCX, + SHIM_IPCX_DONE, 0); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_DONE, 0); +} + +/* + * Probe and remove. + */ +static int bdw_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct resource *mmio; + u32 base, size; + int ret; + + /* LPE base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n", + desc->resindex_lpe_base); + return -EINVAL; + } + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BDW_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BDW_DSP_BAR]) { + dev_err(sdev->dev, + "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BDW_DSP_BAR]); + + /* 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, + desc->resindex_pcicfg_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get PCI base at idx %d\n", + desc->resindex_pcicfg_base); + return -ENODEV; + } + + dev_dbg(sdev->dev, "PCI base at 0x%x size 0x%x", base, size); + sdev->bar[BDW_PCI_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BDW_PCI_BAR]) { + dev_err(sdev->dev, + "error: failed to ioremap PCI base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "PCI VADDR %p\n", sdev->bar[BDW_PCI_BAR]); + + /* register our IRQ */ + sdev->ipc_irq = platform_get_irq(pdev, 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, + bdw_irq_handler, bdw_irq_thread, + IRQF_SHARED, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable the DSP SHIM */ + ret = bdw_set_dsp_D0(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DSP D0\n"); + return ret; + } + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* set default mailbox */ + snd_sof_dsp_mailbox_init(sdev, MBOX_OFFSET, MBOX_SIZE, 0, 0); + + 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[] = { +{ + .name = "ssp0-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp1-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +}; + +/* broadwell ops */ +const struct snd_sof_dsp_ops sof_bdw_ops = { + /*Device init */ + .probe = bdw_probe, + + /* DSP Core Control */ + .run = bdw_run, + .reset = bdw_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* ipc */ + .send_msg = bdw_send_msg, + .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), + .dbg_dump = bdw_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* Module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = 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_NS(sof_bdw_ops, SND_SOC_SOF_BROADWELL); + +const struct sof_intel_dsp_desc bdw_chip_info = { + .cores_num = 1, + .host_managed_cores_mask = 1, +}; +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 new file mode 100644 index 000000000..186736ee5 --- /dev/null +++ b/sound/soc/sof/intel/byt.c @@ -0,0 +1,987 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Baytrail, Braswell and Cherrytrail. + */ + +#include <linux/module.h> +#include <sound/sof.h> +#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 +#define IRAM_SIZE (80 * 1024) +#define DRAM_OFFSET 0x100000 +#define DRAM_SIZE (160 * 1024) +#define SHIM_OFFSET 0x140000 +#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 +#define DMAC1_OFFSET 0x09c000 +#define DMAC2_OFFSET 0x094000 +#define DMAC_SIZE 0x420 +#define SSP0_OFFSET 0x0a0000 +#define SSP1_OFFSET 0x0a1000 +#define SSP2_OFFSET 0x0a2000 +#define SSP3_OFFSET 0x0a4000 +#define SSP4_OFFSET 0x0a5000 +#define SSP5_OFFSET 0x0a6000 +#define SSP_SIZE 0x100 + +#define BYT_STACK_DUMP_SIZE 32 + +#define BYT_PCI_BAR_SIZE 0x200000 + +#define BYT_PANIC_OFFSET(x) (((x) & GENMASK_ULL(47, 32)) >> 32) + +/* + * Debug + */ + +#define MBOX_DUMP_SIZE 0x30 + +/* BARs */ +#define BYT_DSP_BAR 0 +#define BYT_PCI_BAR 1 +#define BYT_IMR_BAR 2 + +static const struct snd_sof_debugfs_map byt_debugfs[] = { + {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE, + 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_BYT, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void byt_host_done(struct snd_sof_dev *sdev); +static void byt_dsp_done(struct snd_sof_dev *sdev); +static void byt_get_reply(struct snd_sof_dev *sdev); + +/* + * Debug + */ + +static void byt_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read regsisters */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* 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)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +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]; + u64 status, panic, imrd, imrx; + + /* now try generic SOF status messages */ + 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); + +} + +/* + * IPC Doorbell IRQ handler and thread. + */ + +static irqreturn_t byt_irq_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u64 ipcx, ipcd; + int ret = IRQ_NONE; + + ipcx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX); + ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD); + + if (ipcx & SHIM_BYT_IPCX_DONE) { + + /* reply message from DSP, Mask Done interrupt first */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, + SHIM_IMRX, + SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + ret = IRQ_WAKE_THREAD; + } + + if (ipcd & SHIM_BYT_IPCD_BUSY) { + + /* new message from DSP, Mask Busy interrupt first */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, + SHIM_IMRX, + SHIM_IMRX_BUSY, + SHIM_IMRX_BUSY); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t byt_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u64 ipcx, ipcd; + + ipcx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX); + ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD); + + /* reply message from DSP */ + if (ipcx & SHIM_BYT_IPCX_DONE) { + + spin_lock_irq(&sdev->ipc_lock); + + /* + * handle immediate reply from DSP core. If the msg is + * found, set done bit in cmd_done which is called at the + * end of message processing function, else set it here + * because the done bit can't be set in cmd_done function + * which is triggered by msg + */ + byt_get_reply(sdev); + snd_sof_ipc_reply(sdev, ipcx); + + byt_dsp_done(sdev); + + spin_unlock_irq(&sdev->ipc_lock); + } + + /* new message from DSP */ + if (ipcd & SHIM_BYT_IPCD_BUSY) { + + /* Handle messages from DSP Core */ + if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, BYT_PANIC_OFFSET(ipcd) + + MBOX_OFFSET); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + byt_host_done(sdev); + } + + return IRQ_HANDLED; +} + +static int byt_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + /* unmask and prepare to receive Done interrupt */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_DONE, 0); + + /* send the message */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write64(sdev, BYT_DSP_BAR, SHIM_IPCX, SHIM_BYT_IPCX_BUSY); + + return 0; +} + +static void byt_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\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 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 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 */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCD, + SHIM_BYT_IPCD_BUSY | + SHIM_BYT_IPCD_DONE, + SHIM_BYT_IPCD_DONE); + + /* unmask and prepare to receive next new message */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY, 0); +} + +static void byt_dsp_done(struct snd_sof_dev *sdev) +{ + /* clear DONE bit - tell DSP we have completed */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCX, + SHIM_BYT_IPCX_DONE, 0); +} + +/* + * DSP control. + */ + +static int byt_run(struct snd_sof_dev *sdev) +{ + int tries = 10; + + /* release stall and wait to unstall */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_STALL, 0x0); + while (tries--) { + if (!(snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_CSR) & + SHIM_BYT_CSR_PWAITMODE)) + break; + msleep(100); + } + if (tries < 0) { + dev_err(sdev->dev, "error: unable to run DSP firmware\n"); + byt_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + return -ENODEV; + } + + /* return init core mask */ + return 1; +} + +static int byt_reset(struct snd_sof_dev *sdev) +{ + /* put DSP into reset, set reset vector and stall */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL | + SHIM_BYT_CSR_STALL, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL | + SHIM_BYT_CSR_STALL); + + usleep_range(10, 15); + + /* take DSP out of reset and keep stalled for FW loading */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_RST, 0); + + 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[] = { +{ + .name = "ssp0-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp1-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp2-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + } +}, +{ + .name = "ssp3-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp4-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "ssp5-port", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +}; + +/* + * Probe and remove. + */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) + +static int tangier_pci_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct pci_dev *pci = to_pci_dev(sdev->dev); + u32 base, size; + int ret; + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* LPE base */ + base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET; + size = BYT_PCI_BAR_SIZE; + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_DSP_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]); + + /* IMR base - optional */ + if (desc->resindex_imr_base == -1) + goto irq; + + base = pci_resource_start(pci, desc->resindex_imr_base); + size = pci_resource_len(pci, desc->resindex_imr_base); + + /* some BIOSes don't map IMR */ + if (base == 0x55aa55aa || base == 0x0) { + dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n"); + goto irq; + } + + dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_IMR_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]); + +irq: + /* register our IRQ */ + sdev->ipc_irq = pci->irq; + dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); + ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, + byt_irq_handler, byt_irq_thread, + 0, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable BUSY and disable DONE Interrupt by default */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY | SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + return ret; +} + +const struct snd_sof_dsp_ops sof_tng_ops = { + /* device init */ + .probe = tangier_pci_probe, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .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), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* 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_NS(sof_tng_ops, SND_SOC_SOF_MERRIFIELD); + +const struct sof_intel_dsp_desc tng_chip_info = { + .cores_num = 1, + .host_managed_cores_mask = 1, +}; +EXPORT_SYMBOL_NS(tng_chip_info, SND_SOC_SOF_MERRIFIELD); + +#endif /* CONFIG_SND_SOC_SOF_MERRIFIELD */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + +static void byt_reset_dsp_disable_int(struct snd_sof_dev *sdev) +{ + /* Disable Interrupt from both sides */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x3); + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x3); + + /* Put DSP into reset, set reset vector */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL); +} + +static int byt_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + byt_reset_dsp_disable_int(sdev); + + return 0; +} + +static int byt_resume(struct snd_sof_dev *sdev) +{ + /* enable BUSY and disable DONE Interrupt by default */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY | SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + + return 0; +} + +static int byt_remove(struct snd_sof_dev *sdev) +{ + byt_reset_dsp_disable_int(sdev); + + return 0; +} + +static const struct snd_sof_debugfs_map cht_debugfs[] = { + {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac2", BYT_DSP_BAR, DMAC2_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp3", BYT_DSP_BAR, SSP3_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp4", BYT_DSP_BAR, SSP4_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp5", BYT_DSP_BAR, SSP5_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE, + 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_CHT, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static int byt_acpi_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct resource *mmio; + u32 base, size; + int ret; + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* LPE base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n", + desc->resindex_lpe_base); + return -EINVAL; + } + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_DSP_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]); + + /* TODO: add offsets */ + sdev->mmio_bar = BYT_DSP_BAR; + sdev->mailbox_bar = BYT_DSP_BAR; + + /* IMR base - optional */ + if (desc->resindex_imr_base == -1) + goto irq; + + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_imr_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get IMR base at idx %d\n", + desc->resindex_imr_base); + return -ENODEV; + } + + /* some BIOSes don't map IMR */ + if (base == 0x55aa55aa || base == 0x0) { + dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n"); + goto irq; + } + + dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_IMR_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]); + +irq: + /* register our IRQ */ + sdev->ipc_irq = platform_get_irq(pdev, 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, + byt_irq_handler, byt_irq_thread, + IRQF_SHARED, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable BUSY and disable DONE Interrupt by default */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY | SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + return ret; +} + +/* baytrail ops */ +const struct snd_sof_dsp_ops sof_byt_ops = { + /* device init */ + .probe = byt_acpi_probe, + .remove = byt_remove, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .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), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* PM */ + .suspend = byt_suspend, + .resume = byt_resume, + + /* 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_NS(sof_byt_ops, SND_SOC_SOF_BAYTRAIL); + +const struct sof_intel_dsp_desc byt_chip_info = { + .cores_num = 1, + .host_managed_cores_mask = 1, +}; +EXPORT_SYMBOL_NS(byt_chip_info, SND_SOC_SOF_BAYTRAIL); + +/* cherrytrail and braswell ops */ +const struct snd_sof_dsp_ops sof_cht_ops = { + /* device init */ + .probe = byt_acpi_probe, + .remove = byt_remove, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .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), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* PM */ + .suspend = byt_suspend, + .resume = byt_resume, + + /* DAI drivers */ + .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_NS(sof_cht_ops, SND_SOC_SOF_BAYTRAIL); + +const struct sof_intel_dsp_desc cht_chip_info = { + .cores_num = 1, + .host_managed_cores_mask = 1, +}; +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 new file mode 100644 index 000000000..a5d325810 --- /dev/null +++ b/sound/soc/sof/intel/cnl.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Cannonlake. + */ + +#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}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void cnl_ipc_host_done(struct snd_sof_dev *sdev); +static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev); + +irqreturn_t cnl_ipc_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 hipci; + u32 hipcida; + u32 hipctdr; + u32 hipctdd; + u32 msg; + u32 msg_ext; + bool ipc_irq = false; + + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDD); + hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR); + + /* reply message from DSP */ + if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) { + msg_ext = hipci & CNL_DSP_REG_HIPCIDR_MSG_MASK; + msg = hipcida & CNL_DSP_REG_HIPCIDA_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, 0); + + spin_lock_irq(&sdev->ipc_lock); + + /* handle immediate reply from DSP core */ + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); + + cnl_ipc_dsp_done(sdev); + + spin_unlock_irq(&sdev->ipc_lock); + + ipc_irq = true; + } + + /* new message from DSP */ + if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { + msg = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK; + msg_ext = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* handle messages from DSP */ + if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) == + SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + cnl_ipc_host_done(sdev); + + ipc_irq = true; + } + + if (!ipc_irq) { + /* + * This interrupt is not shared so no need to return IRQ_NONE. + */ + dev_dbg_ratelimited(sdev->dev, + "nothing to do in IPC IRQ thread\n"); + } + + return IRQ_HANDLED; +} + +static void cnl_ipc_host_done(struct snd_sof_dev *sdev) +{ + /* + * clear busy interrupt to tell dsp controller this + * interrupt has been accepted, not trigger it again + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDR, + CNL_DSP_REG_HIPCTDR_BUSY, + CNL_DSP_REG_HIPCTDR_BUSY); + /* + * set done bit to ack dsp the msg has been + * processed and send reply msg to dsp + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDA, + CNL_DSP_REG_HIPCTDA_DONE, + CNL_DSP_REG_HIPCTDA_DONE); +} + +static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + /* + * set DONE bit - tell DSP we have received the reply msg + * from DSP, and processed it, don't send more reply to host + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCIDA, + CNL_DSP_REG_HIPCIDA_DONE, + CNL_DSP_REG_HIPCIDA_DONE); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, + 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; +} + +int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + struct sof_ipc_cmd_hdr *hdr; + u32 dr = 0; + u32 dd = 0; + + /* + * Currently the only compact IPC supported is the PM_GATE + * IPC which is used for transitioning the DSP between the + * D0I0 and D0I3 states. And these are sent only during the + * set_power_state() op. Therefore, there will never be a case + * that a compact IPC results in the DSP exiting D0I3 without + * the host and FW being in sync. + */ + 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); + return 0; + } + + /* 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); + + hdr = msg->msg_data; + + /* + * Use mod_delayed_work() to schedule the delayed work + * to avoid scheduling multiple workqueue items when + * IPCs are sent at a high-rate. mod_delayed_work() + * modifies the timer if the work is pending. + * Also, a new delayed work should not be queued after the + * CTX_SAVE IPC, which is sent before the DSP enters D3. + */ + if (hdr->cmd != (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE)) + mod_delayed_work(system_wq, &hdev->d0i3_work, + msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS)); + + return 0; +} + +void cnl_ipc_dump(struct snd_sof_dev *sdev) +{ + u32 hipcctl; + u32 hipcida; + u32 hipctdr; + + hda_ipc_irq_dump(sdev); + + /* read IPC status */ + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, + "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", + hipcida, hipctdr, hipcctl); +} + +/* cannonlake ops */ +const struct snd_sof_dsp_ops sof_cnl_ops = { + /* probe and remove */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_thread = cnl_ipc_irq_thread, + + /* ipc */ + .send_msg = cnl_ipc_send_msg, + .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), + .dbg_dump = hda_dsp_dump, + .ipc_dump = cnl_ipc_dump, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_hw_free = hda_dsp_stream_hw_free, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) + /* probe callbacks */ + .probe_assign = hda_probe_compr_assign, + .probe_free = hda_probe_compr_free, + .probe_set_params = hda_probe_compr_set_params, + .probe_trigger = hda_probe_compr_trigger, + .probe_pointer = hda_probe_compr_pointer, +#endif + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + + /* dsp core power up/down */ + .core_power_up = hda_dsp_enable_core, + .core_power_down = hda_dsp_core_reset_power_down, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .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_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc cnl_chip_info = { + /* Cannonlake */ + .cores_num = 4, + .init_core_mask = 1, + .host_managed_cores_mask = GENMASK(3, 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 = CNL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc icl_chip_info = { + /* Icelake */ + .cores_num = 4, + .init_core_mask = 1, + .host_managed_cores_mask = GENMASK(3, 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(icl_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, + .host_managed_cores_mask = BIT(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, + .host_managed_cores_mask = GENMASK(1, 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(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 new file mode 100644 index 000000000..789148e55 --- /dev/null +++ b/sound/soc/sof/intel/hda-bus.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> + +#include <linux/io.h> +#include <sound/hdaudio.h> +#include "../sof-priv.h" +#include "hda.h" + +#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 + +/* + * This can be used for both with/without hda link support. + */ +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; + + INIT_LIST_HEAD(&bus->stream_list); + + bus->irq = -1; + + /* + * There is only one HDA bus atm. keep the index as 0. + * Need to fix when there are more than one HDA bus. + */ + bus->idx = 0; + + spin_lock_init(&bus->reg_lock); +#endif /* CONFIG_SND_SOC_SOF_HDA */ +} diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c new file mode 100644 index 000000000..8d65004c9 --- /dev/null +++ b/sound/soc/sof/intel/hda-codec.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> +// + +#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> +#include "../ops.h" +#include "hda.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#include "../../codecs/hdac_hda.h" +#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#define IDISP_VID_INTEL 0x80860000 + +/* load the legacy HDA codec driver */ +static int request_codec_module(struct hda_codec *codec) +{ +#ifdef MODULE + char alias[MODULE_NAME_LEN]; + const char *mod = NULL; + + switch (codec->probe_id) { + case HDA_CODEC_ID_GENERIC: +#if IS_MODULE(CONFIG_SND_HDA_GENERIC) + mod = "snd-hda-codec-generic"; +#endif + break; + default: + snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias)); + mod = alias; + break; + } + + if (mod) { + dev_dbg(&codec->core.dev, "loading codec module: %s\n", mod); + request_module(mod); + } +#endif /* MODULE */ + return device_attach(hda_codec_dev(codec)); +} + +static int hda_codec_load_module(struct hda_codec *codec) +{ + int ret = request_codec_module(codec); + + if (ret <= 0) { + codec->probe_id = HDA_CODEC_ID_GENERIC; + ret = request_codec_module(codec); + } + + return ret; +} + +/* 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) + pm_request_resume(&codec->core.dev); +} +#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); + +#if IS_ENABLED(CONFIG_SND_HDA_GENERIC) +#define is_generic_config(bus) \ + ((bus)->modelname && !strcmp((bus)->modelname, "generic")) +#else +#define is_generic_config(x) 0 +#endif + +/* probe individual codec */ +static int hda_codec_probe(struct snd_sof_dev *sdev, int address, + bool hda_codec_use_common_hdmi) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + struct hdac_hda_priv *hda_priv; + struct hda_codec *codec; + int type = HDA_DEV_LEGACY; +#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; + int ret; + + mutex_lock(&hbus->core.cmd_mutex); + snd_hdac_bus_send_cmd(&hbus->core, hda_cmd); + snd_hdac_bus_get_response(&hbus->core, address, &resp); + mutex_unlock(&hbus->core.cmd_mutex); + if (resp == -1) + return -EIO; + dev_dbg(sdev->dev, "HDA codec #%d probed OK: response: %x\n", + address, resp); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + hda_priv = devm_kzalloc(sdev->dev, sizeof(*hda_priv), GFP_KERNEL); + if (!hda_priv) + return -ENOMEM; + + hda_priv->codec.bus = hbus; + hdev = &hda_priv->codec.core; + codec = &hda_priv->codec; + + /* only probe ASoC codec drivers for HDAC-HDMI */ + if (!hda_codec_use_common_hdmi && (resp & 0xFFFF0000) == IDISP_VID_INTEL) + type = HDA_DEV_ASOC; + + ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev, type); + if (ret < 0) + return ret; + + if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) { + if (!hdev->bus->audio_component) { + dev_dbg(sdev->dev, + "iDisp hw present but no driver\n"); + ret = -ENOENT; + goto out; + } + hda_priv->need_display_power = true; + } + + if (is_generic_config(hbus)) + codec->probe_id = HDA_CODEC_ID_GENERIC; + else + codec->probe_id = 0; + + if (type == HDA_DEV_LEGACY) { + ret = hda_codec_load_module(codec); + /* + * handle ret==0 (no driver bound) as an error, but pass + * other return codes without modification + */ + if (ret == 0) + ret = -ENOENT; + } + +out: + if (ret < 0) { + snd_hdac_device_unregister(hdev); + put_device(&hdev->dev); + } +#else + hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev, HDA_DEV_ASOC); +#endif + + return ret; +} + +/* Codec initialization */ +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; + + /* probe codecs in avail slots */ + for (i = 0; i < HDA_MAX_CODECS; i++) { + + if (!(bus->codec_mask & (1 << i))) + continue; + + ret = hda_codec_probe(sdev, i, hda_codec_use_common_hdmi); + if (ret < 0) { + dev_warn(bus->dev, "codec #%d probe error, ret: %d\n", + i, ret); + bus->codec_mask &= ~BIT(i); + } + } +} +EXPORT_SYMBOL_NS(hda_codec_probe_bus, SND_SOC_SOF_HDA_AUDIO_CODEC); + +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \ + IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + +void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + + if (HDA_IDISP_CODEC(bus->codec_mask)) { + dev_dbg(bus->dev, "Turning i915 HDAC power %d\n", enable); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, enable); + } +} +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) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + /* i915 exposes a HDA codec for HDMI audio */ + ret = snd_hdac_i915_init(bus); + if (ret < 0) + return ret; + + /* codec_mask not yet known, power up for probe */ + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); + + return 0; +} +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); + + if (!bus->audio_component) + return 0; + + /* power down unconditionally */ + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + return snd_hdac_i915_exit(bus); +} +EXPORT_SYMBOL_NS(hda_codec_i915_exit, SND_SOC_SOF_HDA_AUDIO_CODEC_I915); + +#endif + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/hda-compress.c b/sound/soc/sof/intel/hda-compress.c new file mode 100644 index 000000000..53c08034f --- /dev/null +++ b/sound/soc/sof/intel/hda-compress.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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-2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <sound/hdaudio_ext.h> +#include <sound/soc.h> +#include "../sof-priv.h" +#include "hda.h" + +static inline struct hdac_ext_stream * +hda_compr_get_stream(struct snd_compr_stream *cstream) +{ + return cstream->runtime->private_data; +} + +int hda_probe_compr_assign(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream; + + stream = hda_dsp_stream_get(sdev, cstream->direction); + if (!stream) + return -EBUSY; + + hdac_stream(stream)->curr_pos = 0; + hdac_stream(stream)->cstream = cstream; + cstream->runtime->private_data = stream; + + return hdac_stream(stream)->stream_tag; +} + +int hda_probe_compr_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + int ret; + + ret = hda_dsp_stream_put(sdev, cstream->direction, + hdac_stream(stream)->stream_tag); + if (ret < 0) { + dev_dbg(sdev->dev, "stream put failed: %d\n", ret); + return ret; + } + + hdac_stream(stream)->cstream = NULL; + cstream->runtime->private_data = NULL; + + return 0; +} + +int hda_probe_compr_set_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + struct hdac_stream *hstream = hdac_stream(stream); + struct snd_dma_buffer *dmab; + u32 bits, rate; + int bps, ret; + + dmab = cstream->runtime->dma_buffer_p; + /* compr params do not store bit depth, default to S32_LE */ + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE); + if (bps < 0) + return bps; + bits = hda_dsp_get_bits(sdev, bps); + rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate); + + hstream->format_val = rate | bits | (params->codec.ch_out - 1); + hstream->bufsize = cstream->runtime->buffer_size; + hstream->period_bytes = cstream->runtime->fragment_size; + hstream->no_period_wakeup = 0; + + ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + return ret; + } + + return 0; +} + +int hda_probe_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + + return hda_dsp_stream_trigger(sdev, stream, cmd); +} + +int hda_probe_compr_pointer(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + struct snd_soc_pcm_stream *pstream; + + pstream = &dai->driver->capture; + tstamp->copied_total = hdac_stream(stream)->curr_pos; + tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates); + + return 0; +} diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c new file mode 100644 index 000000000..fa5f0a718 --- /dev/null +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <linux/module.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include <sound/hda_component.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. + */ + +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset) +{ + unsigned long timeout; + u32 gctl = 0; + u32 val; + + /* 0 to enter reset and 1 to exit reset */ + val = reset ? 0 : SOF_HDA_GCTL_RESET; + + /* enter/exit HDA controller reset */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL, + SOF_HDA_GCTL_RESET, val); + + /* wait to enter/exit reset */ + timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT); + while (time_before(jiffies, timeout)) { + gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL); + if ((gctl & SOF_HDA_GCTL_RESET) == val) + return 0; + usleep_range(500, 1000); + } + + /* enter/exit reset failed */ + dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n", + reset ? "reset" : "ready", gctl); + return -EIO; +} + +int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + u32 cap, offset, feature; + int count = 0; + int ret; + + /* + * On some devices, one reset cycle is necessary before reading + * capabilities + */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) + return ret; + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) + return ret; + + offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH); + + do { + dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n", + offset & SOF_HDA_CAP_NEXT_MASK); + + cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset); + + if (cap == -1) { + dev_dbg(bus->dev, "Invalid capability reg read\n"); + break; + } + + feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF; + + switch (feature) { + case SOF_HDA_PP_CAP_ID: + dev_dbg(sdev->dev, "found DSP capability at 0x%x\n", + offset); + bus->ppcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_PP_BAR] = bus->ppcap; + break; + case SOF_HDA_SPIB_CAP_ID: + dev_dbg(sdev->dev, "found SPIB capability at 0x%x\n", + offset); + bus->spbcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_SPIB_BAR] = bus->spbcap; + break; + case SOF_HDA_DRSM_CAP_ID: + dev_dbg(sdev->dev, "found DRSM capability at 0x%x\n", + offset); + bus->drsmcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_DRSM_BAR] = bus->drsmcap; + break; + case SOF_HDA_GTS_CAP_ID: + dev_dbg(sdev->dev, "found GTS capability at 0x%x\n", + offset); + bus->gtscap = bus->remap_addr + offset; + break; + case SOF_HDA_ML_CAP_ID: + dev_dbg(sdev->dev, "found ML capability at 0x%x\n", + offset); + bus->mlcap = bus->remap_addr + offset; + break; + default: + dev_dbg(sdev->dev, "found capability %d at 0x%x\n", + feature, offset); + break; + } + + offset = cap & SOF_HDA_CAP_NEXT_MASK; + } while (count++ <= SOF_HDA_MAX_CAPS && offset); + + return 0; +} + +void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? SOF_HDA_PPCTL_GPROCEN : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, val); +} + +void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? SOF_HDA_PPCTL_PIE : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, val); +} + +void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? PCI_CGCTL_MISCBDCGE_MASK : 0; + + snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val); +} + +/* + * enable/disable audio dsp clock gating and power gating bits. + * This allows the HW to opportunistically power and clock gate + * the audio dsp when it is idle + */ +int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) +{ + 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); + + /* 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; + snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val); + + return 0; +} + +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; + + if (bus->chip_init) + return 0; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_set_codec_wakeup(bus, true); +#endif + hda_dsp_ctrl_misc_clock_gating(sdev, false); + + if (full_reset) { + /* reset HDA controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset HDA controller\n"); + goto err; + } + + usleep_range(500, 1000); + + /* exit HDA controller reset */ + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to exit HDA controller reset\n"); + goto err; + } + + usleep_range(1000, 1200); + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* check to see if controller is ready */ + if (!snd_hdac_chip_readb(bus, GCTL)) { + dev_dbg(bus->dev, "controller not ready!\n"); + ret = -EBUSY; + goto err; + } + + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + + /* detect codecs */ + if (!bus->codec_mask) { + bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS); + dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask); + } + + 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 */ + list_for_each_entry(stream, &bus->stream_list, list) { + sd_offset = SOF_STREAM_SD_OFFSET(stream); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK); + } + + /* clear WAKESTS */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS, + SOF_HDA_WAKESTS_INT_MASK); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* clear rirb status */ + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); +#endif + + /* clear interrupt status register */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* initialize the codec command I/O */ + snd_hdac_bus_init_cmd_io(bus); +#endif + + /* enable CIE and GIE interrupts */ + 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); + + /* program the position buffer */ + if (bus->use_posbuf && 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; + +err: + hda_dsp_ctrl_misc_clock_gating(sdev, true); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_set_codec_wakeup(bus, false); +#endif + + return ret; +} + +void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *stream; + int sd_offset; + + if (!bus->chip_init) + return; + + /* disable interrupts in stream descriptor */ + list_for_each_entry(stream, &bus->stream_list, list) { + sd_offset = SOF_STREAM_SD_OFFSET(stream); + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_CTL, + SOF_HDA_CL_DMA_SD_INT_MASK, + 0); + } + + /* disable SIE for all streams */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_ALL_STREAM, 0); + + /* disable controller CIE and GIE */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, + 0); + + /* clear stream status */ + list_for_each_entry(stream, &bus->stream_list, list) { + sd_offset = SOF_STREAM_SD_OFFSET(stream); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK); + } + + /* clear WAKESTS */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS, + SOF_HDA_WAKESTS_INT_MASK); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* clear rirb status */ + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); +#endif + + /* clear interrupt status register */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* disable CORB/RIRB */ + snd_hdac_bus_stop_cmd_io(bus); +#endif + /* disable position buffer */ + if (bus->posbuf.addr) { + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + SOF_HDA_ADSP_DPLBASE, 0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + SOF_HDA_ADSP_DPUBASE, 0); + } + + bus->chip_init = false; +} diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c new file mode 100644 index 000000000..a6275cc92 --- /dev/null +++ b/sound/soc/sof/intel/hda-dai.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> +// + +#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) + +struct hda_pipe_params { + u8 host_dma_id; + u8 link_dma_id; + u32 ch; + u32 s_freq; + u32 s_fmt; + u8 linktype; + snd_pcm_format_t format; + int link_index; + int stream; + unsigned int host_bps; + unsigned int link_bps; +}; + +/* + * This function checks if the host dma channel corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + int dir, int stream_tag) +{ + struct snd_pcm_substream *fe_substream; + struct hdac_stream *fe_hstream; + struct snd_soc_dpcm *dpcm; + + for_each_dpcm_fe(rtd, dir, dpcm) { + fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir); + fe_hstream = fe_substream->runtime->private_data; + if (fe_hstream->stream_tag == stream_tag) + return true; + } + + return false; +} + +static struct hdac_ext_stream * + hda_link_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *stream = NULL; + + int stream_dir = substream->stream; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(stream, &bus->stream_list, list) { + struct hdac_ext_stream *hstream = + stream_to_hdac_ext_stream(stream); + if (stream->direction != substream->stream) + continue; + + hda_stream = hstream_to_sof_hda_stream(hstream); + + /* check if link is available */ + if (!hstream->link_locked) { + if (stream->opened) { + /* + * check if the stream tag matches the stream + * tag of one of the connected FEs + */ + if (hda_check_fes(rtd, stream_dir, + stream->stream_tag)) { + res = hstream; + break; + } + } else { + res = hstream; + + /* + * This must be a hostless stream. + * So reserve the host DMA channel. + */ + hda_stream->host_reserved = 1; + break; + } + } + } + + if (res) { + /* + * Decouple host and link DMA. The decoupled flag + * is updated in snd_hdac_ext_stream_decouple(). + */ + if (!res->decoupled) + snd_hdac_ext_stream_decouple_locked(bus, res, true); + + res->link_locked = 1; + res->link_substream = substream; + } + spin_unlock_irq(&bus->reg_lock); + + return res; +} + +static int hda_link_dma_params(struct hdac_ext_stream *stream, + struct hda_pipe_params *params) +{ + struct hdac_stream *hstream = &stream->hstream; + unsigned char stream_tag = hstream->stream_tag; + struct hdac_bus *bus = hstream->bus; + struct hdac_ext_link *link; + unsigned int format_val; + + snd_hdac_ext_stream_decouple(bus, stream, true); + snd_hdac_ext_link_stream_reset(stream); + + format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, + params->format, + params->link_bps, 0); + + dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", + format_val, params->s_freq, params->ch, params->format); + + snd_hdac_ext_link_stream_setup(stream, format_val); + + if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { + list_for_each_entry(link, &bus->hlink_list, list) { + if (link->index == params->link_index) + snd_hdac_ext_link_set_stream_id(link, + stream_tag); + } + } + + stream->link_prepared = 1; + + return 0; +} + +/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */ +static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream, + const char *dai_name, int channel, int dir) +{ + struct sof_ipc_dai_config *config; + struct snd_sof_dai *sof_dai; + struct sof_ipc_reply reply; + int ret = 0; + + list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) { + if (!sof_dai->cpu_dai_name) + continue; + + if (!strcmp(dai_name, sof_dai->cpu_dai_name) && + dir == sof_dai->comp_dai.direction) { + config = sof_dai->dai_config; + + if (!config) { + dev_err(hda_stream->sdev->dev, + "error: no config for DAI %s\n", + sof_dai->name); + return -EINVAL; + } + + /* update config with stream tag */ + config->hda.link_dma_ch = channel; + + /* send IPC */ + ret = sof_ipc_tx_message(hda_stream->sdev->ipc, + config->hdr.cmd, + config, + config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) + dev_err(hda_stream->sdev->dev, + "error: failed to set dai config for %s\n", + sof_dai->name); + return ret; + } + } + + return -EINVAL; +} + +static int hda_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_bus *bus = hstream->bus; + struct hdac_ext_stream *link_dev; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct sof_intel_hda_stream *hda_stream; + struct hda_pipe_params p_params = {0}; + struct hdac_ext_link *link; + int stream_tag; + int ret; + + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + /* 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; + + hda_stream = hstream_to_sof_hda_stream(link_dev); + + /* update the DSP with the new tag */ + ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1, + substream->stream); + if (ret < 0) + return ret; + + /* set the hdac_stream in the codec dai */ + snd_soc_dai_set_stream(codec_dai, hdac_stream(link_dev), substream->stream); + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.stream = substream->stream; + p_params.link_dma_id = stream_tag - 1; + p_params.link_index = link->index; + p_params.format = params_format(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + + return hda_link_dma_params(link_dev, &p_params); +} + +static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int stream = substream->stream; + + if (link_dev->link_prepared) + return 0; + + dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); + + return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params, + dai); +} + +static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct sof_intel_hda_stream *hda_stream; + struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_link *link; + struct hdac_stream *hstream; + struct hdac_bus *bus; + int stream_tag; + int ret; + + hstream = substream->runtime->private_data; + bus = hstream->bus; + rtd = asoc_substream_to_rtd(substream); + + link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); + if (!link) + return -EINVAL; + + hda_stream = hstream_to_sof_hda_stream(link_dev); + + dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + /* set up hw_params */ + ret = hda_link_pcm_prepare(substream, dai); + if (ret < 0) { + dev_err(dai->dev, + "error: setting up hw_params during resume\n"); + return ret; + } + + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(link_dev); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + /* + * 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; + + 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: + snd_hdac_ext_link_stream_clear(link_dev); + break; + default: + return -EINVAL; + } + return 0; +} + +static int hda_link_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + unsigned int stream_tag; + struct sof_intel_hda_stream *hda_stream; + struct hdac_bus *bus; + struct hdac_ext_link *link; + struct hdac_stream *hstream; + struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_stream *link_dev; + int ret; + + hstream = substream->runtime->private_data; + bus = hstream->bus; + rtd = asoc_substream_to_rtd(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 */ + ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID, + substream->stream); + if (ret < 0) + return ret; + + link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); + if (!link) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + + snd_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; + + /* free the host DMA channel reserved by hostless streams */ + hda_stream->host_reserved = 0; + + return 0; +} + +static const struct snd_soc_dai_ops hda_link_dai_ops = { + .hw_params = hda_link_hw_params, + .hw_free = hda_link_hw_free, + .trigger = hda_link_pcm_trigger, + .prepare = hda_link_pcm_prepare, +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +#include "../compress.h" + +static struct snd_soc_cdai_ops sof_probe_compr_ops = { + .startup = sof_probe_compr_open, + .shutdown = sof_probe_compr_free, + .set_params = sof_probe_compr_set_params, + .trigger = sof_probe_compr_trigger, + .pointer = sof_probe_compr_pointer, +}; + +#endif +#endif + +/* + * common dai driver for skl+ platforms. + * some products who use this DAI array only physically have a subset of + * the DAIs, but no harm is done here by adding the whole set. + */ +struct snd_soc_dai_driver skl_dai[] = { +{ + .name = "SSP0 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "SSP1 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "SSP2 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "SSP3 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "SSP4 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "SSP5 Pin", + .playback = { + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "DMIC01 Pin", + .capture = { + .channels_min = 1, + .channels_max = 4, + }, +}, +{ + .name = "DMIC16k Pin", + .capture = { + .channels_min = 1, + .channels_max = 4, + }, +}, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +{ + .name = "iDisp1 Pin", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "iDisp2 Pin", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "iDisp3 Pin", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "iDisp4 Pin", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 8, + }, +}, +{ + .name = "Analog CPU DAI", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 16, + }, + .capture = { + .channels_min = 1, + .channels_max = 16, + }, +}, +{ + .name = "Digital CPU DAI", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 16, + }, + .capture = { + .channels_min = 1, + .channels_max = 16, + }, +}, +{ + .name = "Alt Analog CPU DAI", + .ops = &hda_link_dai_ops, + .playback = { + .channels_min = 1, + .channels_max = 16, + }, + .capture = { + .channels_min = 1, + .channels_max = 16, + }, +}, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +{ + .name = "Probe Extraction CPU DAI", + .compress_new = snd_soc_new_compress, + .cops = &sof_probe_compr_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +#endif +#endif +}; diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c new file mode 100644 index 000000000..85ec4361c --- /dev/null +++ b/sound/soc/sof/intel/hda-dsp.c @@ -0,0 +1,964 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <linux/module.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include "../sof-audio.h" +#include "../ops.h" +#include "hda.h" +#include "hda-ipc.h" + +static bool hda_enable_trace_D0I3_S0; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG) +module_param_named(enable_trace_D0I3_S0, hda_enable_trace_D0I3_S0, bool, 0444); +MODULE_PARM_DESC(enable_trace_D0I3_S0, + "SOF HDA enable trace when the DSP is in D0I3 in S0"); +#endif + +/* + * DSP Core control. + */ + +int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + u32 adspcs; + u32 reset; + int ret; + + /* set reset bits for cores */ + reset = HDA_DSP_ADSPCS_CRST_MASK(core_mask); + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + reset, reset), + + /* poll with timeout to check if operation successful */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + ((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, + HDA_DSP_REG_ADSPCS); + if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) != + HDA_DSP_ADSPCS_CRST_MASK(core_mask)) { + dev_err(sdev->dev, + "error: reset enter failed: core_mask %x adspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + unsigned int crst; + u32 adspcs; + int ret; + + /* clear reset bits for cores */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CRST_MASK(core_mask), + 0); + + /* poll with timeout to check if operation successful */ + crst = HDA_DSP_ADSPCS_CRST_MASK(core_mask); + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + !(adspcs & crst), + 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); + if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) != 0) { + dev_err(sdev->dev, + "error: reset leave failed: core_mask %x adspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + /* stall core */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); + + /* set reset state */ + return hda_dsp_core_reset_enter(sdev, core_mask); +} + +int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + int ret; + + /* leave reset state */ + ret = hda_dsp_core_reset_leave(sdev, core_mask); + if (ret < 0) + return ret; + + /* run core */ + dev_dbg(sdev->dev, "unstall/run core: core_mask = %x\n", core_mask); + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), + 0); + + /* is core now running ? */ + if (!hda_dsp_core_is_enabled(sdev, core_mask)) { + hda_dsp_core_stall_reset(sdev, core_mask); + dev_err(sdev->dev, "error: DSP start core failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +/* + * Power Management. + */ + +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + unsigned int cpa; + u32 adspcs; + int ret; + + /* update bits */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_SPA_MASK(core_mask), + HDA_DSP_ADSPCS_SPA_MASK(core_mask)); + + /* poll with timeout to check if operation successful */ + cpa = HDA_DSP_ADSPCS_CPA_MASK(core_mask); + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + (adspcs & cpa) == cpa, + 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; + } + + /* did core power up ? */ + adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS); + if ((adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) != + HDA_DSP_ADSPCS_CPA_MASK(core_mask)) { + dev_err(sdev->dev, + "error: power up core failed core_mask %xadspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +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); + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + !(adspcs & HDA_DSP_ADSPCS_CPA_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, + unsigned int core_mask) +{ + int val; + bool is_enable; + + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS); + +#define MASK_IS_EQUAL(v, m, field) ({ \ + u32 _m = field(m); \ + ((v) & _m) == _m; \ +}) + + is_enable = MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_CPA_MASK) && + MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_SPA_MASK) && + !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) && + !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); + +#undef MASK_IS_EQUAL + + dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); + + return is_enable; +} + +int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + int ret; + + /* restrict core_mask to host managed cores mask */ + core_mask &= chip->host_managed_cores_mask; + + /* return if core_mask is not valid or cores are already enabled */ + if (!core_mask || hda_dsp_core_is_enabled(sdev, core_mask)) + return 0; + + /* power up */ + ret = hda_dsp_core_power_up(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core power up failed: core_mask %x\n", + core_mask); + return ret; + } + + return hda_dsp_core_run(sdev, core_mask); +} + +int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + int ret; + + /* restrict core_mask to host managed cores mask */ + core_mask &= chip->host_managed_cores_mask; + + /* return if core_mask is not valid */ + if (!core_mask) + return 0; + + /* place core in reset prior to power down */ + ret = hda_dsp_core_stall_reset(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core reset failed: core_mask %x\n", + core_mask); + return ret; + } + + /* power down core */ + ret = hda_dsp_core_power_down(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core power down fail mask %x: %d\n", + core_mask, ret); + return ret; + } + + /* make sure we are in OFF state */ + if (hda_dsp_core_is_enabled(sdev, core_mask)) { + dev_err(sdev->dev, "error: dsp core disable fail mask %x: %d\n", + core_mask, ret); + ret = -EIO; + } + + return ret; +} + +void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* enable IPC DONE and BUSY interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY, + HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY); + + /* enable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); +} + +void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* disable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, 0); + + /* disable IPC BUSY and DONE interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_BUSY | HDA_DSP_REG_HIPCCTL_DONE, 0); +} + +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_no_pm(sdev->ipc, pm_gate.hdr.cmd, + &pm_gate, sizeof(pm_gate), &reply, + sizeof(reply)); +} + +static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + /* 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 */ + 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)); + + return 0; +} + +static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + u32 flags = 0; + int ret; + u8 value = 0; + + /* + * Sanity check for illegal state transitions + * The only allowed transitions are: + * 1. D3 -> D0I0 + * 2. D0I0 -> D0I3 + * 3. D0I3 -> D0I0 + */ + switch (sdev->dsp_power_state.state) { + case SOF_DSP_PM_D0: + /* Follow the sequence below for D0 substate transitions */ + break; + case SOF_DSP_PM_D3: + /* Follow regular flow for D3 -> D0 transition */ + return 0; + default: + dev_err(sdev->dev, "error: transition from %d to %d not allowed\n", + sdev->dsp_power_state.state, target_state->state); + return -EINVAL; + } + + /* Set flags and register value for D0 target substate */ + if (target_state->substate == SOF_HDA_DSP_PM_D0I3) { + value = SOF_HDA_VS_D0I3C_I3; + + /* + * Trace DMA need to be disabled when the DSP enters + * D0I3 for S0Ix suspend, but it can be kept enabled + * when the DSP enters D0I3 while the system is in S0 + * for debug purpose. + */ + if (!sdev->dtrace_is_supported || + !hda_enable_trace_D0I3_S0 || + sdev->system_suspend_target != SOF_SUSPEND_NONE) + flags = HDA_PM_NO_DMA_TRACE; + } else { + /* prevent power gating in D0I0 */ + flags = HDA_PM_PPG; + } + + /* update D0I3C register */ + ret = hda_dsp_update_d0i3c_register(sdev, value); + if (ret < 0) + return ret; + + /* + * Notify the DSP of the state change. + * If this IPC fails, revert the D0I3C register update in order + * to prevent partial state change. + */ + ret = hda_dsp_send_pm_gate_ipc(sdev, flags); + if (ret < 0) { + dev_err(sdev->dev, + "error: PM_GATE ipc error %d\n", ret); + goto revert; + } + + return ret; + +revert: + /* fallback to the previous register value */ + value = value ? 0 : SOF_HDA_VS_D0I3C_I3; + + /* + * This can fail but return the IPC error to signal that + * the state change failed. + */ + hda_dsp_update_d0i3c_register(sdev, value); + + return ret; +} + +/* helper to log DSP state */ +static void hda_dsp_state_log(struct snd_sof_dev *sdev) +{ + switch (sdev->dsp_power_state.state) { + case SOF_DSP_PM_D0: + switch (sdev->dsp_power_state.substate) { + case SOF_HDA_DSP_PM_D0I0: + dev_dbg(sdev->dev, "Current DSP power state: D0I0\n"); + break; + case SOF_HDA_DSP_PM_D0I3: + dev_dbg(sdev->dev, "Current DSP power state: D0I3\n"); + break; + default: + dev_dbg(sdev->dev, "Unknown DSP D0 substate: %d\n", + sdev->dsp_power_state.substate); + break; + } + break; + case SOF_DSP_PM_D1: + dev_dbg(sdev->dev, "Current DSP power state: D1\n"); + break; + case SOF_DSP_PM_D2: + dev_dbg(sdev->dev, "Current DSP power state: D2\n"); + break; + case SOF_DSP_PM_D3_HOT: + dev_dbg(sdev->dev, "Current DSP power state: D3_HOT\n"); + break; + case SOF_DSP_PM_D3: + dev_dbg(sdev->dev, "Current DSP power state: D3\n"); + break; + case SOF_DSP_PM_D3_COLD: + dev_dbg(sdev->dev, "Current DSP power state: D3_COLD\n"); + break; + default: + dev_dbg(sdev->dev, "Unknown DSP power state: %d\n", + sdev->dsp_power_state.state); + break; + } +} + +/* + * All DSP power state transitions are initiated by the driver. + * If the requested state change fails, the error is simply returned. + * Further state transitions are attempted only when the set_power_save() op + * is called again either because of a new IPC sent to the DSP or + * during system suspend/resume. + */ +int hda_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + int ret = 0; + + /* + * When the DSP is already in D0I3 and the target state is D0I3, + * it could be the case that the DSP is in D0I3 during S0 + * and the system is suspending to S0Ix. Therefore, + * hda_dsp_set_D0_state() must be called to disable trace DMA + * by sending the PM_GATE IPC to the FW. + */ + if (target_state->substate == SOF_HDA_DSP_PM_D0I3 && + sdev->system_suspend_target == SOF_SUSPEND_S0IX) + goto set_state; + + /* + * For all other cases, return without doing anything if + * the DSP is already in the target state. + */ + if (target_state->state == sdev->dsp_power_state.state && + target_state->substate == sdev->dsp_power_state.substate) + return 0; + +set_state: + switch (target_state->state) { + case SOF_DSP_PM_D0: + ret = hda_dsp_set_D0_state(sdev, target_state); + break; + case SOF_DSP_PM_D3: + /* The only allowed transition is: D0I0 -> D3 */ + if (sdev->dsp_power_state.state == SOF_DSP_PM_D0 && + sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0) + break; + + dev_err(sdev->dev, + "error: transition from %d to %d not allowed\n", + sdev->dsp_power_state.state, target_state->state); + return -EINVAL; + default: + dev_err(sdev->dev, "error: target state unsupported %d\n", + target_state->state); + return -EINVAL; + } + if (ret < 0) { + dev_err(sdev->dev, + "failed to set requested target DSP state %d substate %d\n", + target_state->state, target_state->substate); + return ret; + } + + sdev->dsp_power_state = *target_state; + hda_dsp_state_log(sdev); + return ret; +} + +/* + * Audio DSP states may transform as below:- + * + * Opportunistic D0I3 in S0 + * Runtime +---------------------+ Delayed D0i3 work timeout + * suspend | +--------------------+ + * +------------+ D0I0(active) | | + * | | <---------------+ | + * | +--------> | New IPC | | + * | |Runtime +--^--+---------^--+--+ (via mailbox) | | + * | |resume | | | | | | + * | | | | | | | | + * | | System| | | | | | + * | | resume| | S3/S0IX | | | | + * | | | | suspend | | S0IX | | + * | | | | | |suspend | | + * | | | | | | | | + * | | | | | | | | + * +-v---+-----------+--v-------+ | | +------+----v----+ + * | | | +-----------> | + * | D3 (suspended) | | | D0I3 | + * | | +--------------+ | + * | | System resume | | + * +----------------------------+ +----------------+ + * + * S0IX suspend: The DSP is in D0I3 if any D0I3-compatible streams + * ignored the suspend trigger. Otherwise the DSP + * is in D3. + */ + +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; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); +#endif + int ret; + + hda_sdw_int_enable(sdev, false); + + /* disable IPC interrupts */ + 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 + + /* power down DSP */ + ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to power down core during suspend\n"); + return ret; + } + + /* disable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, false); + hda_dsp_ctrl_ppcap_int_enable(sdev, false); + + /* disable hda bus irq and streams */ + hda_dsp_ctrl_stop_chip(sdev); + + /* disable LP retention mode */ + snd_sof_pci_update_bits(sdev, PCI_PGCTL, + PCI_PGCTL_LSRMD_MASK, PCI_PGCTL_LSRMD_MASK); + + /* reset controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to reset controller during suspend\n"); + return ret; + } + + /* display codec can powered off after link reset */ + hda_codec_i915_display_power(sdev, false); + + return 0; +} + +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); + struct hdac_ext_link *hlink = NULL; +#endif + int ret; + + /* display codec must be powered before link reset */ + hda_codec_i915_display_power(sdev, true); + + /* + * clear TCSEL to clear playback on some HD Audio + * codecs. PCI TCSEL is defined in the Intel manuals. + */ + snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0); + + /* reset and start hda controller */ + ret = hda_dsp_ctrl_init_chip(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to start controller after resume\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* check jack status */ + if (runtime_resume) { + if (sdev->system_suspend_target == SOF_SUSPEND_NONE) + 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) + snd_hdac_ext_bus_link_power_down(hlink); + } + + /* check dma status and clean up CORB/RIRB buffers */ + if (!bus->cmd_dma_state) + 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); + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + .substate = SOF_HDA_DSP_PM_D0I0, + }; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_link *hlink = NULL; +#endif + int ret; + + /* resume from D0I3 */ + if (sdev->dsp_power_state.state == SOF_DSP_PM_D0) { + hda_codec_i915_display_power(sdev, true); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* power up links that were active before suspend */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (hlink->ref_count) { + ret = snd_hdac_ext_bus_link_power_up(hlink); + if (ret < 0) { + dev_dbg(sdev->dev, + "error %x in %s: failed to power up links", + ret, __func__); + return ret; + } + } + } + + /* set up CORB/RIRB buffers if was on before suspend */ + if (bus->cmd_dma_state) + snd_hdac_bus_init_cmd_io(bus); +#endif + + /* Set DSP power state */ + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) { + dev_err(sdev->dev, "error: setting dsp state %d substate %d\n", + target_state.state, target_state.substate); + return ret; + } + + /* 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 */ + ret = hda_resume(sdev, false); + if (ret < 0) + return ret; + + return snd_sof_dsp_set_power_state(sdev, &target_state); +} + +int hda_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + }; + int ret; + + /* init hda controller. DSP cores will be powered up during fw boot */ + ret = hda_resume(sdev, true); + if (ret < 0) + return ret; + + return snd_sof_dsp_set_power_state(sdev, &target_state); +} + +int hda_dsp_runtime_idle(struct snd_sof_dev *sdev) +{ + struct hdac_bus *hbus = sof_to_bus(sdev); + + if (hbus->codec_powered) { + dev_dbg(sdev->dev, "some codecs still powered (%08X), not idle\n", + (unsigned int)hbus->codec_powered); + return -EBUSY; + } + + return 0; +} + +int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D3, + }; + int ret; + + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); + + /* stop hda controller and power dsp off */ + ret = hda_suspend(sdev, true); + if (ret < 0) + return ret; + + return snd_sof_dsp_set_power_state(sdev, &target_state); +} + +int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + 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); + const struct sof_dsp_power_state target_dsp_state = { + .state = target_state, + .substate = target_state == SOF_DSP_PM_D0 ? + SOF_HDA_DSP_PM_D0I3 : 0, + }; + int ret; + + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); + + if (target_state == SOF_DSP_PM_D0) { + /* we can't keep a wakeref to display driver at suspend */ + hda_codec_i915_display_power(sdev, false); + + /* Set DSP power state */ + ret = snd_sof_dsp_set_power_state(sdev, &target_dsp_state); + if (ret < 0) { + dev_err(sdev->dev, "error: setting dsp state %d substate %d\n", + target_dsp_state.state, + target_dsp_state.substate); + return ret; + } + + /* 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); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* stop the CORB/RIRB DMA if it is On */ + if (bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); + + /* no link can be powered in s0ix state */ + ret = snd_hdac_ext_bus_link_power_down_all(bus); + if (ret < 0) { + dev_dbg(sdev->dev, + "error %d in %s: failed to power down links", + ret, __func__); + return ret; + } +#endif + + /* 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, false); + if (ret < 0) { + dev_err(bus->dev, "error: suspending dsp\n"); + return ret; + } + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) +{ +#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; + + /* set internal flag for BE */ + list_for_each_entry(s, &bus->stream_list, list) { + stream = stream_to_hdac_ext_stream(s); + + /* + * 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 = asoc_substream_to_rtd(stream->link_substream); + name = asoc_rtd_to_codec(rtd, 0)->component->name; + 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); + } + } +#endif + return 0; +} + +void hda_dsp_d0i3_work(struct work_struct *work) +{ + struct sof_intel_hda_dev *hdev = container_of(work, + struct sof_intel_hda_dev, + d0i3_work.work); + struct hdac_bus *bus = &hdev->hbus.core; + struct snd_sof_dev *sdev = dev_get_drvdata(bus->dev); + struct sof_dsp_power_state target_state; + int ret; + + target_state.state = SOF_DSP_PM_D0; + + /* DSP can enter D0I3 iff only D0I3-compatible streams are active */ + if (snd_sof_dsp_only_d0i3_compatible_stream_active(sdev)) + target_state.substate = SOF_HDA_DSP_PM_D0I3; + else + target_state.substate = SOF_HDA_DSP_PM_D0I0; + + /* remain in D0I0 */ + if (target_state.substate == SOF_HDA_DSP_PM_D0I0) + return; + + /* This can fail but error cannot be propagated */ + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) + dev_err_ratelimited(sdev->dev, + "error: failed to set DSP state %d substate %d\n", + target_state.state, target_state.substate); +} diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c new file mode 100644 index 000000000..acfeca426 --- /dev/null +++ b/sound/soc/sof/intel/hda-ipc.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include "../ops.h" +#include "hda.h" + +static void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev) +{ + /* + * tell DSP cmd is done - clear busy + * interrupt and send reply msg to dsp + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCT, + HDA_DSP_REG_HIPCT_BUSY, + HDA_DSP_REG_HIPCT_BUSY); + + /* unmask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, + HDA_DSP_REG_HIPCCTL_BUSY); +} + +static void hda_dsp_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + /* + * set DONE bit - tell DSP we have received the reply msg + * from DSP, and processed it, don't send more reply to host + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCIE, + HDA_DSP_REG_HIPCIE_DONE, + HDA_DSP_REG_HIPCIE_DONE); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, + HDA_DSP_REG_HIPCCTL_DONE); +} + +int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + /* send IPC message to DSP */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, + HDA_DSP_REG_HIPCI_BUSY); + + return 0; +} + +void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + struct sof_ipc_cmd_hdr *hdr; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } + + hdr = msg->msg_data; + 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 and PM_GATE + * replies. + */ + reply.error = 0; + reply.hdr.cmd = SOF_IPC_GLB_REPLY; + reply.hdr.size = sizeof(reply); + memcpy(msg->reply_data, &reply, sizeof(reply)); + goto out; + } + + /* get IPC reply from DSP in the mailbox */ + 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 correct size ? */ + if (reply.hdr.size != msg->reply_size && + /* getter payload is never known upfront */ + ((reply.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_PROBE)) { + 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); + } + +out: + msg->reply_error = ret; + +} + +/* IPC handler thread */ +irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 hipci; + u32 hipcie; + u32 hipct; + u32 hipcte; + u32 msg; + u32 msg_ext; + bool ipc_irq = false; + + /* read IPC status */ + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI); + hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE); + + /* is this a reply message from the DSP */ + if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { + msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; + msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, 0); + + /* + * Make sure the interrupt thread cannot be preempted between + * waking up the sender and re-enabling the interrupt. Also + * protect against a theoretical race with sof_ipc_tx_message(): + * if the DSP is fast enough to receive an IPC message, reply to + * it, and the host interrupt processing calls this function on + * a different core from the one, where the sending is taking + * place, the message might not yet be marked as expecting a + * reply. + */ + spin_lock_irq(&sdev->ipc_lock); + + /* handle immediate reply from DSP core */ + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); + + /* set the done bit */ + hda_dsp_ipc_dsp_done(sdev); + + spin_unlock_irq(&sdev->ipc_lock); + + ipc_irq = true; + } + + /* is this a new message from DSP */ + if (hipct & HDA_DSP_REG_HIPCT_BUSY) { + msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; + msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, 0); + + /* handle messages from DSP */ + if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + /* this is a PANIC message !! */ + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + } else { + /* normal message - process normally */ + snd_sof_ipc_msgs_rx(sdev); + } + + hda_dsp_ipc_host_done(sdev); + + ipc_irq = true; + } + + if (!ipc_irq) { + /* + * This interrupt is not shared so no need to return IRQ_NONE. + */ + dev_dbg_ratelimited(sdev->dev, + "nothing to do in IPC IRQ thread\n"); + } + + return IRQ_HANDLED; +} + +/* Check if an IPC IRQ occurred */ +bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) +{ + bool ret = false; + u32 irq_status; + + /* 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); + + /* invalid message ? */ + if (irq_status == 0xffffffff) + goto out; + + /* IPC message ? */ + if (irq_status & HDA_DSP_ADSPIS_IPC) + ret = true; + +out: + return ret; +} + +int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return HDA_DSP_MBOX_UPLINK_OFFSET; +} + +int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return SRAM_WINDOW_OFFSET(id); +} + +void hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + if (!substream || !sdev->stream_box.size) { + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + } else { + struct hdac_stream *hstream = substream->runtime->private_data; + struct sof_intel_hda_stream *hda_stream; + + hda_stream = container_of(hstream, + struct sof_intel_hda_stream, + hda_stream.hstream); + + /* The stream might already be closed */ + if (hstream) + sof_mailbox_read(sdev, hda_stream->stream.posn_offset, + p, sz); + } +} + +int hda_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct sof_intel_hda_stream *hda_stream; + /* validate offset */ + size_t posn_offset = reply->posn_offset; + + hda_stream = container_of(hstream, struct sof_intel_hda_stream, + hda_stream.hstream); + + /* check for unaligned offset or overflow */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) + return -EINVAL; + + hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset; + + dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", + substream->stream, hda_stream->stream.posn_offset); + + return 0; +} diff --git a/sound/soc/sof/intel/hda-ipc.h b/sound/soc/sof/intel/hda-ipc.h new file mode 100644 index 000000000..10fbca593 --- /dev/null +++ b/sound/soc/sof/intel/hda-ipc.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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) + +irqreturn_t cnl_ipc_irq_thread(int irq, void *context); +int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); +void cnl_ipc_dump(struct snd_sof_dev *sdev); + +#endif diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c new file mode 100644 index 000000000..4012097a9 --- /dev/null +++ b/sound/soc/sof/intel/hda-loader.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for HDA DSP code loader + */ + +#include <linux/firmware.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include <sound/sof.h> +#include "../ops.h" +#include "hda.h" + +#define HDA_FW_BOOT_ATTEMPTS 3 +#define HDA_CL_STREAM_FORMAT 0x40 + +static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction) +{ + struct hdac_ext_stream *dsp_stream; + struct hdac_stream *hstream; + struct pci_dev *pci = to_pci_dev(sdev->dev); + int ret; + + dsp_stream = hda_dsp_stream_get(sdev, direction); + + if (!dsp_stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return ERR_PTR(-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); + if (ret < 0) { + dev_err(sdev->dev, "error: memory alloc failed: %x\n", ret); + goto out_put; + } + + hstream->period_bytes = 0;/* initialize period_bytes */ + hstream->format_val = format; + hstream->bufsize = size; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + ret = hda_dsp_iccmax_stream_hw_params(sdev, dsp_stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "error: iccmax stream prepare failed: %x\n", ret); + goto out_free; + } + } else { + ret = hda_dsp_stream_hw_params(sdev, dsp_stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + goto out_free; + } + hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size); + } + + return dsp_stream; + +out_free: + snd_dma_free_pages(dmab); +out_put: + hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + return ERR_PTR(ret); +} + +/* + * first boot sequence has some extra steps. + * power on all host managed cores and only unstall/run the boot core to boot the + * DSP then turn off all non boot cores (if any) is powered on. + */ +static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + unsigned int status; + int ret; + int i; + + /* step 1: power up corex */ + ret = hda_dsp_core_power_up(sdev, chip->host_managed_cores_mask); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n"); + goto err; + } + + /* DSP is powered up, set all SSPs to slave mode */ + for (i = 0; i < chip->ssp_count; i++) { + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + chip->ssp_base_offset + + i * SSP_DEV_MEM_SIZE + + SSP_SSC1_OFFSET, + SSP_SET_SLAVE, + SSP_SET_SLAVE); + } + + /* step 2: purge FW request */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, + chip->ipc_req_mask | (HDA_DSP_IPC_PURGE_FW | + ((stream_tag - 1) << 9))); + + /* step 3: unset core 0 reset state & unstall/run core 0 */ + ret = hda_dsp_core_run(sdev, chip->init_core_mask); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, + "error: dsp core start failed %d\n", ret); + ret = -EIO; + goto err; + } + + /* step 4: wait for IPC DONE bit from ROM */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + chip->ipc_ack, status, + ((status & chip->ipc_ack_mask) + == chip->ipc_ack_mask), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_INIT_TIMEOUT_US); + + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, + "error: %s: timeout for HIPCIE done\n", + __func__); + goto err; + } + + /* set DONE bit to clear the reply IPC message */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + chip->ipc_ack, + chip->ipc_ack_mask, + chip->ipc_ack_mask); + + /* step 5: power down corex */ + ret = hda_dsp_core_power_down(sdev, chip->host_managed_cores_mask & ~(BIT(0))); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, + "error: dsp core x power down failed\n"); + goto err; + } + + /* step 6: enable IPC interrupts */ + hda_dsp_ipc_int_enable(sdev); + + /* step 7: wait for ROM init */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS, status, + ((status & HDA_DSP_ROM_STS_MASK) + == HDA_DSP_ROM_INIT), + HDA_DSP_REG_POLL_INTERVAL_US, + chip->rom_init_timeout * + USEC_PER_MSEC); + if (!ret) + return 0; + + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + 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->host_managed_cores_mask); + + return ret; +} + +static int cl_trigger(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, int cmd) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + + /* code loader is special case that reuses stream ops */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, + 1 << hstream->index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->running = true; + return 0; + default: + return hda_dsp_stream_trigger(sdev, stream, cmd); + } +} + +static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *stream) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + + if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) + ret = hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); + else + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + + hda_dsp_stream_put(sdev, hstream->direction, hstream->stream_tag); + hstream->running = 0; + hstream->substream = NULL; + + /* reset BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, 0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, 0); + + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset, 0); + snd_dma_free_pages(dmab); + dmab->area = NULL; + hstream->bufsize = 0; + hstream->format_val = 0; + + return ret; +} + +static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream) +{ + unsigned int reg; + int ret, status; + + ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, "error: DMA trigger start failed\n"); + return ret; + } + + status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS, reg, + ((reg & HDA_DSP_ROM_STS_MASK) + == HDA_DSP_ROM_FW_ENTERED), + 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"); + if (!status) + status = ret; + } + + return status; +} + +int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct hdac_ext_stream *iccmax_stream; + struct hdac_bus *bus = sof_to_bus(sdev); + struct firmware stripped_firmware; + int ret, ret1; + u8 original_gb; + + /* save the original LTRP guardband value */ + original_gb = snd_hdac_chip_readb(bus, VS_LTRP) & HDA_VS_INTEL_LTRP_GB_MASK; + + if (plat_data->fw->size <= plat_data->fw_offset) { + dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n"); + return -EINVAL; + } + + stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset; + + /* prepare capture stream for ICCMAX */ + iccmax_stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, + &sdev->dmab_bdl, SNDRV_PCM_STREAM_CAPTURE); + if (IS_ERR(iccmax_stream)) { + dev_err(sdev->dev, "error: dma prepare for ICCMAX stream failed\n"); + return PTR_ERR(iccmax_stream); + } + + ret = hda_dsp_cl_boot_firmware(sdev); + + /* + * Perform iccmax 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_bdl, iccmax_stream); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + + /* set return value to indicate cleanup failure */ + if (!ret) + ret = ret1; + } + + /* restore the original guardband value after FW boot */ + snd_hdac_chip_updateb(bus, VS_LTRP, HDA_VS_INTEL_LTRP_GB_MASK, original_gb); + + return ret; +} + +int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct sof_dev_desc *desc = plat_data->desc; + const struct sof_intel_dsp_desc *chip_info; + struct hdac_ext_stream *stream; + struct firmware stripped_firmware; + int ret, ret1, i; + + chip_info = desc->chip_info; + + if (plat_data->fw->size <= plat_data->fw_offset) { + dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n"); + return -EINVAL; + } + + stripped_firmware.data = plat_data->fw->data + plat_data->fw_offset; + stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset; + + /* init for booting wait */ + init_waitqueue_head(&sdev->boot_wait); + + /* prepare DMA for code loader stream */ + stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, + &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK); + if (IS_ERR(stream)) { + dev_err(sdev->dev, "error: dma prepare for fw loading failed\n"); + return PTR_ERR(stream); + } + + memcpy(sdev->dmab.area, stripped_firmware.data, + stripped_firmware.size); + + /* try ROM init a few times before giving up */ + for (i = 0; i < HDA_FW_BOOT_ATTEMPTS; i++) { + dev_dbg(sdev->dev, + "Attempting iteration %d of Core En/ROM load...\n", i); + + hda->boot_iteration = i + 1; + ret = cl_dsp_init(sdev, stream->hstream.stream_tag); + + /* don't retry anymore if successful */ + if (!ret) + break; + } + + if (i == HDA_FW_BOOT_ATTEMPTS) { + dev_err(sdev->dev, "error: dsp init failed after %d attempts with err: %d\n", + i, ret); + dev_err(sdev->dev, "ROM error=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)); + goto cleanup; + } + + /* + * When a SoundWire link is in clock stop state, a Slave + * device may trigger in-band wakes for events such as jack + * insertion or acoustic event detection. This event will lead + * to a WAKEEN interrupt, handled by the PCI device and routed + * to PME if the PCI device is in D3. The resume function in + * audio PCI driver will be invoked by ACPI for PME event and + * initialize the device and process WAKEEN interrupt. + * + * The WAKEEN interrupt should be processed ASAP to prevent an + * interrupt flood, otherwise other interrupts, such IPC, + * cannot work normally. The WAKEEN is handled after the ROM + * is initialized successfully, which ensures power rails are + * enabled before accessing the SoundWire SHIM registers + */ + if (!sdev->first_boot) + hda_sdw_process_wakeen(sdev); + + /* + * at this point DSP ROM has been initialized and + * should be ready for code loading and firmware boot + */ + ret = cl_copy_fw(sdev, stream); + if (!ret) + dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); + else + dev_err(sdev->dev, "error: load fw failed ret: %d\n", ret); + +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 */ + if (!ret) + ret = ret1; + } + + /* + * return primary core id if both fw copy + * and stream clean up are successful + */ + if (!ret) + return chip_info->init_core_mask; + + /* dump dsp registers and disable DSP upon error */ + hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX); + + /* disable DSP */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, 0); + return ret; +} + +/* pre fw run operations */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); +} + +/* post fw run operations */ +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + int ret; + + if (sdev->first_boot) { + ret = hda_sdw_startup(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: could not startup SoundWire links\n"); + return ret; + } + } + + hda_sdw_int_enable(sdev, true); + + /* re-enable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, true); +} diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c new file mode 100644 index 000000000..b527d5958 --- /dev/null +++ b/sound/soc/sof/intel/hda-pcm.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hda_register.h> +#include <sound/pcm_params.h> +#include "../sof-audio.h" +#include "../ops.h" +#include "hda.h" + +#define SDnFMT_BASE(x) ((x) << 14) +#define SDnFMT_MULT(x) (((x) - 1) << 11) +#define SDnFMT_DIV(x) (((x) - 1) << 8) +#define SDnFMT_BITS(x) ((x) << 4) +#define SDnFMT_CHAN(x) ((x) << 0) + +u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate) +{ + switch (rate) { + case 8000: + return SDnFMT_DIV(6); + case 9600: + return SDnFMT_DIV(5); + case 11025: + return SDnFMT_BASE(1) | SDnFMT_DIV(4); + case 16000: + return SDnFMT_DIV(3); + case 22050: + return SDnFMT_BASE(1) | SDnFMT_DIV(2); + case 32000: + return SDnFMT_DIV(3) | SDnFMT_MULT(2); + case 44100: + return SDnFMT_BASE(1); + case 48000: + return 0; + case 88200: + return SDnFMT_BASE(1) | SDnFMT_MULT(2); + case 96000: + return SDnFMT_MULT(2); + case 176400: + return SDnFMT_BASE(1) | SDnFMT_MULT(4); + case 192000: + return SDnFMT_MULT(4); + default: + dev_warn(sdev->dev, "can't find div rate %d using 48kHz\n", + rate); + return 0; /* use 48KHz if not found */ + } +}; + +u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits) +{ + switch (sample_bits) { + case 8: + return SDnFMT_BITS(0); + case 16: + return SDnFMT_BITS(1); + case 20: + return SDnFMT_BITS(2); + case 24: + return SDnFMT_BITS(3); + case 32: + return SDnFMT_BITS(4); + default: + dev_warn(sdev->dev, "can't find %d bits using 16bit\n", + sample_bits); + return SDnFMT_BITS(1); /* use 16bits format if not found */ + } +}; + +int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + 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; + + size = params_buffer_bytes(params); + rate = hda_dsp_get_mult_div(sdev, params_rate(params)); + bits = hda_dsp_get_bits(sdev, params_width(params)); + + hstream->substream = substream; + + dmab = substream->runtime->dma_buffer_p; + + hstream->format_val = rate | bits | (params_channels(params) - 1); + hstream->bufsize = size; + hstream->period_bytes = params_period_bytes(params); + hstream->no_period_wakeup = + (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && + (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); + + ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + return ret; + } + + /* disable SPIB, to enable buffer wrap for stream */ + hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 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; + + return 0; +} + +int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); + + return hda_dsp_stream_trigger(sdev, stream, cmd); +} + +snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + 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(scomp, rtd); + if (!spcm) { + dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + if (hda && !hda->no_ipc_position) { + /* read position from IPC position */ + pos = spcm->stream[substream->stream].posn.host_posn; + goto found; + } + + /* + * DPIB/posbuf position mode: + * For Playback, Use DPIB register from HDA space which + * reflects the actual data transferred. + * For Capture, Use the position buffer for pointer, as DPIB + * is not accurate enough, its update may be completed + * earlier than the data written to DDR. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + } else { + /* + * For capture stream, we need more workaround to fix the + * position incorrect issue: + * + * 1. Wait at least 20us before reading position buffer after + * the interrupt generated(IOC), to make sure position update + * happens on frame boundary i.e. 20.833uSec for 48KHz. + * 2. Perform a dummy Read to DPIB register to flush DMA + * position value. + * 3. Read the DMA Position from posbuf. Now the readback + * value should be >= period boundary. + */ + usleep_range(20, 21); + snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + pos = snd_hdac_stream_get_pos_posbuf(hstream); + } + + if (pos >= hstream->bufsize) + pos = 0; + +found: + pos = bytes_to_frames(substream->runtime, pos); + + dev_vdbg(sdev->dev, "PCM: stream %d dir %d position %lu\n", + hstream->index, substream->stream, pos); + return pos; +} + +int hda_dsp_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *dsp_stream; + int direction = substream->stream; + + dsp_stream = hda_dsp_stream_get(sdev, direction); + + if (!dsp_stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + + /* binding pcm substream to hda stream */ + substream->runtime->private_data = &dsp_stream->hstream; + return 0; +} + +int hda_dsp_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + int direction = substream->stream; + int ret; + + ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + + if (ret) { + dev_dbg(sdev->dev, "stream %s not opened!\n", substream->name); + return -ENODEV; + } + + /* unbinding pcm substream to hda stream */ + substream->runtime->private_data = NULL; + return 0; +} diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c new file mode 100644 index 000000000..0e09ede92 --- /dev/null +++ b/sound/soc/sof/intel/hda-stream.c @@ -0,0 +1,947 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <linux/pm_runtime.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include <sound/sof.h> +#include "../ops.h" +#include "../sof-audio.h" +#include "hda.h" + +#define HDA_LTRP_GB_VALUE_US 95 + +/* + * set up one of BDL entries for a stream + */ +static int hda_setup_bdle(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct hdac_stream *stream, + struct sof_intel_dsp_bdl **bdlp, + int offset, int size, int ioc) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_intel_dsp_bdl *bdl = *bdlp; + + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (stream->frags >= HDA_DSP_MAX_BDL_ENTRIES) { + dev_err(sdev->dev, "error: stream frags exceeded\n"); + return -EINVAL; + } + + addr = snd_sgbuf_get_addr(dmab, offset); + /* program BDL addr */ + bdl->addr_l = cpu_to_le32(lower_32_bits(addr)); + bdl->addr_h = cpu_to_le32(upper_32_bits(addr)); + /* program BDL size */ + chunk = snd_sgbuf_get_chunk_size(dmab, offset, size); + /* one BDLE should not cross 4K boundary */ + if (bus->align_bdle_4k) { + u32 remain = 0x1000 - (offset & 0xfff); + + if (chunk > remain) + chunk = remain; + } + bdl->size = cpu_to_le32(chunk); + /* only program IOC when the whole segment is processed */ + size -= chunk; + bdl->ioc = (size || !ioc) ? 0 : cpu_to_le32(0x01); + bdl++; + stream->frags++; + offset += chunk; + + dev_vdbg(sdev->dev, "bdl, frags:%d, chunk size:0x%x;\n", + stream->frags, chunk); + } + + *bdlp = bdl; + return offset; +} + +/* + * set up Buffer Descriptor List (BDL) for host memory transfer + * BDL describes the location of the individual buffers and is little endian. + */ +int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct hdac_stream *stream) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct sof_intel_dsp_bdl *bdl; + int i, offset, period_bytes, periods; + int remain, ioc; + + period_bytes = stream->period_bytes; + dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes); + if (!period_bytes) + period_bytes = stream->bufsize; + + periods = stream->bufsize / period_bytes; + + dev_dbg(sdev->dev, "periods:%d\n", periods); + + remain = stream->bufsize % period_bytes; + if (remain) + periods++; + + /* program the initial BDL entries */ + bdl = (struct sof_intel_dsp_bdl *)stream->bdl.area; + offset = 0; + stream->frags = 0; + + /* + * set IOC if don't use position IPC + * and period_wakeup needed. + */ + ioc = hda->no_ipc_position ? + !stream->no_period_wakeup : 0; + + for (i = 0; i < periods; i++) { + if (i == (periods - 1) && remain) + /* set the last small entry */ + offset = hda_setup_bdle(sdev, dmab, + stream, &bdl, offset, + remain, 0); + else + offset = hda_setup_bdle(sdev, dmab, + stream, &bdl, offset, + period_bytes, ioc); + } + + return offset; +} + +int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + int enable, u32 size) +{ + struct hdac_stream *hstream = &stream->hstream; + u32 mask; + + if (!sdev->bar[HDA_DSP_SPIB_BAR]) { + dev_err(sdev->dev, "error: address of spib capability is NULL\n"); + return -EINVAL; + } + + mask = (1 << hstream->index); + + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ + sof_io_write(sdev, stream->spib_addr, size); + + return 0; +} + +/* get next unused stream */ +struct hdac_ext_stream * +hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *stream = NULL; + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + + /* get an unused stream */ + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction == direction && !s->opened) { + stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(stream, + struct sof_intel_hda_stream, + hda_stream); + /* check if the host DMA channel is reserved */ + if (hda_stream->host_reserved) + continue; + + s->opened = true; + break; + } + } + + spin_unlock_irq(&bus->reg_lock); + + /* stream found ? */ + if (!stream) + dev_err(sdev->dev, "error: no free %s streams\n", + 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; +} + +/* free a stream */ +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); + + /* + * 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->opened) + continue; + + if (s->direction == direction && s->stream_tag == stream_tag) { + s->opened = false; + found = true; + } else if (s->direction == SNDRV_PCM_STREAM_CAPTURE) { + active_capture_stream = true; + } + } + + spin_unlock_irq(&bus->reg_lock); + + /* 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, + struct hdac_ext_stream *stream, int cmd) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + u32 dma_start = SOF_HDA_SD_CTL_DMA_START; + int ret; + u32 run; + + /* cmd must be for audio stream */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_START: + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, + 1 << hstream->index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK); + + ret = snd_sof_dsp_read_poll_timeout(sdev, + HDA_DSP_HDA_BAR, + sd_offset, run, + ((run & dma_start) == dma_start), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_STREAM_RUN_TIMEOUT); + + 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; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, 0x0); + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_HDA_BAR, + sd_offset, run, + !(run & dma_start), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_STREAM_RUN_TIMEOUT); + + 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, + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->running = false; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, 0x0); + break; + default: + dev_err(sdev->dev, "error: unknown command: %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +/* minimal recommended programming for ICCMAX stream */ +int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + struct snd_pcm_hw_params *params) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret; + u32 mask = 0x1 << hstream->index; + + if (!stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + + if (hstream->posbuf) + *hstream->posbuf = 0; + + /* reset BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + 0x0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + 0x0); + + hstream->frags = 0; + + ret = hda_dsp_stream_setup_bdl(sdev, dmab, hstream); + if (ret < 0) { + dev_err(sdev->dev, "error: set up of BDL failed\n"); + return ret; + } + + /* program BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + (u32)hstream->bdl.addr); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + upper_32_bits(hstream->bdl.addr)); + + /* program cyclic buffer length */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL, + hstream->bufsize); + + /* program last valid index */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI, + 0xffff, (hstream->frags - 1)); + + /* decouple host and link DMA, enable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + + /* Follow HW recommendation to set the guardband value to 95us during FW boot */ + snd_hdac_chip_updateb(bus, VS_LTRP, HDA_VS_INTEL_LTRP_GB_MASK, HDA_LTRP_GB_VALUE_US); + + /* start DMA */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, SOF_HDA_SD_CTL_DMA_START); + + return 0; +} + +/* + * prepare for common hdac registers settings, for both code loader + * and normal stream. + */ +int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + struct snd_pcm_hw_params *params) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + u32 dma_start = SOF_HDA_SD_CTL_DMA_START; + u32 val, mask; + u32 run; + + if (!stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + + /* decouple host and link DMA */ + mask = 0x1 << hstream->index; + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + + if (!dmab) { + dev_err(sdev->dev, "error: no dma buffer allocated!\n"); + return -ENODEV; + } + + /* clear stream status */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK | + SOF_HDA_SD_CTL_DMA_START, 0); + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_HDA_BAR, + sd_offset, run, + !(run & dma_start), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_STREAM_RUN_TIMEOUT); + + 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, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + /* stream reset */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, + 0x1); + udelay(3); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset); + if (val & 0x1) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "error: stream reset failed\n"); + return -ETIMEDOUT; + } + + timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, + 0x0); + + /* wait for hardware to report that stream is out of reset */ + udelay(3); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset); + if ((val & 0x1) == 0) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "error: timeout waiting for stream reset\n"); + return -ETIMEDOUT; + } + + if (hstream->posbuf) + *hstream->posbuf = 0; + + /* reset BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + 0x0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + 0x0); + + /* clear stream status */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK | + SOF_HDA_SD_CTL_DMA_START, 0); + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_HDA_BAR, + sd_offset, run, + !(run & dma_start), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_STREAM_RUN_TIMEOUT); + + 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, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->frags = 0; + + ret = hda_dsp_stream_setup_bdl(sdev, dmab, hstream); + if (ret < 0) { + dev_err(sdev->dev, "error: set up of BDL failed\n"); + return ret; + } + + /* program stream tag to set up stream descriptor for DMA */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_SD_CTL_STREAM_TAG_MASK, + hstream->stream_tag << + SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT); + + /* program cyclic buffer length */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL, + hstream->bufsize); + + /* + * Recommended hardware programming sequence for HDAudio DMA format + * + * 1. Put DMA into coupled mode by clearing PPCTL.PROCEN bit + * for corresponding stream index before the time of writing + * format to SDxFMT register. + * 2. Write SDxFMT + * 3. Set PPCTL.PROCEN bit for corresponding stream index to + * enable decoupled mode + */ + + /* couple host and link DMA, disable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, 0); + + /* program stream format */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_FORMAT, + 0xffff, hstream->format_val); + + /* decouple host and link DMA, enable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + + /* program last valid index */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI, + 0xffff, (hstream->frags - 1)); + + /* program BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + (u32)hstream->bdl.addr); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + upper_32_bits(hstream->bdl.addr)); + + /* enable position buffer */ + if (!(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE) + & SOF_HDA_ADSP_DPLBASE_ENABLE)) { + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE, + upper_32_bits(bus->posbuf.addr)); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE, + (u32)bus->posbuf.addr | + SOF_HDA_ADSP_DPLBASE_ENABLE); + } + + /* set interrupt enable bits */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + /* read FIFO size */ + if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) { + hstream->fifo_size = + snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE); + hstream->fifo_size &= 0xffff; + hstream->fifo_size += 1; + } else { + hstream->fifo_size = 0; + } + + return ret; +} + +int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct hdac_stream *stream = substream->runtime->private_data; + struct hdac_ext_stream *link_dev = container_of(stream, + struct hdac_ext_stream, + hstream); + struct hdac_bus *bus = sof_to_bus(sdev); + u32 mask = 0x1 << stream->index; + + spin_lock_irq(&bus->reg_lock); + /* couple host and link DMA if link DMA channel is idle */ + if (!link_dev->link_locked) + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, mask, 0); + spin_unlock_irq(&bus->reg_lock); + + stream->substream = NULL; + + return 0; +} + +bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + bool ret = false; + u32 status; + + /* 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); + + /* if Register inaccessible, ignore it.*/ + if (status != 0xffffffff) + ret = true; + + spin_unlock_irq(&bus->reg_lock); + + return ret; +} + +static void +hda_dsp_set_bytes_transferred(struct hdac_stream *hstream, u64 buffer_size) +{ + u64 prev_pos, pos, num_bytes; + + div64_u64_rem(hstream->curr_pos, buffer_size, &prev_pos); + pos = snd_hdac_stream_get_pos_posbuf(hstream); + + if (pos < prev_pos) + num_bytes = (buffer_size - prev_pos) + pos; + else + num_bytes = pos - prev_pos; + + hstream->curr_pos += num_bytes; +} + +static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status) +{ + struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *s; + bool active = false; + u32 sd_status; + + list_for_each_entry(s, &bus->stream_list, list) { + if (status & BIT(s->index) && s->opened) { + sd_status = snd_hdac_stream_readb(s, SD_STS); + + dev_vdbg(bus->dev, "stream %d status 0x%x\n", + s->index, sd_status); + + snd_hdac_stream_writeb(s, SD_STS, sd_status); + + active = true; + if ((!s->substream && !s->cstream) || + !s->running || + (sd_status & SOF_HDA_CL_DMA_SD_INT_COMPLETE) == 0) + continue; + + /* Inform ALSA only in case not do that with IPC */ + if (s->substream && sof_hda->no_ipc_position) { + snd_sof_pcm_period_elapsed(s->substream); + } else if (s->cstream) { + hda_dsp_set_bytes_transferred(s, + s->cstream->runtime->buffer_size); + snd_compr_fragment_elapsed(s->cstream); + } + } + } + + return active; +} + +irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *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 + bool active; + u32 status; + int i; + + /* + * Loop 10 times to handle missed interrupts caused by + * unsolicited responses from the codec + */ + for (i = 0, active = true; i < 10 && active; i++) { + spin_lock_irq(&bus->reg_lock); + + status = snd_hdac_chip_readl(bus, INTSTS); + + /* check streams */ + active = hda_dsp_stream_check(bus, status); + + /* check and clear RIRB interrupt */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + if (status & AZX_INT_CTRL_EN) { + rirb_status = snd_hdac_chip_readb(bus, RIRBSTS); + if (rirb_status & RIRB_INT_MASK) { + /* + * Clearing the interrupt status here ensures + * that no interrupt gets masked after the RIRB + * wp is read in snd_hdac_bus_update_rirb. + */ + snd_hdac_chip_writeb(bus, RIRBSTS, + RIRB_INT_MASK); + active = true; + if (rirb_status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + } + } +#endif + spin_unlock_irq(&bus->reg_lock); + } + + return IRQ_HANDLED; +} + +int hda_dsp_stream_init(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_stream *stream; + struct hdac_stream *hstream; + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + int sd_offset; + int i, num_playback, num_capture, num_total, ret; + u32 gcap; + + gcap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCAP); + dev_dbg(sdev->dev, "hda global caps = 0x%x\n", gcap); + + /* get stream count from GCAP */ + num_capture = (gcap >> 8) & 0x0f; + num_playback = (gcap >> 12) & 0x0f; + num_total = num_playback + num_capture; + + dev_dbg(sdev->dev, "detected %d playback and %d capture streams\n", + num_playback, num_capture); + + if (num_playback >= SOF_HDA_PLAYBACK_STREAMS) { + dev_err(sdev->dev, "error: too many playback streams %d\n", + num_playback); + return -EINVAL; + } + + if (num_capture >= SOF_HDA_CAPTURE_STREAMS) { + dev_err(sdev->dev, "error: too many capture streams %d\n", + num_playback); + return -EINVAL; + } + + /* + * mem alloc for the position buffer + * TODO: check position buffer update + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + SOF_HDA_DPIB_ENTRY_SIZE * num_total, + &bus->posbuf); + if (ret < 0) { + dev_err(sdev->dev, "error: posbuffer dma alloc failed\n"); + return -ENOMEM; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* mem alloc for the CORB/RIRB ringbuffers */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + PAGE_SIZE, &bus->rb); + if (ret < 0) { + dev_err(sdev->dev, "error: RB alloc failed\n"); + return -ENOMEM; + } +#endif + + /* create capture streams */ + for (i = 0; i < num_capture; i++) { + struct sof_intel_hda_stream *hda_stream; + + hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream), + GFP_KERNEL); + if (!hda_stream) + return -ENOMEM; + + hda_stream->sdev = sdev; + + stream = &hda_stream->hda_stream; + + stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + + stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + + /* do we support SPIB */ + if (sdev->bar[HDA_DSP_SPIB_BAR]) { + stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_SPIB; + + stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_MAXFIFO; + } + + hstream = &stream->hstream; + hstream->bus = bus; + hstream->sd_int_sta_mask = 1 << i; + hstream->index = i; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset; + hstream->stream_tag = i + 1; + hstream->opened = false; + hstream->running = false; + hstream->direction = SNDRV_PCM_STREAM_CAPTURE; + + /* memory alloc for stream BDL */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + HDA_DSP_BDL_SIZE, &hstream->bdl); + if (ret < 0) { + dev_err(sdev->dev, "error: stream bdl dma alloc failed\n"); + return -ENOMEM; + } + hstream->posbuf = (__le32 *)(bus->posbuf.area + + (hstream->index) * 8); + + list_add_tail(&hstream->list, &bus->stream_list); + } + + /* create playback streams */ + for (i = num_capture; i < num_total; i++) { + struct sof_intel_hda_stream *hda_stream; + + hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream), + GFP_KERNEL); + if (!hda_stream) + return -ENOMEM; + + hda_stream->sdev = sdev; + + stream = &hda_stream->hda_stream; + + /* we always have DSP support */ + stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + + stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + + /* do we support SPIB */ + if (sdev->bar[HDA_DSP_SPIB_BAR]) { + stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_SPIB; + + stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_MAXFIFO; + } + + hstream = &stream->hstream; + hstream->bus = bus; + hstream->sd_int_sta_mask = 1 << i; + hstream->index = i; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset; + hstream->stream_tag = i - num_capture + 1; + hstream->opened = false; + hstream->running = false; + hstream->direction = SNDRV_PCM_STREAM_PLAYBACK; + + /* mem alloc for stream BDL */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + HDA_DSP_BDL_SIZE, &hstream->bdl); + if (ret < 0) { + dev_err(sdev->dev, "error: stream bdl dma alloc failed\n"); + return -ENOMEM; + } + + hstream->posbuf = (__le32 *)(bus->posbuf.area + + (hstream->index) * 8); + + list_add_tail(&hstream->list, &bus->stream_list); + } + + /* store total stream count (playback + capture) from GCAP */ + sof_hda->stream_max = num_total; + + return 0; +} + +void hda_dsp_stream_free(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *s, *_s; + struct hdac_ext_stream *stream; + struct sof_intel_hda_stream *hda_stream; + + /* free position buffer */ + if (bus->posbuf.area) + snd_dma_free_pages(&bus->posbuf); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* free position buffer */ + if (bus->rb.area) + snd_dma_free_pages(&bus->rb); +#endif + + list_for_each_entry_safe(s, _s, &bus->stream_list, list) { + /* TODO: decouple */ + + /* free bdl buffer */ + if (s->bdl.area) + snd_dma_free_pages(&s->bdl); + list_del(&s->list); + stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(stream, struct sof_intel_hda_stream, + hda_stream); + devm_kfree(sdev->dev, hda_stream); + } +} diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c new file mode 100644 index 000000000..1eb746d5a --- /dev/null +++ b/sound/soc/sof/intel/hda-trace.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hdaudio_ext.h> +#include "../ops.h" +#include "hda.h" + +static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_ext_stream *stream = hda->dtrace_stream; + struct hdac_stream *hstream = &stream->hstream; + struct snd_dma_buffer *dmab = &sdev->dmatb; + int ret; + + hstream->period_bytes = 0;/* initialize period_bytes */ + hstream->bufsize = sdev->dmatb.bytes; + + ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); + if (ret < 0) + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + + return ret; +} + +int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + int ret; + + hda->dtrace_stream = hda_dsp_stream_get(sdev, + SNDRV_PCM_STREAM_CAPTURE); + + if (!hda->dtrace_stream) { + dev_err(sdev->dev, + "error: no available capture stream for DMA trace\n"); + return -ENODEV; + } + + *stream_tag = hda->dtrace_stream->hstream.stream_tag; + + /* + * initialize capture stream, set BDL address and return corresponding + * stream tag which will be sent to the firmware by IPC message. + */ + ret = hda_dsp_trace_prepare(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac trace init failed: %x\n", ret); + hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag); + hda->dtrace_stream = NULL; + *stream_tag = 0; + } + + return ret; +} + +int hda_dsp_trace_release(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_stream *hstream; + + if (hda->dtrace_stream) { + hstream = &hda->dtrace_stream->hstream; + hda_dsp_stream_put(sdev, + SNDRV_PCM_STREAM_CAPTURE, + hstream->stream_tag); + hda->dtrace_stream = NULL; + return 0; + } + + dev_dbg(sdev->dev, "DMA trace stream is not opened!\n"); + return -ENODEV; +} + +int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + + return hda_dsp_stream_trigger(sdev, hda->dtrace_stream, cmd); +} diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c new file mode 100644 index 000000000..b4cc72483 --- /dev/null +++ b/sound/soc/sof/intel/hda.c @@ -0,0 +1,1247 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_intel.h> +#include <sound/intel-nhlt.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../sof-audio.h" +#include "../ops.h" +#include "hda.h" + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#include <sound/soc-acpi-intel-match.h> +#endif + +/* platform specific devices */ +#include "shim.h" + +#define EXCEPT_MAX_HDR_SIZE 0x400 +#define HDA_EXT_ROM_STATUS_SIZE 8 + +static const struct sof_intel_dsp_desc + *get_chip_info(struct snd_sof_pdata *pdata) +{ + const struct sof_dev_desc *desc = pdata->desc; + const struct sof_intel_dsp_desc *chip_info; + + chip_info = desc->chip_info; + + return chip_info; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + +/* + * The default for SoundWire clock stop quirks is to power gate the IP + * and do a Bus Reset, this will need to be modified when the DSP + * needs to remain in D0i3 so that the Master does not lose context + * and enumeration is not required on clock restart + */ +static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET; +module_param(sdw_clock_stop_quirks, int, 0444); +MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks"); + +static int sdw_params_stream(struct device *dev, + struct sdw_intel_stream_params_data *params_data) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_soc_dai *d = params_data->dai; + struct sof_ipc_dai_config config; + struct sof_ipc_reply reply; + int link_id = params_data->link_id; + int alh_stream_id = params_data->alh_stream_id; + int ret; + u32 size = sizeof(config); + + memset(&config, 0, size); + config.hdr.size = size; + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.type = SOF_DAI_INTEL_ALH; + config.dai_index = (link_id << 8) | (d->id); + config.alh.stream_id = alh_stream_id; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config.hdr.cmd, &config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set DAI hw_params for link %d dai->id %d ALH %d\n", + link_id, d->id, alh_stream_id); + } + + return ret; +} + +static int sdw_free_stream(struct device *dev, + struct sdw_intel_stream_free_data *free_data) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_soc_dai *d = free_data->dai; + struct sof_ipc_dai_config config; + struct sof_ipc_reply reply; + int link_id = free_data->link_id; + int ret; + u32 size = sizeof(config); + + memset(&config, 0, size); + config.hdr.size = size; + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.type = SOF_DAI_INTEL_ALH; + config.dai_index = (link_id << 8) | d->id; + config.alh.stream_id = 0xFFFF; /* invalid value on purpose */ + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config.hdr.cmd, &config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to free stream for link %d dai->id %d\n", + link_id, d->id); + } + + return ret; +} + +static const struct sdw_intel_ops sdw_callback = { + .params_stream = sdw_params_stream, + .free_stream = sdw_free_stream, +}; + +void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) +{ + sdw_intel_enable_irq(sdev->bar[HDA_DSP_BAR], enable); +} + +static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + acpi_handle handle; + int ret; + + handle = ACPI_HANDLE(sdev->dev); + + /* save ACPI info for the probe step */ + hdev = sdev->pdata->hw_pdata; + + ret = sdw_intel_acpi_scan(handle, &hdev->info); + if (ret < 0) + return -EINVAL; + + return 0; +} + +static int hda_sdw_probe(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + struct sdw_intel_res res; + void *sdw; + + hdev = sdev->pdata->hw_pdata; + + memset(&res, 0, sizeof(res)); + + res.mmio_base = sdev->bar[HDA_DSP_BAR]; + res.irq = sdev->ipc_irq; + res.handle = hdev->info.handle; + res.parent = sdev->dev; + res.ops = &sdw_callback; + res.dev = sdev->dev; + res.clock_stop_quirks = sdw_clock_stop_quirks; + + /* + * ops and arg fields are not populated for now, + * they will be needed when the DAI callbacks are + * provided + */ + + /* we could filter links here if needed, e.g for quirks */ + res.count = hdev->info.count; + res.link_mask = hdev->info.link_mask; + + sdw = sdw_intel_probe(&res); + if (!sdw) { + dev_err(sdev->dev, "error: SoundWire probe failed\n"); + return -EINVAL; + } + + /* save context */ + hdev->sdw = sdw; + + return 0; +} + +int hda_sdw_startup(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + + if (!hdev->sdw) + return 0; + + return sdw_intel_startup(hdev->sdw); +} + +static int hda_sdw_exit(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + + hda_sdw_int_enable(sdev, false); + + if (hdev->sdw) + sdw_intel_exit(hdev->sdw); + hdev->sdw = NULL; + + return 0; +} + +static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + bool ret = false; + u32 irq_status; + + hdev = sdev->pdata->hw_pdata; + + if (!hdev->sdw) + return ret; + + /* store status */ + irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS2); + + /* invalid message ? */ + if (irq_status == 0xffffffff) + goto out; + + /* SDW message ? */ + if (irq_status & HDA_DSP_REG_ADSPIS2_SNDW) + ret = true; + +out: + return ret; +} + +static irqreturn_t hda_dsp_sdw_thread(int irq, void *context) +{ + return sdw_intel_thread(irq, context); +} + +static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + if (hdev->sdw && + snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_SNDW_WAKE_STS)) + return true; + + return false; +} + +void hda_sdw_process_wakeen(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + if (!hdev->sdw) + return; + + sdw_intel_process_wakeen_event(hdev->sdw); +} + +#endif + +/* + * Debug + */ + +struct hda_dsp_msg_code { + u32 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 + +static char *hda_model; +module_param(hda_model, charp, 0444); +MODULE_PARM_DESC(hda_model, "Use the given HDA board model."); + +#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_HDA_CODEC_HDMI); +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"}, + {HDA_DSP_ROM_FW_ENTERED, "status: fw entered"}, + {HDA_DSP_ROM_CSE_ERROR, "error: cse error"}, + {HDA_DSP_ROM_CSE_WRONG_RESPONSE, "error: cse wrong response"}, + {HDA_DSP_ROM_IMR_TO_SMALL, "error: IMR too small"}, + {HDA_DSP_ROM_BASE_FW_NOT_FOUND, "error: base fw not found"}, + {HDA_DSP_ROM_CSE_VALIDATION_FAILED, "error: signature verification failed"}, + {HDA_DSP_ROM_IPC_FATAL_ERROR, "error: ipc fatal error"}, + {HDA_DSP_ROM_L2_CACHE_ERROR, "error: L2 cache error"}, + {HDA_DSP_ROM_LOAD_OFFSET_TO_SMALL, "error: load offset too small"}, + {HDA_DSP_ROM_API_PTR_INVALID, "error: API ptr invalid"}, + {HDA_DSP_ROM_BASEFW_INCOMPAT, "error: base fw incompatible"}, + {HDA_DSP_ROM_UNHANDLED_INTERRUPT, "error: unhandled interrupt"}, + {HDA_DSP_ROM_MEMORY_HOLE_ECC, "error: ECC memory hole"}, + {HDA_DSP_ROM_KERNEL_EXCEPTION, "error: kernel exception"}, + {HDA_DSP_ROM_USER_EXCEPTION, "error: user exception"}, + {HDA_DSP_ROM_UNEXPECTED_RESET, "error: unexpected reset"}, + {HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"}, +}; + +static void hda_dsp_get_status_skl(struct snd_sof_dev *sdev) +{ + u32 status; + int i; + + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_FW_STATUS_SKL); + + for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { + if (status == hda_dsp_rom_msg[i].code) { + dev_err(sdev->dev, "%s - code %8.8x\n", + hda_dsp_rom_msg[i].msg, status); + return; + } + } + + /* not for us, must be generic sof message */ + dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); +} + +static void hda_dsp_get_status(struct snd_sof_dev *sdev) +{ + u32 status; + int i; + + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS); + + for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { + if (status == hda_dsp_rom_msg[i].code) { + dev_err(sdev->dev, "%s - code %8.8x\n", + hda_dsp_rom_msg[i].msg, status); + return; + } + } + + /* not for us, must be generic sof message */ + dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); +} + +static void hda_dsp_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* 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)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_block_read(sdev, sdev->mmio_bar, offset, stack, + stack_words * sizeof(u32)); +} + +void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[HDA_DSP_STACK_DUMP_SIZE]; + u32 status, panic; + + /* try APL specific status message types first */ + hda_dsp_get_status_skl(sdev); + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_ERROR_CODE_SKL); + + /*TODO: Check: there is no define in spec, but it is used in the code*/ + panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_ERROR_CODE_SKL + 0x4); + + 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, + stack, HDA_DSP_STACK_DUMP_SIZE); + } else { + dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n", + status, panic); + hda_dsp_get_status_skl(sdev); + } +} + +/* dump the first 8 dwords representing the extended ROM status */ +static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + char msg[128]; + int len = 0; + u32 value; + int i; + + for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) { + value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_STATUS + i * 0x4); + len += snprintf(msg + len, sizeof(msg) - len, " 0x%x", value); + } + + sof_dev_dbg_or_err(sdev->dev, hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS, + "extended rom status: %s", msg); + +} + +void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[HDA_DSP_STACK_DUMP_SIZE]; + u32 status, panic; + + /* try APL specific status message types first */ + hda_dsp_get_status(sdev); + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_FW_STATUS); + panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP); + + 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, + stack, HDA_DSP_STACK_DUMP_SIZE); + } else { + sof_dev_dbg_or_err(sdev->dev, hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS, + "status = 0x%8.8x panic = 0x%8.8x\n", + status, panic); + + hda_dsp_dump_ext_rom_status(sdev); + hda_dsp_get_status(sdev); + } +} + +void hda_ipc_irq_dump(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + u32 adspis; + u32 intsts; + u32 intctl; + u32 ppsts; + u8 rirbsts; + + /* read key IRQ stats and config registers */ + adspis = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); + intsts = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS); + intctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL); + ppsts = snd_sof_dsp_read(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPSTS); + rirbsts = snd_hdac_chip_readb(bus, RIRBSTS); + + dev_err(sdev->dev, + "error: hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n", + intsts, intctl, rirbsts); + dev_err(sdev->dev, + "error: dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n", + ppsts, adspis); +} + +void hda_ipc_dump(struct snd_sof_dev *sdev) +{ + u32 hipcie; + u32 hipct; + u32 hipcctl; + + hda_ipc_irq_dump(sdev); + + /* read IPC status */ + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, + "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", + hipcie, hipct, hipcctl); +} + +static int hda_init(struct snd_sof_dev *sdev) +{ + struct hda_bus *hbus; + struct hdac_bus *bus; + struct pci_dev *pci = to_pci_dev(sdev->dev); + int ret; + + hbus = sof_to_hbus(sdev); + bus = sof_to_bus(sdev); + + /* HDA bus init */ + 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; + hbus->mixer_assigned = -1; + hbus->modelname = hda_model; + + /* initialise hdac bus */ + bus->addr = pci_resource_start(pci, 0); +#if IS_ENABLED(CONFIG_PCI) + bus->remap_addr = pci_ioremap_bar(pci, 0); +#endif + if (!bus->remap_addr) { + dev_err(bus->dev, "error: ioremap error\n"); + return -ENXIO; + } + + /* HDA base */ + sdev->bar[HDA_DSP_HDA_BAR] = bus->remap_addr; + + /* init i915 and HDMI codecs */ + ret = hda_codec_i915_init(sdev); + if (ret < 0) + dev_warn(sdev->dev, "init of i915 and HDMI codec failed\n"); + + /* get controller capabilities */ + ret = hda_dsp_ctrl_get_caps(sdev); + if (ret < 0) + dev_err(sdev->dev, "error: get caps error\n"); + + return ret; +} + +#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 >= 1 && 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 *idisp_str, + const char *dmic_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%s.tplg", + split_ext, idisp_str, dmic_str); + if (!tplg_filename) + return NULL; + } + return tplg_filename; +} + +#endif + +static int hda_init_caps(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct snd_sof_pdata *pdata = sdev->pdata; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_ext_link *hlink; +#endif + struct sof_intel_hda_dev *hdev = pdata->hw_pdata; + u32 link_mask; + int ret = 0; + + device_disable_async_suspend(bus->dev); + + /* check if dsp is there */ + if (bus->ppcap) + dev_dbg(sdev->dev, "PP capability, will probe DSP later.\n"); + + /* 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); + return ret; + } + + /* scan SoundWire capabilities exposed by DSDT */ + ret = hda_sdw_acpi_scan(sdev); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n"); + goto skip_soundwire; + } + + link_mask = hdev->info.link_mask; + if (!link_mask) { + dev_dbg(sdev->dev, "skipping SoundWire, no links enabled\n"); + goto skip_soundwire; + } + + /* + * probe/allocate SoundWire resources. + * The hardware configuration takes place in hda_sdw_startup + * after power rails are enabled. + * It's entirely possible to have a mix of I2S/DMIC/SoundWire + * devices, so we allocate the resources in all cases. + */ + ret = hda_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + return ret; + } + +skip_soundwire: + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + if (bus->mlcap) + snd_hdac_ext_bus_get_ml_capabilities(bus); + + /* create codec instances */ + hda_codec_probe_bus(sdev, hda_codec_use_common_hdmi); + + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + + /* + * we are done probing so decrement link counts + */ + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_ext_bus_link_put(bus, hlink); +#endif + return 0; +} + +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; + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + /* 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); + + if (hda_dsp_check_sdw_irq(sdev)) + hda_dsp_sdw_thread(irq, hdev->sdw); + + if (hda_sdw_check_wakeen_irq(sdev)) + hda_sdw_process_wakeen(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); + struct sof_intel_hda_dev *hdev; + struct hdac_bus *bus; + const struct sof_intel_dsp_desc *chip; + int ret = 0; + + /* + * detect DSP by checking class/subclass/prog-id information + * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required + * class=04 subclass 01 prog-if 00: DSP is present + * (and may be required e.g. for DMIC or SSP support) + * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works + */ + if (pci->class == 0x040300) { + dev_err(sdev->dev, "error: the DSP is not enabled on this platform, aborting probe\n"); + return -ENODEV; + } else if (pci->class != 0x040100 && pci->class != 0x040380) { + dev_err(sdev->dev, "error: unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", pci->class); + return -ENODEV; + } + dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", pci->class); + + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "error: no such device supported, chip id:%x\n", + pci->device); + ret = -EIO; + goto err; + } + + hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + sdev->pdata->hw_pdata = hdev; + hdev->desc = chip; + + hdev->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(hdev->dmic_dev)) { + dev_err(sdev->dev, "error: failed to create DMIC device\n"); + return PTR_ERR(hdev->dmic_dev); + } + + /* + * use position update IPC if either it is forced + * or we don't have other choice + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_FORCE_IPC_POSITION) + hdev->no_ipc_position = 0; +#else + hdev->no_ipc_position = sof_ops(sdev)->pcm_pointer ? 1 : 0; +#endif + + /* set up HDA base */ + bus = sof_to_bus(sdev); + ret = hda_init(sdev); + if (ret < 0) + goto hdac_bus_unmap; + + /* DSP base */ +#if IS_ENABLED(CONFIG_PCI) + sdev->bar[HDA_DSP_BAR] = pci_ioremap_bar(pci, HDA_DSP_BAR); +#endif + if (!sdev->bar[HDA_DSP_BAR]) { + dev_err(sdev->dev, "error: ioremap error\n"); + ret = -ENXIO; + goto hdac_bus_unmap; + } + + sdev->mmio_bar = HDA_DSP_BAR; + sdev->mailbox_bar = HDA_DSP_BAR; + + /* allow 64bit DMA address if supported by H/W */ + if (!dma_set_mask(&pci->dev, DMA_BIT_MASK(64))) { + dev_dbg(sdev->dev, "DMA mask is 64 bit\n"); + dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(64)); + } else { + dev_dbg(sdev->dev, "DMA mask is 32 bit\n"); + dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)); + } + + /* init streams */ + ret = hda_dsp_stream_init(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to init streams\n"); + /* + * not all errors are due to memory issues, but trying + * to free everything does not harm + */ + goto free_streams; + } + + /* + * register our IRQ + * let's try to enable msi firstly + * if it fails, use legacy interrupt mode + * TODO: support msi multiple vectors + */ + 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 + */ + sdev->ipc_irq = pci->irq; + } + + dev_dbg(sdev->dev, "using IPC IRQ %d\n", sdev->ipc_irq); + 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_irq_vector; + } + + pci_set_master(pci); + synchronize_irq(pci->irq); + + /* + * clear TCSEL to clear playback on some HD Audio + * codecs. PCI TCSEL is defined in the Intel manuals. + */ + snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0); + + /* init HDA capabilities */ + ret = hda_init_caps(sdev); + if (ret < 0) + goto free_ipc_irq; + + /* enable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, true); + hda_dsp_ctrl_ppcap_int_enable(sdev, true); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET; + + INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work); + + return 0; + +free_ipc_irq: + free_irq(sdev->ipc_irq, sdev); +free_irq_vector: + if (sdev->msi_enabled) + pci_free_irq_vectors(pci); +free_streams: + hda_dsp_stream_free(sdev); +/* dsp_unmap: not currently used */ + iounmap(sdev->bar[HDA_DSP_BAR]); +hdac_bus_unmap: + platform_device_unregister(hdev->dmic_dev); + iounmap(bus->remap_addr); + hda_codec_i915_exit(sdev); +err: + return ret; +} + +int hda_dsp_remove(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); + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* codec removal, invoke bus_device_remove */ + snd_hdac_ext_bus_device_remove(bus); +#endif + + hda_sdw_exit(sdev); + + if (!IS_ERR_OR_NULL(hda->dmic_dev)) + platform_device_unregister(hda->dmic_dev); + + /* disable DSP IRQ */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, 0); + + /* disable CIE and GIE interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0); + + /* disable cores */ + if (chip) + hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + + /* disable DSP */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, 0); + + free_irq(sdev->ipc_irq, sdev); + if (sdev->msi_enabled) + pci_free_irq_vectors(pci); + + hda_dsp_stream_free(sdev); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_link_free_all(bus); +#endif + + iounmap(sdev->bar[HDA_DSP_BAR]); + iounmap(bus->remap_addr); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_ext_bus_exit(bus); +#endif + hda_codec_i915_exit(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: + * + * generic hda machine driver can handle: + * - one HDMI codec, and/or + * - one external HDAudio codec + */ + if (!pdata->machine && codec_num <= 2) { + 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 && HDA_IDISP_CODEC(bus->codec_mask)) + 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 1: + dmic_str = "-1ch"; + break; + case 2: + dmic_str = "-2ch"; + break; + case 3: + dmic_str = "-3ch"; + 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; + + dev_info(bus->dev, + "DMICs detected in NHLT tables: %d\n", + dmic_num); + + 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 + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) +/* Check if all Slaves defined on the link can be found */ +static bool link_slaves_found(struct snd_sof_dev *sdev, + const struct snd_soc_acpi_link_adr *link, + struct sdw_intel_ctx *sdw) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct sdw_intel_slave_id *ids = sdw->ids; + int num_slaves = sdw->num_slaves; + unsigned int part_id, link_id, unique_id, mfg_id; + int i, j; + + for (i = 0; i < link->num_adr; i++) { + u64 adr = link->adr_d[i].adr; + + mfg_id = SDW_MFG_ID(adr); + part_id = SDW_PART_ID(adr); + link_id = SDW_DISCO_LINK_ID(adr); + for (j = 0; j < num_slaves; j++) { + if (ids[j].link_id != link_id || + ids[j].id.part_id != part_id || + ids[j].id.mfg_id != mfg_id) + continue; + /* + * we have to check unique id + * if there is more than one + * Slave on the link + */ + unique_id = SDW_UNIQUE_ID(adr); + if (link->num_adr == 1 || + ids[j].id.unique_id == SDW_IGNORED_UNIQUE_ID || + ids[j].id.unique_id == unique_id) { + dev_dbg(bus->dev, + "found %x at link %d\n", + part_id, link_id); + break; + } + } + if (j == num_slaves) { + dev_dbg(bus->dev, + "Slave %x not found\n", + part_id); + return false; + } + } + return true; +} + +static int hda_sdw_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct snd_soc_acpi_link_adr *link; + struct hdac_bus *bus = sof_to_bus(sdev); + struct snd_soc_acpi_mach *mach; + struct sof_intel_hda_dev *hdev; + u32 link_mask; + int i; + + hdev = pdata->hw_pdata; + link_mask = hdev->info.link_mask; + + /* + * Select SoundWire machine driver if needed using the + * alternate tables. This case deals with SoundWire-only + * machines, for mixed cases with I2C/I2S the detection relies + * on the HID list. + */ + if (link_mask && !pdata->machine) { + for (mach = pdata->desc->alt_machines; + mach && mach->link_mask; mach++) { + /* + * On some platforms such as Up Extreme all links + * are enabled but only one link can be used by + * external codec. Instead of exact match of two masks, + * first check whether link_mask of mach is subset of + * link_mask supported by hw and then go on searching + * link_adr + */ + if (~link_mask & mach->link_mask) + continue; + + /* No need to match adr if there is no links defined */ + if (!mach->links) + break; + + link = mach->links; + for (i = 0; i < hdev->info.count && link->num_adr; + i++, link++) { + /* + * Try next machine if any expected Slaves + * are not found on this link. + */ + if (!link_slaves_found(sdev, link, hdev->sdw)) + break; + } + /* Found if all Slaves are checked */ + if (i == hdev->info.count || !link->num_adr) + break; + } + if (mach && mach->link_mask) { + dev_dbg(bus->dev, + "SoundWire machine driver %s topology %s\n", + mach->drv_name, + mach->sof_tplg_filename); + pdata->machine = mach; + mach->mach_params.links = mach->links; + mach->mach_params.link_mask = mach->link_mask; + mach->mach_params.platform = dev_name(sdev->dev); + pdata->fw_filename = mach->sof_fw_filename; + pdata->tplg_filename = mach->sof_tplg_filename; + } else { + dev_info(sdev->dev, + "No SoundWire machine driver found\n"); + } + } + + return 0; +} +#else +static int hda_sdw_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) { + /* + * If tplg file name is overridden, use it instead of + * the one set in mach table + */ + if (!sof_pdata->tplg_filename) + sof_pdata->tplg_filename = mach->sof_tplg_filename; + + sof_pdata->machine = mach; + + if (mach->link_mask) { + mach->mach_params.links = mach->links; + mach->mach_params.link_mask = mach->link_mask; + } + } + + /* + * If I2S fails, try SoundWire + */ + hda_sdw_machine_select(sdev); + + /* + * 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); +MODULE_IMPORT_NS(SOUNDWIRE_INTEL_INIT); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h new file mode 100644 index 000000000..1bc4dabdd --- /dev/null +++ b/sound/soc/sof/intel/hda.h @@ -0,0 +1,751 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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) 2017 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOF_INTEL_HDA_H +#define __SOF_INTEL_HDA_H + +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_intel.h> +#include <sound/compress_driver.h> +#include <sound/hda_codec.h> +#include <sound/hdaudio_ext.h> +#include "shim.h" + +/* PCI registers */ +#define PCI_TCSEL 0x44 +#define PCI_PGCTL PCI_TCSEL +#define PCI_CGCTL 0x48 + +/* PCI_PGCTL bits */ +#define PCI_PGCTL_ADSPPGD BIT(2) +#define PCI_PGCTL_LSRMD_MASK BIT(4) + +/* PCI_CGCTL bits */ +#define PCI_CGCTL_MISCBDCGE_MASK BIT(6) +#define PCI_CGCTL_ADSPDCGE BIT(1) + +/* Legacy HDA registers and bits used - widths are variable */ +#define SOF_HDA_GCAP 0x0 +#define SOF_HDA_GCTL 0x8 +/* accept unsol. response enable */ +#define SOF_HDA_GCTL_UNSOL BIT(8) +#define SOF_HDA_LLCH 0x14 +#define SOF_HDA_INTCTL 0x20 +#define SOF_HDA_INTSTS 0x24 +#define SOF_HDA_WAKESTS 0x0E +#define SOF_HDA_WAKESTS_INT_MASK ((1 << 8) - 1) +#define SOF_HDA_RIRBSTS 0x5d + +/* SOF_HDA_GCTL register bist */ +#define SOF_HDA_GCTL_RESET BIT(0) + +/* 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,\ + SOF_HDA_CAP_ID_OFF) +#define SOF_HDA_CAP_NEXT_MASK 0xFFFF + +#define SOF_HDA_GTS_CAP_ID 0x1 +#define SOF_HDA_ML_CAP_ID 0x2 + +#define SOF_HDA_PP_CAP_ID 0x3 +#define SOF_HDA_REG_PP_PPCH 0x10 +#define SOF_HDA_REG_PP_PPCTL 0x04 +#define SOF_HDA_REG_PP_PPSTS 0x08 +#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 + +#define SOF_HDA_SPIB_CAP_ID 0x4 +#define SOF_HDA_DRSM_CAP_ID 0x5 + +#define SOF_HDA_SPIB_BASE 0x08 +#define SOF_HDA_SPIB_INTERVAL 0x08 +#define SOF_HDA_SPIB_SPIB 0x00 +#define SOF_HDA_SPIB_MAXFIFO 0x04 + +#define SOF_HDA_PPHC_BASE 0x10 +#define SOF_HDA_PPHC_INTERVAL 0x10 + +#define SOF_HDA_PPLC_BASE 0x10 +#define SOF_HDA_PPLC_MULTI 0x10 +#define SOF_HDA_PPLC_INTERVAL 0x10 + +#define SOF_HDA_DRSM_BASE 0x08 +#define SOF_HDA_DRSM_INTERVAL 0x08 + +/* Descriptor error interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_DESC_ERR 0x10 + +/* FIFO error interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_FIFO_ERR 0x08 + +/* Buffer completion interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_COMPLETE 0x04 + +#define SOF_HDA_CL_DMA_SD_INT_MASK \ + (SOF_HDA_CL_DMA_SD_INT_DESC_ERR | \ + SOF_HDA_CL_DMA_SD_INT_FIFO_ERR | \ + SOF_HDA_CL_DMA_SD_INT_COMPLETE) +#define SOF_HDA_SD_CTL_DMA_START 0x02 /* Stream DMA start bit */ + +/* Intel HD Audio Code Loader DMA Registers */ +#define SOF_HDA_ADSP_LOADER_BASE 0x80 +#define SOF_HDA_ADSP_DPLBASE 0x70 +#define SOF_HDA_ADSP_DPUBASE 0x74 +#define SOF_HDA_ADSP_DPLBASE_ENABLE 0x01 + +/* Stream Registers */ +#define SOF_HDA_ADSP_REG_CL_SD_CTL 0x00 +#define SOF_HDA_ADSP_REG_CL_SD_STS 0x03 +#define SOF_HDA_ADSP_REG_CL_SD_LPIB 0x04 +#define SOF_HDA_ADSP_REG_CL_SD_CBL 0x08 +#define SOF_HDA_ADSP_REG_CL_SD_LVI 0x0C +#define SOF_HDA_ADSP_REG_CL_SD_FIFOW 0x0E +#define SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE 0x10 +#define SOF_HDA_ADSP_REG_CL_SD_FORMAT 0x12 +#define SOF_HDA_ADSP_REG_CL_SD_FIFOL 0x14 +#define SOF_HDA_ADSP_REG_CL_SD_BDLPL 0x18 +#define SOF_HDA_ADSP_REG_CL_SD_BDLPU 0x1C +#define SOF_HDA_ADSP_SD_ENTRY_SIZE 0x20 + +/* CL: Software Position Based FIFO Capability Registers */ +#define SOF_DSP_REG_CL_SPBFIFO \ + (SOF_HDA_ADSP_LOADER_BASE + 0x20) +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCH 0x0 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL 0x4 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB 0x8 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_MAXFIFOS 0xc + +/* Stream Number */ +#define SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT 20 +#define SOF_HDA_CL_SD_CTL_STREAM_TAG_MASK \ + GENMASK(SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT + 3,\ + SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT) + +#define HDA_DSP_HDA_BAR 0 +#define HDA_DSP_PP_BAR 1 +#define HDA_DSP_SPIB_BAR 2 +#define HDA_DSP_DRSM_BAR 3 +#define HDA_DSP_BAR 4 + +#define SRAM_WINDOW_OFFSET(x) (0x80000 + (x) * 0x20000) + +#define HDA_DSP_MBOX_OFFSET SRAM_WINDOW_OFFSET(0) + +#define HDA_DSP_PANIC_OFFSET(x) \ + (((x) & 0xFFFFFF) + HDA_DSP_MBOX_OFFSET) + +/* SRAM window 0 FW "registers" */ +#define HDA_DSP_SRAM_REG_ROM_STATUS (HDA_DSP_MBOX_OFFSET + 0x0) +#define HDA_DSP_SRAM_REG_ROM_ERROR (HDA_DSP_MBOX_OFFSET + 0x4) +/* FW and ROM share offset 4 */ +#define HDA_DSP_SRAM_REG_FW_STATUS (HDA_DSP_MBOX_OFFSET + 0x4) +#define HDA_DSP_SRAM_REG_FW_TRACEP (HDA_DSP_MBOX_OFFSET + 0x8) +#define HDA_DSP_SRAM_REG_FW_END (HDA_DSP_MBOX_OFFSET + 0xc) + +#define HDA_DSP_MBOX_UPLINK_OFFSET 0x81000 + +#define HDA_DSP_STREAM_RESET_TIMEOUT 300 +/* + * Timeout in us, for setting the stream RUN bit, during + * start/stop the stream. The timeout expires if new RUN bit + * value cannot be read back within the specified time. + */ +#define HDA_DSP_STREAM_RUN_TIMEOUT 300 + +#define HDA_DSP_SPIB_ENABLE 1 +#define HDA_DSP_SPIB_DISABLE 0 + +#define SOF_HDA_MAX_BUFFER_SIZE (32 * PAGE_SIZE) + +#define HDA_DSP_STACK_DUMP_SIZE 32 + +/* ROM status/error values */ +#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 +#define HDA_DSP_ROM_FW_ENTERED 0x5 +#define HDA_DSP_ROM_RFW_START 0xf +#define HDA_DSP_ROM_CSE_ERROR 40 +#define HDA_DSP_ROM_CSE_WRONG_RESPONSE 41 +#define HDA_DSP_ROM_IMR_TO_SMALL 42 +#define HDA_DSP_ROM_BASE_FW_NOT_FOUND 43 +#define HDA_DSP_ROM_CSE_VALIDATION_FAILED 44 +#define HDA_DSP_ROM_IPC_FATAL_ERROR 45 +#define HDA_DSP_ROM_L2_CACHE_ERROR 46 +#define HDA_DSP_ROM_LOAD_OFFSET_TO_SMALL 47 +#define HDA_DSP_ROM_API_PTR_INVALID 50 +#define HDA_DSP_ROM_BASEFW_INCOMPAT 51 +#define HDA_DSP_ROM_UNHANDLED_INTERRUPT 0xBEE00000 +#define HDA_DSP_ROM_MEMORY_HOLE_ECC 0xECC00000 +#define HDA_DSP_ROM_KERNEL_EXCEPTION 0xCAFE0000 +#define HDA_DSP_ROM_USER_EXCEPTION 0xBEEF0000 +#define HDA_DSP_ROM_UNEXPECTED_RESET 0xDECAF000 +#define HDA_DSP_ROM_NULL_FW_ENTRY 0x4c4c4e55 +#define HDA_DSP_IPC_PURGE_FW 0x01004000 + +/* various timeout values */ +#define HDA_DSP_PU_TIMEOUT 50 +#define HDA_DSP_PD_TIMEOUT 50 +#define HDA_DSP_RESET_TIMEOUT_US 50000 +#define HDA_DSP_BASEFW_TIMEOUT_US 3000000 +#define HDA_DSP_INIT_TIMEOUT_US 500000 +#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 + +/* Intel HD Audio General DSP Registers */ +#define HDA_DSP_GEN_BASE 0x0 +#define HDA_DSP_REG_ADSPCS (HDA_DSP_GEN_BASE + 0x04) +#define HDA_DSP_REG_ADSPIC (HDA_DSP_GEN_BASE + 0x08) +#define HDA_DSP_REG_ADSPIS (HDA_DSP_GEN_BASE + 0x0C) +#define HDA_DSP_REG_ADSPIC2 (HDA_DSP_GEN_BASE + 0x10) +#define HDA_DSP_REG_ADSPIS2 (HDA_DSP_GEN_BASE + 0x14) + +#define HDA_DSP_REG_ADSPIS2_SNDW BIT(5) +#define HDA_DSP_REG_SNDW_WAKE_STS 0x2C192 + +/* Intel HD Audio Inter-Processor Communication Registers */ +#define HDA_DSP_IPC_BASE 0x40 +#define HDA_DSP_REG_HIPCT (HDA_DSP_IPC_BASE + 0x00) +#define HDA_DSP_REG_HIPCTE (HDA_DSP_IPC_BASE + 0x04) +#define HDA_DSP_REG_HIPCI (HDA_DSP_IPC_BASE + 0x08) +#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) +#define HDA_VS_INTEL_LTRP_GB_MASK 0x3F + +/* HIPCI */ +#define HDA_DSP_REG_HIPCI_BUSY BIT(31) +#define HDA_DSP_REG_HIPCI_MSG_MASK 0x7FFFFFFF + +/* HIPCIE */ +#define HDA_DSP_REG_HIPCIE_DONE BIT(30) +#define HDA_DSP_REG_HIPCIE_MSG_MASK 0x3FFFFFFF + +/* HIPCCTL */ +#define HDA_DSP_REG_HIPCCTL_DONE BIT(1) +#define HDA_DSP_REG_HIPCCTL_BUSY BIT(0) + +/* HIPCT */ +#define HDA_DSP_REG_HIPCT_BUSY BIT(31) +#define HDA_DSP_REG_HIPCT_MSG_MASK 0x7FFFFFFF + +/* HIPCTE */ +#define HDA_DSP_REG_HIPCTE_MSG_MASK 0x3FFFFFFF + +#define HDA_DSP_ADSPIC_CL_DMA 0x2 +#define HDA_DSP_ADSPIS_CL_DMA 0x2 + +/* Delay before scheduling D0i3 entry */ +#define BXT_D0I3_DELAY 5000 + +#define FW_CL_STREAM_NUMBER 0x1 +#define HDA_FW_BOOT_ATTEMPTS 3 + +/* ADSPCS - Audio DSP Control & Status */ + +/* + * Core Reset - asserted high + * CRST Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CRST_SHIFT 0 +#define HDA_DSP_ADSPCS_CRST_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CRST_SHIFT) + +/* + * Core run/stall - when set to '1' core is stalled + * CSTALL Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CSTALL_SHIFT 8 +#define HDA_DSP_ADSPCS_CSTALL_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CSTALL_SHIFT) + +/* + * Set Power Active - when set to '1' turn cores on + * SPA Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_SPA_SHIFT 16 +#define HDA_DSP_ADSPCS_SPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_SPA_SHIFT) + +/* + * Current Power Active - power status of cores, set by hardware + * CPA Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CPA_SHIFT 24 +#define HDA_DSP_ADSPCS_CPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CPA_SHIFT) + +/* + * Mask for a given number of cores + * nc = number of supported cores + */ +#define SOF_DSP_CORES_MASK(nc) GENMASK(((nc) - 1), 0) + +/* Intel HD Audio Inter-Processor Communication Registers for Cannonlake*/ +#define CNL_DSP_IPC_BASE 0xc0 +#define CNL_DSP_REG_HIPCTDR (CNL_DSP_IPC_BASE + 0x00) +#define CNL_DSP_REG_HIPCTDA (CNL_DSP_IPC_BASE + 0x04) +#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 */ +#define CNL_DSP_REG_HIPCIDR_BUSY BIT(31) +#define CNL_DSP_REG_HIPCIDR_MSG_MASK 0x7FFFFFFF + +/* HIPCIE */ +#define CNL_DSP_REG_HIPCIDA_DONE BIT(31) +#define CNL_DSP_REG_HIPCIDA_MSG_MASK 0x7FFFFFFF + +/* HIPCCTL */ +#define CNL_DSP_REG_HIPCCTL_DONE BIT(1) +#define CNL_DSP_REG_HIPCCTL_BUSY BIT(0) + +/* HIPCT */ +#define CNL_DSP_REG_HIPCTDR_BUSY BIT(31) +#define CNL_DSP_REG_HIPCTDR_MSG_MASK 0x7FFFFFFF + +/* HIPCTDA */ +#define CNL_DSP_REG_HIPCTDA_DONE BIT(31) +#define CNL_DSP_REG_HIPCTDA_MSG_MASK 0x7FFFFFFF + +/* HIPCTDD */ +#define CNL_DSP_REG_HIPCTDD_MSG_MASK 0x7FFFFFFF + +/* BDL */ +#define HDA_DSP_BDL_SIZE 4096 +#define HDA_DSP_MAX_BDL_ENTRIES \ + (HDA_DSP_BDL_SIZE / sizeof(struct sof_intel_dsp_bdl)) + +/* Number of DAIs */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +#define SOF_SKL_NUM_DAIS 16 +#else +#define SOF_SKL_NUM_DAIS 15 +#endif + +#else +#define SOF_SKL_NUM_DAIS 8 +#endif + +/* Intel HD Audio SRAM Window 0*/ +#define HDA_ADSP_SRAM0_BASE_SKL 0x8000 + +/* Firmware status window */ +#define HDA_ADSP_FW_STATUS_SKL HDA_ADSP_SRAM0_BASE_SKL +#define HDA_ADSP_ERROR_CODE_SKL (HDA_ADSP_FW_STATUS_SKL + 0x4) + +/* Host Device Memory Space */ +#define APL_SSP_BASE_OFFSET 0x2000 +#define CNL_SSP_BASE_OFFSET 0x10000 + +/* Host Device Memory Size of a Single SSP */ +#define SSP_DEV_MEM_SIZE 0x1000 + +/* SSP Count of the Platform */ +#define APL_SSP_COUNT 6 +#define CNL_SSP_COUNT 3 +#define ICL_SSP_COUNT 6 + +/* SSP Registers */ +#define SSP_SSC1_OFFSET 0x4 +#define SSP_SET_SCLK_SLAVE BIT(25) +#define SSP_SET_SFRM_SLAVE BIT(24) +#define SSP_SET_SLAVE (SSP_SET_SCLK_SLAVE | SSP_SET_SFRM_SLAVE) + +#define HDA_IDISP_CODEC(x) ((x) & BIT(2)) + +struct sof_intel_dsp_bdl { + __le32 addr_l; + __le32 addr_h; + __le32 size; + __le32 ioc; +} __attribute((packed)); + +#define SOF_HDA_PLAYBACK_STREAMS 16 +#define SOF_HDA_CAPTURE_STREAMS 16 +#define SOF_HDA_PLAYBACK 0 +#define SOF_HDA_CAPTURE 1 + +/* + * Time in ms for opportunistic D0I3 entry delay. + * This has been deliberately chosen to be long to avoid race conditions. + * Could be optimized in future. + */ +#define SOF_HDA_D0I3_WORK_DELAY_MS 5000 + +/* HDA DSP D0 substate */ +enum sof_hda_D0_substate { + SOF_HDA_DSP_PM_D0I0, /* default D0 substate */ + SOF_HDA_DSP_PM_D0I3, /* low power D0 substate */ +}; + +/* represents DSP HDA controller frontend - i.e. host facing control */ +struct sof_intel_hda_dev { + int boot_iteration; + + struct hda_bus hbus; + + /* hw config */ + const struct sof_intel_dsp_desc *desc; + + /* trace */ + struct hdac_ext_stream *dtrace_stream; + + /* if position update IPC needed */ + u32 no_ipc_position; + + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + + /* PM related */ + bool l1_support_changed;/* during suspend, is L1SEN changed or not */ + + /* DMIC device */ + struct platform_device *dmic_dev; + + /* delayed work to enter D0I3 opportunistically */ + struct delayed_work d0i3_work; + + /* ACPI information stored between scan and probe steps */ + struct sdw_intel_acpi_info info; + + /* sdw context allocated by SoundWire driver */ + struct sdw_intel_ctx *sdw; +}; + +static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) +{ + struct sof_intel_hda_dev *hda = s->pdata->hw_pdata; + + return &hda->hbus.core; +} + +static inline struct hda_bus *sof_to_hbus(struct snd_sof_dev *s) +{ + struct sof_intel_hda_dev *hda = s->pdata->hw_pdata; + + return &hda->hbus; +} + +struct sof_intel_hda_stream { + struct snd_sof_dev *sdev; + struct hdac_ext_stream hda_stream; + struct sof_intel_stream stream; + int host_reserved; /* reserve host DMA channel */ +}; + +#define hstream_to_sof_hda_stream(hstream) \ + container_of(hstream, struct sof_intel_hda_stream, hda_stream) + +#define bus_to_sof_hda(bus) \ + container_of(bus, struct sof_intel_hda_dev, hbus.core) + +#define SOF_STREAM_SD_OFFSET(s) \ + (SOF_HDA_ADSP_SD_ENTRY_SIZE * ((s)->index) \ + + SOF_HDA_ADSP_LOADER_BASE) + +/* + * DSP Core services. + */ +int hda_dsp_probe(struct snd_sof_dev *sdev); +int hda_dsp_remove(struct snd_sof_dev *sdev); +int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask); +bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask); +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_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state); + +int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state); +int hda_dsp_resume(struct snd_sof_dev *sdev); +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); +void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags); +void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags); +void hda_ipc_dump(struct snd_sof_dev *sdev); +void hda_ipc_irq_dump(struct snd_sof_dev *sdev); +void hda_dsp_d0i3_work(struct work_struct *work); + +/* + * DSP PCM Operations. + */ +u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate); +u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits); +int hda_dsp_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int hda_dsp_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params); +int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd); +snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + +/* + * DSP Stream Operations. + */ + +int hda_dsp_stream_init(struct snd_sof_dev *sdev); +void hda_dsp_stream_free(struct snd_sof_dev *sdev); +int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + struct snd_pcm_hw_params *params); +int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + 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_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); +int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag); +int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + int enable, u32 size); + +void hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); +int hda_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +/* + * Probe Compress Operations. + */ +int hda_probe_compr_assign(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int hda_probe_compr_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int hda_probe_compr_set_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); +int hda_probe_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai); +int hda_probe_compr_pointer(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); +#endif + +/* + * DSP IPC Operations. + */ +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_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); + +/* + * DSP Code loader. + */ +int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev); +int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev); +int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev); + +/* pre and post fw run ops */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev); + +/* + * HDA Controller Operations. + */ +int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev); +void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable); +void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable); +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset); +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); +int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset); +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); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +/* + * HDA Codec operations. + */ +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_HDA_CODEC_HDMI) || \ + IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) + +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_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 + +/* + * Trace Control. + */ +int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag); +int hda_dsp_trace_release(struct snd_sof_dev *sdev); +int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); + +/* + * SoundWire support + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + +int hda_sdw_startup(struct snd_sof_dev *sdev); +void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable); +void hda_sdw_process_wakeen(struct snd_sof_dev *sdev); + +#else + +static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_probe(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_startup(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_exit(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) +{ +} + +static inline bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev) +{ + return false; +} + +static inline irqreturn_t hda_dsp_sdw_thread(int irq, void *context) +{ + return IRQ_HANDLED; +} + +static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) +{ + return false; +} + +static inline void hda_sdw_process_wakeen(struct snd_sof_dev *sdev) +{ +} +#endif + +/* common dai driver */ +extern struct snd_soc_dai_driver skl_dai[]; + +/* + * Platform Specific HW abstraction Ops. + */ +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_tgl_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 tglh_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 new file mode 100644 index 000000000..310f9168c --- /dev/null +++ b/sound/soc/sof/intel/intel-ipc.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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. +// +// Authors: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> + +/* Intel-specific SOF IPC code */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/types.h> + +#include <sound/pcm.h> +#include <sound/sof/stream.h> + +#include "../ops.h" +#include "../sof-priv.h" + +struct intel_stream { + size_t posn_offset; +}; + +/* Mailbox-based Intel IPC implementation */ +void intel_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + if (!substream || !sdev->stream_box.size) { + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + } else { + struct intel_stream *stream = substream->runtime->private_data; + + /* The stream might already be closed */ + if (stream) + sof_mailbox_read(sdev, stream->posn_offset, p, sz); + } +} +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, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct intel_stream *stream = substream->runtime->private_data; + size_t posn_offset = reply->posn_offset; + + /* check if offset is overflow or it is not aligned */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) + return -EINVAL; + + stream->posn_offset = sdev->stream_box.offset + posn_offset; + + dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", + substream->stream, stream->posn_offset); + + return 0; +} +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) +{ + struct intel_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL); + + if (!stream) + return -ENOMEM; + + /* binding pcm substream to hda stream */ + substream->runtime->private_data = stream; + + return 0; +} +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) +{ + struct intel_stream *stream = substream->runtime->private_data; + + substream->runtime->private_data = NULL; + kfree(stream); + + return 0; +} +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 new file mode 100644 index 000000000..1e0afb5c8 --- /dev/null +++ b/sound/soc/sof/intel/shim.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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) 2017 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOF_INTEL_SHIM_H +#define __SOF_INTEL_SHIM_H + +/* + * SHIM registers for BYT, BSW, CHT, BDW + */ + +#define SHIM_CSR (SHIM_OFFSET + 0x00) +#define SHIM_PISR (SHIM_OFFSET + 0x08) +#define SHIM_PIMR (SHIM_OFFSET + 0x10) +#define SHIM_ISRX (SHIM_OFFSET + 0x18) +#define SHIM_ISRD (SHIM_OFFSET + 0x20) +#define SHIM_IMRX (SHIM_OFFSET + 0x28) +#define SHIM_IMRD (SHIM_OFFSET + 0x30) +#define SHIM_IPCX (SHIM_OFFSET + 0x38) +#define SHIM_IPCD (SHIM_OFFSET + 0x40) +#define SHIM_ISRSC (SHIM_OFFSET + 0x48) +#define SHIM_ISRLPESC (SHIM_OFFSET + 0x50) +#define SHIM_IMRSC (SHIM_OFFSET + 0x58) +#define SHIM_IMRLPESC (SHIM_OFFSET + 0x60) +#define SHIM_IPCSC (SHIM_OFFSET + 0x68) +#define SHIM_IPCLPESC (SHIM_OFFSET + 0x70) +#define SHIM_CLKCTL (SHIM_OFFSET + 0x78) +#define SHIM_CSR2 (SHIM_OFFSET + 0x80) +#define SHIM_LTRC (SHIM_OFFSET + 0xE0) +#define SHIM_HMDC (SHIM_OFFSET + 0xE8) + +#define SHIM_PWMCTRL 0x1000 + +/* + * SST SHIM register bits for BYT, BSW, CHT, BDW + * Register bit naming and functionaility can differ between devices. + */ + +/* CSR / CS */ +#define SHIM_CSR_RST BIT(1) +#define SHIM_CSR_SBCS0 BIT(2) +#define SHIM_CSR_SBCS1 BIT(3) +#define SHIM_CSR_DCS(x) ((x) << 4) +#define SHIM_CSR_DCS_MASK (0x7 << 4) +#define SHIM_CSR_STALL BIT(10) +#define SHIM_CSR_S0IOCS BIT(21) +#define SHIM_CSR_S1IOCS BIT(23) +#define SHIM_CSR_LPCS BIT(31) +#define SHIM_CSR_24MHZ_LPCS \ + (SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1 | SHIM_CSR_LPCS) +#define SHIM_CSR_24MHZ_NO_LPCS (SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1) +#define SHIM_BYT_CSR_RST BIT(0) +#define SHIM_BYT_CSR_VECTOR_SEL BIT(1) +#define SHIM_BYT_CSR_STALL BIT(2) +#define SHIM_BYT_CSR_PWAITMODE BIT(3) + +/* ISRX / ISC */ +#define SHIM_ISRX_BUSY BIT(1) +#define SHIM_ISRX_DONE BIT(0) +#define SHIM_BYT_ISRX_REQUEST BIT(1) + +/* ISRD / ISD */ +#define SHIM_ISRD_BUSY BIT(1) +#define SHIM_ISRD_DONE BIT(0) + +/* IMRX / IMC */ +#define SHIM_IMRX_BUSY BIT(1) +#define SHIM_IMRX_DONE BIT(0) +#define SHIM_BYT_IMRX_REQUEST BIT(1) + +/* IMRD / IMD */ +#define SHIM_IMRD_DONE BIT(0) +#define SHIM_IMRD_BUSY BIT(1) +#define SHIM_IMRD_SSP0 BIT(16) +#define SHIM_IMRD_DMAC0 BIT(21) +#define SHIM_IMRD_DMAC1 BIT(22) +#define SHIM_IMRD_DMAC (SHIM_IMRD_DMAC0 | SHIM_IMRD_DMAC1) + +/* IPCX / IPCC */ +#define SHIM_IPCX_DONE BIT(30) +#define SHIM_IPCX_BUSY BIT(31) +#define SHIM_BYT_IPCX_DONE BIT_ULL(62) +#define SHIM_BYT_IPCX_BUSY BIT_ULL(63) + +/* IPCD */ +#define SHIM_IPCD_DONE BIT(30) +#define SHIM_IPCD_BUSY BIT(31) +#define SHIM_BYT_IPCD_DONE BIT_ULL(62) +#define SHIM_BYT_IPCD_BUSY BIT_ULL(63) + +/* CLKCTL */ +#define SHIM_CLKCTL_SMOS(x) ((x) << 24) +#define SHIM_CLKCTL_MASK (3 << 24) +#define SHIM_CLKCTL_DCPLCG BIT(18) +#define SHIM_CLKCTL_SCOE1 BIT(17) +#define SHIM_CLKCTL_SCOE0 BIT(16) + +/* CSR2 / CS2 */ +#define SHIM_CSR2_SDFD_SSP0 BIT(1) +#define SHIM_CSR2_SDFD_SSP1 BIT(2) + +/* LTRC */ +#define SHIM_LTRC_VAL(x) ((x) << 0) + +/* HMDC */ +#define SHIM_HMDC_HDDA0(x) ((x) << 0) +#define SHIM_HMDC_HDDA1(x) ((x) << 7) +#define SHIM_HMDC_HDDA_E0_CH0 1 +#define SHIM_HMDC_HDDA_E0_CH1 2 +#define SHIM_HMDC_HDDA_E0_CH2 4 +#define SHIM_HMDC_HDDA_E0_CH3 8 +#define SHIM_HMDC_HDDA_E1_CH0 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH0) +#define SHIM_HMDC_HDDA_E1_CH1 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH1) +#define SHIM_HMDC_HDDA_E1_CH2 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH2) +#define SHIM_HMDC_HDDA_E1_CH3 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH3) +#define SHIM_HMDC_HDDA_E0_ALLCH \ + (SHIM_HMDC_HDDA_E0_CH0 | SHIM_HMDC_HDDA_E0_CH1 | \ + SHIM_HMDC_HDDA_E0_CH2 | SHIM_HMDC_HDDA_E0_CH3) +#define SHIM_HMDC_HDDA_E1_ALLCH \ + (SHIM_HMDC_HDDA_E1_CH0 | SHIM_HMDC_HDDA_E1_CH1 | \ + SHIM_HMDC_HDDA_E1_CH2 | SHIM_HMDC_HDDA_E1_CH3) + +/* Audio DSP PCI registers */ +#define PCI_VDRTCTL0 0xa0 +#define PCI_VDRTCTL1 0xa4 +#define PCI_VDRTCTL2 0xa8 +#define PCI_VDRTCTL3 0xaC + +/* VDRTCTL0 */ +#define PCI_VDRTCL0_D3PGD BIT(0) +#define PCI_VDRTCL0_D3SRAMPGD BIT(1) +#define PCI_VDRTCL0_DSRAMPGE_SHIFT 12 +#define PCI_VDRTCL0_DSRAMPGE_MASK GENMASK(PCI_VDRTCL0_DSRAMPGE_SHIFT + 19,\ + PCI_VDRTCL0_DSRAMPGE_SHIFT) +#define PCI_VDRTCL0_ISRAMPGE_SHIFT 2 +#define PCI_VDRTCL0_ISRAMPGE_MASK GENMASK(PCI_VDRTCL0_ISRAMPGE_SHIFT + 9,\ + PCI_VDRTCL0_ISRAMPGE_SHIFT) + +/* VDRTCTL2 */ +#define PCI_VDRTCL2_DCLCGE BIT(1) +#define PCI_VDRTCL2_DTCGE BIT(10) +#define PCI_VDRTCL2_APLLSE_MASK BIT(31) + +/* PMCS */ +#define PCI_PMCS 0x84 +#define PCI_PMCS_PS_MASK 0x3 + +/* DSP hardware descriptor */ +struct sof_intel_dsp_desc { + int cores_num; + int host_managed_cores_mask; + int init_core_mask; /* cores available after fw boot */ + int ipc_req; + int ipc_req_mask; + int ipc_ack; + int ipc_ack_mask; + int ipc_ctl; + int rom_init_timeout; + int ssp_count; /* ssp count of the platform */ + int ssp_base_offset; /* base address of the SSPs */ +}; + +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_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 tng_chip_info; + +struct sof_intel_stream { + size_t posn_offset; +}; + +#endif diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c new file mode 100644 index 000000000..0278b67de --- /dev/null +++ b/sound/soc/sof/intel/tgl.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Tigerlake. + */ + +#include "../ops.h" +#include "hda.h" +#include "hda-ipc.h" +#include "../sof-audio.h" + +static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = { + {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +/* Tigerlake ops */ +const struct snd_sof_dsp_ops sof_tgl_ops = { + /* probe and remove */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_thread = cnl_ipc_irq_thread, + + /* ipc */ + .send_msg = cnl_ipc_send_msg, + .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 = tgl_dsp_debugfs, + .debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs), + .dbg_dump = hda_dsp_dump, + .ipc_dump = cnl_ipc_dump, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_hw_free = hda_dsp_stream_hw_free, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) + /* probe callbacks */ + .probe_assign = hda_probe_compr_assign, + .probe_free = hda_probe_compr_free, + .probe_set_params = hda_probe_compr_set_params, + .probe_trigger = hda_probe_compr_trigger, + .probe_pointer = hda_probe_compr_pointer, +#endif + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + + /* dsp core power up/down */ + .core_power_up = hda_dsp_enable_core, + .core_power_down = hda_dsp_core_reset_power_down, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware_iccmax, + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .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_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc tgl_chip_info = { + /* Tigerlake */ + .cores_num = 4, + .init_core_mask = 1, + .host_managed_cores_mask = BIT(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 tglh_chip_info = { + /* Tigerlake-H */ + .cores_num = 2, + .init_core_mask = 1, + .host_managed_cores_mask = BIT(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(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c new file mode 100644 index 000000000..fd2b96ae4 --- /dev/null +++ b/sound/soc/sof/ipc.c @@ -0,0 +1,866 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided +// by platform driver code. +// + +#include <linux/mutex.h> +#include <linux/types.h> + +#include "sof-priv.h" +#include "sof-audio.h" +#include "ops.h" + +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); + +/* + * IPC message Tx/Rx message handling. + */ + +/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* protects messages and the disable flag */ + struct mutex tx_mutex; + /* disables further sending of ipc's */ + bool disable_ipc_tx; + + struct snd_sof_ipc_msg msg; +}; + +struct sof_ipc_ctrl_data_params { + size_t msg_bytes; + size_t hdr_bytes; + size_t pl_size; + size_t elems; + u32 num_msg; + u8 *src; + u8 *dst; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + u8 *str; + u8 *str2 = NULL; + u32 glb; + u32 type; + bool vdbg = false; + + glb = cmd & SOF_GLB_TYPE_MASK; + type = cmd & SOF_CMD_TYPE_MASK; + + switch (glb) { + case SOF_IPC_GLB_REPLY: + str = "GLB_REPLY"; break; + case SOF_IPC_GLB_COMPOUND: + str = "GLB_COMPOUND"; break; + case SOF_IPC_GLB_TPLG_MSG: + str = "GLB_TPLG_MSG"; + switch (type) { + case SOF_IPC_TPLG_COMP_NEW: + str2 = "COMP_NEW"; break; + case SOF_IPC_TPLG_COMP_FREE: + str2 = "COMP_FREE"; break; + case SOF_IPC_TPLG_COMP_CONNECT: + str2 = "COMP_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_NEW: + str2 = "PIPE_NEW"; break; + case SOF_IPC_TPLG_PIPE_FREE: + str2 = "PIPE_FREE"; break; + case SOF_IPC_TPLG_PIPE_CONNECT: + str2 = "PIPE_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_COMPLETE: + str2 = "PIPE_COMPLETE"; break; + case SOF_IPC_TPLG_BUFFER_NEW: + str2 = "BUFFER_NEW"; break; + case SOF_IPC_TPLG_BUFFER_FREE: + str2 = "BUFFER_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PM_MSG: + str = "GLB_PM_MSG"; + switch (type) { + case SOF_IPC_PM_CTX_SAVE: + str2 = "CTX_SAVE"; break; + case SOF_IPC_PM_CTX_RESTORE: + str2 = "CTX_RESTORE"; break; + case SOF_IPC_PM_CTX_SIZE: + str2 = "CTX_SIZE"; break; + case SOF_IPC_PM_CLK_SET: + str2 = "CLK_SET"; break; + case SOF_IPC_PM_CLK_GET: + str2 = "CLK_GET"; break; + case SOF_IPC_PM_CLK_REQ: + str2 = "CLK_REQ"; break; + case SOF_IPC_PM_CORE_ENABLE: + str2 = "CORE_ENABLE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_COMP_MSG: + str = "GLB_COMP_MSG"; + switch (type) { + case SOF_IPC_COMP_SET_VALUE: + str2 = "SET_VALUE"; break; + case SOF_IPC_COMP_GET_VALUE: + str2 = "GET_VALUE"; break; + case SOF_IPC_COMP_SET_DATA: + str2 = "SET_DATA"; break; + case SOF_IPC_COMP_GET_DATA: + str2 = "GET_DATA"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_STREAM_MSG: + str = "GLB_STREAM_MSG"; + switch (type) { + case SOF_IPC_STREAM_PCM_PARAMS: + str2 = "PCM_PARAMS"; break; + case SOF_IPC_STREAM_PCM_PARAMS_REPLY: + str2 = "PCM_REPLY"; break; + case SOF_IPC_STREAM_PCM_FREE: + str2 = "PCM_FREE"; break; + case SOF_IPC_STREAM_TRIG_START: + str2 = "TRIG_START"; break; + case SOF_IPC_STREAM_TRIG_STOP: + str2 = "TRIG_STOP"; break; + case SOF_IPC_STREAM_TRIG_PAUSE: + str2 = "TRIG_PAUSE"; break; + case SOF_IPC_STREAM_TRIG_RELEASE: + str2 = "TRIG_RELEASE"; break; + case SOF_IPC_STREAM_TRIG_DRAIN: + str2 = "TRIG_DRAIN"; break; + case SOF_IPC_STREAM_TRIG_XRUN: + str2 = "TRIG_XRUN"; break; + case SOF_IPC_STREAM_POSITION: + vdbg = true; + str2 = "POSITION"; break; + case SOF_IPC_STREAM_VORBIS_PARAMS: + str2 = "VORBIS_PARAMS"; break; + case SOF_IPC_STREAM_VORBIS_FREE: + str2 = "VORBIS_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_FW_READY: + str = "FW_READY"; break; + case SOF_IPC_GLB_DAI_MSG: + str = "GLB_DAI_MSG"; + switch (type) { + case SOF_IPC_DAI_CONFIG: + str2 = "CONFIG"; break; + case SOF_IPC_DAI_LOOPBACK: + str2 = "LOOPBACK"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TRACE_MSG: + str = "GLB_TRACE_MSG"; break; + case SOF_IPC_GLB_TEST_MSG: + str = "GLB_TEST_MSG"; + switch (type) { + case SOF_IPC_TEST_IPC_FLOOD: + str2 = "IPC_FLOOD"; break; + default: + str2 = "unknown type"; break; + } + break; + default: + str = "unknown GLB command"; break; + } + + if (str2) { + if (vdbg) + dev_vdbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + else + dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + } else { + dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); + } +} +#else +static inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG) + dev_dbg(dev, "%s: 0x%x\n", text, cmd); +} +#endif + +/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = msg->msg_data; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + 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_handle_fw_exception(ipc->sdev); + ret = -ETIMEDOUT; + } else { + ret = msg->reply_error; + if (ret < 0) { + dev_err(sdev->dev, "error: ipc error for 0x%x size %zu\n", + hdr->cmd, msg->reply_size); + } else { + ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); + if (msg->reply_size) + /* copy the data returned from DSP */ + memcpy(reply_data, msg->reply_data, + msg->reply_size); + } + } + + return ret; +} + +/* send IPC message from host to DSP */ +static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + int ret; + + if (ipc->disable_ipc_tx) + return -ENODEV; + + /* + * The spin-lock is also still needed to protect message objects against + * other atomic contexts. + */ + spin_lock_irq(&sdev->ipc_lock); + + /* initialise the message */ + msg = &ipc->msg; + + msg->header = header; + msg->msg_size = msg_bytes; + msg->reply_size = reply_bytes; + msg->reply_error = 0; + + /* attach any data */ + if (msg_bytes) + memcpy(msg->msg_data, msg_data, msg_bytes); + + sdev->msg = msg; + + ret = snd_sof_dsp_send_msg(sdev, msg); + /* Next reply that we receive will be related to this message */ + if (!ret) + msg->ipc_complete = false; + + spin_unlock_irq(&sdev->ipc_lock); + + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: ipc tx failed with error %d\n", + ret); + return ret; + } + + ipc_log_header(sdev->dev, "ipc tx", msg->header); + + /* now wait for completion */ + if (!ret) + ret = tx_wait_done(ipc, msg, reply_data); + + return ret; +} + +/* send IPC message from host to DSP */ +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) +{ + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + }; + int ret; + + /* ensure the DSP is in D0 before sending a new IPC */ + ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state); + if (ret < 0) { + dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret); + return ret; + } + + return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes, + reply_data, reply_bytes); +} +EXPORT_SYMBOL(sof_ipc_tx_message); + +/* + * send IPC message from host to DSP without modifying the DSP state. + * This will be used for IPC's that can be handled by the DSP + * even in a low-power D0 substate. + */ +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + int ret; + + if (msg_bytes > SOF_IPC_MSG_MAX_SIZE || + reply_bytes > SOF_IPC_MSG_MAX_SIZE) + return -ENOBUFS; + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = sof_ipc_tx_message_unlocked(ipc, header, msg_data, msg_bytes, + reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + + return ret; +} +EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); + +/* handle reply message from DSP */ +void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + + if (msg->ipc_complete) { + dev_dbg(sdev->dev, + "no reply expected, received 0x%x, will be ignored", + msg_id); + return; + } + + /* wake up and return the error if we have waiters on this message ? */ + msg->ipc_complete = true; + wake_up(&msg->waitq); +} +EXPORT_SYMBOL(snd_sof_ipc_reply); + +/* DSP firmware has sent host a message */ +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +{ + struct sof_ipc_cmd_hdr hdr; + u32 cmd, type; + int err = 0; + + /* read back header */ + snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); + + cmd = hdr.cmd & SOF_GLB_TYPE_MASK; + type = hdr.cmd & SOF_CMD_TYPE_MASK; + + /* check message type */ + switch (cmd) { + case SOF_IPC_GLB_REPLY: + dev_err(sdev->dev, "error: ipc reply unknown\n"); + break; + case SOF_IPC_FW_READY: + /* check for FW boot completion */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + err = sof_ops(sdev)->fw_ready(sdev, cmd); + 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); + } + break; + case SOF_IPC_GLB_COMPOUND: + case SOF_IPC_GLB_TPLG_MSG: + case SOF_IPC_GLB_PM_MSG: + case SOF_IPC_GLB_COMP_MSG: + break; + case SOF_IPC_GLB_STREAM_MSG: + /* need to pass msg id into the function */ + ipc_stream_message(sdev, hdr.cmd); + break; + case SOF_IPC_GLB_TRACE_MSG: + ipc_trace_message(sdev, type); + break; + default: + dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); + break; + } + + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); +} +EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); + +/* + * IPC trace mechanism. + */ + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_dma_trace_posn posn; + + switch (msg_id) { + case SOF_IPC_TRACE_DMA_POSITION: + /* read back full message */ + snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); + snd_sof_trace_update_pos(sdev, &posn); + break; + default: + dev_err(sdev->dev, "error: unhandled trace message %x\n", + msg_id); + break; + } +} + +/* + * IPC stream position. + */ + +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(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, + "error: period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + + dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", + posn.host_posn, posn.dai_posn, posn.wallclock); + + memcpy(&stream->posn, &posn, sizeof(posn)); + + /* only inform ALSA for period_wakeup mode */ + if (!stream->substream->runtime->no_period_wakeup) + snd_sof_pcm_period_elapsed(stream->substream); +} + +/* 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(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + + dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", + posn.host_posn, posn.xrun_comp_id, posn.xrun_size); + +#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) + /* stop PCM on XRUN - used for pipeline debug */ + memcpy(&stream->posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(stream->substream); +#endif +} + +/* stream notifications from DSP FW */ +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) +{ + /* get msg cmd type and msd id */ + u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd); + + switch (msg_type) { + case SOF_IPC_STREAM_POSITION: + ipc_period_elapsed(sdev, msg_id); + break; + case SOF_IPC_STREAM_TRIG_XRUN: + ipc_xrun(sdev, msg_id); + break; + default: + dev_err(sdev->dev, "error: unhandled stream message %x\n", + msg_id); + break; + } +} + +/* get stream position IPC - use faster MMIO method if available on platform */ +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; + + /* read position via slower IPC */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION; + stream.comp_id = spcm->stream[direction].comp_id; + + /* send IPC to the DSP */ + err = sof_ipc_tx_message(sdev->ipc, + stream.hdr.cmd, &stream, sizeof(stream), posn, + sizeof(*posn)); + if (err < 0) { + dev_err(sdev->dev, "error: failed to get stream %d position\n", + stream.comp_id); + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_stream_posn); + +static int sof_get_ctrl_copy_params(enum sof_ipc_ctrl_type ctrl_type, + struct sof_ipc_ctrl_data *src, + struct sof_ipc_ctrl_data *dst, + struct sof_ipc_ctrl_data_params *sparams) +{ + switch (ctrl_type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + sparams->src = (u8 *)src->chanv; + sparams->dst = (u8 *)dst->chanv; + break; + case SOF_CTRL_TYPE_VALUE_COMP_GET: + case SOF_CTRL_TYPE_VALUE_COMP_SET: + sparams->src = (u8 *)src->compv; + sparams->dst = (u8 *)dst->compv; + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + sparams->src = (u8 *)src->data->data; + sparams->dst = (u8 *)dst->data->data; + break; + default: + return -EINVAL; + } + + /* calculate payload size and number of messages */ + sparams->pl_size = SOF_IPC_MSG_MAX_SIZE - sparams->hdr_bytes; + sparams->num_msg = DIV_ROUND_UP(sparams->msg_bytes, sparams->pl_size); + + return 0; +} + +static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, + struct sof_ipc_ctrl_data *cdata, + struct sof_ipc_ctrl_data_params *sparams, + bool send) +{ + struct sof_ipc_ctrl_data *partdata; + size_t send_bytes; + size_t offset = 0; + size_t msg_bytes; + size_t pl_size; + int err; + int i; + + /* allocate max ipc size because we have at least one */ + partdata = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!partdata) + return -ENOMEM; + + if (send) + err = sof_get_ctrl_copy_params(cdata->type, cdata, partdata, + sparams); + else + err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata, + sparams); + if (err < 0) { + kfree(partdata); + return err; + } + + msg_bytes = sparams->msg_bytes; + pl_size = sparams->pl_size; + + /* copy the header data */ + memcpy(partdata, cdata, sparams->hdr_bytes); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + /* copy the payload data in a loop */ + for (i = 0; i < sparams->num_msg; i++) { + send_bytes = min(msg_bytes, pl_size); + partdata->num_elems = send_bytes; + partdata->rhdr.hdr.size = sparams->hdr_bytes + send_bytes; + partdata->msg_index = i; + msg_bytes -= send_bytes; + partdata->elems_remaining = msg_bytes; + + if (send) + memcpy(sparams->dst, sparams->src + offset, send_bytes); + + err = sof_ipc_tx_message_unlocked(sdev->ipc, + partdata->rhdr.hdr.cmd, + partdata, + partdata->rhdr.hdr.size, + partdata, + partdata->rhdr.hdr.size); + if (err < 0) + break; + + if (!send) + memcpy(sparams->dst + offset, sparams->src, send_bytes); + + offset += pl_size; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(partdata); + return err; +} + +/* + * IPC get()/set() for kcontrols. + */ +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 = 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; + size_t send_bytes; + int err; + + /* read or write firmware volume */ + if (scontrol->readback_offset != 0) { + /* write/read value header via mmaped region */ + send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems; + if (send) + snd_sof_dsp_block_write(sdev, sdev->mmio_bar, + scontrol->readback_offset, + cdata->chanv, send_bytes); + + else + snd_sof_dsp_block_read(sdev, sdev->mmio_bar, + scontrol->readback_offset, + cdata->chanv, send_bytes); + return 0; + } + + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->comp_id = scontrol->comp_id; + cdata->msg_index = 0; + + /* calculate header and data size */ + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + sparams.msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_chan); + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + sparams.elems = scontrol->num_channels; + break; + case SOF_CTRL_TYPE_VALUE_COMP_GET: + case SOF_CTRL_TYPE_VALUE_COMP_SET: + sparams.msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_comp); + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + sparams.elems = scontrol->num_channels; + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + sparams.msg_bytes = cdata->data->size; + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_abi_hdr); + sparams.elems = cdata->data->size; + break; + default: + return -EINVAL; + } + + cdata->rhdr.hdr.size = sparams.msg_bytes + sparams.hdr_bytes; + cdata->num_elems = sparams.elems; + cdata->elems_remaining = 0; + + /* send normal size ipc in one part */ + if (cdata->rhdr.hdr.size <= SOF_IPC_MSG_MAX_SIZE) { + err = sof_ipc_tx_message(sdev->ipc, cdata->rhdr.hdr.cmd, cdata, + cdata->rhdr.hdr.size, cdata, + cdata->rhdr.hdr.size); + + if (err < 0) + dev_err(sdev->dev, "error: set/get ctrl ipc comp %d\n", + cdata->comp_id); + + return err; + } + + /* data is bigger than max ipc size, chop into smaller pieces */ + dev_dbg(sdev->dev, "large ipc size %u, control size %u\n", + cdata->rhdr.hdr.size, scontrol->size); + + /* large messages is only supported from ABI 3.3.0 onwards */ + if (v->abi_version < SOF_ABI_VER(3, 3, 0)) { + dev_err(sdev->dev, "error: incompatible FW ABI version\n"); + return -EINVAL; + } + + err = sof_set_get_large_ctrl_data(sdev, cdata, &sparams, send); + + if (err < 0) + dev_err(sdev->dev, "error: set/get large ctrl ipc comp %d\n", + cdata->comp_id); + + return err; +} +EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data); + +/* + * IPC layer enumeration. + */ + +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size) +{ + sdev->dsp_box.offset = dspbox; + sdev->dsp_box.size = dspbox_size; + sdev->host_box.offset = hostbox; + sdev->host_box.size = hostbox_size; + return 0; +} +EXPORT_SYMBOL(snd_sof_dsp_mailbox_init); + +int snd_sof_ipc_valid(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + + dev_info(sdev->dev, + "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, + v->micro, v->tag); + dev_info(sdev->dev, + "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(v->abi_version), + SOF_ABI_VERSION_MINOR(v->abi_version), + SOF_ABI_VERSION_PATCH(v->abi_version), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { + dev_err(sdev->dev, "error: incompatible FW ABI version\n"); + return -EINVAL; + } + + if (v->abi_version > SOF_ABI_VERSION) { + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { + dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n"); + } else { + dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n"); + return -EINVAL; + } + } + + if (ready->flags & SOF_IPC_INFO_BUILD) { + dev_info(sdev->dev, + "Firmware debug build %d on %s-%s - options:\n" + " GDB: %s\n" + " lock debug: %s\n" + " lock vdebug: %s\n", + v->build, v->date, v->time, + (ready->flags & SOF_IPC_INFO_GDB) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKS) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKSV) ? + "enabled" : "disabled"); + } + + /* copy the fw_version into debugfs at first boot */ + memcpy(&sdev->fw_version, v, sizeof(*v)); + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_valid); + +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc *ipc; + struct snd_sof_ipc_msg *msg; + + ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return NULL; + + mutex_init(&ipc->tx_mutex); + ipc->sdev = sdev; + msg = &ipc->msg; + + /* indicate that we aren't sending a message ATM */ + msg->ipc_complete = true; + + /* pre-allocate message data */ + msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + if (!msg->msg_data) + return NULL; + + msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + if (!msg->reply_data) + return NULL; + + init_waitqueue_head(&msg->waitq); + + return ipc; +} +EXPORT_SYMBOL(snd_sof_ipc_init); + +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; + mutex_unlock(&ipc->tx_mutex); +} +EXPORT_SYMBOL(snd_sof_ipc_free); diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c new file mode 100644 index 000000000..2d5c3fc93 --- /dev/null +++ b/sound/soc/sof/loader.c @@ -0,0 +1,836 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic firmware loader. +// + +#include <linux/firmware.h> +#include <sound/sof.h> +#include <sound/sof/ext_manifest.h> +#include "ops.h" + +static int get_ext_windows(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + const struct sof_ipc_window *w = + container_of(ext_hdr, struct sof_ipc_window, ext_hdr); + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + if (sdev->info_window) { + if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) { + dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox"); + return -EINVAL; + } + return 0; + } + + /* keep a local copy of the data */ + sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size, + GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + return 0; +} + +static int get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + int ret; + + const struct sof_ipc_cc_version *cc = + container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); + + if (sdev->cc_version) { + if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) { + dev_err(sdev->dev, "error: receive diverged cc_version descriptions"); + return -EINVAL; + } + return 0; + } + + 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) +{ + struct sof_ipc_ext_data_hdr *ext_hdr; + void *ext_data; + int ret = 0; + + ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!ext_data) + return -ENOMEM; + + /* get first header */ + snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + 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)); + + dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + + /* process structure data */ + switch (ext_hdr->type) { + 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; + case SOF_IPC_EXT_UNUSED: + case SOF_IPC_EXT_PROBE_INFO: + case SOF_IPC_EXT_USER_ABI_INFO: + /* They are supported but we don't do anything here */ + 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; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to parse ext data type %d\n", + ext_hdr->type); + break; + } + + /* move to next header */ + offset += ext_hdr->hdr.size; + snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + } + + kfree(ext_data); + return ret; +} +EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); + +static int ext_man_get_fw_version(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_fw_version *v = + container_of(hdr, struct sof_ext_man_fw_version, hdr); + + memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version)); + sdev->fw_ready.flags = v->flags; + + /* log ABI versions and check FW compatibility */ + return snd_sof_ipc_valid(sdev); +} + +static int ext_man_get_windows(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_window *w; + + w = container_of(hdr, struct sof_ext_man_window, hdr); + + return get_ext_windows(sdev, &w->ipc_window.ext_hdr); +} + +static int ext_man_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_cc_version *cc; + + cc = container_of(hdr, struct sof_ext_man_cc_version, hdr); + + return get_cc_info(sdev, &cc->cc_version.ext_hdr); +} + +static int ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct ext_man_dbg_abi *dbg_abi = + container_of(hdr, struct ext_man_dbg_abi, hdr); + + if (sdev->first_boot) + dev_dbg(sdev->dev, + "Firmware: DBG_ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version)); + + return 0; +} + +static ssize_t snd_sof_ext_man_size(const struct firmware *fw) +{ + const struct sof_ext_man_header *head; + + head = (struct sof_ext_man_header *)fw->data; + + /* + * assert fw size is big enough to contain extended manifest header, + * it prevents from reading unallocated memory from `head` in following + * step. + */ + if (fw->size < sizeof(*head)) + return -EINVAL; + + /* + * When fw points to extended manifest, + * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER. + */ + if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER) + return head->full_size; + + /* otherwise given fw don't have an extended manifest */ + return 0; +} + +/* parse extended FW manifest data structures */ +static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev, + const struct firmware *fw) +{ + const struct sof_ext_man_elem_header *elem_hdr; + const struct sof_ext_man_header *head; + ssize_t ext_man_size; + ssize_t remaining; + uintptr_t iptr; + int ret = 0; + + head = (struct sof_ext_man_header *)fw->data; + remaining = head->full_size - head->header_size; + ext_man_size = snd_sof_ext_man_size(fw); + + /* Assert firmware starts with extended manifest */ + if (ext_man_size <= 0) + return ext_man_size; + + /* incompatible version */ + if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION, + head->header_version)) { + dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n", + head->header_version, SOF_EXT_MAN_VERSION); + return -EINVAL; + } + + /* get first extended manifest element header */ + iptr = (uintptr_t)fw->data + head->header_size; + + while (remaining > sizeof(*elem_hdr)) { + elem_hdr = (struct sof_ext_man_elem_header *)iptr; + + dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n", + elem_hdr->type, elem_hdr->size); + + if (elem_hdr->size < sizeof(*elem_hdr) || + elem_hdr->size > remaining) { + dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n", + elem_hdr->type, elem_hdr->size); + return -EINVAL; + } + + /* process structure data */ + switch (elem_hdr->type) { + case SOF_EXT_MAN_ELEM_FW_VERSION: + ret = ext_man_get_fw_version(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_WINDOW: + ret = ext_man_get_windows(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_CC_VERSION: + ret = ext_man_get_cc_info(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_DBG_ABI: + ret = ext_man_get_dbg_abi_info(sdev, elem_hdr); + break; + default: + dev_warn(sdev->dev, "warning: unknown sof_ext_man header type %d size 0x%X\n", + elem_hdr->type, elem_hdr->size); + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n", + elem_hdr->type, elem_hdr->size); + return ret; + } + + remaining -= elem_hdr->size; + iptr += elem_hdr->size; + } + + if (remaining) { + dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n"); + return -EINVAL; + } + + return ext_man_size; +} + +/* + * 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; + u32 debug_size = 0; + u32 debug_offset = 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: + debug_offset = window_offset + elem->offset; + debug_size = elem->size; + 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; + + sdev->debug_box.offset = debug_offset; + sdev->debug_box.size = debug_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); + dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n", + debug_offset, debug_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, bar; + u32 offset; + size_t remaining; + + dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", + module->size, module->num_blocks, module->type); + + block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); + + /* module->size doesn't include header size */ + remaining = module->size; + for (count = 0; count < module->num_blocks; count++) { + /* check for wrap */ + if (remaining < sizeof(*block)) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of block */ + remaining -= sizeof(*block); + + if (block->size == 0) { + dev_warn(sdev->dev, + "warning: block %d size zero\n", count); + dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", + block->type, block->offset); + continue; + } + + switch (block->type) { + case SOF_FW_BLK_TYPE_RSRVD0: + 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", + block->type, count); + return -EINVAL; + } + + dev_dbg(sdev->dev, + "block %d type 0x%x size 0x%x ==> offset 0x%x\n", + count, block->type, block->size, offset); + + /* checking block->size to avoid unaligned access */ + if (block->size % sizeof(u32)) { + dev_err(sdev->dev, "error: invalid block size 0x%x\n", + block->size); + return -EINVAL; + } + snd_sof_dsp_block_write(sdev, bar, offset, + block + 1, block->size); + + if (remaining < block->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus body size of block */ + remaining -= block->size; + /* next block */ + block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) + + block->size); + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_parse_module_memcpy); + +static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw, + size_t fw_offset) +{ + struct snd_sof_fw_header *header; + size_t fw_size = fw->size - fw_offset; + + if (fw->size <= fw_offset) { + dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n"); + return -EINVAL; + } + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)(fw->data + fw_offset); + + /* verify FW sig */ + if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { + dev_err(sdev->dev, "error: invalid firmware signature\n"); + return -EINVAL; + } + + /* check size is valid */ + if (fw_size != header->file_size + sizeof(*header)) { + dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", + fw_size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", + header->file_size, header->num_modules, + header->abi, sizeof(*header)); + + return 0; +} + +static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw, + size_t fw_offset) +{ + struct snd_sof_fw_header *header; + struct snd_sof_mod_hdr *module; + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); + int ret, count; + size_t remaining; + + header = (struct snd_sof_fw_header *)(fw->data + fw_offset); + load_module = sof_ops(sdev)->load_module; + if (!load_module) + return -EINVAL; + + /* parse each module */ + module = (struct snd_sof_mod_hdr *)(fw->data + fw_offset + + sizeof(*header)); + remaining = fw->size - sizeof(*header) - fw_offset; + /* check for wrap */ + if (remaining > fw->size) { + dev_err(sdev->dev, "error: fw size smaller than header size\n"); + return -EINVAL; + } + + for (count = 0; count < header->num_modules; count++) { + /* check for wrap */ + if (remaining < sizeof(*module)) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of module */ + remaining -= sizeof(*module); + + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid module %d\n", count); + return ret; + } + + if (remaining < module->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus body size of module */ + remaining -= module->size; + module = (struct snd_sof_mod_hdr *)((u8 *)module + + sizeof(*module) + module->size); + } + + return 0; +} + +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *fw_filename; + ssize_t ext_man_size; + int ret; + + /* Don't request firmware again if firmware is already requested */ + if (plat_data->fw) + return 0; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, + plat_data->fw_filename); + if (!fw_filename) + return -ENOMEM; + + ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev); + + if (ret < 0) { + dev_err(sdev->dev, "error: request firmware %s failed err: %d\n", + fw_filename, ret); + goto err; + } else { + dev_dbg(sdev->dev, "request_firmware %s successful\n", + fw_filename); + } + + /* check for extended manifest */ + ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw); + if (ext_man_size > 0) { + /* when no error occurred, drop extended manifest */ + plat_data->fw_offset = ext_man_size; + } else if (!ext_man_size) { + /* No extended manifest, so nothing to skip during FW load */ + dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n"); + } else { + ret = ext_man_size; + dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n", + fw_filename, ret); + } + +err: + kfree(fw_filename); + + return ret; +} +EXPORT_SYMBOL(snd_sof_load_firmware_raw); + +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + int ret; + + ret = snd_sof_load_firmware_raw(sdev); + if (ret < 0) + return ret; + + /* make sure the FW header and file is valid */ + ret = check_header(sdev, plat_data->fw, plat_data->fw_offset); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW header\n"); + goto error; + } + + /* prepare the DSP for FW loading */ + ret = snd_sof_dsp_reset(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + goto error; + } + + /* parse and load firmware modules to DSP */ + ret = load_modules(sdev, plat_data->fw, plat_data->fw_offset); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW modules\n"); + goto error; + } + + return 0; + +error: + release_firmware(plat_data->fw); + plat_data->fw = NULL; + return ret; + +} +EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); + +int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{ + dev_dbg(sdev->dev, "loading firmware\n"); + + if (sof_ops(sdev)->load_firmware) + return sof_ops(sdev)->load_firmware(sdev); + return 0; +} +EXPORT_SYMBOL(snd_sof_load_firmware); + +int snd_sof_run_firmware(struct snd_sof_dev *sdev) +{ + int ret; + int init_core_mask; + + init_waitqueue_head(&sdev->boot_wait); + + /* create read-only fw_version debugfs to store boot version info */ + if (sdev->first_boot) { + ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version, + sizeof(sdev->fw_version), + "fw_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; + } + } + + /* perform pre fw run operations */ + ret = snd_sof_dsp_pre_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed pre fw run op\n"); + return ret; + } + + dev_dbg(sdev->dev, "booting DSP firmware\n"); + + /* boot the firmware on the DSP */ + ret = snd_sof_dsp_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + return ret; + } + + init_core_mask = ret; + + /* + * 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"); + 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; + } + + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) + dev_dbg(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); + if (ret < 0) { + dev_err(sdev->dev, "error: failed post fw run op\n"); + return ret; + } + + /* fw boot is complete. Update the active cores mask */ + sdev->enabled_cores_mask = init_core_mask; + + return 0; +} +EXPORT_SYMBOL(snd_sof_run_firmware); + +void snd_sof_fw_unload(struct snd_sof_dev *sdev) +{ + /* TODO: support module unloading at runtime */ + release_firmware(sdev->pdata->fw); + sdev->pdata->fw = NULL; +} +EXPORT_SYMBOL(snd_sof_fw_unload); diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c new file mode 100644 index 000000000..9e922df6a --- /dev/null +++ b/sound/soc/sof/nocodec.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/sof.h> +#include "sof-priv.h" + +static struct snd_soc_card sof_nocodec_card = { + .name = "nocodec", /* the sof- prefix is added by the core */ + .owner = THIS_MODULE +}; + +static int sof_nocodec_bes_setup(struct device *dev, + const struct snd_sof_dsp_ops *ops, + struct snd_soc_dai_link *links, + int link_num, struct snd_soc_card *card) +{ + struct snd_soc_dai_link_component *dlc; + int i; + + if (!ops || !links || !card) + return -EINVAL; + + /* set up BE dai_links */ + for (i = 0; i < link_num; i++) { + dlc = devm_kzalloc(dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + links[i].name = devm_kasprintf(dev, GFP_KERNEL, + "NoCodec-%d", i); + if (!links[i].name) + return -ENOMEM; + + links[i].cpus = &dlc[0]; + links[i].codecs = &dlc[1]; + links[i].platforms = &dlc[2]; + + links[i].num_cpus = 1; + links[i].num_codecs = 1; + links[i].num_platforms = 1; + + links[i].id = i; + links[i].no_pcm = 1; + links[i].cpus->dai_name = ops->drv[i].name; + links[i].platforms->name = dev_name(dev); + links[i].codecs->dai_name = "snd-soc-dummy-dai"; + links[i].codecs->name = "snd-soc-dummy"; + if (ops->drv[i].playback.channels_min) + links[i].dpcm_playback = 1; + if (ops->drv[i].capture.channels_min) + links[i].dpcm_capture = 1; + } + + card->dai_link = links; + card->num_links = link_num; + + return 0; +} + +int sof_nocodec_setup(struct device *dev, + const struct snd_sof_dsp_ops *ops) +{ + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * + ops->num_drv, GFP_KERNEL); + if (!links) + return -ENOMEM; + + return sof_nocodec_bes_setup(dev, ops, links, ops->num_drv, + &sof_nocodec_card); +} +EXPORT_SYMBOL(sof_nocodec_setup); + +static int sof_nocodec_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &sof_nocodec_card; + + card->dev = &pdev->dev; + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static int sof_nocodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver sof_nocodec_audio = { + .probe = sof_nocodec_probe, + .remove = sof_nocodec_remove, + .driver = { + .name = "sof-nocodec", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(sof_nocodec_audio) + +MODULE_DESCRIPTION("ASoC sof nocodec"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-nocodec"); diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c new file mode 100644 index 000000000..1a394b4c6 --- /dev/null +++ b/sound/soc/sof/ops.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/pci.h> +#include "ops.h" + +static +bool snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + unsigned int old, new; + u32 ret = 0; + + pci_read_config_dword(pci, offset, &ret); + old = ret; + dev_dbg(sdev->dev, "Debug PCIR: %8.8x at %8.8x\n", old & mask, offset); + + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + pci_write_config_dword(pci, offset, new); + dev_dbg(sdev->dev, "Debug PCIW: %8.8x at %8.8x\n", value, + offset); + + return true; +} + +bool snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_pci_update_bits_unlocked(sdev, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_pci_update_bits); + +bool snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + snd_sof_dsp_write(sdev, bar, offset, new); + + return true; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_unlocked); + +bool snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value) +{ + u64 old, new; + + old = snd_sof_dsp_read64(sdev, bar, offset); + + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + snd_sof_dsp_write64(sdev, bar, offset, new); + + return true; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64_unlocked); + +/* This is for registers bits with attribute RWC */ +bool snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits); + +bool snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u64 mask, u64 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits64_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64); + +static +void snd_sof_dsp_update_bits_forced_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & ~mask) | (value & mask); + + snd_sof_dsp_write(sdev, bar, offset, new); +} + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sdev->hw_lock, flags); + snd_sof_dsp_update_bits_forced_unlocked(sdev, bar, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) +{ + dev_err(sdev->dev, "error : DSP panic!\n"); + + /* + * check if DSP is not ready and did not set the dsp_oops_offset. + * if the dsp_oops_offset is not set, set it from the panic message. + * Also add a check to memory window setting with panic message. + */ + if (!sdev->dsp_oops_offset) + sdev->dsp_oops_offset = offset; + else + dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n", + sdev->dsp_oops_offset, offset); + + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_trace_notify_for_error(sdev); +} +EXPORT_SYMBOL(snd_sof_dsp_panic); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h new file mode 100644 index 000000000..b21632f55 --- /dev/null +++ b/sound/soc/sof/ops.h @@ -0,0 +1,556 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOUND_SOC_SOF_IO_H +#define __SOUND_SOC_SOF_IO_H + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <sound/pcm.h> +#include "sof-priv.h" + +#define sof_ops(sdev) \ + ((sdev)->pdata->desc->ops) + +/* Mandatory operations are verified during probing */ + +/* init */ +static inline int snd_sof_probe(struct snd_sof_dev *sdev) +{ + return sof_ops(sdev)->probe(sdev); +} + +static inline int snd_sof_remove(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->remove) + return sof_ops(sdev)->remove(sdev); + + return 0; +} + +/* control */ + +/* + * snd_sof_dsp_run returns the core mask of the cores that are available + * after successful fw boot + */ +static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev) +{ + return sof_ops(sdev)->run(sdev); +} + +static inline int snd_sof_dsp_stall(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->stall) + return sof_ops(sdev)->stall(sdev); + + return 0; +} + +static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->reset) + return sof_ops(sdev)->reset(sdev); + + return 0; +} + +/* dsp core power up/power down */ +static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sof_ops(sdev)->core_power_up) + return sof_ops(sdev)->core_power_up(sdev, core_mask); + + return 0; +} + +static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sof_ops(sdev)->core_power_down) + return sof_ops(sdev)->core_power_down(sdev, core_mask); + + return 0; +} + +/* pre/post fw load */ +static inline int snd_sof_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->pre_fw_run) + return sof_ops(sdev)->pre_fw_run(sdev); + + return 0; +} + +static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->post_fw_run) + return sof_ops(sdev)->post_fw_run(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) +{ + if (sof_ops(sdev)->resume) + return sof_ops(sdev)->resume(sdev); + + return 0; +} + +static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev, + u32 target_state) +{ + if (sof_ops(sdev)->suspend) + return sof_ops(sdev)->suspend(sdev, target_state); + + return 0; +} + +static inline int snd_sof_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->runtime_resume) + return sof_ops(sdev)->runtime_resume(sdev); + + return 0; +} + +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); + + return 0; +} + +static inline int snd_sof_dsp_runtime_idle(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->runtime_idle) + return sof_ops(sdev)->runtime_idle(sdev); + + return 0; +} + +static inline int snd_sof_dsp_hw_params_upon_resume(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->set_hw_params_upon_resume) + return sof_ops(sdev)->set_hw_params_upon_resume(sdev); + return 0; +} + +static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq) +{ + if (sof_ops(sdev)->set_clk) + return sof_ops(sdev)->set_clk(sdev, freq); + + return 0; +} + +static inline int +snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + if (sof_ops(sdev)->set_power_state) + return sof_ops(sdev)->set_power_state(sdev, target_state); + + /* D0 substate is not supported, do nothing here. */ + return 0; +} + +/* debug */ +static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) +{ + if (sof_ops(sdev)->dbg_dump) + return sof_ops(sdev)->dbg_dump(sdev, flags); +} + +static inline void snd_sof_ipc_dump(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->ipc_dump) + return sof_ops(sdev)->ipc_dump(sdev); +} + +/* register IO */ +static inline void snd_sof_dsp_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 value) +{ + if (sof_ops(sdev)->write) { + sof_ops(sdev)->write(sdev, sdev->bar[bar] + offset, value); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 value) +{ + if (sof_ops(sdev)->write64) { + sof_ops(sdev)->write64(sdev, sdev->bar[bar] + offset, value); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline u32 snd_sof_dsp_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sof_ops(sdev)->read) + return sof_ops(sdev)->read(sdev, sdev->bar[bar] + offset); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sof_ops(sdev)->read64) + return sof_ops(sdev)->read64(sdev, sdev->bar[bar] + offset); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +/* block IO */ +static inline void snd_sof_dsp_block_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *dest, size_t bytes) +{ + sof_ops(sdev)->block_read(sdev, bar, offset, dest, bytes); +} + +static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *src, size_t bytes) +{ + sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); +} + +/* ipc */ +static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + return sof_ops(sdev)->send_msg(sdev, msg); +} + +/* host DMA trace */ +static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, + u32 *stream_tag) +{ + if (sof_ops(sdev)->trace_init) + return sof_ops(sdev)->trace_init(sdev, stream_tag); + + return 0; +} + +static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->trace_release) + return sof_ops(sdev)->trace_release(sdev); + + return 0; +} + +static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) +{ + if (sof_ops(sdev)->trace_trigger) + return sof_ops(sdev)->trace_trigger(sdev, cmd); + + return 0; +} + +/* host PCM ops */ +static inline int +snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_open) + return sof_ops(sdev)->pcm_open(sdev, substream); + + return 0; +} + +/* disconnect pcm substream to a host stream */ +static inline int +snd_sof_pcm_platform_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_close) + return sof_ops(sdev)->pcm_close(sdev, substream); + + return 0; +} + +/* host stream hw params */ +static inline int +snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_hw_params) + return sof_ops(sdev)->pcm_hw_params(sdev, substream, + params, ipc_params); + + return 0; +} + +/* host stream hw free */ +static inline int +snd_sof_pcm_platform_hw_free(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_hw_free) + return sof_ops(sdev)->pcm_hw_free(sdev, substream); + + return 0; +} + +/* host stream trigger */ +static inline int +snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_trigger) + return sof_ops(sdev)->pcm_trigger(sdev, substream, cmd); + + return 0; +} + +/* host DSP message data */ +static inline void snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); +} + +/* host configure DSP HW parameters */ +static inline int +snd_sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return sof_ops(sdev)->ipc_pcm_params(sdev, substream, reply); +} + +/* host stream pointer */ +static inline snd_pcm_uframes_t +snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_pointer) + return sof_ops(sdev)->pcm_pointer(sdev, substream); + + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) +static inline int +snd_sof_probe_compr_assign(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, struct snd_soc_dai *dai) +{ + return sof_ops(sdev)->probe_assign(sdev, cstream, dai); +} + +static inline int +snd_sof_probe_compr_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, struct snd_soc_dai *dai) +{ + return sof_ops(sdev)->probe_free(sdev, cstream, dai); +} + +static inline int +snd_sof_probe_compr_set_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, struct snd_soc_dai *dai) +{ + return sof_ops(sdev)->probe_set_params(sdev, cstream, params, dai); +} + +static inline int +snd_sof_probe_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + return sof_ops(sdev)->probe_trigger(sdev, cstream, cmd, dai); +} + +static inline int +snd_sof_probe_compr_pointer(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) +{ + if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer) + return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai); + + return 0; +} +#endif + +/* 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) +{ + int i; + + for (i = 0; i < asize; i++) { + if (d == mach_ops[i].desc) + return mach_ops[i].ops; + } + + /* not found */ + return NULL; +} + +/** + * snd_sof_dsp_register_poll_timeout - Periodically poll an address + * until a condition is met or a timeout occurs + * @op: accessor function (takes @addr as its only argument) + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * @sleep_us: Maximum time to sleep between reads in us (0 + * tight-loops). Should be less than ~20ms since usleep_range + * is used (see Documentation/timers/timers-howto.rst). + * @timeout_us: Timeout in us, 0 means never timeout + * + * Returns 0 on success and -ETIMEDOUT upon a timeout. In either + * case, the last read value at @addr is stored in @val. Must not + * be called from atomic context if sleep_us or timeout_us are used. + * + * This is modelled after the readx_poll_timeout macros in linux/iopoll.h. + */ +#define snd_sof_dsp_read_poll_timeout(sdev, bar, offset, val, cond, sleep_us, timeout_us) \ +({ \ + u64 __timeout_us = (timeout_us); \ + unsigned long __sleep_us = (sleep_us); \ + ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \ + might_sleep_if((__sleep_us) != 0); \ + for (;;) { \ + (val) = snd_sof_dsp_read(sdev, bar, offset); \ + if (cond) { \ + dev_dbg(sdev->dev, \ + "FW Poll Status: reg=%#x successful\n", (val)); \ + break; \ + } \ + if (__timeout_us && \ + ktime_compare(ktime_get(), __timeout) > 0) { \ + (val) = snd_sof_dsp_read(sdev, bar, offset); \ + dev_dbg(sdev->dev, \ + "FW Poll Status: reg=%#x timedout\n", (val)); \ + break; \ + } \ + if (__sleep_us) \ + usleep_range((__sleep_us >> 2) + 1, __sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +/* This is for registers bits with attribute RWC */ +bool snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value); + +bool snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +bool snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +bool snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value); + +bool snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 target, u32 timeout_ms, + u32 interval_us); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset); +#endif diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c new file mode 100644 index 000000000..cbac6f17c --- /dev/null +++ b/sound/soc/sof/pcm.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// PCM Layer, interface between ALSA and IPC. +// + +#include <linux/pm_runtime.h> +#include <sound/pcm_params.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ops.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) +#include "compress.h" +#endif + +/* Create DMA buffer page table for DSP */ +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 = asoc_substream_to_rtd(substream); + 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(component, rtd); + if (!spcm) + return -EINVAL; + + 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_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(scomp->dev, "error: got wrong reply for PCM %d\n", + spcm->pcm.pcm_id); + + return ret; +} + +/* + * sof pcm period elapse work + */ +void snd_sof_pcm_period_elapsed_work(struct work_struct *work) +{ + struct snd_sof_pcm_stream *sps = + container_of(work, struct snd_sof_pcm_stream, + period_elapsed_work); + + snd_pcm_period_elapsed(sps->substream); +} + +/* + * sof pcm period elapse, this could be called at irq thread context. + */ +void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(component->dev, + "error: period elapsed for unknown stream!\n"); + return; + } + + /* + * snd_pcm_period_elapsed() can be called in interrupt context + * before IRQ_HANDLED is returned. Inside snd_pcm_period_elapsed(), + * when the PCM is done draining or xrun happened, a STOP IPC will + * then be sent and this IPC will hit IPC timeout. + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ + schedule_work(&spcm->stream[substream->stream].period_elapsed_work); +} +EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +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 = asoc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_pcm_params pcm; + struct sof_ipc_pcm_params_reply ipc_params_reply; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + /* + * 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)); + + /* 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; + } + + /* number of pages should be rounded up */ + pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes); + + /* set IPC PCM parameters */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = spcm->stream[substream->stream].comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.buffer.phy_addr = + spcm->stream[substream->stream].page_table.addr; + pcm.params.buffer.size = runtime->dma_bytes; + pcm.params.direction = substream->stream; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* container size */ + ret = snd_pcm_format_physical_width(params_format(params)); + if (ret < 0) + return ret; + pcm.params.sample_container_bytes = ret >> 3; + + /* format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + case SNDRV_PCM_FORMAT_FLOAT: + pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; + break; + default: + return -EINVAL; + } + + /* firmware already configured host stream */ + ret = snd_sof_pcm_platform_hw_params(sdev, + substream, + params, + &pcm.params); + if (ret < 0) { + dev_err(component->dev, "error: platform hw params failed\n"); + return ret; + } + + 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(component->dev, "error: hw params ipc failed for stream %d\n", + pcm.params.stream_tag); + return ret; + } + + ret = sof_pcm_dsp_params(spcm, substream, &ipc_params_reply); + if (ret < 0) + return ret; + + spcm->prepared[substream->stream] = true; + + /* save pcm hw_params */ + memcpy(&spcm->params[substream->stream], params, sizeof(*params)); + + return ret; +} + +static int sof_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + int ret, err = 0; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(component->dev, "pcm: free stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); + + 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); + + ret = snd_sof_pcm_platform_hw_free(sdev, substream); + if (ret < 0) { + dev_err(component->dev, "error: platform hw free failed\n"); + err = ret; + } + + return err; +} + +static int sof_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_sof_pcm *spcm; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + if (spcm->prepared[substream->stream]) + return 0; + + 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(component, + substream, &spcm->params[substream->stream]); + if (ret < 0) { + dev_err(component->dev, + "error: set pcm hw_params after resume\n"); + return ret; + } + + return 0; +} + +/* + * FE dai link trigger actions are always executed in non-atomic context because + * they involve IPC's. + */ +static int sof_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + 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(component, rtd); + if (!spcm) + return -EINVAL; + + 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); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + 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(component, substream); + if (ret < 0) { + 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->system_suspend_target == SOF_SUSPEND_S0IX && + 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(component->dev, "error: unhandled trigger cmd %d\n", + cmd); + return -EINVAL; + } + + /* + * 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)); + + /* need to STOP DMA even if STOP IPC failed */ + if (ipc_first) + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + + /* 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); + + return ret; +} + +static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + snd_pcm_uframes_t host, dai; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + /* use dsp ops pointer callback directly if set */ + if (sof_ops(sdev)->pcm_pointer) + return sof_ops(sdev)->pcm_pointer(sdev, substream); + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + /* read position from DSP */ + host = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.host_posn); + dai = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.dai_posn); + + dev_vdbg(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_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + 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; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(component->dev, "pcm: open stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); + + + caps = &spcm->pcm.caps[substream->stream]; + + /* set any runtime constraints based on topology */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + le32_to_cpu(caps->period_size_min)); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + le32_to_cpu(caps->period_size_min)); + + /* set runtime config */ + 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); + runtime->hw.periods_min = le32_to_cpu(caps->periods_min); + runtime->hw.periods_max = le32_to_cpu(caps->periods_max); + + /* + * caps->buffer_size_min is not used since the + * snd_pcm_hardware structure only defines buffer_bytes_max + */ + runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max); + + dev_dbg(component->dev, "period min %zd max %zd bytes\n", + runtime->hw.period_bytes_min, + runtime->hw.period_bytes_max); + dev_dbg(component->dev, "period count %d max %d\n", + runtime->hw.periods_min, + runtime->hw.periods_max); + dev_dbg(component->dev, "buffer max %zd bytes\n", + runtime->hw.buffer_bytes_max); + + /* set wait time - TODO: come from topology */ + substream->wait_time = 500; + + 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(component->dev, "error: pcm open failed %d\n", ret); + + return ret; +} + +static int sof_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + int err; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + 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(component->dev, "error: pcm close failed %d\n", + err); + /* + * keep going, no point in preventing the close + * from happening + */ + } + + return 0; +} + +/* + * 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_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_tplg_stream_caps *caps; + int stream = SNDRV_PCM_STREAM_PLAYBACK; + + /* find SOF PCM for this RTD */ + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_warn(component->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + 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) + goto capture; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate playback audio buffer pages */ + 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); + + 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, + 0, le32_to_cpu(caps->buffer_size_max)); +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to pre-allocate capture audio buffer pages */ + if (!spcm->pcm.capture) + return 0; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate capture audio buffer pages */ + 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); + + 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, + 0, le32_to_cpu(caps->buffer_size_max)); + + return 0; +} + +/* fixup the BE DAI link to match any values from topology */ +static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + struct snd_sof_dai *dai = + snd_sof_find_dai(component, (char *)rtd->dai_link->name); + struct snd_soc_dpcm *dpcm; + + /* no topology exists for this BE, try a common configuration */ + if (!dai) { + dev_warn(component->dev, + "warning: no topology found for BE DAI %s config\n", + rtd->dai_link->name); + + /* set 48k, stereo, 16bits by default */ + rate->min = 48000; + rate->max = 48000; + + channels->min = 2; + channels->max = 2; + + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; + } + + /* read format from topology */ + snd_mask_none(fmt); + + switch (dai->comp_dai.config.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + default: + dev_err(component->dev, "error: No available DAI format!\n"); + return -EINVAL; + } + + /* read rate and channels from topology */ + switch (dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + rate->min = dai->dai_config->ssp.fsync_rate; + rate->max = dai->dai_config->ssp.fsync_rate; + channels->min = dai->dai_config->ssp.tdm_slots; + channels->max = dai->dai_config->ssp.tdm_slots; + + dev_dbg(component->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + + break; + 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(component->dev, + "error: invalid fmt %d for DAI type %d\n", + dai->comp_dai.config.frame_fmt, + dai->dai_config->type); + } + break; + case SOF_DAI_INTEL_HDA: + /* + * HDAudio does not follow the default trigger + * sequence due to firmware implementation + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + struct snd_soc_pcm_runtime *fe = dpcm->fe; + + fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = + SND_SOC_DPCM_TRIGGER_POST; + } + break; + case SOF_DAI_INTEL_ALH: + /* do nothing for ALH dai_link */ + break; + case SOF_DAI_IMX_ESAI: + rate->min = dai->dai_config->esai.fsync_rate; + rate->max = dai->dai_config->esai.fsync_rate; + channels->min = dai->dai_config->esai.tdm_slots; + channels->max = dai->dai_config->esai.tdm_slots; + + dev_dbg(component->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_IMX_SAI: + rate->min = dai->dai_config->sai.fsync_rate; + rate->max = dai->dai_config->sai.fsync_rate; + channels->min = dai->dai_config->sai.tdm_slots; + channels->max = dai->dai_config->sai.tdm_slots; + + dev_dbg(component->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + default: + dev_err(component->dev, "error: invalid DAI type %d\n", + dai->dai_config->type); + break; + } + + return 0; +} + +static int sof_pcm_probe(struct snd_soc_component *component) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *tplg_filename; + int ret; + + /* load the default topology */ + sdev->component = component; + + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s/%s", + plat_data->tplg_filename_prefix, + plat_data->tplg_filename); + if (!tplg_filename) + return -ENOMEM; + + ret = snd_sof_load_topology(component, tplg_filename); + if (ret < 0) { + dev_err(component->dev, "error: failed to load DSP topology %d\n", + ret); + return ret; + } + + return ret; +} + +static void sof_pcm_remove(struct snd_soc_component *component) +{ + /* remove topology */ + snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL); +} + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) +{ + struct snd_soc_component_driver *pd = &sdev->plat_drv; + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *drv_name; + + drv_name = plat_data->machine->drv_name; + + pd->name = "sof-audio-component"; + pd->probe = sof_pcm_probe; + pd->remove = sof_pcm_remove; + 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->compress_ops = &sof_compressed_ops; +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) + /* override cops when probe support is enabled */ + pd->compress_ops = &sof_probe_compressed_ops; +#endif + 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; + pd->use_dai_pcm_id = true; + pd->topology_name_prefix = "sof"; + + /* increment module refcount when a pcm is opened */ + pd->module_get_upon_open = 1; +} diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c new file mode 100644 index 000000000..c83fb6255 --- /dev/null +++ b/sound/soc/sof/pm.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include "ops.h" +#include "sof-priv.h" +#include "sof-audio.h" + +/* + * Helper function to determine the target DSP state during + * system suspend. This function only cares about the device + * D-states. Platform-specific substates, if any, should be + * handled by the platform-specific parts. + */ +static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) +{ + u32 target_dsp_state; + + switch (sdev->system_suspend_target) { + case SOF_SUSPEND_S3: + /* DSP should be in D3 if the system is suspending to S3 */ + target_dsp_state = SOF_DSP_PM_D3; + break; + case SOF_SUSPEND_S0IX: + /* + * Currently, the only criterion for retaining the DSP in D0 + * is that there are streams that ignored the suspend trigger. + * Additional criteria such Soundwire clock-stop mode and + * device suspend latency considerations will be added later. + */ + if (snd_sof_stream_suspend_ignored(sdev)) + target_dsp_state = SOF_DSP_PM_D0; + else + target_dsp_state = SOF_DSP_PM_D3; + break; + default: + /* This case would be during runtime suspend */ + target_dsp_state = SOF_DSP_PM_D3; + break; + } + + return target_dsp_state; +} + +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; + + memset(&pm_ctx, 0, sizeof(pm_ctx)); + + /* configure ctx save ipc message */ + pm_ctx.hdr.size = sizeof(pm_ctx); + pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; + + /* send ctx save ipc to dsp */ + return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, + sizeof(pm_ctx), &reply, sizeof(reply)); +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) +static void sof_cache_debugfs(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + list_for_each_entry(dfse, &sdev->dfsentry_list, list) { + + /* nothing to do if debugfs buffer is not IO mem */ + if (dfse->type == SOF_DFSENTRY_TYPE_BUF) + continue; + + /* cache memory that is only accessible in D0 */ + if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) + memcpy_fromio(dfse->cache_buf, dfse->io_mem, + dfse->size); + } +} +#endif + +static int sof_resume(struct device *dev, bool runtime_resume) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + u32 old_state = sdev->dsp_power_state.state; + int ret; + + /* do nothing if dsp resume callbacks are not set */ + if (!runtime_resume && !sof_ops(sdev)->resume) + return 0; + + if (runtime_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 + */ + if (runtime_resume) + ret = snd_sof_dsp_runtime_resume(sdev); + else + ret = snd_sof_dsp_resume(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to power up DSP after resume\n"); + return ret; + } + + /* + * Nothing further to be done for platforms that support the low power + * D0 substate. + */ + if (!runtime_resume && sof_ops(sdev)->set_power_state && + old_state == SOF_DSP_PM_D0) + return 0; + + sdev->fw_state = SOF_FW_BOOT_PREPARE; + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load DSP firmware after resume %d\n", + ret); + return ret; + } + + 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 after resume %d\n", + ret); + return ret; + } + + /* resume DMA trace, only need send ipc */ + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to init trace after resume %d\n", + ret); + } + + /* restore pipelines */ + ret = sof_restore_pipelines(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to restore pipeline after resume %d\n", + ret); + return ret; + } + + /* notify DSP of system resume */ + 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); + + return ret; +} + +static int sof_suspend(struct device *dev, bool runtime_suspend) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + u32 target_state = 0; + int ret; + + /* do nothing if dsp suspend callback is not set */ + if (!runtime_suspend && !sof_ops(sdev)->suspend) + return 0; + + if (runtime_suspend && !sof_ops(sdev)->runtime_suspend) + return 0; + + if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) + goto suspend; + + /* set restore_stream for all streams during system suspend */ + if (!runtime_suspend) { + 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", + ret); + return ret; + } + } + + target_state = snd_sof_dsp_power_target(sdev); + + /* Skip to platform-specific suspend if DSP is entering D0 */ + if (target_state == SOF_DSP_PM_D0) + goto suspend; + + /* release trace */ + snd_sof_release_trace(sdev); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* cache debugfs contents during runtime suspend */ + if (runtime_suspend) + sof_cache_debugfs(sdev); +#endif + /* notify DSP of upcoming power down */ + 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 + * pass these errors up + */ + dev_err(sdev->dev, + "error: ctx_save ipc error during suspend %d\n", + ret); + return ret; + } else if (ret < 0) { + /* FW in unexpected state, continue to power down */ + dev_warn(sdev->dev, + "ctx_save ipc error %d, proceeding with suspend\n", + ret); + } + +suspend: + + /* return if the DSP was not probed successfully */ + if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) + return 0; + + /* platform-specific suspend */ + if (runtime_suspend) + ret = snd_sof_dsp_runtime_suspend(sdev); + else + ret = snd_sof_dsp_suspend(sdev, target_state); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to power down DSP during suspend %d\n", + ret); + + /* Do not reset FW state if DSP is in D0 */ + if (target_state == SOF_DSP_PM_D0) + return ret; + + /* reset FW state */ + sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sdev->enabled_cores_mask = 0; + + return ret; +} + +int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) +{ + /* Notify DSP of upcoming power down */ + if (sof_ops(sdev)->remove) + return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + + return 0; +} + +int snd_sof_runtime_suspend(struct device *dev) +{ + return sof_suspend(dev, true); +} +EXPORT_SYMBOL(snd_sof_runtime_suspend); + +int snd_sof_runtime_idle(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + return snd_sof_dsp_runtime_idle(sdev); +} +EXPORT_SYMBOL(snd_sof_runtime_idle); + +int snd_sof_runtime_resume(struct device *dev) +{ + return sof_resume(dev, true); +} +EXPORT_SYMBOL(snd_sof_runtime_resume); + +int snd_sof_resume(struct device *dev) +{ + return sof_resume(dev, false); +} +EXPORT_SYMBOL(snd_sof_resume); + +int snd_sof_suspend(struct device *dev) +{ + 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); + const struct sof_dev_desc *desc = sdev->pdata->desc; + + /* will suspend to S3 by default */ + sdev->system_suspend_target = SOF_SUSPEND_S3; + + if (!desc->use_acpi_target_states) + return 0; + +#if defined(CONFIG_ACPI) + if (acpi_target_system_state() == ACPI_STATE_S0) + sdev->system_suspend_target = SOF_SUSPEND_S0IX; +#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->system_suspend_target = SOF_SUSPEND_NONE; +} +EXPORT_SYMBOL(snd_sof_complete); diff --git a/sound/soc/sof/probe.c b/sound/soc/sof/probe.c new file mode 100644 index 000000000..14509f4d3 --- /dev/null +++ b/sound/soc/sof/probe.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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-2020 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include "sof-priv.h" +#include "probe.h" + +/** + * sof_ipc_probe_init - initialize data probing + * @sdev: SOF sound device + * @stream_tag: Extractor stream tag + * @buffer_size: DMA buffer size to set for extractor + * + * Host chooses whether extraction is supported or not by providing + * valid stream tag to DSP. Once specified, stream described by that + * tag will be tied to DSP for extraction for the entire lifetime of + * probe. + * + * Probing is initialized only once and each INIT request must be + * matched by DEINIT call. + */ +int sof_ipc_probe_init(struct snd_sof_dev *sdev, + u32 stream_tag, size_t buffer_size) +{ + struct sof_ipc_probe_dma_add_params *msg; + struct sof_ipc_reply reply; + size_t size = struct_size(msg, dma, 1); + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; + msg->num_elems = 1; + msg->dma[0].stream_tag = stream_tag; + msg->dma[0].dma_buffer_size = buffer_size; + + ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); + kfree(msg); + return ret; +} +EXPORT_SYMBOL(sof_ipc_probe_init); + +/** + * sof_ipc_probe_deinit - cleanup after data probing + * @sdev: SOF sound device + * + * Host sends DEINIT request to free previously initialized probe + * on DSP side once it is no longer needed. DEINIT only when there + * are no probes connected and with all injectors detached. + */ +int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) +{ + struct sof_ipc_cmd_hdr msg; + struct sof_ipc_reply reply; + + msg.size = sizeof(msg); + msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; + + return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, + &reply, sizeof(reply)); +} +EXPORT_SYMBOL(sof_ipc_probe_deinit); + +static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, + void **params, size_t *num_params) +{ + struct sof_ipc_probe_info_params msg = {{{0}}}; + struct sof_ipc_probe_info_params *reply; + size_t bytes; + int ret; + + *params = NULL; + *num_params = 0; + + reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!reply) + return -ENOMEM; + msg.rhdr.hdr.size = sizeof(msg); + msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; + + ret = sof_ipc_tx_message(sdev->ipc, msg.rhdr.hdr.cmd, &msg, + msg.rhdr.hdr.size, reply, SOF_IPC_MSG_MAX_SIZE); + if (ret < 0 || reply->rhdr.error < 0) + goto exit; + + if (!reply->num_elems) + goto exit; + + if (cmd == SOF_IPC_PROBE_DMA_INFO) + bytes = sizeof(reply->dma[0]); + else + bytes = sizeof(reply->desc[0]); + bytes *= reply->num_elems; + *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); + if (!*params) { + ret = -ENOMEM; + goto exit; + } + *num_params = reply->num_elems; + +exit: + kfree(reply); + return ret; +} + +/** + * sof_ipc_probe_dma_info - retrieve list of active injection dmas + * @sdev: SOF sound device + * @dma: Returned list of active dmas + * @num_dma: Returned count of active dmas + * + * Host sends DMA_INFO request to obtain list of injection dmas it + * can use to transfer data over with. + * + * Note that list contains only injection dmas as there is only one + * extractor (dma) and it is always assigned on probing init. + * DSP knows exactly where data from extraction probes is going to, + * which is not the case for injection where multiple streams + * could be engaged. + */ +int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, + struct sof_probe_dma **dma, size_t *num_dma) +{ + return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_DMA_INFO, + (void **)dma, num_dma); +} +EXPORT_SYMBOL(sof_ipc_probe_dma_info); + +/** + * sof_ipc_probe_dma_add - attach to specified dmas + * @sdev: SOF sound device + * @dma: List of streams (dmas) to attach to + * @num_dma: Number of elements in @dma + * + * Contrary to extraction, injection streams are never assigned + * on init. Before attempting any data injection, host is responsible + * for specifying streams which will be later used to transfer data + * to connected probe points. + */ +int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, + struct sof_probe_dma *dma, size_t num_dma) +{ + struct sof_ipc_probe_dma_add_params *msg; + struct sof_ipc_reply reply; + size_t size = struct_size(msg, dma, num_dma); + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_dma; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_ADD; + memcpy(&msg->dma[0], dma, size - sizeof(*msg)); + + ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); + kfree(msg); + return ret; +} +EXPORT_SYMBOL(sof_ipc_probe_dma_add); + +/** + * sof_ipc_probe_dma_remove - detach from specified dmas + * @sdev: SOF sound device + * @stream_tag: List of stream tags to detach from + * @num_stream_tag: Number of elements in @stream_tag + * + * Host sends DMA_REMOVE request to free previously attached stream + * from being occupied for injection. Each detach operation should + * match equivalent DMA_ADD. Detach only when all probes tied to + * given stream have been disconnected. + */ +int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, + unsigned int *stream_tag, size_t num_stream_tag) +{ + struct sof_ipc_probe_dma_remove_params *msg; + struct sof_ipc_reply reply; + size_t size = struct_size(msg, stream_tag, num_stream_tag); + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_stream_tag; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_REMOVE; + memcpy(&msg->stream_tag[0], stream_tag, size - sizeof(*msg)); + + ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); + kfree(msg); + return ret; +} +EXPORT_SYMBOL(sof_ipc_probe_dma_remove); + +/** + * sof_ipc_probe_points_info - retrieve list of active probe points + * @sdev: SOF sound device + * @desc: Returned list of active probes + * @num_desc: Returned count of active probes + * + * Host sends PROBE_POINT_INFO request to obtain list of active probe + * points, valid for disconnection when given probe is no longer + * required. + */ +int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, + struct sof_probe_point_desc **desc, size_t *num_desc) +{ + return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO, + (void **)desc, num_desc); +} +EXPORT_SYMBOL(sof_ipc_probe_points_info); + +/** + * sof_ipc_probe_points_add - connect specified probes + * @sdev: SOF sound device + * @desc: List of probe points to connect + * @num_desc: Number of elements in @desc + * + * Dynamically connects to provided set of endpoints. Immediately + * after connection is established, host must be prepared to + * transfer data from or to target stream given the probing purpose. + * + * Each probe point should be removed using PROBE_POINT_REMOVE + * request when no longer needed. + */ +int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, + struct sof_probe_point_desc *desc, size_t num_desc) +{ + struct sof_ipc_probe_point_add_params *msg; + struct sof_ipc_reply reply; + size_t size = struct_size(msg, desc, num_desc); + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_desc; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; + memcpy(&msg->desc[0], desc, size - sizeof(*msg)); + + ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); + kfree(msg); + return ret; +} +EXPORT_SYMBOL(sof_ipc_probe_points_add); + +/** + * sof_ipc_probe_points_remove - disconnect specified probes + * @sdev: SOF sound device + * @buffer_id: List of probe points to disconnect + * @num_buffer_id: Number of elements in @desc + * + * Removes previously connected probes from list of active probe + * points and frees all resources on DSP side. + */ +int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, + unsigned int *buffer_id, size_t num_buffer_id) +{ + struct sof_ipc_probe_point_remove_params *msg; + struct sof_ipc_reply reply; + size_t size = struct_size(msg, buffer_id, num_buffer_id); + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_buffer_id; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; + memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); + + ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); + kfree(msg); + return ret; +} +EXPORT_SYMBOL(sof_ipc_probe_points_remove); diff --git a/sound/soc/sof/probe.h b/sound/soc/sof/probe.h new file mode 100644 index 000000000..5e159ab23 --- /dev/null +++ b/sound/soc/sof/probe.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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-2020 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOF_PROBE_H +#define __SOF_PROBE_H + +#include <sound/sof/header.h> + +struct snd_sof_dev; + +#define SOF_PROBE_INVALID_NODE_ID UINT_MAX + +struct sof_probe_dma { + unsigned int stream_tag; + unsigned int dma_buffer_size; +} __packed; + +enum sof_connection_purpose { + SOF_CONNECTION_PURPOSE_EXTRACT = 1, + SOF_CONNECTION_PURPOSE_INJECT, +}; + +struct sof_probe_point_desc { + unsigned int buffer_id; + unsigned int purpose; + unsigned int stream_tag; +} __packed; + +struct sof_ipc_probe_dma_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_dma dma[]; +} __packed; + +struct sof_ipc_probe_info_params { + struct sof_ipc_reply rhdr; + unsigned int num_elems; + union { + struct sof_probe_dma dma[0]; + struct sof_probe_point_desc desc[0]; + }; +} __packed; + +struct sof_ipc_probe_dma_remove_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + unsigned int stream_tag[]; +} __packed; + +struct sof_ipc_probe_point_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_point_desc desc[]; +} __packed; + +struct sof_ipc_probe_point_remove_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + unsigned int buffer_id[]; +} __packed; + +int sof_ipc_probe_init(struct snd_sof_dev *sdev, + u32 stream_tag, size_t buffer_size); +int sof_ipc_probe_deinit(struct snd_sof_dev *sdev); +int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, + struct sof_probe_dma **dma, size_t *num_dma); +int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, + struct sof_probe_dma *dma, size_t num_dma); +int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, + unsigned int *stream_tag, size_t num_stream_tag); +int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, + struct sof_probe_point_desc **desc, size_t *num_desc); +int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, + struct sof_probe_point_desc *desc, size_t num_desc); +int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, + unsigned int *buffer_id, size_t num_buffer_id); + +#endif diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c new file mode 100644 index 000000000..a78b76ef3 --- /dev/null +++ b/sound/soc/sof/sof-acpi-dev.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/acpi.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/sof.h> +#include "../intel/common/soc-intel-quirks.h" +#include "ops.h" + +/* platform specific devices */ +#include "intel/shim.h" + +static char *fw_path; +module_param(fw_path, charp, 0444); +MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); + +static char *tplg_path; +module_param(tplg_path, charp, 0444); +MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); + +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_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) +static const struct sof_dev_desc sof_acpi_broadwell_desc = { + .machines = snd_soc_acpi_intel_broadwell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = -1, + .irqindex_host_ipc = 0, + .chip_info = &bdw_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-bdw.ri", + .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", + .ops = &sof_bdw_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + +/* BYTCR uses different IRQ index */ +static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { + .machines = snd_soc_acpi_intel_baytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 0, + .chip_info = &byt_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt-nocodec.tplg", + .ops = &sof_byt_ops, +}; + +static const struct sof_dev_desc sof_acpi_baytrail_desc = { + .machines = snd_soc_acpi_intel_baytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 5, + .chip_info = &byt_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt-nocodec.tplg", + .ops = &sof_byt_ops, +}; + +static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { + .machines = snd_soc_acpi_intel_cherrytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 5, + .chip_info = &cht_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cht.ri", + .nocodec_tplg_filename = "sof-cht-nocodec.tplg", + .ops = &sof_cht_ops, +}; + +#endif + +static const struct dev_pm_ops sof_acpi_pm = { + 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) +}; + +static void sof_acpi_probe_complete(struct device *dev) +{ + dev_dbg(dev, "Completing SOF ACPI probe"); + + 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); + pm_runtime_enable(dev); +} + +static int sof_acpi_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_dbg(&pdev->dev, "ACPI 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; + +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev)) + desc = &sof_acpi_baytrailcr_desc; +#endif + + /* get ops for platform */ + ops = desc->ops; + if (!ops) { + dev_err(dev, "error: no matching ACPI descriptor ops\n"); + return -ENODEV; + } + + sof_pdata->desc = desc; + sof_pdata->dev = &pdev->dev; + sof_pdata->fw_filename = desc->default_fw_filename; + + /* alternate fw and tplg filenames ? */ + if (fw_path) + sof_pdata->fw_filename_prefix = fw_path; + else + sof_pdata->fw_filename_prefix = + sof_pdata->desc->default_fw_path; + + if (tplg_path) + sof_pdata->tplg_filename_prefix = tplg_path; + else + 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_acpi_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_acpi_probe_complete(dev); +#endif + + return ret; +} + +static int sof_acpi_remove(struct platform_device *pdev) +{ + 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); + + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id sof_acpi_match[] = { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) + { "INT3438", (unsigned long)&sof_acpi_broadwell_desc }, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + { "80860F28", (unsigned long)&sof_acpi_baytrail_desc }, + { "808622A8", (unsigned long)&sof_acpi_cherrytrail_desc }, +#endif + { } +}; +MODULE_DEVICE_TABLE(acpi, sof_acpi_match); +#endif + +/* acpi_driver definition */ +static struct platform_driver snd_sof_acpi_driver = { + .probe = sof_acpi_probe, + .remove = sof_acpi_remove, + .driver = { + .name = "sof-audio-acpi", + .pm = &sof_acpi_pm, + .acpi_match_table = ACPI_PTR(sof_acpi_match), + }, +}; +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 000000000..afe7e503b --- /dev/null +++ b/sound/soc/sof/sof-audio.c @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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" + +/* + * helper to determine if there are only D0i3 compatible + * streams active + */ +bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev) +{ + struct snd_pcm_substream *substream; + struct snd_sof_pcm *spcm; + bool d0i3_compatible_active = false; + int dir; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for_each_pcm_streams(dir) { + substream = spcm->stream[dir].substream; + if (!substream || !substream->runtime) + continue; + + /* + * substream->runtime being not NULL indicates + * that the stream is open. No need to check the + * stream state. + */ + if (!spcm->stream[dir].d0i3_compatible) + return false; + + d0i3_compatible_active = true; + } + } + + return d0i3_compatible_active; +} +EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active); + +bool snd_sof_stream_suspend_ignored(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_each_pcm_streams(dir) { + /* + * do not reset hw_params upon resume for streams that + * were kept running during suspend + */ + if (spcm->stream[dir].suspend_ignored) + continue; + + 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; +} + +const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, + int pipeline_id) +{ + const struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) + if (swidget->id == snd_soc_dapm_scheduler) { + const struct sof_ipc_pipe_new *pipeline = + swidget->private; + if (pipeline->pipeline_id == pipeline_id) + return pipeline; + } + + return NULL; +} + +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_cmd_hdr *hdr; + struct sof_ipc_comp *comp; + size_t ipc_size; + 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; + + ret = sof_pipeline_core_enable(sdev, swidget); + if (ret < 0) { + dev_err(dev, + "error: failed to enable target core: %d\n", + ret); + + return ret; + } + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + ipc_size = sizeof(struct sof_ipc_comp_dai) + + sizeof(struct sof_ipc_comp_ext); + comp = kzalloc(ipc_size, GFP_KERNEL); + if (!comp) + return -ENOMEM; + + dai = swidget->private; + memcpy(comp, &dai->comp_dai, + sizeof(struct sof_ipc_comp_dai)); + + /* append extended data to the end of the component */ + memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai), + &swidget->comp_ext, sizeof(swidget->comp_ext)); + + ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, + comp, ipc_size, + &r, sizeof(r)); + kfree(comp); + 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) { + for_each_pcm_streams(dir) { + 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 = pdata; + const char *drv_name; + const void *mach; + int size; + + drv_name = plat_data->machine->drv_name; + mach = 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 = 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 000000000..9f645a2e5 --- /dev/null +++ b/sound/soc/sof/sof-audio.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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 <linux/workqueue.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; + 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 core; + int id; + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ + + /* extended data for UUID components */ + struct sof_ipc_comp_ext comp_ext; + + 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); +int snd_sof_bytes_ext_volatile_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); +int sof_pipeline_core_enable(struct snd_sof_dev *sdev, + const struct snd_sof_widget *swidget); + +/* + * 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); +const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, + int pipeline_id); +void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); +void snd_sof_pcm_period_elapsed_work(struct work_struct *work); + +/* + * 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_stream_suspend_ignored(struct snd_sof_dev *sdev); +bool snd_sof_dsp_only_d0i3_compatible_stream_active(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 000000000..85ff0db88 --- /dev/null +++ b/sound/soc/sof/sof-of-dev.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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; +extern struct snd_sof_dsp_ops sof_imx8x_ops; +extern struct snd_sof_dsp_ops sof_imx8m_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-imx8x.ri", + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8x_ops, +}; + +static struct sof_dev_desc sof_of_imx8qm_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 + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) +static struct sof_dev_desc sof_of_imx8mp_desc = { + .default_fw_path = "imx/sof", + .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8m.ri", + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8m_ops, +}; +#endif + +static const struct dev_pm_ops sof_of_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, + 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_set_active(dev); + pm_runtime_enable(dev); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(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}, + { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) + { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_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 new file mode 100644 index 000000000..ade292a61 --- /dev/null +++ b/sound/soc/sof/sof-pci-dev.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_data/x86/soc.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> +#include "ops.h" + +/* platform specific devices */ +#include "intel/shim.h" +#include "intel/hda.h" + +static char *fw_path; +module_param(fw_path, charp, 0444); +MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); + +static char *tplg_path; +module_param(tplg_path, charp, 0444); +MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); + +static char *tplg_filename; +module_param(tplg_filename, charp, 0444); +MODULE_PARM_DESC(tplg_filename, "alternate filename 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)"); + +static const char *sof_dmi_override_tplg_name; +static bool sof_dmi_use_community_key; + +#define SOF_PCI_DISABLE_PM_RUNTIME BIT(0) + +static int sof_tplg_cb(const struct dmi_system_id *id) +{ + sof_dmi_override_tplg_name = id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_tplg_table[] = { + { + .callback = sof_tplg_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Volteer"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98373_ALC5682I_I2S_UP4"), + }, + .driver_data = "sof-tgl-rt5682-ssp0-max98373-ssp2.tplg", + }, + {} +}; + +/* all Up boards use the community key */ +static int up_use_community_key(const struct dmi_system_id *id) +{ + sof_dmi_use_community_key = true; + return 1; +} + +/* + * For ApolloLake Chromebooks we want to force the use of the Intel production key. + * All newer platforms use the community key + */ +static int chromebook_use_community_key(const struct dmi_system_id *id) +{ + if (!soc_intel_is_apl()) + sof_dmi_use_community_key = true; + return 1; +} + +static const struct dmi_system_id community_key_platforms[] = { + { + .ident = "Up boards", + .callback = up_use_community_key, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + } + }, + { + .ident = "Google Chromebooks", + .callback = chromebook_use_community_key, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google"), + } + }, + { + .ident = "Google firmware", + .callback = chromebook_use_community_key, + .matches = { + DMI_MATCH(DMI_BIOS_VERSION, "Google"), + } + }, + {}, +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) +static const struct sof_dev_desc bxt_desc = { + .machines = snd_soc_acpi_intel_bxt_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &apl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-apl.ri", + .nocodec_tplg_filename = "sof-apl-nocodec.tplg", + .ops = &sof_apl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) +static const struct sof_dev_desc glk_desc = { + .machines = snd_soc_acpi_intel_glk_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &apl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-glk.ri", + .nocodec_tplg_filename = "sof-glk-nocodec.tplg", + .ops = &sof_apl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) +static struct snd_soc_acpi_mach sof_tng_machines[] = { + { + .id = "INT343A", + .drv_name = "edison", + .sof_fw_filename = "sof-byt.ri", + .sof_tplg_filename = "sof-byt.tplg", + }, + {} +}; + +static const struct sof_dev_desc tng_desc = { + .machines = sof_tng_machines, + .resindex_lpe_base = 3, /* IRAM, but subtract IRAM offset */ + .resindex_pcicfg_base = -1, + .resindex_imr_base = 0, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &tng_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt.tplg", + .ops = &sof_tng_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, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cnl.ri", + .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", + .ops = &sof_cnl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) +static const struct sof_dev_desc cfl_desc = { + .machines = snd_soc_acpi_intel_cfl_machines, + .alt_machines = snd_soc_acpi_intel_cfl_sdw_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cfl.ri", + .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", + .ops = &sof_cnl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE) +static const struct sof_dev_desc cml_desc = { + .machines = snd_soc_acpi_intel_cml_machines, + .alt_machines = snd_soc_acpi_intel_cml_sdw_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cml.ri", + .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", + .ops = &sof_cnl_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, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &icl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-icl.ri", + .nocodec_tplg_filename = "sof-icl-nocodec.tplg", + .ops = &sof_cnl_ops, +}; +#endif + +#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, + .use_acpi_target_states = true, + .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", + .default_fw_filename = "sof-tgl.ri", + .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", + .ops = &sof_tgl_ops, +}; + +static const struct sof_dev_desc tglh_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 = &tglh_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-tgl-h.ri", + .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", + .ops = &sof_tgl_ops, +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE) +static const struct sof_dev_desc ehl_desc = { + .machines = snd_soc_acpi_intel_ehl_machines, + .use_acpi_target_states = true, + .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, + .use_acpi_target_states = true, + .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", + .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) +}; + +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); + + /* + * runtime pm for pci device is "forbidden" by default. + * so call pm_runtime_allow() to enable it. + */ + 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); +} + +static int sof_pci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct device *dev = &pci->dev; + const struct sof_dev_desc *desc = + (const struct sof_dev_desc *)pci_id->driver_data; + 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) { + dev_dbg(&pci->dev, "SOF PCI driver not selected, aborting probe\n"); + return -ENODEV; + } + dev_dbg(&pci->dev, "PCI DSP detected"); + + /* get ops for platform */ + ops = desc->ops; + if (!ops) { + dev_err(dev, "error: no matching PCI descriptor ops\n"); + return -ENODEV; + } + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + ret = pcim_enable_device(pci); + if (ret < 0) + return ret; + + ret = pci_request_regions(pci, "Audio DSP"); + if (ret < 0) + return ret; + + sof_pdata->name = pci_name(pci); + sof_pdata->desc = (struct sof_dev_desc *)pci_id->driver_data; + sof_pdata->dev = 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) { + sof_pdata->fw_filename_prefix = fw_path; + + 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_dmi_use_community_key) { + 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; + else + sof_pdata->tplg_filename_prefix = + sof_pdata->desc->default_tplg_path; + + /* + * the topology filename will be provided in the machine descriptor, unless + * it is overridden by a module parameter or DMI quirk. + */ + if (tplg_filename) { + sof_pdata->tplg_filename = tplg_filename; + + dev_dbg(dev, "Module parameter used, changed tplg filename to %s\n", + sof_pdata->tplg_filename); + } else { + dmi_check_system(sof_tplg_table); + if (sof_dmi_override_tplg_name) + sof_pdata->tplg_filename = sof_dmi_override_tplg_name; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + /* set callback to enable runtime_pm */ + sof_pdata->sof_probe_complete = sof_pci_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"); + goto release_regions; + } + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + sof_pci_probe_complete(dev); +#endif + + return ret; + +release_regions: + pci_release_regions(pci); + + return ret; +} + +static void sof_pci_remove(struct pci_dev *pci) +{ + /* call sof helper for DSP hardware remove */ + snd_sof_device_remove(&pci->dev); + + /* follow recommendation in pci-driver.c to increment usage counter */ + 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); +} + +/* PCI IDs */ +static const struct pci_device_id sof_pci_ids[] = { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) + { PCI_DEVICE(0x8086, 0x119a), + .driver_data = (unsigned long)&tng_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) + /* BXT-P & Apollolake */ + { PCI_DEVICE(0x8086, 0x5a98), + .driver_data = (unsigned long)&bxt_desc}, + { PCI_DEVICE(0x8086, 0x1a98), + .driver_data = (unsigned long)&bxt_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) + { PCI_DEVICE(0x8086, 0x3198), + .driver_data = (unsigned long)&glk_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE) + { PCI_DEVICE(0x8086, 0x9dc8), + .driver_data = (unsigned long)&cnl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) + { PCI_DEVICE(0x8086, 0xa348), + .driver_data = (unsigned long)&cfl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) + { PCI_DEVICE(0x8086, 0x34C8), /* ICL-LP */ + .driver_data = (unsigned long)&icl_desc}, + { PCI_DEVICE(0x8086, 0x3dc8), /* ICL-H */ + .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) + { PCI_DEVICE(0x8086, 0x02c8), /* CML-LP */ + .driver_data = (unsigned long)&cml_desc}, + { PCI_DEVICE(0x8086, 0x06c8), /* CML-H */ + .driver_data = (unsigned long)&cml_desc}, + { PCI_DEVICE(0x8086, 0xa3f0), /* CML-S */ + .driver_data = (unsigned long)&cml_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) + { PCI_DEVICE(0x8086, 0xa0c8), /* TGL-LP */ + .driver_data = (unsigned long)&tgl_desc}, + { PCI_DEVICE(0x8086, 0x43c8), /* TGL-H */ + .driver_data = (unsigned long)&tglh_desc}, + +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE) + { PCI_DEVICE(0x8086, 0x4b55), + .driver_data = (unsigned long)&ehl_desc}, + { PCI_DEVICE(0x8086, 0x4b58), + .driver_data = (unsigned long)&ehl_desc}, +#endif + { 0, } +}; +MODULE_DEVICE_TABLE(pci, sof_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_driver = { + .name = "sof-audio-pci", + .id_table = sof_pci_ids, + .probe = sof_pci_probe, + .remove = sof_pci_remove, + .driver = { + .pm = &sof_pci_pm, + }, +}; +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 new file mode 100644 index 000000000..0aed2a7ab --- /dev/null +++ b/sound/soc/sof/sof-priv.h @@ -0,0 +1,589 @@ +/* SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOUND_SOC_SOF_PRIV_H +#define __SOUND_SOC_SOF_PRIV_H + +#include <linux/device.h> +#include <sound/hdaudio.h> +#include <sound/sof.h> +#include <sound/sof/info.h> +#include <sound/sof/pm.h> +#include <sound/sof/trace.h> +#include <uapi/sound/sof/fw.h> + +/* debug flags */ +#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 + +/* time in ms for runtime suspend delay */ +#define SND_SOF_SUSPEND_DELAY_MS 2000 + +/* DMA buffer size for trace */ +#define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16) + +#define SOF_IPC_DSP_REPLY 0 +#define SOF_IPC_HOST_REPLY 1 + +/* convenience constructor for DAI driver streams */ +#define SOF_DAI_STREAM(sname, scmin, scmax, srates, sfmt) \ + {.stream_name = sname, .channels_min = scmin, .channels_max = scmax, \ + .rates = srates, .formats = sfmt} + +#define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) + +#define ENABLE_DEBUGFS_CACHEBUF \ + (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ + IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) + +/* So far the primary core on all DSPs has ID 0 */ +#define SOF_DSP_PRIMARY_CORE 0 + +/* DSP power state */ +enum sof_dsp_power_states { + SOF_DSP_PM_D0, + SOF_DSP_PM_D1, + SOF_DSP_PM_D2, + SOF_DSP_PM_D3_HOT, + SOF_DSP_PM_D3, + SOF_DSP_PM_D3_COLD, +}; + +struct sof_dsp_power_state { + u32 state; + u32 substate; /* platform-specific */ +}; + +/* System suspend target state */ +enum sof_system_suspend_state { + SOF_SUSPEND_NONE = 0, + SOF_SUSPEND_S0IX, + SOF_SUSPEND_S3, +}; + +struct snd_sof_dev; +struct snd_sof_ipc_msg; +struct snd_sof_ipc; +struct snd_sof_debugfs_map; +struct snd_soc_tplg_ops; +struct snd_soc_component; +struct snd_sof_pdata; + +/* + * SOF DSP HW abstraction operations. + * Used to abstract DSP HW architecture and any IO busses between host CPU + * and DSP device(s). + */ +struct snd_sof_dsp_ops { + + /* probe and remove */ + int (*probe)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*remove)(struct snd_sof_dev *sof_dev); /* optional */ + + /* DSP core boot / reset */ + int (*run)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*stall)(struct snd_sof_dev *sof_dev); /* optional */ + int (*reset)(struct snd_sof_dev *sof_dev); /* optional */ + int (*core_power_up)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); /* optional */ + int (*core_power_down)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); /* optional */ + + /* + * Register IO: only used by respective drivers themselves, + * TODO: consider removing these operations and calling respective + * implementations directly + */ + void (*write)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u32 value); /* optional */ + u32 (*read)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* optional */ + void (*write64)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u64 value); /* optional */ + u64 (*read64)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* optional */ + + /* memcpy IO */ + void (*block_read)(struct snd_sof_dev *sof_dev, u32 bar, + u32 offset, void *dest, + size_t size); /* mandatory */ + void (*block_write)(struct snd_sof_dev *sof_dev, u32 bar, + u32 offset, void *src, + size_t size); /* mandatory */ + + /* doorbell */ + irqreturn_t (*irq_handler)(int irq, void *context); /* optional */ + irqreturn_t (*irq_thread)(int irq, void *context); /* optional */ + + /* ipc */ + int (*send_msg)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); /* mandatory */ + + /* FW loading */ + int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); /* optional */ + /* + * FW ready checks for ABI compatibility and creates + * memory windows at first boot + */ + 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, + struct snd_pcm_substream *substream); /* optional */ + /* disconnect pcm substream to a host stream */ + int (*pcm_close)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + + /* host stream hw params */ + int (*pcm_hw_params)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params); /* optional */ + + /* host stream hw_free */ + int (*pcm_hw_free)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + + /* host stream trigger */ + int (*pcm_trigger)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + int cmd); /* optional */ + + /* host stream pointer */ + snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) + /* Except for probe_pointer, all probe ops are mandatory */ + int (*probe_assign)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); /* mandatory */ + int (*probe_free)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); /* mandatory */ + int (*probe_set_params)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); /* mandatory */ + int (*probe_trigger)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai); /* mandatory */ + int (*probe_pointer)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); /* optional */ +#endif + + /* host read DSP stream data */ + void (*ipc_msg_data)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); /* mandatory */ + + /* host configure DSP HW parameters */ + int (*ipc_pcm_params)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); /* mandatory */ + + /* pre/post firmware run */ + int (*pre_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ + int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ + + /* DSP PM */ + int (*suspend)(struct snd_sof_dev *sof_dev, + u32 target_state); /* optional */ + int (*resume)(struct snd_sof_dev *sof_dev); /* 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, + const struct sof_dsp_power_state *target_state); /* optional */ + + /* DSP clocking */ + int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); /* optional */ + + /* debug */ + const struct snd_sof_debugfs_map *debug_map; /* optional */ + int debug_map_count; /* optional */ + void (*dbg_dump)(struct snd_sof_dev *sof_dev, + u32 flags); /* optional */ + void (*ipc_dump)(struct snd_sof_dev *sof_dev); /* optional */ + + /* host DMA trace initialization */ + int (*trace_init)(struct snd_sof_dev *sdev, + u32 *stream_tag); /* optional */ + int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ + 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 */ +struct sof_arch_ops { + void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops); + void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops, + u32 *stack, u32 stack_words); +}; + +#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 { + const struct sof_dev_desc *desc; + const struct snd_sof_dsp_ops *ops; +}; + +enum sof_dfsentry_type { + SOF_DFSENTRY_TYPE_IOMEM = 0, + SOF_DFSENTRY_TYPE_BUF, +}; + +enum sof_debugfs_access_type { + SOF_DEBUGFS_ACCESS_ALWAYS = 0, + SOF_DEBUGFS_ACCESS_D0_ONLY, +}; + +/* FS entry for debug files that can expose DSP memories, registers */ +struct snd_sof_dfsentry { + size_t size; + enum sof_dfsentry_type type; + /* + * access_type specifies if the + * memory -> DSP resource (memory, register etc) is always accessible + * or if it is accessible only when the DSP is in D0. + */ + enum sof_debugfs_access_type access_type; +#if ENABLE_DEBUGFS_CACHEBUF + char *cache_buf; /* buffer to cache the contents of debugfs memory */ +#endif + struct snd_sof_dev *sdev; + struct list_head list; /* list in sdev dfsentry list */ + union { + void __iomem *io_mem; + void *buf; + }; +}; + +/* Debug mapping for any DSP memory or registers that can used for debug */ +struct snd_sof_debugfs_map { + const char *name; + u32 bar; + u32 offset; + u32 size; + /* + * access_type specifies if the memory is always accessible + * or if it is accessible only when the DSP is in D0. + */ + enum sof_debugfs_access_type access_type; +}; + +/* mailbox descriptor, used for host <-> DSP IPC */ +struct snd_sof_mailbox { + u32 offset; + size_t size; +}; + +/* IPC message descriptor for host <-> DSP IO */ +struct snd_sof_ipc_msg { + /* message data */ + u32 header; + void *msg_data; + void *reply_data; + size_t msg_size; + size_t reply_size; + int reply_error; + + wait_queue_head_t waitq; + bool ipc_complete; +}; + +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, +}; + +/* + * SOF Device Level. + */ +struct snd_sof_dev { + struct device *dev; + spinlock_t ipc_lock; /* lock for IPC users */ + spinlock_t hw_lock; /* lock for HW IO access */ + + /* + * ASoC components. plat_drv fields are set dynamically so + * can't use const + */ + struct snd_soc_component_driver plat_drv; + + /* current DSP power state */ + struct sof_dsp_power_state dsp_power_state; + + /* Intended power target of system suspend */ + enum sof_system_suspend_state system_suspend_target; + + /* DSP firmware boot */ + wait_queue_head_t boot_wait; + enum snd_sof_fw_state fw_state; + bool first_boot; + + /* work queue in case the probe is implemented in two steps */ + struct work_struct probe_work; + + /* DSP HW differentiation */ + struct snd_sof_pdata *pdata; + + /* IPC */ + struct snd_sof_ipc *ipc; + struct snd_sof_mailbox dsp_box; /* DSP initiated IPC */ + struct snd_sof_mailbox host_box; /* Host initiated IPC */ + struct snd_sof_mailbox stream_box; /* Stream position update */ + struct snd_sof_mailbox debug_box; /* Debug info updates */ + struct snd_sof_ipc_msg *msg; + int ipc_irq; + u32 next_comp_id; /* monotonic - reset during S3 */ + + /* memory bases for mmaped DSPs - set by dsp_init() */ + void __iomem *bar[SND_SOF_BARS]; /* DSP base address */ + int mmio_bar; + int mailbox_bar; + size_t dsp_oops_offset; + + /* debug */ + struct dentry *debugfs_root; + struct list_head dfsentry_list; + + /* firmware loader */ + struct snd_dma_buffer dmab; + 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; + struct list_head pcm_list; + struct list_head kcontrol_list; + struct list_head widget_list; + struct list_head dai_list; + struct list_head route_list; + struct snd_soc_component *component; + u32 enabled_cores_mask; /* keep track of enabled cores */ + + /* FW configuration */ + struct sof_ipc_window *info_window; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) + unsigned int extractor_stream_tag; +#endif + + /* DMA for Trace */ + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + int dma_trace_pages; + wait_queue_head_t trace_sleep; + u32 host_offset; + bool dtrace_is_supported; /* set with Kconfig or module parameter */ + bool dtrace_is_enabled; + bool dtrace_error; + bool dtrace_draining; + + bool msi_enabled; + + void *private; /* core does not touch this */ +}; + +/* + * Device Level. + */ + +int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data); +int snd_sof_device_remove(struct device *dev); + +int snd_sof_runtime_suspend(struct device *dev); +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_dsp_power_down_notify(struct snd_sof_dev *sdev); +int snd_sof_prepare(struct device *dev); +void snd_sof_complete(struct device *dev); + +void snd_sof_new_platform_drv(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); + +/* + * Firmware loading. + */ +int snd_sof_load_firmware(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); +int snd_sof_run_firmware(struct snd_sof_dev *sdev); +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module); +void snd_sof_fw_unload(struct snd_sof_dev *sdev); +int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset); + +/* + * IPC low level APIs. + */ +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev); +void snd_sof_ipc_free(struct snd_sof_dev *sdev); +void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); +int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev, + struct sof_ipc_pcm_params *params); +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size); +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); +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes); + +/* + * Trace/debug + */ +int snd_sof_init_trace(struct snd_sof_dev *sdev); +void snd_sof_release_trace(struct snd_sof_dev *sdev); +void snd_sof_free_trace(struct snd_sof_dev *sdev); +int snd_sof_dbg_init(struct snd_sof_dev *sdev); +void snd_sof_free_debug(struct snd_sof_dev *sdev); +int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name, + enum sof_debugfs_access_type access_type); +int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name, mode_t mode); +int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); +void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); +void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + 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. + */ +extern struct snd_compress_ops sof_compressed_ops; + +/* + * DSP Architectures. + */ +static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); +} + +static inline void sof_oops(struct snd_sof_dev *sdev, void *oops) +{ + if (sof_arch_ops(sdev)->dsp_oops) + sof_arch_ops(sdev)->dsp_oops(sdev, oops); +} + +extern const struct sof_arch_ops sof_xtensa_arch_ops; + +/* + * Utilities + */ +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value); +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value); +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr); +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr); +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, + size_t size); +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); +int intel_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); + +int intel_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int intel_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + +int sof_machine_check(struct snd_sof_dev *sdev); + +#define sof_dev_dbg_or_err(dev, is_err, fmt, ...) \ + do { \ + if (is_err) \ + dev_err(dev, "error: " fmt, __VA_ARGS__); \ + else \ + dev_dbg(dev, fmt, __VA_ARGS__); \ + } while (0) + +#endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c new file mode 100644 index 000000000..b6327c30c --- /dev/null +++ b/sound/soc/sof/topology.c @@ -0,0 +1,3758 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <sound/tlv.h> +#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 +/* + * Constants used in the computation of linear volume gain + * from dB gain 20th root of 10 in Q1.16 fixed-point notation + */ +#define VOL_TWENTIETH_ROOT_OF_TEN 73533 +/* 40th root of 10 in Q1.16 fixed-point notation*/ +#define VOL_FORTIETH_ROOT_OF_TEN 69419 +/* + * Volume fractional word length define to 16 sets + * the volume linear gain value to use Qx.16 format + */ +#define VOLUME_FWL 16 +/* 0.5 dB step value in topology TLV */ +#define VOL_HALF_DB_STEP 50 +/* Full volume for default values */ +#define VOL_ZERO_DB BIT(VOLUME_FWL) + +/* TLV data items */ +#define TLV_ITEMS 3 +#define TLV_MIN 0 +#define TLV_STEP 1 +#define TLV_MUTE 2 + +/* 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_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; + int ret; + + memset(&pcm, 0, sizeof(pcm)); + + /* 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; + } + + params = &spcm->params[dir]; + + /* set IPC PCM params */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = swidget->comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.direction = dir; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* set format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + default: + return -EINVAL; + } + + /* 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(scomp->dev, "error: pcm params failed for %s\n", + swidget->widget->name); + + return ret; +} + + /* send stream trigger ipc */ +static int ipc_trigger(struct snd_sof_widget *swidget, int cmd) +{ + 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; + + /* set IPC stream params */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; + stream.comp_id = swidget->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 < 0) + dev_err(scomp->dev, "error: failed to trigger %s\n", + swidget->widget->name); + + return ret; +} + +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_soc_component *scomp; + int stream = SNDRV_PCM_STREAM_CAPTURE; + struct snd_sof_pcm *spcm; + int ret = 0; + + if (!swidget) + return 0; + + scomp = swidget->scomp; + + 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, stream); + if (ret < 0) { + dev_err(scomp->dev, + "error: failed to set pcm params for widget %s\n", + swidget->widget->name); + break; + } + + /* start trigger */ + ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START); + if (ret < 0) + 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(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(scomp->dev, + "error: failed to trigger widget %s\n", + swidget->widget->name); + break; + default: + break; + } + + return ret; +} + +/* event handlers for keyword detect component */ +static const struct snd_soc_tplg_widget_events sof_kwd_events[] = { + {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_keyword_dapm_event}, +}; + +static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS]) +{ + /* we only support dB scale TLV type at the moment */ + if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) + return -EINVAL; + + /* min value in topology tlv data is multiplied by 100 */ + tlv[TLV_MIN] = (int)p[SNDRV_CTL_TLVO_DB_SCALE_MIN] / 100; + + /* volume steps */ + tlv[TLV_STEP] = (int)(p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MASK); + + /* mute ON/OFF */ + if ((p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MUTE) == 0) + tlv[TLV_MUTE] = 0; + else + tlv[TLV_MUTE] = 1; + + return 0; +} + +/* + * Function to truncate an unsigned 64-bit number + * by x bits and return 32-bit unsigned number. This + * function also takes care of rounding while truncating + */ +static inline u32 vol_shift_64(u64 i, u32 x) +{ + /* do not truncate more than 32 bits */ + if (x > 32) + x = 32; + + if (x == 0) + return (u32)i; + + return (u32)(((i >> (x - 1)) + 1) >> 1); +} + +/* + * Function to compute a ^ exp where, + * a is a fractional number represented by a fixed-point + * integer with a fractional world length of "fwl" + * exp is an integer + * fwl is the fractional word length + * Return value is a fractional number represented by a + * fixed-point integer with a fractional word length of "fwl" + */ +static u32 vol_pow32(u32 a, int exp, u32 fwl) +{ + int i, iter; + u32 power = 1 << fwl; + u64 numerator; + + /* if exponent is 0, return 1 */ + if (exp == 0) + return power; + + /* determine the number of iterations based on the exponent */ + if (exp < 0) + iter = exp * -1; + else + iter = exp; + + /* mutiply a "iter" times to compute power */ + for (i = 0; i < iter; i++) { + /* + * Product of 2 Qx.fwl fixed-point numbers yields a Q2*x.2*fwl + * Truncate product back to fwl fractional bits with rounding + */ + power = vol_shift_64((u64)power * a, fwl); + } + + if (exp > 0) { + /* if exp is positive, return the result */ + return power; + } + + /* if exp is negative, return the multiplicative inverse */ + numerator = (u64)1 << (fwl << 1); + do_div(numerator, power); + + return (u32)numerator; +} + +/* + * Function to calculate volume gain from TLV data. + * This function can only handle gain steps that are multiples of 0.5 dB + */ +static u32 vol_compute_gain(u32 value, int *tlv) +{ + int dB_gain; + u32 linear_gain; + int f_step; + + /* mute volume */ + if (value == 0 && tlv[TLV_MUTE]) + return 0; + + /* + * compute dB gain from tlv. tlv_step + * in topology is multiplied by 100 + */ + dB_gain = tlv[TLV_MIN] + (value * tlv[TLV_STEP]) / 100; + + /* + * compute linear gain represented by fixed-point + * int with VOLUME_FWL fractional bits + */ + linear_gain = vol_pow32(VOL_TWENTIETH_ROOT_OF_TEN, dB_gain, VOLUME_FWL); + + /* extract the fractional part of volume step */ + f_step = tlv[TLV_STEP] - (tlv[TLV_STEP] / 100); + + /* if volume step is an odd multiple of 0.5 dB */ + if (f_step == VOL_HALF_DB_STEP && (value & 1)) + linear_gain = vol_shift_64((u64)linear_gain * + VOL_FORTIETH_ROOT_OF_TEN, + VOLUME_FWL); + + return linear_gain; +} + +/* + * Set up volume table for kcontrols from tlv data + * "size" specifies the number of entries in the table + */ +static int set_up_volume_table(struct snd_sof_control *scontrol, + int tlv[TLV_ITEMS], int size) +{ + int j; + + /* init the volume table */ + scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!scontrol->volume_table) + return -ENOMEM; + + /* populate the volume table */ + for (j = 0; j < size ; j++) + scontrol->volume_table[j] = vol_compute_gain(j, tlv); + + return 0; +} + +struct sof_dai_types { + const char *name; + enum sof_ipc_dai_type type; +}; + +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) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_dais); i++) { + if (strcmp(name, sof_dais[i].name) == 0) + return sof_dais[i].type; + } + + return SOF_DAI_INTEL_NONE; +} + +/* + * Supported Frame format types and lookup, add new ones to end of list. + */ + +struct sof_frame_types { + const char *name; + enum sof_ipc_frame frame; +}; + +static const struct sof_frame_types sof_frames[] = { + {"s16le", SOF_IPC_FRAME_S16_LE}, + {"s24le", SOF_IPC_FRAME_S24_4LE}, + {"s32le", SOF_IPC_FRAME_S32_LE}, + {"float", SOF_IPC_FRAME_FLOAT}, +}; + +static enum sof_ipc_frame find_format(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_frames); i++) { + if (strcmp(name, sof_frames[i].name) == 0) + return sof_frames[i].frame; + } + + /* use s32le if nothing is specified */ + return SOF_IPC_FRAME_S32_LE; +} + +struct sof_process_types { + const char *name; + enum sof_ipc_process_type type; + enum sof_comp_type comp_type; +}; + +static const struct sof_process_types sof_process[] = { + {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR}, + {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR}, + {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT}, + {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB}, + {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, + {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX}, + {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX}, + {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK}, + {"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP}, +}; + +static enum sof_ipc_process_type find_process(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (strcmp(name, sof_process[i].name) == 0) + return sof_process[i].type; + } + + return SOF_PROCESS_NONE; +} + +static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (sof_process[i].type == type) + return sof_process[i].comp_type; + } + + return SOF_COMP_NONE; +} + +/* + * Topology Token Parsing. + * New tokens should be added to headers and parsing tables below. + */ + +struct sof_topology_token { + u32 token; + u32 type; + int (*get_token)(void *elem, void *object, u32 offset, u32 size); + u32 offset; + u32 size; +}; + +static int get_token_u32(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = le32_to_cpu(velem->value); + return 0; +} + +static int get_token_u16(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u16 *val = (u16 *)((u8 *)object + offset); + + *val = (u16)le32_to_cpu(velem->value); + return 0; +} + +static int get_token_uuid(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_uuid_elem *velem = elem; + u8 *dst = (u8 *)object + offset; + + memcpy(dst, velem->uuid, UUID_SIZE); + + return 0; +} + +static int get_token_comp_format(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_format(velem->string); + return 0; +} + +static int get_token_dai_type(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_dai(velem->string); + return 0; +} + +static int get_token_process_type(void *elem, void *object, u32 offset, + u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_process(velem->string); + return 0; +} + +/* Buffers */ +static const struct sof_topology_token buffer_tokens[] = { + {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, size), 0}, + {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, caps), 0}, +}; + +/* DAI */ +static const struct sof_topology_token dai_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_comp_dai, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, dai_index), 0}, + {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, direction), 0}, +}; + +/* BE DAI link */ +static const struct sof_topology_token dai_link_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_dai_config, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_config, dai_index), 0}, +}; + +/* scheduling */ +static const struct sof_topology_token sched_tokens[] = { + {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period), 0}, + {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, priority), 0}, + {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period_mips), 0}, + {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, core), 0}, + {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, frames_per_sched), 0}, + {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, time_domain), 0}, +}; + +/* volume */ +static const struct sof_topology_token volume_tokens[] = { + {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc_comp_volume, ramp), 0}, + {SOF_TKN_VOLUME_RAMP_STEP_MS, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_volume, initial_ramp), 0}, +}; + +/* SRC */ +static const struct sof_topology_token src_tokens[] = { + {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, source_rate), 0}, + {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + 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[] = { +}; + +/* EFFECT */ +static const struct sof_topology_token process_tokens[] = { + {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, + get_token_process_type, + offsetof(struct sof_ipc_comp_process, type), 0}, +}; + +/* PCM */ +static const struct sof_topology_token pcm_tokens[] = { + {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + 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, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_sink), 0}, + {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_source), 0}, + {SOF_TKN_COMP_FORMAT, + SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, + offsetof(struct sof_ipc_comp_config, frame_fmt), 0}, +}; + +/* SSP */ +static const struct sof_topology_token ssp_tokens[] = { + {SOF_TKN_INTEL_SSP_CLKS_CONTROL, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, clks_control), 0}, + {SOF_TKN_INTEL_SSP_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, mclk_id), 0}, + {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits), 0}, + {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, + get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width), 0}, + {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, quirks), 0}, + {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, + 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}, + +}; + +/* ALH */ +static const struct sof_topology_token alh_tokens[] = { + {SOF_TKN_INTEL_ALH_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_alh_params, rate), 0}, + {SOF_TKN_INTEL_ALH_CH, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_alh_params, channels), 0}, +}; + +/* DMIC */ +static const struct sof_topology_token dmic_tokens[] = { + {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version), + 0}, + {SOF_TKN_INTEL_DMIC_CLK_MIN, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min), 0}, + {SOF_TKN_INTEL_DMIC_CLK_MAX, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max), 0}, + {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, fifo_fs), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MIN, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_min), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MAX, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_max), 0}, + {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, + num_pdm_active), 0}, + {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, fifo_bits), 0}, + {SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time), 0}, + +}; + +/* 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}, +}; + +/* Core tokens */ +static const struct sof_topology_token core_tokens[] = { + {SOF_TKN_COMP_CORE_ID, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp, core), 0}, +}; + +/* Component extended tokens */ +static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_UUID, + SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid, + offsetof(struct sof_ipc_comp_ext, uuid), 0}, +}; + +/* + * DMIC PDM Tokens + * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token + * as it increments the index while parsing the array of pdm tokens + * and determines the correct offset + */ +static const struct sof_topology_token dmic_pdm_tokens[] = { + {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_SKEW, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew), + 0}, +}; + +/* HDA */ +static const struct sof_topology_token hda_tokens[] = { + {SOF_TKN_INTEL_HDA_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_hda_params, rate), 0}, + {SOF_TKN_INTEL_HDA_CH, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_hda_params, channels), 0}, +}; + +/* 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 int sof_parse_uuid_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + size_t offset) +{ + struct snd_soc_tplg_vendor_uuid_elem *elem; + int found = 0; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->uuid[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + + found++; + } + } + + return found; +} + +static int sof_parse_string_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + size_t offset) +{ + struct snd_soc_tplg_vendor_string_elem *elem; + int found = 0; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->string[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + + found++; + } + } + + return found; +} + +static int sof_parse_word_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + size_t offset) +{ + struct snd_soc_tplg_vendor_value_elem *elem; + int found = 0; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->value[i]; + + /* search for token */ + 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_BYTE || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL)) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + + found++; + } + } + + return found; +} + +/** + * sof_parse_token_sets - Parse multiple sets of tokens + * @scomp: pointer to soc component + * @object: target ipc struct for parsed values + * @tokens: token definition array describing what tokens to parse + * @count: number of tokens in definition array + * @array: source pointer to consecutive vendor arrays to be parsed + * @priv_size: total size of the consecutive source arrays + * @sets: number of similar token sets to be parsed, 1 set has count elements + * @object_size: offset to next target ipc struct with multiple sets + * + * This function parses multiple sets of tokens in vendor arrays into + * consecutive ipc structs. + */ +static int sof_parse_token_sets(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + int priv_size, int sets, size_t object_size) +{ + size_t offset = 0; + int found = 0; + int total = 0; + int asize; + + while (priv_size > 0 && total < count * sets) { + asize = le32_to_cpu(array->size); + + /* validate asize */ + if (asize < 0) { /* FIXME: A zero-size array makes no sense */ + dev_err(scomp->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* make sure there is enough data before parsing */ + priv_size -= asize; + if (priv_size < 0) { + dev_err(scomp->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* call correct parser depending on type */ + switch (le32_to_cpu(array->type)) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + found += sof_parse_uuid_tokens(scomp, object, tokens, + count, array, offset); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + found += sof_parse_string_tokens(scomp, object, tokens, + count, array, offset); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + found += sof_parse_word_tokens(scomp, object, tokens, + count, array, offset); + break; + default: + dev_err(scomp->dev, "error: unknown token type %d\n", + array->type); + return -EINVAL; + } + + /* next array */ + array = (struct snd_soc_tplg_vendor_array *)((u8 *)array + + asize); + + /* move to next target struct */ + if (found >= count) { + offset += object_size; + total += found; + found = 0; + } + } + + return 0; +} + +static int sof_parse_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + int priv_size) +{ + /* + * sof_parse_tokens is used when topology contains only a single set of + * identical tuples arrays. So additional parameters to + * sof_parse_token_sets are sets = 1 (only 1 set) and + * object_size = 0 (irrelevant). + */ + return sof_parse_token_sets(scomp, object, tokens, count, array, + priv_size, 1, 0); +} + +static void sof_dbg_comp_config(struct snd_soc_component *scomp, + struct sof_ipc_comp_config *config) +{ + 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; + + /* 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 0; + +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; + size_t max_size = sbe->max; + size_t priv_size = le32_to_cpu(control->priv.size); + int ret; + + if (max_size < sizeof(struct sof_ipc_ctrl_data) || + max_size < sizeof(struct sof_abi_hdr)) { + ret = -EINVAL; + goto out; + } + + /* init the get/put bytes data */ + if (priv_size > max_size - sizeof(struct sof_ipc_ctrl_data)) { + dev_err(scomp->dev, "err: bytes data size %zu exceeds max %zu.\n", + priv_size, max_size - sizeof(struct sof_ipc_ctrl_data)); + ret = -EINVAL; + goto out; + } + + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + priv_size; + + 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 0; + +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, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct soc_enum *se; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dobj *dobj; + struct snd_sof_control *scontrol; + int ret; + + 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->scomp = scomp; + + switch (le32_to_cpu(hdr->ops.info)) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + sm = (struct soc_mixer_control *)kc->private_value; + dobj = &sm->dobj; + ret = sof_control_load_volume(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + dobj = &sbe->dobj; + ret = sof_control_load_bytes(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + se = (struct soc_enum *)kc->private_value; + dobj = &se->dobj; + ret = sof_control_load_enum(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_PIN: + default: + 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; + } + + scontrol->led_ctl.led_value = -1; + + dobj->private = scontrol; + list_add(&scontrol->list, &sdev->kcontrol_list); + return 0; +} + +static int sof_control_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_free fcomp; + struct snd_sof_control *scontrol = dobj->private; + + 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); + fcomp.id = scontrol->comp_id; + + kfree(scontrol->control_data); + list_del(&scontrol->list); + kfree(scontrol); + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + NULL, 0); +} + +/* + * DAI Topology + */ + +/* Static DSP core power management so far, should be extended in the future */ +static int sof_core_enable(struct snd_sof_dev *sdev, int core) +{ + struct sof_ipc_pm_core_config pm_core_config = { + .hdr = { + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE, + .size = sizeof(pm_core_config), + }, + .enable_mask = sdev->enabled_cores_mask | BIT(core), + }; + int ret; + + if (sdev->enabled_cores_mask & BIT(core)) + return 0; + + /* power up the core if it is host managed */ + ret = snd_sof_dsp_core_power_up(sdev, BIT(core)); + if (ret < 0) { + dev_err(sdev->dev, "error: %d powering up core %d\n", + ret, core); + return ret; + } + + /* Now notify DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd, + &pm_core_config, sizeof(pm_core_config), + &pm_core_config, sizeof(pm_core_config)); + if (ret < 0) { + dev_err(sdev->dev, "error: core %d enable ipc failure %d\n", + core, ret); + goto err; + } + + /* update enabled cores mask */ + sdev->enabled_cores_mask |= BIT(core); + + return ret; +err: + /* power down core if it is host managed and return the original error if this fails too */ + if (snd_sof_dsp_core_power_down(sdev, BIT(core)) < 0) + dev_err(sdev->dev, "error: powering down core %d\n", core); + + return ret; +} + +int sof_pipeline_core_enable(struct snd_sof_dev *sdev, + const struct snd_sof_widget *swidget) +{ + const struct sof_ipc_pipe_new *pipeline; + int ret; + + if (swidget->id == snd_soc_dapm_scheduler) { + pipeline = swidget->private; + } else { + pipeline = snd_sof_pipeline_find(sdev, swidget->pipeline_id); + if (!pipeline) + return -ENOENT; + } + + /* First enable the pipeline core */ + ret = sof_core_enable(sdev, pipeline->core); + if (ret < 0) + return ret; + + return sof_core_enable(sdev, swidget->core); +} + +static int sof_connect_dai_widget(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw, + struct snd_sof_dai *dai) +{ + struct snd_soc_card *card = scomp->card; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *cpu_dai; + int i; + + list_for_each_entry(rtd, &card->rtd_list, list) { + 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) + continue; + + /* does stream match DAI link ? */ + if (strcmp(w->sname, rtd->dai_link->stream_name)) + continue; + + switch (w->id) { + case snd_soc_dapm_dai_out: + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + /* + * Please create DAI widget in the right order + * to ensure BE will connect to the right DAI + * widget. + */ + if (!cpu_dai->capture_widget) { + cpu_dai->capture_widget = w; + break; + } + } + if (i == rtd->num_cpus) { + dev_err(scomp->dev, "error: can't find BE for DAI %s\n", + w->name); + + return -EINVAL; + } + dai->name = rtd->dai_link->name; + 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: + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + /* + * Please create DAI widget in the right order + * to ensure BE will connect to the right DAI + * widget. + */ + if (!cpu_dai->playback_widget) { + cpu_dai->playback_widget = w; + break; + } + } + if (i == rtd->num_cpus) { + dev_err(scomp->dev, "error: can't find BE for DAI %s\n", + w->name); + + return -EINVAL; + } + dai->name = rtd->dai_link->name; + dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + default: + break; + } + } + + /* check we have a connection */ + if (!dai->name) { + dev_err(scomp->dev, "error: can't connect DAI %s stream %s\n", + w->name, w->sname); + return -EINVAL; + } + + return 0; +} + +/** + * sof_comp_alloc - allocate and initialize buffer for a new component + * @swidget: pointer to struct snd_sof_widget containing extended data + * @ipc_size: IPC payload size that will be updated depending on valid + * extended data. + * @index: ID of the pipeline the component belongs to + * + * Return: The pointer to the new allocated component, NULL if failed. + */ +static struct sof_ipc_comp *sof_comp_alloc(struct snd_sof_widget *swidget, + size_t *ipc_size, int index) +{ + u8 nil_uuid[SOF_UUID_SIZE] = {0}; + struct sof_ipc_comp *comp; + size_t total_size = *ipc_size; + + /* only non-zero UUID is valid */ + if (memcmp(&swidget->comp_ext, nil_uuid, SOF_UUID_SIZE)) + total_size += sizeof(swidget->comp_ext); + + comp = kzalloc(total_size, GFP_KERNEL); + if (!comp) + return NULL; + + /* configure comp new IPC message */ + comp->hdr.size = total_size; + comp->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + comp->id = swidget->comp_id; + comp->pipeline_id = index; + comp->core = swidget->core; + + /* handle the extended data if needed */ + if (total_size > *ipc_size) { + /* append extended data to the end of the component */ + memcpy((u8 *)comp + *ipc_size, &swidget->comp_ext, sizeof(swidget->comp_ext)); + comp->ext_data_length = sizeof(swidget->comp_ext); + } + + /* update ipc_size and return */ + *ipc_size = total_size; + return comp; +} + +static int sof_widget_load_dai(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_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_dai *comp_dai; + size_t ipc_size = sizeof(*comp_dai); + int ret; + + comp_dai = (struct sof_ipc_comp_dai *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!comp_dai) + return -ENOMEM; + + /* configure dai IPC message */ + comp_dai->comp.type = SOF_COMP_DAI; + comp_dai->config.hdr.size = sizeof(comp_dai->config); + + ret = sof_parse_tokens(scomp, comp_dai, dai_tokens, + ARRAY_SIZE(dai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse dai tokens failed %d\n", + le32_to_cpu(private->size)); + goto finish; + } + + ret = sof_parse_tokens(scomp, &comp_dai->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse dai.cfg tokens failed %d\n", + private->size); + goto finish; + } + + 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); + + ret = sof_ipc_tx_message(sdev->ipc, comp_dai->comp.hdr.cmd, + comp_dai, ipc_size, r, sizeof(*r)); + + if (ret == 0 && dai) { + dai->scomp = scomp; + + /* + * copy only the sof_ipc_comp_dai to avoid collapsing + * the snd_sof_dai, the extended data is kept in the + * snd_sof_widget. + */ + memcpy(&dai->comp_dai, comp_dai, sizeof(*comp_dai)); + } + +finish: + kfree(comp_dai); + return ret; +} + +/* + * Buffer topology + */ + +static int sof_widget_load_buffer(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_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* configure dai IPC message */ + buffer->comp.hdr.size = sizeof(*buffer); + buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW; + buffer->comp.id = swidget->comp_id; + buffer->comp.type = SOF_COMP_BUFFER; + buffer->comp.pipeline_id = index; + buffer->comp.core = swidget->core; + + ret = sof_parse_tokens(scomp, buffer, buffer_tokens, + ARRAY_SIZE(buffer_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse buffer tokens failed %d\n", + private->size); + kfree(buffer); + return ret; + } + + dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n", + swidget->widget->name, buffer->size, buffer->caps); + + swidget->private = buffer; + + ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, + sizeof(*buffer), r, sizeof(*r)); + if (ret < 0) { + dev_err(scomp->dev, "error: buffer %s load failed\n", + swidget->widget->name); + kfree(buffer); + } + + return ret; +} + +/* bind PCM ID to host component ID */ +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(scomp, + spcm->pcm.caps[dir].name, + dir); + if (!host_widget) { + dev_err(scomp->dev, "can't find host comp to bind pcm\n"); + return -EINVAL; + } + + spcm->stream[dir].comp_id = host_widget->comp_id; + + return 0; +} + +/* + * PCM Topology + */ + +static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + enum sof_ipc_stream_direction dir, + 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_host *host; + size_t ipc_size = sizeof(*host); + int ret; + + host = (struct sof_ipc_comp_host *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!host) + return -ENOMEM; + + /* configure host comp IPC message */ + host->comp.type = SOF_COMP_HOST; + host->direction = dir; + host->config.hdr.size = sizeof(host->config); + + ret = sof_parse_tokens(scomp, host, pcm_tokens, + ARRAY_SIZE(pcm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse host tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &host->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse host.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &host->config); + + swidget->private = host; + + ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(host); + return ret; +} + +/* + * Pipeline Topology + */ +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); + int ret = sof_core_enable(sdev, pipeline->core); + + if (ret < 0) + return ret; + + ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, + sizeof(*pipeline), r, sizeof(*r)); + if (ret < 0) + dev_err(dev, "error: load pipeline ipc failure\n"); + + return ret; +} + +static int sof_widget_load_pipeline(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_soc_tplg_private *private = &tw->priv; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_widget *comp_swidget; + int ret; + + pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return -ENOMEM; + + /* configure dai IPC message */ + pipeline->hdr.size = sizeof(*pipeline); + pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW; + pipeline->pipeline_id = index; + pipeline->comp_id = swidget->comp_id; + + /* component at start of pipeline is our stream id */ + comp_swidget = snd_sof_find_swidget(scomp, tw->sname); + if (!comp_swidget) { + dev_err(scomp->dev, "error: widget %s refers to non existent widget %s\n", + tw->name, tw->sname); + ret = -EINVAL; + goto err; + } + + pipeline->sched_id = comp_swidget->comp_id; + + 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(scomp->dev, "error: parse pipeline tokens failed %d\n", + private->size); + goto err; + } + + 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(scomp->dev, pipeline, r); + if (ret >= 0) + return ret; +err: + kfree(pipeline); + return ret; +} + +/* + * Mixer topology + */ + +static int sof_widget_load_mixer(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_mixer *mixer; + size_t ipc_size = sizeof(*mixer); + int ret; + + mixer = (struct sof_ipc_comp_mixer *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!mixer) + return -ENOMEM; + + /* configure mixer IPC message */ + mixer->comp.type = SOF_COMP_MIXER; + mixer->config.hdr.size = sizeof(mixer->config); + + ret = sof_parse_tokens(scomp, &mixer->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse mixer.cfg tokens failed %d\n", + private->size); + kfree(mixer); + return ret; + } + + sof_dbg_comp_config(scomp, &mixer->config); + + swidget->private = mixer; + + ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer, + ipc_size, r, sizeof(*r)); + if (ret < 0) + kfree(mixer); + + return ret; +} + +/* + * Mux topology + */ +static int sof_widget_load_mux(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_mux *mux; + size_t ipc_size = sizeof(*mux); + int ret; + + mux = (struct sof_ipc_comp_mux *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!mux) + return -ENOMEM; + + /* configure mux IPC message */ + mux->comp.type = SOF_COMP_MUX; + mux->config.hdr.size = sizeof(mux->config); + + ret = sof_parse_tokens(scomp, &mux->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse mux.cfg tokens failed %d\n", + private->size); + kfree(mux); + return ret; + } + + sof_dbg_comp_config(scomp, &mux->config); + + swidget->private = mux; + + ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux, + ipc_size, r, sizeof(*r)); + if (ret < 0) + kfree(mux); + + return ret; +} + +/* + * PGA Topology + */ + +static int sof_widget_load_pga(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_volume *volume; + struct snd_sof_control *scontrol; + size_t ipc_size = sizeof(*volume); + int min_step; + int max_step; + int ret; + + volume = (struct sof_ipc_comp_volume *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!volume) + return -ENOMEM; + + 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; + } + + /* configure volume IPC message */ + volume->comp.type = SOF_COMP_VOLUME; + volume->config.hdr.size = sizeof(volume->config); + + ret = sof_parse_tokens(scomp, volume, volume_tokens, + ARRAY_SIZE(volume_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse volume tokens failed %d\n", + private->size); + goto err; + } + ret = sof_parse_tokens(scomp, &volume->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse volume.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &volume->config); + + swidget->private = volume; + + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + 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]; + volume->max_value = scontrol->volume_table[max_step]; + volume->channels = scontrol->num_channels; + break; + } + } + + ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(volume); + return ret; +} + +/* + * SRC Topology + */ + +static int sof_widget_load_src(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_src *src; + size_t ipc_size = sizeof(*src); + int ret; + + src = (struct sof_ipc_comp_src *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!src) + return -ENOMEM; + + /* configure src IPC message */ + src->comp.type = SOF_COMP_SRC; + src->config.hdr.size = sizeof(src->config); + + ret = sof_parse_tokens(scomp, src, src_tokens, + ARRAY_SIZE(src_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse src tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &src->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse src.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + 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); + + swidget->private = src; + + ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(src); + return ret; +} + +/* + * 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; + size_t ipc_size = sizeof(*asrc); + int ret; + + asrc = (struct sof_ipc_comp_asrc *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!asrc) + return -ENOMEM; + + /* configure ASRC IPC message */ + asrc->comp.type = SOF_COMP_ASRC; + 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, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(asrc); + return ret; +} + +/* + * Signal Generator Topology + */ + +static int sof_widget_load_siggen(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_tone *tone; + size_t ipc_size = sizeof(*tone); + int ret; + + tone = (struct sof_ipc_comp_tone *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!tone) + return -ENOMEM; + + /* configure siggen IPC message */ + tone->comp.type = SOF_COMP_TONE; + tone->config.hdr.size = sizeof(tone->config); + + ret = sof_parse_tokens(scomp, tone, tone_tokens, + ARRAY_SIZE(tone_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse tone tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + ret = sof_parse_tokens(scomp, &tone->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse tone.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + 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); + + swidget->private = tone; + + ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(tone); + return ret; +} + +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) +{ + const struct snd_kcontrol_new *kc; + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct soc_enum *se; + int i; + + *size = 0; + + 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; + wdata[i].control = sm->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + wdata[i].control = sbe->dobj.private; + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + wdata[i].control = se->dobj.private; + break; + default: + dev_err(scomp->dev, "error: unknown kcontrol type %d in widget %s\n", + widget->dobj.widget.kcontrol_type, + widget->name); + return -EINVAL; + } + + if (!wdata[i].control) { + dev_err(scomp->dev, "error: no scontrol for widget %s\n", + widget->name); + return -EINVAL; + } + + 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 (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; + struct sof_widget_data *wdata = NULL; + size_t ipc_data_size = 0; + size_t ipc_size; + int offset = 0; + int ret; + int i; + + /* 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); + + if (ret < 0) + goto out; + } + + ipc_size = sizeof(struct sof_ipc_comp_process) + 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 = (struct sof_ipc_comp_process *) + sof_comp_alloc(swidget, &ipc_size, index); + if (!process) { + ret = -ENOMEM; + goto out; + } + + /* configure iir IPC message */ + process->comp.type = type; + process->config.hdr.size = sizeof(process->config); + + ret = sof_parse_tokens(scomp, &process->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse process.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &process->config); + + /* + * found private data in control, so copy it. + * get possible component controls - get size of all pdata, + * then memcpy with headers + */ + 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; + } + } + + process->size = ipc_data_size; + swidget->private = process; + + ret = sof_ipc_tx_message(sdev->ipc, process->comp.hdr.cmd, process, + ipc_size, r, sizeof(*r)); + + 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: + if (ret < 0) + kfree(process); +out: + kfree(wdata); + return ret; +} + +/* + * Processing Component Topology - can be "effect", "codec", or general + * "processing". + */ + +static int sof_widget_load_process(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_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(scomp->dev, "error: process tokens not found\n"); + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + config.comp.core = swidget->core; + + /* get the process token */ + ret = sof_parse_tokens(scomp, &config, process_tokens, + ARRAY_SIZE(process_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse process tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* now load process specific data and send IPC */ + ret = sof_process_load(scomp, index, swidget, tw, r, + find_process_comp_type(config.type)); + if (ret < 0) { + dev_err(scomp->dev, "error: process loading failed\n"); + return ret; + } + + return 0; +} + +static int sof_widget_bind_event(struct snd_soc_component *scomp, + struct snd_sof_widget *swidget, + u16 event_type) +{ + struct sof_ipc_comp *ipc_comp; + + /* validate widget event type */ + switch (event_type) { + case SOF_KEYWORD_DETECT_DAPM_EVENT: + /* only KEYWORD_DETECT comps should handle this */ + if (swidget->id != snd_soc_dapm_effect) + break; + + ipc_comp = swidget->private; + if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT) + break; + + /* bind event to keyword detect comp */ + return snd_soc_tplg_widget_bind_event(swidget->widget, + sof_kwd_events, + ARRAY_SIZE(sof_kwd_events), + event_type); + default: + break; + } + + dev_err(scomp->dev, + "error: invalid event type %d for widget %s\n", + event_type, swidget->widget->name); + return -EINVAL; +} + +/* external widget init - used for any driver specific init */ +static int sof_widget_ready(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + struct snd_sof_dai *dai; + struct sof_ipc_comp_reply reply; + struct snd_sof_control *scontrol; + struct sof_ipc_comp comp = { + .core = SOF_DSP_PRIMARY_CORE, + }; + int ret = 0; + + swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); + if (!swidget) + return -ENOMEM; + + swidget->scomp = scomp; + swidget->widget = w; + swidget->comp_id = sdev->next_comp_id++; + swidget->complete = 0; + swidget->id = w->id; + swidget->pipeline_id = index; + swidget->private = NULL; + memset(&reply, 0, sizeof(reply)); + + 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"); + + ret = sof_parse_tokens(scomp, &comp, core_tokens, + ARRAY_SIZE(core_tokens), tw->priv.array, + le32_to_cpu(tw->priv.size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parsing core tokens failed %d\n", + ret); + kfree(swidget); + return ret; + } + + swidget->core = comp.core; + + /* default is primary core, safe to call for already enabled cores */ + ret = sof_core_enable(sdev, comp.core); + if (ret < 0) { + dev_err(scomp->dev, "error: enable core: %d\n", ret); + kfree(swidget); + return ret; + } + + ret = sof_parse_tokens(scomp, &swidget->comp_ext, comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens), tw->priv.array, + le32_to_cpu(tw->priv.size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parsing comp_ext_tokens failed %d\n", + ret); + kfree(swidget); + return ret; + } + + /* handle any special case widgets */ + switch (w->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = kzalloc(sizeof(*dai), GFP_KERNEL); + if (!dai) { + kfree(swidget); + return -ENOMEM; + } + + ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply, dai); + if (ret == 0) { + sof_connect_dai_widget(scomp, w, tw, dai); + list_add(&dai->list, &sdev->dai_list); + swidget->private = dai; + } else { + kfree(dai); + } + break; + case snd_soc_dapm_mixer: + ret = sof_widget_load_mixer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_pga: + ret = sof_widget_load_pga(scomp, index, swidget, tw, &reply); + /* Find scontrol for this pga and set readback offset*/ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + if (scontrol->comp_id == swidget->comp_id) { + scontrol->readback_offset = reply.offset; + break; + } + } + break; + case snd_soc_dapm_buffer: + ret = sof_widget_load_buffer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_scheduler: + ret = sof_widget_load_pipeline(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_aif_out: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_CAPTURE, tw, &reply); + break; + case snd_soc_dapm_aif_in: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_PLAYBACK, tw, &reply); + break; + 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; + case snd_soc_dapm_effect: + ret = sof_widget_load_process(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_mux: + case snd_soc_dapm_demux: + ret = sof_widget_load_mux(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_switch: + case snd_soc_dapm_dai_link: + case snd_soc_dapm_kcontrol: + default: + dev_dbg(scomp->dev, "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(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 + ? tw->sname : "none", reply.rhdr.error); + kfree(swidget); + return ret; + } + + /* bind widget to external event */ + if (tw->event_type) { + ret = sof_widget_bind_event(scomp, swidget, + le16_to_cpu(tw->event_type)); + if (ret) { + dev_err(scomp->dev, "error: widget event binding failed\n"); + kfree(swidget->private); + kfree(swidget); + return ret; + } + } + + w->dobj.private = swidget; + list_add(&swidget->list, &sdev->widget_list); + return ret; +} + +static int sof_route_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_route *sroute; + + sroute = dobj->private; + if (!sroute) + return 0; + + /* free sroute and its private data */ + kfree(sroute->private); + list_del(&sroute->list); + kfree(sroute); + + return 0; +} + +static int sof_widget_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct snd_kcontrol_new *kc; + struct snd_soc_dapm_widget *widget; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_control *scontrol; + struct snd_sof_widget *swidget; + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct snd_sof_dai *dai; + struct soc_enum *se; + int ret = 0; + int i; + + swidget = dobj->private; + if (!swidget) + return 0; + + widget = swidget->widget; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = swidget->private; + + if (dai) { + /* free dai config */ + kfree(dai->dai_config); + list_del(&dai->list); + } + break; + case snd_soc_dapm_scheduler: + + /* power down the pipeline schedule core */ + pipeline = swidget->private; + + /* + * Runtime PM should still function normally if topology loading fails and + * it's components are unloaded. Do not power down the primary core so that the + * CTX_SAVE IPC can succeed during runtime suspend. + */ + if (pipeline->core == SOF_DSP_PRIMARY_CORE) + break; + + ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); + if (ret < 0) + dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n", + pipeline->core); + + /* update enabled cores mask */ + sdev->enabled_cores_mask &= ~(1 << pipeline->core); + + break; + default: + break; + } + for (i = 0; i < widget->num_kcontrols; i++) { + kc = &widget->kcontrol_news[i]; + switch (dobj->widget.kcontrol_type) { + case SND_SOC_TPLG_TYPE_MIXER: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + if (sm->max > 1) + kfree(scontrol->volume_table); + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + break; + default: + dev_warn(scomp->dev, "unsupported kcontrol_type\n"); + goto out; + } + kfree(scontrol->control_data); + list_del(&scontrol->list); + kfree(scontrol); + } + +out: + /* free private value */ + kfree(swidget->private); + + /* remove and free swidget object */ + list_del(&swidget->list); + kfree(swidget); + + return ret; +} + +/* + * DAI HW configuration. + */ + +/* FE DAI - used for any driver specific init */ +static int sof_dai_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) +{ + 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; + int ret; + + /* nothing to do for BEs atm */ + if (!pcm) + return 0; + + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->scomp = scomp; + + for_each_pcm_streams(stream) { + spcm->stream[stream].comp_id = COMP_ID_UNASSIGNED; + INIT_WORK(&spcm->stream[stream].period_elapsed_work, + snd_sof_pcm_period_elapsed_work); + } + + 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; + + stream = SNDRV_PCM_STREAM_PLAYBACK; + + dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: playback d0i3:%d\n", + spcm->pcm.pcm_name, spcm->stream[stream].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(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(scomp, spcm, stream); + if (ret) { + dev_err(scomp->dev, + "error: can't bind pcm to host\n"); + goto free_playback_tables; + } + +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to allocate capture PCM DMA pages */ + 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[stream].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(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(scomp, spcm, stream); + if (ret) { + 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; + } + + return ret; + +free_playback_tables: + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + + return ret; +} + +static int sof_dai_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_pcm *spcm = dobj->private; + + /* free PCM DMA pages */ + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + + if (spcm->pcm.capture) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].page_table); + + /* remove from list and free spcm */ + list_del(&spcm->list); + kfree(spcm); + + return 0; +} + +static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + /* clock directions wrt codec */ + if (hw_config->bclk_master == SND_SOC_TPLG_BCLK_CM) { + /* codec is bclk master */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBM_CFM; + else + config->format |= SOF_DAI_FMT_CBM_CFS; + } else { + /* codec is bclk slave */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBS_CFM; + else + config->format |= SOF_DAI_FMT_CBS_CFS; + } + + /* inverted clocks ? */ + if (hw_config->invert_bclk) { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_IB_IF; + else + config->format |= SOF_DAI_FMT_IB_NF; + } else { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_NB_IF; + else + config->format |= SOF_DAI_FMT_NB_NF; + } +} + +/* + * Send IPC and set the same config for all DAIs with name matching the link + * name. Note that the function can only be used for the case that all DAIs + * have a common DAI config for now. + */ +static int sof_set_dai_config(struct snd_sof_dev *sdev, u32 size, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dai *dai; + int found = 0; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(link->name, dai->name) == 0) { + struct sof_ipc_reply reply; + int ret; + + /* + * the same dai config will be applied to all DAIs in + * the same dai link. We have to ensure that the ipc + * dai config's dai_index match to the component's + * dai_index. + */ + config->dai_index = dai->comp_dai.dai_index; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for %s index %d\n", + dai->name, config->dai_index); + return ret; + } + dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!dai->dai_config) + return -ENOMEM; + + /* set cpu_dai_name */ + dai->cpu_dai_name = link->cpus->dai_name; + + found = 1; + } + } + + /* + * machine driver may define a dai link with playback and capture + * dai enabled, but the dai link in topology would support both, one + * or none of them. Here print a warning message to notify user + */ + if (!found) { + dev_warn(sdev->dev, "warning: failed to find dai for dai link %s", + link->name); + } + + return 0; +} + +static int sof_link_ssp_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; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->ssp, 0, sizeof(struct sof_ipc_dai_ssp_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->ssp, ssp_tokens, + ARRAY_SIZE(ssp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse ssp tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->ssp.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->ssp.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->ssp.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->ssp.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->ssp.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->ssp.mclk_direction = hw_config->mclk_direction; + config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots); + + 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, + config->ssp.tdm_slot_width, config->ssp.tdm_slots, + config->ssp.mclk_id, config->ssp.quirks); + + /* validate SSP fsync rate and channel count */ + if (config->ssp.fsync_rate < 8000 || config->ssp.fsync_rate > 192000) { + 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(scomp->dev, "error: invalid channel count for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + /* 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; + 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.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_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; + } + + /* 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; + 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; + } + + /* 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 ESAI%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_dmic_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_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + size_t size = sizeof(*config); + int ret, j; + + /* Ensure the entire DMIC config struct is zeros */ + memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params)); + + /* get DMIC tokens */ + ret = sof_parse_tokens(scomp, &config->dmic, dmic_tokens, + ARRAY_SIZE(dmic_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse dmic tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* get DMIC PDM tokens */ + ret = sof_parse_token_sets(scomp, &config->dmic.pdm[0], dmic_pdm_tokens, + ARRAY_SIZE(dmic_pdm_tokens), private->array, + le32_to_cpu(private->size), + config->dmic.num_pdm_active, + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl)); + + if (ret != 0) { + dev_err(scomp->dev, "error: parse dmic pdm tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* set IPC header size */ + config->hdr.size = size; + + /* debug messages */ + dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n", + config->dai_index, config->dmic.driver_ipc_version); + dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", + config->dmic.pdmclk_min, config->dmic.pdmclk_max, + config->dmic.duty_min); + dev_dbg(scomp->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", + config->dmic.duty_max, config->dmic.fifo_fs, + config->dmic.num_pdm_active); + dev_dbg(scomp->dev, "fifo word length %hd\n", config->dmic.fifo_bits); + + for (j = 0; j < config->dmic.num_pdm_active; j++) { + dev_dbg(scomp->dev, "pdm %hd mic a %hd mic b %hd\n", + config->dmic.pdm[j].id, + config->dmic.pdm[j].enable_mic_a, + config->dmic.pdm[j].enable_mic_b); + dev_dbg(scomp->dev, "pdm %hd polarity a %hd polarity b %hd\n", + config->dmic.pdm[j].id, + config->dmic.pdm[j].polarity_mic_a, + config->dmic.pdm[j].polarity_mic_b); + dev_dbg(scomp->dev, "pdm %hd clk_edge %hd skew %hd\n", + config->dmic.pdm[j].id, + config->dmic.pdm[j].clk_edge, + config->dmic.pdm[j].skew); + } + + /* + * this takes care of backwards compatible handling of fifo_bits_b. + * It is deprecated since firmware ABI version 3.0.1. + */ + if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) + config->dmic.fifo_bits_b = config->dmic.fifo_bits; + + /* 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 DMIC%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_hda_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 snd_soc_dai *dai; + u32 size = sizeof(*config); + int ret; + + /* init IPC */ + memset(&config->hda, 0, sizeof(struct sof_ipc_dai_hda_params)); + config->hdr.size = size; + + /* get any bespoke DAI tokens */ + ret = sof_parse_tokens(scomp, &config->hda, hda_tokens, + ARRAY_SIZE(hda_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse hda tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + dev_dbg(scomp->dev, "HDA config rate %d channels %d\n", + config->hda.rate, config->hda.channels); + + dai = snd_soc_find_dai(link->cpus); + if (!dai) { + dev_err(scomp->dev, "error: failed to find dai %s in %s", + link->cpus->dai_name, __func__); + return -EINVAL; + } + + config->hda.link_dma_ch = DMA_CHAN_INVALID; + + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + 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 snd_soc_tplg_private *private = &cfg->priv; + u32 size = sizeof(*config); + int ret; + + ret = sof_parse_tokens(scomp, &config->alh, alh_tokens, + ARRAY_SIZE(alh_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse alh tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* init IPC */ + config->hdr.size = size; + + /* 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_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config config; + struct snd_soc_tplg_hw_config *hw_config; + int num_hw_configs; + int ret; + int i = 0; + + if (!link->platforms) { + dev_err(scomp->dev, "error: no platforms\n"); + return -EINVAL; + } + link->platforms->name = dev_name(scomp->dev); + + /* + * Set nonatomic property for FE dai links as their trigger action + * involves IPC's. + */ + if (!link->no_pcm) { + link->nonatomic = true; + + /* + * set default trigger order for all links. Exceptions to + * the rule will be handled in sof_pcm_dai_link_fixup() + * For playback, the sequence is the following: start FE, + * start BE, stop BE, stop FE; for Capture the sequence is + * inverted start BE, start FE, stop FE, stop BE + */ + link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = + SND_SOC_DPCM_TRIGGER_PRE; + link->trigger[SNDRV_PCM_STREAM_CAPTURE] = + 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(scomp->dev, "error: expected tokens for DAI, none found\n"); + return -EINVAL; + } + + /* Send BE DAI link configurations to DSP */ + memset(&config, 0, sizeof(config)); + + /* get any common DAI tokens */ + ret = sof_parse_tokens(scomp, &config, dai_link_tokens, + ARRAY_SIZE(dai_link_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse link tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* + * DAI links are expected to have at least 1 hw_config. + * But some older topologies might have no hw_config for HDA dai links. + */ + num_hw_configs = le32_to_cpu(cfg->num_hw_configs); + if (!num_hw_configs) { + if (config.type != SOF_DAI_INTEL_HDA) { + dev_err(scomp->dev, "error: unexpected DAI config count %d!\n", + le32_to_cpu(cfg->num_hw_configs)); + return -EINVAL; + } + } else { + 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++) { + if (cfg->hw_config[i].id == cfg->default_hw_config_id) + break; + } + + if (i == num_hw_configs) { + dev_err(scomp->dev, "error: default hw_config id: %d not found!\n", + le32_to_cpu(cfg->default_hw_config_id)); + return -EINVAL; + } + } + + /* configure dai IPC message */ + hw_config = &cfg->hw_config[i]; + + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.format = le32_to_cpu(hw_config->fmt); + + /* now load DAI specific data and send IPC - type comes from token */ + switch (config.type) { + case SOF_DAI_INTEL_SSP: + ret = sof_link_ssp_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_DMIC: + ret = sof_link_dmic_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_HDA: + 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(scomp->dev, "error: invalid DAI type %d\n", + config.type); + ret = -EINVAL; + break; + } + if (ret < 0) + return ret; + + return 0; +} + +static int sof_link_hda_unload(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link) +{ + struct snd_soc_dai *dai; + + dai = snd_soc_find_dai(link->cpus); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s in %s", + link->cpus->dai_name, __func__); + return -EINVAL; + } + + return 0; +} + +static int sof_link_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link *link = + container_of(dobj, struct snd_soc_dai_link, dobj); + + struct snd_sof_dai *sof_dai; + int ret = 0; + + /* only BE link is loaded by sof */ + if (!link->no_pcm) + return 0; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) + goto found; + } + + dev_err(scomp->dev, "error: failed to find dai %s in %s", + link->name, __func__); + return -EINVAL; +found: + + switch (sof_dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + case SOF_DAI_INTEL_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(scomp->dev, "error: invalid DAI type %d\n", + sof_dai->dai_config->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/* DAI link - used for any driver specific init */ +static int sof_route_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_route *route) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_pipe_comp_connect *connect; + struct snd_sof_widget *source_swidget, *sink_swidget; + struct snd_soc_dobj *dobj = &route->dobj; + struct snd_sof_route *sroute; + struct sof_ipc_reply reply; + int ret = 0; + + /* allocate memory for sroute and connect */ + sroute = kzalloc(sizeof(*sroute), GFP_KERNEL); + if (!sroute) + return -ENOMEM; + + sroute->scomp = scomp; + + connect = kzalloc(sizeof(*connect), GFP_KERNEL); + if (!connect) { + kfree(sroute); + return -ENOMEM; + } + + connect->hdr.size = sizeof(*connect); + connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; + + 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(scomp, (char *)route->source); + if (!source_swidget) { + dev_err(scomp->dev, "error: source %s not found\n", + route->source); + ret = -EINVAL; + goto err; + } + + /* + * Virtual widgets of type output/out_drv may be added in topology + * for compatibility. These are not handled by the FW. + * So, don't send routes whose source/sink widget is of such types + * to the DSP. + */ + if (source_swidget->id == snd_soc_dapm_out_drv || + source_swidget->id == snd_soc_dapm_output) + goto err; + + connect->source_id = source_swidget->comp_id; + + /* sink component */ + sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink); + if (!sink_swidget) { + dev_err(scomp->dev, "error: sink %s not found\n", + route->sink); + ret = -EINVAL; + goto err; + } + + /* + * Don't send routes whose sink widget is of type + * output or out_drv to the DSP + */ + if (sink_swidget->id == snd_soc_dapm_out_drv || + sink_swidget->id == snd_soc_dapm_output) + goto err; + + connect->sink_id = sink_swidget->comp_id; + + /* + * For virtual routes, both sink and source are not + * buffer. Since only buffer linked to component is supported by + * FW, others are reported as error, add check in route function, + * do not send it to FW when both source and sink are not buffer + */ + if (source_swidget->id != snd_soc_dapm_buffer && + sink_swidget->id != snd_soc_dapm_buffer) { + 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); + goto err; + } else { + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + + /* check IPC return value */ + if (ret < 0) { + 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); + goto err; + } + + /* check IPC reply */ + if (reply.error < 0) { + 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); + ret = reply.error; + goto err; + } + + sroute->route = route; + dobj->private = sroute; + sroute->private = connect; + + /* add route to route list */ + list_add(&sroute->list, &sdev->route_list); + + return 0; + } + +err: + kfree(connect); + kfree(sroute); + return ret; +} + +/* 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_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; + + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + + /* 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_GET_VALUE; + ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_GET; + break; + case SOF_CTRL_CMD_BINARY: + ipc_cmd = SOF_IPC_COMP_GET_DATA; + ctrl_type = SOF_CTRL_TYPE_DATA_GET; + break; + default: + dev_err(scomp->dev, + "error: Invalid scontrol->cmd: %d\n", + scontrol->cmd); + return -EINVAL; + } + ret = snd_sof_ipc_set_get_comp_data(scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd, + false); + if (ret < 0) { + dev_warn(scomp->dev, + "error: kcontrol value get for widget: %d\n", + scontrol->comp_id); + } + } + + return ret; +} + +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(dev, "tplg: complete pipeline %s id %d\n", + swidget->widget->name, swidget->comp_id); + + memset(&ready, 0, sizeof(ready)); + ready.hdr.size = sizeof(ready); + ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; + ready.comp_id = swidget->comp_id; + + ret = sof_ipc_tx_message(sdev->ipc, + ready.hdr.cmd, &ready, sizeof(ready), &reply, + sizeof(reply)); + if (ret < 0) + return ret; + return 1; +} + +/* completion - called at completion of firmware loading */ +static void sof_complete(struct snd_soc_component *scomp) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + + /* some widget types require completion notificattion */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->complete) + continue; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(scomp->dev, swidget); + break; + default: + break; + } + } + /* + * 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(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) +{ + u32 size; + u32 abi_version; + + size = le32_to_cpu(man->priv.size); + + /* backward compatible with tplg without ABI info */ + if (!size) { + dev_dbg(scomp->dev, "No topology ABI info\n"); + return 0; + } + + if (size != SOF_TPLG_ABI_SIZE) { + dev_err(scomp->dev, "error: invalid topology ABI size\n"); + return -EINVAL; + } + + 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, + SOF_ABI_PATCH); + + abi_version = SOF_ABI_VER(man->priv.data[0], + man->priv.data[1], + man->priv.data[2]); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) { + 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(scomp->dev, "warn: topology ABI is more recent than kernel\n"); + } else { + dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n"); + return -EINVAL; + } + } + + return 0; +} + +/* vendor specific kcontrol handlers available for binding */ +static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = { + {SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put}, + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put}, + {SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put}, + {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_switch_get, snd_sof_switch_put}, +}; + +/* vendor specific bytes ext handlers available for binding */ +static const struct snd_soc_tplg_bytes_ext_ops sof_bytes_ext_ops[] = { + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_ext_get, snd_sof_bytes_ext_put}, + {SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_bytes_ext_volatile_get}, +}; + +static struct snd_soc_tplg_ops sof_tplg_ops = { + /* external kcontrol init - used for any driver specific init */ + .control_load = sof_control_load, + .control_unload = sof_control_unload, + + /* external kcontrol init - used for any driver specific init */ + .dapm_route_load = sof_route_load, + .dapm_route_unload = sof_route_unload, + + /* external widget init - used for any driver specific init */ + /* .widget_load is not currently used */ + .widget_ready = sof_widget_ready, + .widget_unload = sof_widget_unload, + + /* FE DAI - used for any driver specific init */ + .dai_load = sof_dai_load, + .dai_unload = sof_dai_unload, + + /* DAI link - used for any driver specific init */ + .link_load = sof_link_load, + .link_unload = sof_link_unload, + + /* completion - called at completion of firmware loading */ + .complete = sof_complete, + + /* manifest - optional to inform component of manifest */ + .manifest = sof_manifest, + + /* vendor specific kcontrol handlers available for binding */ + .io_ops = sof_io_ops, + .io_ops_count = ARRAY_SIZE(sof_io_ops), + + /* vendor specific bytes ext handlers available for binding */ + .bytes_ext_ops = sof_bytes_ext_ops, + .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), +}; + +int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) +{ + const struct firmware *fw; + int ret; + + dev_dbg(scomp->dev, "loading topology:%s\n", file); + + ret = request_firmware(&fw, file, scomp->dev); + if (ret < 0) { + dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n", + file, ret); + return ret; + } + + ret = snd_soc_tplg_component_load(scomp, + &sof_tplg_ops, fw, + SND_SOC_TPLG_INDEX_ALL); + if (ret < 0) { + dev_err(scomp->dev, "error: tplg component load failed %d\n", + ret); + ret = -EINVAL; + } + + release_firmware(fw); + return ret; +} +EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c new file mode 100644 index 000000000..69889241a --- /dev/null +++ b/sound/soc/sof/trace.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "ops.h" + +static size_t sof_trace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + loff_t host_offset = READ_ONCE(sdev->host_offset); + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (host_offset > pos) + return host_offset - pos; + + return 0; +} + +static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + wait_queue_entry_t wait; + size_t ret = sof_trace_avail(sdev, pos, buffer_size); + + /* data immediately available */ + if (ret) + return ret; + + if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) { + /* + * tracing has ended and all traces have been + * read by client, return EOF + */ + sdev->dtrace_draining = false; + return 0; + } + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&sdev->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&sdev->trace_sleep, &wait); + + return sof_trace_avail(sdev, pos, buffer_size); +} + +static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail, buffer_size = dfse->size; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + sdev->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + if (count > buffer_size - lpos) /* min() not used to avoid sparse warnings */ + count = buffer_size - lpos; + + /* get available count based on current host offset */ + avail = sof_wait_trace_avail(sdev, lpos, buffer_size); + if (sdev->dtrace_error) { + dev_err(sdev->dev, "error: trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + count = avail > count ? count : avail; + + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static int sof_dfsentry_trace_release(struct inode *inode, struct file *file) +{ + struct snd_sof_dfsentry *dfse = inode->i_private; + struct snd_sof_dev *sdev = dfse->sdev; + + /* avoid duplicate traces at next open */ + if (!sdev->dtrace_is_enabled) + sdev->host_offset = 0; + + return 0; +} + +static const struct file_operations sof_dfs_trace_fops = { + .open = simple_open, + .read = sof_dfsentry_trace_read, + .llseek = default_llseek, + .release = sof_dfsentry_trace_release, +}; + +static int trace_debugfs_create(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = sdev->dmatb.area; + dfse->size = sdev->dmatb.bytes; + dfse->sdev = sdev; + + debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, + &sof_dfs_trace_fops); + + return 0; +} + +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_dma_trace_params_ext params; + 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; + + /* set IPC parameters */ + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; + /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ + if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; + params.timestamp_ns = ktime_get(); /* in nanosecond */ + } else { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; + } + params.buffer.phy_addr = sdev->dmatp.addr; + params.buffer.size = sdev->dmatb.bytes; + params.buffer.pages = sdev->dma_trace_pages; + params.stream_tag = 0; + + sdev->host_offset = 0; + sdev->dtrace_draining = false; + + ret = snd_sof_dma_trace_init(sdev, ¶ms.stream_tag); + if (ret < 0) { + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_init %d\n", ret); + return ret; + } + dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + params.hdr.cmd, ¶ms, sizeof(params), + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't set params for DMA for trace %d\n", ret); + goto trace_release; + } + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: start: %d\n", ret); + goto trace_release; + } + + sdev->dtrace_is_enabled = true; + + return 0; + +trace_release: + snd_sof_dma_trace_release(sdev); + return ret; +} + +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; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &sdev->dmatp); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* create compressed page table for audio firmware */ + ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, + sdev->dmatp.area, sdev->dmatb.bytes); + if (ret < 0) + goto table_err; + + sdev->dma_trace_pages = ret; + dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages); + + if (sdev->first_boot) { + ret = trace_debugfs_create(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&sdev->trace_sleep); + + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + sdev->dma_trace_pages = 0; + snd_dma_free_pages(&sdev->dmatb); +page_err: + snd_dma_free_pages(&sdev->dmatp); + return ret; +} +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); + } + + if (posn->overflow != 0) + dev_err(sdev->dev, + "error: DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); + + return 0; +} + +/* 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; + wake_up(&sdev->trace_sleep); + } +} +EXPORT_SYMBOL(snd_sof_trace_notify_for_error); + +void snd_sof_release_trace(struct snd_sof_dev *sdev) +{ + int ret; + + if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled) + return; + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); + + ret = snd_sof_dma_trace_release(sdev); + if (ret < 0) + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_release %d\n", ret); + + sdev->dtrace_is_enabled = false; + sdev->dtrace_draining = true; + wake_up(&sdev->trace_sleep); +} +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); + + 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 new file mode 100644 index 000000000..5539d3afb --- /dev/null +++ b/sound/soc/sof/utils.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie <yang.jie@linux.intel.com> +// + +#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" + +/* + * Register IO + * + * The sof_io_xyz() wrappers are typically referenced in snd_sof_dsp_ops + * structures and cannot be inlined. + */ + +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value) +{ + writel(value, addr); +} +EXPORT_SYMBOL(sof_io_write); + +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr) +{ + return readl(addr); +} +EXPORT_SYMBOL(sof_io_read); + +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value) +{ + writeq(value, addr); +} +EXPORT_SYMBOL(sof_io_write64); + +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr) +{ + return readq(addr); +} +EXPORT_SYMBOL(sof_io_read64); + +/* + * IPC Mailbox IO + */ + +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *dest = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_toio(dest, message, bytes); +} +EXPORT_SYMBOL(sof_mailbox_write); + +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *src = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_fromio(message, src, bytes); +} +EXPORT_SYMBOL(sof_mailbox_read); + +/* + * Memory copy. + */ + +void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, + size_t size) +{ + void __iomem *dest = sdev->bar[bar] + offset; + const u8 *src_byte = src; + u32 affected_mask; + u32 tmp; + int m, n; + + m = size / 4; + n = size % 4; + + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy(dest, src, m); + + if (n) { + affected_mask = (1 << (8 * n)) - 1; + + /* first read the 32bit data of dest, then change affected + * bytes, and write back to dest. For unaffected bytes, it + * should not be changed + */ + tmp = ioread32(dest + m * 4); + tmp &= ~affected_mask; + + tmp |= *(u32 *)(src_byte + m * 4) & affected_mask; + iowrite32(tmp, dest + m * 4); + } +} +EXPORT_SYMBOL(sof_block_write); + +void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, + size_t size) +{ + void __iomem *src = sdev->bar[bar] + offset; + + 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/Kconfig b/sound/soc/sof/xtensa/Kconfig new file mode 100644 index 000000000..defd6d3dc --- /dev/null +++ b/sound/soc/sof/xtensa/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SOC_SOF_XTENSA + tristate diff --git a/sound/soc/sof/xtensa/Makefile b/sound/soc/sof/xtensa/Makefile new file mode 100644 index 000000000..b8376ea04 --- /dev/null +++ b/sound/soc/sof/xtensa/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) + +snd-sof-xtensa-dsp-objs := core.o + +obj-$(CONFIG_SND_SOC_SOF_XTENSA) += snd-sof-xtensa-dsp.o diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c new file mode 100644 index 000000000..bbb9a2282 --- /dev/null +++ b/sound/soc/sof/xtensa/core.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Pan Xiuli <xiuli.pan@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../sof-priv.h" + +struct xtensa_exception_cause { + u32 id; + const char *msg; + const char *description; +}; + +/* + * From 4.4.1.5 table 4-64 Exception Causes of Xtensa + * Instruction Set Architecture (ISA) Reference Manual + */ +static const struct xtensa_exception_cause xtensa_exception_causes[] = { + {0, "IllegalInstructionCause", "Illegal instruction"}, + {1, "SyscallCause", "SYSCALL instruction"}, + {2, "InstructionFetchErrorCause", + "Processor internal physical address or data error during instruction fetch"}, + {3, "LoadStoreErrorCause", + "Processor internal physical address or data error during load or store"}, + {4, "Level1InterruptCause", + "Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register"}, + {5, "AllocaCause", + "MOVSP instruction, if caller’s registers are not in the register file"}, + {6, "IntegerDivideByZeroCause", + "QUOS, QUOU, REMS, or REMU divisor operand is zero"}, + {8, "PrivilegedCause", + "Attempt to execute a privileged operation when CRING ? 0"}, + {9, "LoadStoreAlignmentCause", "Load or store to an unaligned address"}, + {12, "InstrPIFDataErrorCause", + "PIF data error during instruction fetch"}, + {13, "LoadStorePIFDataErrorCause", + "Synchronous PIF data error during LoadStore access"}, + {14, "InstrPIFAddrErrorCause", + "PIF address error during instruction fetch"}, + {15, "LoadStorePIFAddrErrorCause", + "Synchronous PIF address error during LoadStore access"}, + {16, "InstTLBMissCause", "Error during Instruction TLB refill"}, + {17, "InstTLBMultiHitCause", + "Multiple instruction TLB entries matched"}, + {18, "InstFetchPrivilegeCause", + "An instruction fetch referenced a virtual address at a ring level less than CRING"}, + {20, "InstFetchProhibitedCause", + "An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch"}, + {24, "LoadStoreTLBMissCause", + "Error during TLB refill for a load or store"}, + {25, "LoadStoreTLBMultiHitCause", + "Multiple TLB entries matched for a load or store"}, + {26, "LoadStorePrivilegeCause", + "A load or store referenced a virtual address at a ring level less than CRING"}, + {28, "LoadProhibitedCause", + "A load referenced a page mapped with an attribute that does not permit loads"}, + {32, "Coprocessor0Disabled", + "Coprocessor 0 instruction when cp0 disabled"}, + {33, "Coprocessor1Disabled", + "Coprocessor 1 instruction when cp1 disabled"}, + {34, "Coprocessor2Disabled", + "Coprocessor 2 instruction when cp2 disabled"}, + {35, "Coprocessor3Disabled", + "Coprocessor 3 instruction when cp3 disabled"}, + {36, "Coprocessor4Disabled", + "Coprocessor 4 instruction when cp4 disabled"}, + {37, "Coprocessor5Disabled", + "Coprocessor 5 instruction when cp5 disabled"}, + {38, "Coprocessor6Disabled", + "Coprocessor 6 instruction when cp6 disabled"}, + {39, "Coprocessor7Disabled", + "Coprocessor 7 instruction when cp7 disabled"}, +}; + +/* only need xtensa atm */ +static void xtensa_dsp_oops(struct snd_sof_dev *sdev, void *oops) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + int i; + + dev_err(sdev->dev, "error: DSP Firmware Oops\n"); + for (i = 0; i < ARRAY_SIZE(xtensa_exception_causes); i++) { + if (xtensa_exception_causes[i].id == xoops->exccause) { + dev_err(sdev->dev, "error: Exception Cause: %s, %s\n", + xtensa_exception_causes[i].msg, + xtensa_exception_causes[i].description); + } + } + dev_err(sdev->dev, "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n", + xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar); + dev_err(sdev->dev, "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x", + xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4); + dev_err(sdev->dev, "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x", + xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc); + dev_err(sdev->dev, "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x", + xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5); + dev_err(sdev->dev, "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x", + xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt); +} + +static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + u32 stack_ptr = xoops->plat_hdr.stackptr; + /* 4 * 8chars + 3 ws + 1 terminating NUL */ + unsigned char buf[4 * 8 + 3 + 1]; + int i; + + dev_err(sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr); + + /* + * example output: + * 0x0049fbb0: 8000f2d0 0049fc00 6f6c6c61 00632e63 + */ + for (i = 0; i < stack_words; i += 4) { + hex_dump_to_buffer(stack + i * 4, 16, 16, 4, + buf, sizeof(buf), false); + dev_err(sdev->dev, "0x%08x: %s\n", stack_ptr + i, buf); + } +} + +const struct sof_arch_ops sof_xtensa_arch_ops = { + .dsp_oops = xtensa_dsp_oops, + .dsp_stack = xtensa_stack, +}; +EXPORT_SYMBOL_NS(sof_xtensa_arch_ops, SND_SOC_SOF_XTENSA); + +MODULE_DESCRIPTION("SOF Xtensa DSP support"); +MODULE_LICENSE("Dual BSD/GPL"); |