[alsa-devel] [PATCH v5 00/14] ASoC: Sound Open Firmware (SOF) core
Sound Open Firmware (SOF) is a host and DSP architecture agnostic audio DSP firmware. SOF is not tied to any specific host architecture or any specific physical IO communication type (it will work with on SoC DSPs, or DSP connected via SPI/I2C).
SOF is also not coupled to any particular DSP architecture and has abstraction similar to Linux to allow porting to other DSP architectures.
This patch series introduces the SOF core and utilities. Support for Intel devices is provided as a follow-up series.
The SOF core manages all the core DSP services and ALSA/ASoC IO. The core is responsible for loading firmware, parsing topology, exposing PCMs and kcontrols, providing debug and trace mechanisms and performing IPC between host and DSP.
The SOF core also has logic to allow reuse of existing machine drivers for other platforms/machines without any code modification. i.e. DAI links can be modified at runtime to bind with SOF and SOF topologies instead of existing hard coded DAI links and topology.
Future patches will simplify the IPC interface to allow e.g. for the use of RPMsg or mailboxes on other hardware, the changes are not included just yet.
This patchset is dependent on 3 patches already shared on alsa-devel but not yet merged: ASoC: topology: Align tplg pointer increment across all kcontrols ASoC: core: support driver alias names for FE topology overrides ASoC: dapm: set power_check callback for widgets that shouldnt be always on
Changes since v4:
feedback from Mark: Removed need for private field in soc_runtime Completely reworked IPC, now much simpler and robust. IPC refactoring work will continue to e.g. allow for different types of IPC hardware and a better split between core and platform-specific drivers.
Feedback from Takashi: Clarified endianness and alignment issues for page tables Removed useless mutexes Removed unneeded initializations, return types and wrappers Use snd_mask_set_format() Trace code flow optimization Removed checks on preallocate_pages() Test status of open/close functions.
Released reference when pm_runtime_get_sync fails Fixed multiple memory allocation issues Fixed static analysis warnings Fixed error checks with copy_to_user() Added support for multi-core DSPs Fixed leaked references with firmware handling on resume Multiple topology fixes/improvements (enum/switch, mux, removal of duplicate connections, widget power not reported as ON) Code simplifications (removed set-but-unused, typecasts, etc).
Changes since v3:
Addressed dozens ofcomments from Takashi Iwai, Mark Brown, Andy Shevchenko, Daniel Baluta (Thanks!) Hardened memory allocation, fixed module load/unload oops or errors (now at hundreds of cycles on ApolloLake devices) Fixed suspend/resume and removed use of suspend_late, now using snd_soc_pm_ops and standard flow Fixed topology free (multiple patches already contributed for ASoC-core) Fixed debugfs/pm_runtime interaction Removed error checks on debugfs, changed to EXPORT_SYMBOL_GPL Added filenames in debug messages, ues hex_dump_to_buffer Removed "sof-audio" platform device and added better error checks on probe Reworked data structures to remove mix of const/variable fields and duplication of pointers for platform-specific changes Removed redundant ops tables Fixed trace/logger issues Fixed issues with loader (alignment and block size errors) Fixed release_firmware handling (was all over the place) Simplified PCI handling Added comments on non-atomic triggers Removed enum controls (not supported in topology) Simplified include file dependencies Fix cppcheck warnings Fixed Kconfigs to deal with Kbuild warnings on exotic architectures
Precisions:
The code in this patchset is directly squashed from the SOF development branch [1], which tracks Mark Brown's for-next branch on a weekly basis and the configurations used for testing are based on the defconfigs at [2]. The patches are also used backported for Chromebook devices.
Full disclosure on known limitation and issues (full list at [3])
a) the interaction with ASoC is not always perfect, there are a couple of points where a better solution is desired (use of private data, support for link DMA, etc). The known points are explicitly documented in the code and will be updated. To be clearer, it's our belief that SOF does not cripple ASoC in any way, and that merging this SOF core does not harm others. It's just complicated to get things right, especially on a couple of cases where the topology and DPCM frameworks generate complex flows that very few people understand. b) The get/put methods for controls generate an IPC and possibly a wake-up. This is not optimal and is being fixed. c) runtime_pm is being hardened and the use of SMART_SUSPEND was suggested.
Thank you for reviews and comments, we appreciate the time spent commenting on this large patchset. Thanks in particular to Alan Cox and Andy Shevchenko for their comments on an earlier version. This patchset also includes contributions from Daniel Baluta for loading code on non-Intel platforms.
Pierre
[1] https://github.com/thesofproject/linux [2] https://github.com/thesofproject/kconfig [3] https://github.com/thesofproject/linux/issues
Liam Girdwood (12): ASoC: SOF: Add Sound Open Firmware driver core ASoC: SOF: Add Sound Open Firmware KControl support ASoC: SOF: Add driver debug support. ASoC: SOF: Add support for IPC IO between DSP and Host ASoC: SOF: Add PCM operations support ASoC: SOF: Add support for loading topologies ASoC: SOF: Add DSP firmware logger support ASoC: SOF: Add DSP HW abstraction operations ASoC: SOF: Add firmware loader support ASoC: SOF: Add userspace ABI support ASoC: SOF: Add PM support ASoC: SOF: Add Nocodec machine driver support
Pierre-Louis Bossart (2): ASoC: SOF: Add xtensa support ASoC: SOF: Add utils
include/sound/sof.h | 100 + include/sound/sof/control.h | 158 ++ include/sound/sof/dai-intel.h | 178 ++ include/sound/sof/dai.h | 75 + include/sound/sof/header.h | 158 ++ include/sound/sof/info.h | 118 ++ include/sound/sof/pm.h | 48 + include/sound/sof/stream.h | 148 ++ include/sound/sof/topology.h | 256 +++ include/sound/sof/trace.h | 66 + include/sound/sof/xtensa.h | 44 + include/uapi/sound/sof/abi.h | 62 + include/uapi/sound/sof/eq.h | 172 ++ include/uapi/sound/sof/fw.h | 78 + include/uapi/sound/sof/header.h | 27 + include/uapi/sound/sof/manifest.h | 188 ++ include/uapi/sound/sof/tokens.h | 103 + include/uapi/sound/sof/tone.h | 21 + include/uapi/sound/sof/trace.h | 97 + sound/soc/sof/control.c | 544 ++++++ sound/soc/sof/core.c | 489 +++++ sound/soc/sof/debug.c | 219 +++ sound/soc/sof/ipc.c | 742 ++++++++ sound/soc/sof/loader.c | 376 ++++ sound/soc/sof/nocodec.c | 109 ++ sound/soc/sof/ops.c | 204 ++ sound/soc/sof/ops.h | 403 ++++ sound/soc/sof/pcm.c | 723 +++++++ sound/soc/sof/pm.c | 373 ++++ sound/soc/sof/sof-priv.h | 616 ++++++ sound/soc/sof/topology.c | 2913 +++++++++++++++++++++++++++++ sound/soc/sof/trace.c | 295 +++ sound/soc/sof/utils.c | 112 ++ sound/soc/sof/xtensa/Kconfig | 2 + sound/soc/sof/xtensa/Makefile | 5 + sound/soc/sof/xtensa/core.c | 138 ++ 36 files changed, 10360 insertions(+) create mode 100644 include/sound/sof.h create mode 100644 include/sound/sof/control.h create mode 100644 include/sound/sof/dai-intel.h create mode 100644 include/sound/sof/dai.h create mode 100644 include/sound/sof/header.h create mode 100644 include/sound/sof/info.h create mode 100644 include/sound/sof/pm.h create mode 100644 include/sound/sof/stream.h create mode 100644 include/sound/sof/topology.h create mode 100644 include/sound/sof/trace.h create mode 100644 include/sound/sof/xtensa.h create mode 100644 include/uapi/sound/sof/abi.h create mode 100644 include/uapi/sound/sof/eq.h create mode 100644 include/uapi/sound/sof/fw.h create mode 100644 include/uapi/sound/sof/header.h create mode 100644 include/uapi/sound/sof/manifest.h create mode 100644 include/uapi/sound/sof/tokens.h create mode 100644 include/uapi/sound/sof/tone.h create mode 100644 include/uapi/sound/sof/trace.h create mode 100644 sound/soc/sof/control.c create mode 100644 sound/soc/sof/core.c create mode 100644 sound/soc/sof/debug.c create mode 100644 sound/soc/sof/ipc.c create mode 100644 sound/soc/sof/loader.c create mode 100644 sound/soc/sof/nocodec.c create mode 100644 sound/soc/sof/ops.c create mode 100644 sound/soc/sof/ops.h create mode 100644 sound/soc/sof/pcm.c create mode 100644 sound/soc/sof/pm.c create mode 100644 sound/soc/sof/sof-priv.h create mode 100644 sound/soc/sof/topology.c create mode 100644 sound/soc/sof/trace.c create mode 100644 sound/soc/sof/utils.c create mode 100644 sound/soc/sof/xtensa/Kconfig create mode 100644 sound/soc/sof/xtensa/Makefile create mode 100644 sound/soc/sof/xtensa/core.c
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different architectures and platforms. It abstracts DSP operations and IO methods so that the target DSP can be an internal memory mapped or external SPI or I2C based device. This abstraction also allows SOF to be run on many different VMs on the same physical HW.
SOF also requires some data in ASoC PCM runtime data for looking up SOF data during ASoC PCM operations.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof.h | 94 ++++++ sound/soc/sof/core.c | 489 +++++++++++++++++++++++++++++++ sound/soc/sof/sof-priv.h | 616 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1199 insertions(+) create mode 100644 include/sound/sof.h create mode 100644 sound/soc/sof/core.c create mode 100644 sound/soc/sof/sof-priv.h
diff --git a/include/sound/sof.h b/include/sound/sof.h new file mode 100644 index 000000000000..54f65ec33a6c --- /dev/null +++ b/include/sound/sof.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood liam.r.girdwood@linux.intel.com + */ + +#ifndef __INCLUDE_SOUND_SOF_H +#define __INCLUDE_SOUND_SOF_H + +#include <linux/pci.h> +#include <sound/soc-acpi.h> + +struct snd_sof_dsp_ops; + +/* + * SOF Platform data. + */ +struct snd_sof_pdata { + const struct firmware *fw; + const char *drv_name; + const char *name; + const char *platform; + + struct device *dev; + + /* + * notification callback used if the hardware initialization + * can take time or is handled in a workqueue. This callback + * can be used by the caller to e.g. enable runtime_pm + * or limit functionality until all low-level inits are + * complete. + */ + void (*sof_probe_complete)(struct device *dev); + + /* descriptor */ + const struct sof_dev_desc *desc; + + /* firmware and topology filenames */ + const char *fw_filename_prefix; + const char *fw_filename; + const char *tplg_filename_prefix; + const char *tplg_filename; + + /* machine */ + struct platform_device *pdev_mach; + const struct snd_soc_acpi_mach *machine; + + void *hw_pdata; +}; + +/* + * Descriptor used for setting up SOF platform data. This is used when + * ACPI/PCI data is missing or mapped differently. + */ +struct sof_dev_desc { + /* list of machines using this configuration */ + struct snd_soc_acpi_mach *machines; + + /* Platform resource indexes in BAR / ACPI resources. */ + /* Must set to -1 if not used - add new items to end */ + int resindex_lpe_base; + int resindex_pcicfg_base; + int resindex_imr_base; + int irqindex_host_ipc; + int resindex_dma_base; + + /* DMA only valid when resindex_dma_base != -1*/ + int dma_engine; + int dma_size; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + + /* chip information for dsp */ + const void *chip_info; + + /* defaults for no codec mode */ + const char *nocodec_fw_filename; + const char *nocodec_tplg_filename; + + /* defaults paths for firmware and topology files */ + const char *default_fw_path; + const char *default_tplg_path; + + const struct snd_sof_dsp_ops *ops; + const struct sof_arch_ops *arch_ops; +}; + +#endif diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c new file mode 100644 index 000000000000..04a6716e9762 --- /dev/null +++ b/sound/soc/sof/core.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <asm/unaligned.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" + +/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC_MS 5 +#define TIMEOUT_DEFAULT_BOOT_MS 100 + +/* + * Generic object lookup APIs. + */ + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (strcmp(spcm->pcm.dai_name, name) == 0) + return spcm; + + if (strcmp(spcm->pcm.caps[0].name, name) == 0) + return spcm; + + if (strcmp(spcm->pcm.caps[1].name, name) == 0) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) { + *direction = SNDRV_PCM_STREAM_PLAYBACK; + return spcm; + } + if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { + *direction = SNDRV_PCM_STREAM_CAPTURE; + return spcm; + } + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) + return spcm; + } + + return NULL; +} + +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_widget *swidget = NULL; + + 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_sof_dev *sdev, + char *pcm_name, int dir) +{ + struct snd_sof_widget *swidget = NULL; + enum snd_soc_dapm_type type; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + type = snd_soc_dapm_aif_in; + else + type = snd_soc_dapm_aif_out; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type) + return swidget; + } + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_dai *dai = NULL; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (dai->name && (strcmp(name, dai->name) == 0)) + return dai; + } + + return NULL; +} + +/* + * 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"}, +}; + +int 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 0; /* 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 happen at %s:%d\n", + panic_info->filename, panic_info->linenum); + sof_oops(sdev, oops); + sof_stack(sdev, oops, stack, stack_words); + return -EFAULT; +} +EXPORT_SYMBOL(snd_sof_get_status); + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bites from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (1) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} + +/* + * SOF Driver enumeration. + */ +static int sof_machine_check(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct snd_soc_acpi_mach *machine; + int ret; + + if (plat_data->machine) + return 0; + + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) { + dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); + return -ENODEV; + } + + /* fallback to nocodec mode */ + dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n"); + machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + ret = sof_nocodec_setup(sdev->dev, plat_data, machine, + plat_data->desc, plat_data->desc->ops); + if (ret < 0) + return ret; + + plat_data->machine = machine; + + return 0; +} + +static int sof_probe_continue(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *drv_name; + const void *mach; + int size; + int ret; + + /* probe the DSP hardware */ + ret = snd_sof_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); + return ret; + } + + /* 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) { + 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; + } + + /* boot the firmware */ + 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; + } + + /* 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); + } + + /* hereafter all FW boot flows are for PM reasons */ + sdev->first_boot = false; + + /* now register audio DSP platform driver and dai */ + ret = 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_run_err; + } + + drv_name = plat_data->machine->drv_name; + mach = (const void *)plat_data->machine; + size = sizeof(*plat_data->machine); + + /* register machine driver, pass machine info as pdata */ + plat_data->pdev_mach = + platform_device_register_data(sdev->dev, drv_name, + PLATFORM_DEVID_NONE, mach, size); + + if (IS_ERR(plat_data->pdev_mach)) { + ret = PTR_ERR(plat_data->pdev_mach); + goto comp_err; + } + + dev_dbg(sdev->dev, "created machine %s\n", + dev_name(&plat_data->pdev_mach->dev)); + + if (plat_data->sof_probe_complete) + plat_data->sof_probe_complete(sdev->dev); + + return 0; + +comp_err: + snd_soc_unregister_component(sdev->dev); +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); + + 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; + + sdev->pdata = plat_data; + sdev->first_boot = true; + dev_set_drvdata(dev, sdev); + + 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; + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) + cancel_work_sync(&sdev->probe_work); + + snd_soc_unregister_component(dev); + snd_sof_fw_unload(sdev); + snd_sof_ipc_free(sdev); + snd_sof_free_debug(sdev); + snd_sof_free_trace(sdev); + snd_sof_remove(sdev); + /* + * platform_device_unregister() frees the card and its resources. + * So it should be called after unregistering the comp driver + * so that the card is valid while unregistering comp driver. + */ + if (!IS_ERR_OR_NULL(pdata->pdev_mach)) + platform_device_unregister(pdata->pdev_mach); + + /* release firmware */ + release_firmware(pdata->fw); + pdata->fw = NULL; + + 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/sof-priv.h b/sound/soc/sof/sof-priv.h new file mode 100644 index 000000000000..f7fa0d19efd4 --- /dev/null +++ b/sound/soc/sof/sof-priv.h @@ -0,0 +1,616 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 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/soc.h> + +#include <sound/sof.h> +#include <sound/sof/stream.h> /* needs to be included before control.h */ +#include <sound/sof/control.h> +#include <sound/sof/dai.h> +#include <sound/sof/info.h> +#include <sound/sof/pm.h> +#include <sound/sof/topology.h> +#include <sound/sof/trace.h> + +#include <uapi/sound/sof/fw.h> + +/* debug flags */ +#define SOF_DBG_REGS BIT(1) +#define SOF_DBG_MBOX BIT(2) +#define SOF_DBG_TEXT BIT(3) +#define SOF_DBG_PCI BIT(4) + +/* 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) + +/* max number of FE PCMs before BEs */ +#define SOF_BE_PCM_BASE 16 + +#define SOF_IPC_DSP_REPLY 0 +#define SOF_IPC_HOST_REPLY 1 + +/* 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) + +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 */ + void (*write)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u32 value); /* mandatory */ + u32 (*read)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* mandatory */ + void (*write64)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u64 value); /* mandatory */ + u64 (*read64)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* mandatory */ + + /* 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); /* mandatory */ + irqreturn_t (*irq_thread)(int irq, void *context); /* mandatory */ + + /* mailbox */ + void (*mailbox_read)(struct snd_sof_dev *sof_dev, u32 offset, + void *addr, size_t bytes); /* mandatory */ + void (*mailbox_write)(struct snd_sof_dev *sof_dev, u32 offset, + void *addr, size_t bytes); /* mandatory */ + + /* ipc */ + int (*send_msg)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); /* mandatory */ + int (*get_reply)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); /* mandatory */ + int (*cmd_done)(struct snd_sof_dev *sof_dev, int dir); /* 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 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 */ + + /* 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, int state); /* optional */ + int (*resume)(struct snd_sof_dev *sof_dev); /* optional */ + int (*runtime_suspend)(struct snd_sof_dev *sof_dev, + int state); /* optional */ + int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* 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 */ + + /* 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 */ + + /* DAI ops */ + struct snd_soc_dai_driver *drv; + int num_drv; +}; + +/* 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->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 { + struct dentry *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 IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + 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; + + wait_queue_head_t waitq; + bool ipc_complete; +}; + +/* PCM stream, mapped to FW component */ +struct snd_sof_pcm_stream { + u32 comp_id; + struct snd_dma_buffer page_table; + struct sof_ipc_stream_posn posn; + struct snd_pcm_substream *substream; +}; + +/* ALSA SOF PCM device */ +struct snd_sof_pcm { + struct snd_sof_dev *sdev; + struct snd_soc_tplg_pcm pcm; + struct snd_sof_pcm_stream stream[2]; + u32 posn_offset[2]; + struct list_head list; /* list in sdev pcm list */ + struct snd_pcm_hw_params params[2]; + int restore_stream[2]; /* restore hw_params for paused stream */ +}; + +/* ALSA SOF Kcontrol device */ +struct snd_sof_control { + struct snd_sof_dev *sdev; + int comp_id; + int num_channels; + u32 readback_offset; /* offset to mmaped data if used */ + struct sof_ipc_ctrl_data *control_data; + u32 size; /* cdata size */ + enum sof_ipc_ctrl_cmd cmd; + u32 *volume_table; /* volume table computed from tlv data*/ + + struct list_head list; /* list in sdev control list */ +}; + +/* ASoC SOF DAPM widget */ +struct snd_sof_widget { + struct snd_sof_dev *sdev; + int comp_id; + int pipeline_id; + int complete; + int id; + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ + + void *private; /* core does not touch this */ +}; + +/* ASoC SOF DAPM route */ +struct snd_sof_route { + struct snd_sof_dev *sdev; + + struct snd_soc_dapm_route *route; + struct list_head list; /* list in sdev route list */ + + void *private; +}; + +/* ASoC DAI device */ +struct snd_sof_dai { + struct snd_sof_dev *sdev; + const char *name; + + struct sof_ipc_comp_dai comp_dai; + struct sof_ipc_dai_config *dai_config; + struct list_head list; /* list in sdev dai list */ +}; + +/* + * 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; + + /* DSP firmware boot */ + wait_queue_head_t boot_wait; + u32 boot_complete; + u32 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 */ + u64 irq_status; + 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; + + /* 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_dma_buffer_data *info_buffer; + struct sof_ipc_window *info_window; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + + /* Wait queue for code loading */ + wait_queue_head_t waitq; + int code_loading; + + /* 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; + u32 dtrace_is_enabled; + u32 dtrace_error; + u32 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_resume(struct device *dev); +int snd_sof_suspend(struct device *dev); + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + 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); +int 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); +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + char *name); +struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, + char *pcm_name, int dir); +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + char *name); + +static inline +struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + char *name); +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction); +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id); + +/* + * Stream IPC + */ +int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn); + +/* + * Mixer IPC + */ +int snd_sof_ipc_set_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd); +int snd_sof_ipc_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd); + +/* + * Topology. + * There is no snd_sof_free_topology since topology components will + * be freed by snd_soc_unregister_component, + */ +int snd_sof_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops); +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file); +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget); + +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r); + +/* + * Trace/debug + */ +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); +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); +int 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); + +/* + * Platform specific ops. + */ +extern struct snd_compr_ops sof_compressed_ops; + +/* + * Kcontrols. + */ + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size); +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size); + +/* + * DSP Architectures. + */ +static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + if (sof_arch_ops(sdev)->dsp_stack) + sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); +} + +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); + +#endif
On Thu, 21 Mar 2019 17:10:03 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different
One different is enough.
--- /dev/null +++ b/sound/soc/sof/core.c
....
+struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev,
char *name)
Make it const char *.
+{
- struct snd_sof_pcm *spcm = NULL;
Superfluous initialization.
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
if (strcmp(spcm->pcm.dai_name, name) == 0)
return spcm;
if (strcmp(spcm->pcm.caps[0].name, name) == 0)
return spcm;
if (strcmp(spcm->pcm.caps[1].name, name) == 0)
return spcm;
- }
- return NULL;
+}
+struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev,
unsigned int comp_id,
int *direction)
+{
- struct snd_sof_pcm *spcm = NULL;
Ditto, superfluous.
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) {
*direction = SNDRV_PCM_STREAM_PLAYBACK;
return spcm;
}
if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) {
*direction = SNDRV_PCM_STREAM_CAPTURE;
return spcm;
}
- }
- return NULL;
+}
+struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev,
unsigned int pcm_id)
+{
- struct snd_sof_pcm *spcm = NULL;
Ditto... I stop repeating.
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id)
return spcm;
- }
- return NULL;
+}
+struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev,
char *name)
const char *. Some other functions follow the similar pattern...
+int 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 0; /* no fault ? */
- }
....
+out:
- dev_err(sdev->dev, "error: panic happen at %s:%d\n",
panic_info->filename, panic_info->linenum);
- sof_oops(sdev, oops);
- sof_stack(sdev, oops, stack, stack_words);
- return -EFAULT;
+}
So this function returns -EFAULT for the normal operation while 0 for unexpected case? I see that no callers actually check the return value, but it's some what unintuitive. Worth for adding a comment about the return code.
thanks,
Takashi
Thanks Takashi for your review!
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different
One different is enough.
Indeed
+struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev,
char *name)
Make it const char *.
yep, will fix all occurrences
+{
- struct snd_sof_pcm *spcm = NULL;
Superfluous initialization.
indeed, that was missed. will fix all occurrences, thanks!
+int 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 0; /* no fault ? */
- }
....
+out:
- dev_err(sdev->dev, "error: panic happen at %s:%d\n",
panic_info->filename, panic_info->linenum);
- sof_oops(sdev, oops);
- sof_stack(sdev, oops, stack, stack_words);
- return -EFAULT;
+}
So this function returns -EFAULT for the normal operation while 0 for unexpected case? I see that no callers actually check the return value, but it's some what unintuitive. Worth for adding a comment about the return code.
Good point. Will need to re-look at the flow. At some point, this is the sort of error that should lead to firmware recovery, where the driver takes the initiative of resetting hardware and reinitializing. We know this is on the list of features to be implemented but it's not ready yet.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF exposes regular ALSA Kcontrols that are defined by topology. This patch converts the Kcontrol IO to DSP IPC.
The current implementation is aligned with previous Intel solutions, but is not optimal and can be improved: a) for every get/put the host wakes up the DSP and generates an IPC. The kernel should cache the values and generate an IPC only when strictly necessary. b) the firmware can be implemented to only instantiate the pipelines and related control-related parts that are needed at a given time, and power-gate the relevant SRAM blocks.
The development tasks for these two improvements has started, once validated they willl be provided in an update.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/control.c | 544 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 sound/soc/sof/control.c
diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c new file mode 100644 index 000000000000..189cdc645939 --- /dev/null +++ b/sound/soc/sof/control.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +/* Mixer Controls */ + +#include <linux/pm_runtime.h> +#include "sof-priv.h" + +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 snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: volume get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME); + + /* 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); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: volume get failed to idle %d\n", + err); + 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_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: volume put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = + mixer_to_ipc(ucontrol->value.integer.value[i], + scontrol->volume_table, sm->max + 1); + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: volume put failed to idle %d\n", + err); + return 0; +} + +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 snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: switch get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: switch get failed to idle %d\n", + err); + 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_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: switch put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = ucontrol->value.integer.value[i]; + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: switch put failed to idle %d\n", + err); + return 0; +} + +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 snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: enum get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_ENUM); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: enum get failed to idle %d\n", + err); + 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_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: enum put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = ucontrol->value.enumerated.item[i]; + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_ENUM); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: enum put failed to idle %d\n", + err); + return 0; +} + +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_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + size_t size; + int ret, err; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(sdev->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, scontrol->cmd); + size = data->size + sizeof(*data); + if (size > be->max) { + dev_err_ratelimited(sdev->dev, + "error: DSP sent %zu bytes max is %d\n", + size, be->max); + ret = -EINVAL; + goto out; + } + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes get failed to idle %d\n", + err); + return ret; +} + +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_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + int ret, err; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(sdev->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + if (data->size > be->max) { + dev_err_ratelimited(sdev->dev, + "error: size too big %d bytes max is %d\n", + data->size, be->max); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, data->size); + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, scontrol->cmd); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes put failed to idle %d\n", + err); + return ret; +} + +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_sof_dev *sdev = scontrol->sdev; + 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; + int ret; + int err; + int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + + /* + * 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; + + /* + * The maximum length that can be copied is limited by IPC max + * length and topology defined length for ext bytes control. + */ + if (be->max < max_size) /* min() not used to avoid sparse warnings */ + max_size = be->max; + if (header.length > max_size) { + dev_err_ratelimited(sdev->dev, + "error: Bytes data size %d exceeds max %d.\n", + header.length, max_size); + return -EINVAL; + } + + /* Check that header id matches the command */ + if (header.numid != scontrol->cmd) { + dev_err_ratelimited(sdev->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(sdev->dev, + "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + return -EINVAL; + } + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { + dev_err_ratelimited(sdev->dev, "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + + if (cdata->data->size + sizeof(const struct sof_abi_hdr) > max_size) { + dev_err_ratelimited(sdev->dev, + "error: Mismatch in ABI data size (truncated?).\n"); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes_ext put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, scontrol->cmd); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes_ext put 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_sof_dev *sdev = scontrol->sdev; + 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; + int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + int data_size; + int err; + int ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes_ext get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + size -= sizeof(const struct snd_ctl_tlv); + + /* 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_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, scontrol->cmd); + + /* Prevent read of other kernel data or possibly corrupt response */ + data_size = cdata->data->size + sizeof(const struct sof_abi_hdr); + + /* check size. min3() is not used to avoid sparse warnings */ + if (size < max_size) + max_size = size; + if (be->max < max_size) + max_size = be->max; + if (data_size > max_size) { + dev_err_ratelimited(sdev->dev, + "error: user data size %d exceeds max size %d.\n", + data_size, max_size); + ret = -EINVAL; + 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)) + return -EFAULT; + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes_ext get failed to idle %d\n", + err); + return ret; +}
On Thu, 21 Mar 2019 17:10:04 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF exposes regular ALSA Kcontrols that are defined by topology. This patch converts the Kcontrol IO to DSP IPC.
The current implementation is aligned with previous Intel solutions, but is not optimal and can be improved: a) for every get/put the host wakes up the DSP and generates an IPC. The kernel should cache the values and generate an IPC only when strictly necessary. b) the firmware can be implemented to only instantiate the pipelines and related control-related parts that are needed at a given time, and power-gate the relevant SRAM blocks.
The development tasks for these two improvements has started, once validated they willl be provided in an update.
will
thanks,
Takashi
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add debugFS files that can be used to expose DSP memories and and peripherals to userspace to assist with firmware debugging.
Since we cannot rely on debugFS, errors are logged but don't stop execution.
When a resource cannot be read in D3, it is optionally cached on suspend. Copying memories from IO will increase the suspend latency, this should only used in engineering builds w/ debug options. This part will have to be enhanced when support for D0ix states is provided, currently only D0 and D3 are supported.
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/debug.c | 219 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 sound/soc/sof/debug.c
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c new file mode 100644 index 000000000000..5876269aa776 --- /dev/null +++ b/sound/soc/sof/debug.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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" + +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; + int size; + u32 *buf; + loff_t pos = *ppos; + size_t size_ret; + + 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; + + /* intermediate buffer size must be u32 multiple */ + size = ALIGN(count, 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 %s cannot be read in DSP D3\n", + dfse->dfsentry->d_name.name); + 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, 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, +}; + +/* 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 + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", + name); + } else { + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + + } + + 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) +{ + 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; + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", + name); + } else { + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + } + + 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); + if (IS_ERR_OR_NULL(sdev->debugfs_root)) { + dev_err(sdev->dev, "error: failed to create debugfs directory\n"); + return 0; + } + + /* init dfsentry list */ + INIT_LIST_HEAD(&sdev->dfsentry_list); + + /* 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; + } + + 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);
On Thu, 21 Mar 2019 17:10:05 +0100, Pierre-Louis Bossart wrote:
+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;
- int size;
- u32 *buf;
- loff_t pos = *ppos;
- size_t size_ret;
- 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;
- /* intermediate buffer size must be u32 multiple */
- size = ALIGN(count, 4);
Doesn't pos need to be aligned to 32bit as well (at least at actually reading from iomem)?
thanks,
Takashi
On 3/29/19 12:04 PM, Takashi Iwai wrote:
On Thu, 21 Mar 2019 17:10:05 +0100, Pierre-Louis Bossart wrote:
+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;
- int size;
- u32 *buf;
- loff_t pos = *ppos;
- size_t size_ret;
- 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;
- /* intermediate buffer size must be u32 multiple */
- size = ALIGN(count, 4);
Doesn't pos need to be aligned to 32bit as well (at least at actually reading from iomem)?
Good point, we can either return an error if the count in both a multiple of 32 bits, or do some caching to allow for arbitrary size access. The former is very simple but not sure if this is accepted behavior for debugfs.
On Mon, Apr 01, 2019 at 01:15:46PM -0400, Pierre-Louis Bossart wrote:
On 3/29/19 12:04 PM, Takashi Iwai wrote:
Doesn't pos need to be aligned to 32bit as well (at least at actually reading from iomem)?
Good point, we can either return an error if the count in both a multiple of 32 bits, or do some caching to allow for arbitrary size access. The former is very simple but not sure if this is accepted behavior for debugfs.
You really want something that will work with arbatrary userspace tools if you can rather than requiring special magic read sizes - things like cat and cp should probably work. A common thing is to do reads/writes of the correct sizes into a buffer and then pick the relevant bits out of the buffer to satisfy what userspace actually asked for.
Doesn't pos need to be aligned to 32bit as well (at least at actually reading from iomem)?
Good point, we can either return an error if the count in both a multiple of 32 bits, or do some caching to allow for arbitrary size access. The former is very simple but not sure if this is accepted behavior for debugfs.
You really want something that will work with arbatrary userspace tools if you can rather than requiring special magic read sizes - things like cat and cp should probably work. A common thing is to do reads/writes of the correct sizes into a buffer and then pick the relevant bits out of the buffer to satisfy what userspace actually asked for.
Thanks for the advice. The data is already in a buffer so it's not difficult to implement support for sizes that aren't multiple of 4 bytes.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Define an IPC ABI for all host <--> DSP communication. This ABI should be transport agnostic. i.e. it should work on MMIO and SPI/I2C style interfaces.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/control.h | 158 ++++++++ include/sound/sof/dai-intel.h | 178 ++++++++ include/sound/sof/dai.h | 75 ++++ include/sound/sof/header.h | 158 ++++++++ include/sound/sof/info.h | 118 ++++++ include/sound/sof/pm.h | 48 +++ include/sound/sof/stream.h | 148 +++++++ include/sound/sof/trace.h | 66 +++ sound/soc/sof/ipc.c | 742 ++++++++++++++++++++++++++++++++++ 9 files changed, 1691 insertions(+) create mode 100644 include/sound/sof/control.h create mode 100644 include/sound/sof/dai-intel.h create mode 100644 include/sound/sof/dai.h create mode 100644 include/sound/sof/header.h create mode 100644 include/sound/sof/info.h create mode 100644 include/sound/sof/pm.h create mode 100644 include/sound/sof/stream.h create mode 100644 include/sound/sof/trace.h create mode 100644 sound/soc/sof/ipc.c
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h new file mode 100644 index 000000000000..0604acbfdb93 --- /dev/null +++ b/include/sound/sof/control.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_CONTROL_H__ +#define __INCLUDE_SOUND_SOF_CONTROL_H__ + +#include <uapi/sound/sof/header.h> +#include <sound/sof/header.h> + +/* + * Component Mixers and Controls + */ + +/* channel positions - uses same values as ALSA */ +enum sof_ipc_chmap { + SOF_CHMAP_UNKNOWN = 0, + SOF_CHMAP_NA, /**< N/A, silent */ + SOF_CHMAP_MONO, /**< mono stream */ + SOF_CHMAP_FL, /**< front left */ + SOF_CHMAP_FR, /**< front right */ + SOF_CHMAP_RL, /**< rear left */ + SOF_CHMAP_RR, /**< rear right */ + SOF_CHMAP_FC, /**< front centre */ + SOF_CHMAP_LFE, /**< LFE */ + SOF_CHMAP_SL, /**< side left */ + SOF_CHMAP_SR, /**< side right */ + SOF_CHMAP_RC, /**< rear centre */ + SOF_CHMAP_FLC, /**< front left centre */ + SOF_CHMAP_FRC, /**< front right centre */ + SOF_CHMAP_RLC, /**< rear left centre */ + SOF_CHMAP_RRC, /**< rear right centre */ + SOF_CHMAP_FLW, /**< front left wide */ + SOF_CHMAP_FRW, /**< front right wide */ + SOF_CHMAP_FLH, /**< front left high */ + SOF_CHMAP_FCH, /**< front centre high */ + SOF_CHMAP_FRH, /**< front right high */ + SOF_CHMAP_TC, /**< top centre */ + SOF_CHMAP_TFL, /**< top front left */ + SOF_CHMAP_TFR, /**< top front right */ + SOF_CHMAP_TFC, /**< top front centre */ + SOF_CHMAP_TRL, /**< top rear left */ + SOF_CHMAP_TRR, /**< top rear right */ + SOF_CHMAP_TRC, /**< top rear centre */ + SOF_CHMAP_TFLC, /**< top front left centre */ + SOF_CHMAP_TFRC, /**< top front right centre */ + SOF_CHMAP_TSL, /**< top side left */ + SOF_CHMAP_TSR, /**< top side right */ + SOF_CHMAP_LLFE, /**< left LFE */ + SOF_CHMAP_RLFE, /**< right LFE */ + SOF_CHMAP_BC, /**< bottom centre */ + SOF_CHMAP_BLC, /**< bottom left centre */ + SOF_CHMAP_BRC, /**< bottom right centre */ + SOF_CHMAP_LAST = SOF_CHMAP_BRC, +}; + +/* control data type and direction */ +enum sof_ipc_ctrl_type { + /* per channel data - uses struct sof_ipc_ctrl_value_chan */ + SOF_CTRL_TYPE_VALUE_CHAN_GET = 0, + SOF_CTRL_TYPE_VALUE_CHAN_SET, + /* component data - uses struct sof_ipc_ctrl_value_comp */ + SOF_CTRL_TYPE_VALUE_COMP_GET, + SOF_CTRL_TYPE_VALUE_COMP_SET, + /* bespoke data - struct struct sof_abi_hdr */ + SOF_CTRL_TYPE_DATA_GET, + SOF_CTRL_TYPE_DATA_SET, +}; + +/* control command type */ +enum sof_ipc_ctrl_cmd { + SOF_CTRL_CMD_VOLUME = 0, /**< maps to ALSA volume style controls */ + SOF_CTRL_CMD_ENUM, /**< maps to ALSA enum style controls */ + SOF_CTRL_CMD_SWITCH, /**< maps to ALSA switch style controls */ + SOF_CTRL_CMD_BINARY, /**< maps to ALSA binary style controls */ +}; + +/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan { + uint32_t channel; /**< channel map - enum sof_ipc_chmap */ + uint32_t value; +} __packed; + +/* generic component mapped value data */ +struct sof_ipc_ctrl_value_comp { + uint32_t index; /**< component source/sink/control index in control */ + union { + uint32_t uvalue; + int32_t svalue; + }; +} __packed; + +/* generic control data */ +struct sof_ipc_ctrl_data { + struct sof_ipc_reply rhdr; + uint32_t comp_id; + + /* control access and data type */ + uint32_t type; /**< enum sof_ipc_ctrl_type */ + uint32_t cmd; /**< enum sof_ipc_ctrl_cmd */ + uint32_t index; /**< control index for comps > 1 control */ + + /* control data - can either be appended or DMAed from host */ + struct sof_ipc_host_buffer buffer; + uint32_t num_elems; /**< in array elems or bytes for data type */ + uint32_t elems_remaining; /**< elems remaining if sent in parts */ + + uint32_t msg_index; /**< for large messages sent in parts */ + + /* reserved for future use */ + uint32_t reserved[6]; + + /* control data - add new types if needed */ + union { + /* channel values can be used by volume type controls */ + struct sof_ipc_ctrl_value_chan chanv[0]; + /* component values used by routing controls like mux, mixer */ + struct sof_ipc_ctrl_value_comp compv[0]; + /* data can be used by binary controls */ + struct sof_abi_hdr data[0]; + }; +} __packed; + +/** Event type */ +enum sof_ipc_ctrl_event_type { + SOF_CTRL_EVENT_GENERIC = 0, /**< generic event */ + SOF_CTRL_EVENT_GENERIC_METADATA, /**< generic event with metadata */ + SOF_CTRL_EVENT_KD, /**< keyword detection event */ + SOF_CTRL_EVENT_VAD, /**< voice activity detection event */ +}; + +/** + * Generic notification data. + */ +struct sof_ipc_comp_event { + struct sof_ipc_reply rhdr; + uint16_t src_comp_type; /**< COMP_TYPE_ */ + uint32_t src_comp_id; /**< source component id */ + uint32_t event_type; /**< event type - SOF_CTRL_EVENT_* */ + uint32_t num_elems; /**< in array elems or bytes for data type */ + + /* reserved for future use */ + uint32_t reserved[8]; + + /* control data - add new types if needed */ + union { + /* data can be used by binary controls */ + struct sof_abi_hdr data[0]; + /* event specific values */ + uint32_t event_value; + }; +} __packed; + +#endif diff --git a/include/sound/sof/dai-intel.h b/include/sound/sof/dai-intel.h new file mode 100644 index 000000000000..4bd83f7adddf --- /dev/null +++ b/include/sound/sof/dai-intel.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_DAI_INTEL_H__ +#define __INCLUDE_SOUND_SOF_DAI_INTEL_H__ + +#include <sound/sof/header.h> + + /* ssc1: TINTE */ +#define SOF_DAI_INTEL_SSP_QUIRK_TINTE (1 << 0) + /* ssc1: PINTE */ +#define SOF_DAI_INTEL_SSP_QUIRK_PINTE (1 << 1) + /* ssc2: SMTATF */ +#define SOF_DAI_INTEL_SSP_QUIRK_SMTATF (1 << 2) + /* ssc2: MMRATF */ +#define SOF_DAI_INTEL_SSP_QUIRK_MMRATF (1 << 3) + /* ssc2: PSPSTWFDFD */ +#define SOF_DAI_INTEL_SSP_QUIRK_PSPSTWFDFD (1 << 4) + /* ssc2: PSPSRWFDFD */ +#define SOF_DAI_INTEL_SSP_QUIRK_PSPSRWFDFD (1 << 5) +/* ssc1: LBM */ +#define SOF_DAI_INTEL_SSP_QUIRK_LBM (1 << 6) + + /* here is the possibility to define others aux macros */ + +#define SOF_DAI_INTEL_SSP_FRAME_PULSE_WIDTH_MAX 38 +#define SOF_DAI_INTEL_SSP_SLOT_PADDING_MAX 31 + +/* SSP clocks control settings + * + * Macros for clks_control field in sof_ipc_dai_ssp_params struct. + */ + +/* mclk 0 disable */ +#define SOF_DAI_INTEL_SSP_MCLK_0_DISABLE BIT(0) +/* mclk 1 disable */ +#define SOF_DAI_INTEL_SSP_MCLK_1_DISABLE BIT(1) +/* mclk keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_KA BIT(2) +/* bclk keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_KA BIT(3) +/* fs keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_FS_KA BIT(4) +/* bclk idle */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_IDLE_HIGH BIT(5) + +/* SSP Configuration Request - SOF_IPC_DAI_SSP_CONFIG */ +struct sof_ipc_dai_ssp_params { + struct sof_ipc_hdr hdr; + uint16_t reserved1; + uint16_t mclk_id; + + uint32_t mclk_rate; /* mclk frequency in Hz */ + uint32_t fsync_rate; /* fsync frequency in Hz */ + uint32_t bclk_rate; /* bclk frequency in Hz */ + + /* TDM */ + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots; + + /* data */ + uint32_t sample_valid_bits; + uint16_t tdm_slot_width; + uint16_t reserved2; /* alignment */ + + /* MCLK */ + uint32_t mclk_direction; + + uint16_t frame_pulse_width; + uint16_t tdm_per_slot_padding_flag; + uint32_t clks_control; + uint32_t quirks; +} __packed; + +/* HDA Configuration Request - SOF_IPC_DAI_HDA_CONFIG */ +struct sof_ipc_dai_hda_params { + struct sof_ipc_hdr hdr; + uint32_t link_dma_ch; +} __packed; + +/* DMIC Configuration Request - SOF_IPC_DAI_DMIC_CONFIG */ + +/* This struct is defined per 2ch PDM controller available in the platform. + * Normally it is sufficient to set the used microphone specific enables to 1 + * and keep other parameters as zero. The customizations are: + * + * 1. If a device mixes different microphones types with different polarity + * and/or the absolute polarity matters the PCM signal from a microphone + * can be inverted with the controls. + * + * 2. If the microphones in a stereo pair do not appear in captured stream + * in desired order due to board schematics choises they can be swapped with + * the clk_edge parameter. + * + * 3. If PDM bit errors are seen in capture (poor quality) the skew parameter + * that delays the sampling time of data by half cycles of DMIC source clock + * can be tried for improvement. However there is no guarantee for this to fix + * data integrity problems. + */ +struct sof_ipc_dai_dmic_pdm_ctrl { + struct sof_ipc_hdr hdr; + uint16_t id; /**< PDM controller ID */ + + uint16_t enable_mic_a; /**< Use A (left) channel mic (0 or 1)*/ + uint16_t enable_mic_b; /**< Use B (right) channel mic (0 or 1)*/ + + uint16_t polarity_mic_a; /**< Optionally invert mic A signal (0 or 1) */ + uint16_t polarity_mic_b; /**< Optionally invert mic B signal (0 or 1) */ + + uint16_t clk_edge; /**< Optionally swap data clock edge (0 or 1) */ + uint16_t skew; /**< Adjust PDM data sampling vs. clock (0..15) */ + + uint16_t reserved[3]; /**< Make sure the total size is 4 bytes aligned */ +} __packed; + +/* This struct contains the global settings for all 2ch PDM controllers. The + * version number used in configuration data is checked vs. version used by + * device driver src/drivers/dmic.c need to match. It is incremented from + * initial value 1 if updates done for the to driver would alter the operation + * of the microhone. + * + * Note: The microphone clock (pdmclk_min, pdmclk_max, duty_min, duty_max) + * parameters need to be set as defined in microphone data sheet. E.g. clock + * range 1.0 - 3.2 MHz is usually supported microphones. Some microphones are + * multi-mode capable and there may be denied mic clock frequencies between + * the modes. In such case set the clock range limits of the desired mode to + * avoid the driver to set clock to an illegal rate. + * + * The duty cycle could be set to 48-52% if not known. Generally these + * parameters can be altered within data sheet specified limits to match + * required audio application performance power. + * + * The microphone clock needs to be usually about 50-80 times the used audio + * sample rate. With highest sample rates above 48 kHz this can relaxed + * somewhat. + * + * The parameter wake_up_time describes how long time the microphone needs + * for the data line to produce valid output from mic clock start. The driver + * will mute the captured audio for the given time. The min_clock_on_time + * parameter is used to prevent too short clock bursts to happen. The driver + * will keep the clock active after capture stop if this time is not yet + * met. The unit for both is microseconds (us). Exceed of 100 ms will be + * treated as an error. + */ +struct sof_ipc_dai_dmic_params { + struct sof_ipc_hdr hdr; + uint32_t driver_ipc_version; /**< Version (1..N) */ + + uint32_t pdmclk_min; /**< Minimum microphone clock in Hz (100000..N) */ + uint32_t pdmclk_max; /**< Maximum microphone clock in Hz (min...N) */ + + uint32_t fifo_fs; /**< FIFO sample rate in Hz (8000..96000) */ + uint32_t reserved_1; /**< Reserved */ + uint16_t fifo_bits; /**< FIFO word length (16 or 32) */ + uint16_t reserved_2; /**< Reserved */ + + uint16_t duty_min; /**< Min. mic clock duty cycle in % (20..80) */ + uint16_t duty_max; /**< Max. mic clock duty cycle in % (min..80) */ + + uint32_t num_pdm_active; /**< Number of active pdm controllers */ + + uint32_t wake_up_time; /**< Time from clock start to data (us) */ + uint32_t min_clock_on_time; /**< Min. time that clk is kept on (us) */ + + /* reserved for future use */ + uint32_t reserved[6]; + + /**< variable number of pdm controller config */ + struct sof_ipc_dai_dmic_pdm_ctrl pdm[0]; +} __packed; + +#endif diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h new file mode 100644 index 000000000000..3b67c93ff101 --- /dev/null +++ b/include/sound/sof/dai.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_DAI_H__ +#define __INCLUDE_SOUND_SOF_DAI_H__ + +#include <sound/sof/header.h> +#include <sound/sof/dai-intel.h> + +/* + * DAI Configuration. + * + * Each different DAI type will have it's own structure and IPC cmd. + */ + +#define SOF_DAI_FMT_I2S 1 /**< I2S mode */ +#define SOF_DAI_FMT_RIGHT_J 2 /**< Right Justified mode */ +#define SOF_DAI_FMT_LEFT_J 3 /**< Left Justified mode */ +#define SOF_DAI_FMT_DSP_A 4 /**< L data MSB after FRM LRC */ +#define SOF_DAI_FMT_DSP_B 5 /**< L data MSB during FRM LRC */ +#define SOF_DAI_FMT_PDM 6 /**< Pulse density modulation */ + +#define SOF_DAI_FMT_CONT (1 << 4) /**< continuous clock */ +#define SOF_DAI_FMT_GATED (0 << 4) /**< clock is gated */ + +#define SOF_DAI_FMT_NB_NF (0 << 8) /**< normal bit clock + frame */ +#define SOF_DAI_FMT_NB_IF (2 << 8) /**< normal BCLK + inv FRM */ +#define SOF_DAI_FMT_IB_NF (3 << 8) /**< invert BCLK + nor FRM */ +#define SOF_DAI_FMT_IB_IF (4 << 8) /**< invert BCLK + FRM */ + +#define SOF_DAI_FMT_CBM_CFM (0 << 12) /**< codec clk & FRM master */ +#define SOF_DAI_FMT_CBS_CFM (2 << 12) /**< codec clk slave & FRM master */ +#define SOF_DAI_FMT_CBM_CFS (3 << 12) /**< codec clk master & frame slave */ +#define SOF_DAI_FMT_CBS_CFS (4 << 12) /**< codec clk & FRM slave */ + +#define SOF_DAI_FMT_FORMAT_MASK 0x000f +#define SOF_DAI_FMT_CLOCK_MASK 0x00f0 +#define SOF_DAI_FMT_INV_MASK 0x0f00 +#define SOF_DAI_FMT_MASTER_MASK 0xf000 + +/** \brief Types of DAI */ +enum sof_ipc_dai_type { + SOF_DAI_INTEL_NONE = 0, /**< None */ + SOF_DAI_INTEL_SSP, /**< Intel SSP */ + SOF_DAI_INTEL_DMIC, /**< Intel DMIC */ + SOF_DAI_INTEL_HDA, /**< Intel HD/A */ +}; + +/* general purpose DAI configuration */ +struct sof_ipc_dai_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t type; /**< DAI type - enum sof_ipc_dai_type */ + uint32_t dai_index; /**< index of this type dai */ + + /* physical protocol and clocking */ + uint16_t format; /**< SOF_DAI_FMT_ */ + uint16_t reserved16; /**< alignment */ + + /* reserved for future use */ + uint32_t reserved[8]; + + /* HW specific data */ + union { + struct sof_ipc_dai_ssp_params ssp; + struct sof_ipc_dai_dmic_params dmic; + struct sof_ipc_dai_hda_params hda; + }; +} __packed; + +#endif diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h new file mode 100644 index 000000000000..ccb6a004b37b --- /dev/null +++ b/include/sound/sof/header.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_HEADER_H__ +#define __INCLUDE_SOUND_SOF_HEADER_H__ + +#include <uapi/sound/sof/abi.h> + +/** \addtogroup sof_uapi uAPI + * SOF uAPI specification. + * @{ + */ + +/* + * IPC messages have a prefixed 32 bit identifier made up as follows :- + * + * 0xGCCCNNNN where + * G is global cmd type (4 bits) + * C is command type (12 bits) + * I is the ID number (16 bits) - monotonic and overflows + * + * This is sent at the start of the IPM message in the mailbox. Messages should + * not be sent in the doorbell (special exceptions for firmware . + */ + +/* Global Message - Generic */ +#define SOF_GLB_TYPE_SHIFT 28 +#define SOF_GLB_TYPE_MASK (0xf << SOF_GLB_TYPE_SHIFT) +#define SOF_GLB_TYPE(x) ((x) << SOF_GLB_TYPE_SHIFT) + +/* Command Message - Generic */ +#define SOF_CMD_TYPE_SHIFT 16 +#define SOF_CMD_TYPE_MASK (0xfff << SOF_CMD_TYPE_SHIFT) +#define SOF_CMD_TYPE(x) ((x) << SOF_CMD_TYPE_SHIFT) + +/* Global Message Types */ +#define SOF_IPC_GLB_REPLY SOF_GLB_TYPE(0x1U) +#define SOF_IPC_GLB_COMPOUND SOF_GLB_TYPE(0x2U) +#define SOF_IPC_GLB_TPLG_MSG SOF_GLB_TYPE(0x3U) +#define SOF_IPC_GLB_PM_MSG SOF_GLB_TYPE(0x4U) +#define SOF_IPC_GLB_COMP_MSG SOF_GLB_TYPE(0x5U) +#define SOF_IPC_GLB_STREAM_MSG SOF_GLB_TYPE(0x6U) +#define SOF_IPC_FW_READY SOF_GLB_TYPE(0x7U) +#define SOF_IPC_GLB_DAI_MSG SOF_GLB_TYPE(0x8U) +#define SOF_IPC_GLB_TRACE_MSG SOF_GLB_TYPE(0x9U) + +/* + * DSP Command Message Types + */ + +/* topology */ +#define SOF_IPC_TPLG_COMP_NEW SOF_CMD_TYPE(0x001) +#define SOF_IPC_TPLG_COMP_FREE SOF_CMD_TYPE(0x002) +#define SOF_IPC_TPLG_COMP_CONNECT SOF_CMD_TYPE(0x003) +#define SOF_IPC_TPLG_PIPE_NEW SOF_CMD_TYPE(0x010) +#define SOF_IPC_TPLG_PIPE_FREE SOF_CMD_TYPE(0x011) +#define SOF_IPC_TPLG_PIPE_CONNECT SOF_CMD_TYPE(0x012) +#define SOF_IPC_TPLG_PIPE_COMPLETE SOF_CMD_TYPE(0x013) +#define SOF_IPC_TPLG_BUFFER_NEW SOF_CMD_TYPE(0x020) +#define SOF_IPC_TPLG_BUFFER_FREE SOF_CMD_TYPE(0x021) + +/* PM */ +#define SOF_IPC_PM_CTX_SAVE SOF_CMD_TYPE(0x001) +#define SOF_IPC_PM_CTX_RESTORE SOF_CMD_TYPE(0x002) +#define SOF_IPC_PM_CTX_SIZE SOF_CMD_TYPE(0x003) +#define SOF_IPC_PM_CLK_SET SOF_CMD_TYPE(0x004) +#define SOF_IPC_PM_CLK_GET SOF_CMD_TYPE(0x005) +#define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006) +#define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007) + +/* component runtime config - multiple different types */ +#define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001) +#define SOF_IPC_COMP_GET_VALUE SOF_CMD_TYPE(0x002) +#define SOF_IPC_COMP_SET_DATA SOF_CMD_TYPE(0x003) +#define SOF_IPC_COMP_GET_DATA SOF_CMD_TYPE(0x004) + +/* DAI messages */ +#define SOF_IPC_DAI_CONFIG SOF_CMD_TYPE(0x001) +#define SOF_IPC_DAI_LOOPBACK SOF_CMD_TYPE(0x002) + +/* stream */ +#define SOF_IPC_STREAM_PCM_PARAMS SOF_CMD_TYPE(0x001) +#define SOF_IPC_STREAM_PCM_PARAMS_REPLY SOF_CMD_TYPE(0x002) +#define SOF_IPC_STREAM_PCM_FREE SOF_CMD_TYPE(0x003) +#define SOF_IPC_STREAM_TRIG_START SOF_CMD_TYPE(0x004) +#define SOF_IPC_STREAM_TRIG_STOP SOF_CMD_TYPE(0x005) +#define SOF_IPC_STREAM_TRIG_PAUSE SOF_CMD_TYPE(0x006) +#define SOF_IPC_STREAM_TRIG_RELEASE SOF_CMD_TYPE(0x007) +#define SOF_IPC_STREAM_TRIG_DRAIN SOF_CMD_TYPE(0x008) +#define SOF_IPC_STREAM_TRIG_XRUN SOF_CMD_TYPE(0x009) +#define SOF_IPC_STREAM_POSITION SOF_CMD_TYPE(0x00a) +#define SOF_IPC_STREAM_VORBIS_PARAMS SOF_CMD_TYPE(0x010) +#define SOF_IPC_STREAM_VORBIS_FREE SOF_CMD_TYPE(0x011) + +/* trace and debug */ +#define SOF_IPC_TRACE_DMA_PARAMS SOF_CMD_TYPE(0x001) +#define SOF_IPC_TRACE_DMA_POSITION SOF_CMD_TYPE(0x002) + +/* Get message component id */ +#define SOF_IPC_MESSAGE_ID(x) ((x) & 0xffff) + +/* maximum message size for mailbox Tx/Rx */ +#define SOF_IPC_MSG_MAX_SIZE 384 + +/* + * Structure Header - Header for all IPC structures except command structs. + * The size can be greater than the structure size and that means there is + * extended bespoke data beyond the end of the structure including variable + * arrays. + */ + +struct sof_ipc_hdr { + uint32_t size; /**< size of structure */ +} __packed; + +/* + * Command Header - Header for all IPC commands. Identifies IPC message. + * The size can be greater than the structure size and that means there is + * extended bespoke data beyond the end of the structure including variable + * arrays. + */ + +struct sof_ipc_cmd_hdr { + uint32_t size; /**< size of structure */ + uint32_t cmd; /**< SOF_IPC_GLB_ + cmd */ +} __packed; + +/* + * Generic reply message. Some commands override this with their own reply + * types that must include this at start. + */ +struct sof_ipc_reply { + struct sof_ipc_cmd_hdr hdr; + int32_t error; /**< negative error numbers */ +} __packed; + +/* + * Compound commands - SOF_IPC_GLB_COMPOUND. + * + * Compound commands are sent to the DSP as a single IPC operation. The + * commands are split into blocks and each block has a header. This header + * identifies the command type and the number of commands before the next + * header. + */ + +struct sof_ipc_compound_hdr { + struct sof_ipc_cmd_hdr hdr; + uint32_t count; /**< count of 0 means end of compound sequence */ +} __packed; + +/** @}*/ + +#endif diff --git a/include/sound/sof/info.h b/include/sound/sof/info.h new file mode 100644 index 000000000000..21dae04d8183 --- /dev/null +++ b/include/sound/sof/info.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_INFO_H__ +#define __INCLUDE_SOUND_SOF_INFO_H__ + +#include <sound/sof/header.h> +#include <sound/sof/stream.h> + +/* + * Firmware boot and version + */ + +#define SOF_IPC_MAX_ELEMS 16 + +/* extended data types that can be appended onto end of sof_ipc_fw_ready */ +enum sof_ipc_ext_data { + SOF_IPC_EXT_DMA_BUFFER = 0, + SOF_IPC_EXT_WINDOW, +}; + +/* FW version - SOF_IPC_GLB_VERSION */ +struct sof_ipc_fw_version { + struct sof_ipc_hdr hdr; + uint16_t major; + uint16_t minor; + uint16_t micro; + uint16_t build; + uint8_t date[12]; + uint8_t time[10]; + uint8_t tag[6]; + uint32_t abi_version; + + /* reserved for future use */ + uint32_t reserved[4]; +} __packed; + +/* FW ready Message - sent by firmware when boot has completed */ +struct sof_ipc_fw_ready { + struct sof_ipc_cmd_hdr hdr; + uint32_t dspbox_offset; /* dsp initiated IPC mailbox */ + uint32_t hostbox_offset; /* host initiated IPC mailbox */ + uint32_t dspbox_size; + uint32_t hostbox_size; + struct sof_ipc_fw_version version; + + /* Miscellaneous debug flags showing build/debug features enabled */ + union { + uint64_t reserved; + struct { + uint64_t build:1; + uint64_t locks:1; + uint64_t locks_verbose:1; + uint64_t gdb:1; + } bits; + } debug; + + /* reserved for future use */ + uint32_t reserved[4]; +} __packed; + +/* + * Extended Firmware data. All optional, depends on platform/arch. + */ +enum sof_ipc_region { + SOF_IPC_REGION_DOWNBOX = 0, + SOF_IPC_REGION_UPBOX, + SOF_IPC_REGION_TRACE, + SOF_IPC_REGION_DEBUG, + SOF_IPC_REGION_STREAM, + SOF_IPC_REGION_REGS, + SOF_IPC_REGION_EXCEPTION, +}; + +struct sof_ipc_ext_data_hdr { + struct sof_ipc_cmd_hdr hdr; + uint32_t type; /**< SOF_IPC_EXT_ */ +} __packed; + +struct sof_ipc_dma_buffer_elem { + struct sof_ipc_hdr hdr; + uint32_t type; /**< SOF_IPC_REGION_ */ + uint32_t id; /**< platform specific - used to map to host memory */ + struct sof_ipc_host_buffer buffer; +} __packed; + +/* extended data DMA buffers for IPC, trace and debug */ +struct sof_ipc_dma_buffer_data { + struct sof_ipc_ext_data_hdr ext_hdr; + uint32_t num_buffers; + + /* host files in buffer[n].buffer */ + struct sof_ipc_dma_buffer_elem buffer[]; +} __packed; + +struct sof_ipc_window_elem { + struct sof_ipc_hdr hdr; + uint32_t type; /**< SOF_IPC_REGION_ */ + uint32_t id; /**< platform specific - used to map to host memory */ + uint32_t flags; /**< R, W, RW, etc - to define */ + uint32_t size; /**< size of region in bytes */ + /* offset in window region as windows can be partitioned */ + uint32_t offset; +} __packed; + +/* extended data memory windows for IPC, trace and debug */ +struct sof_ipc_window { + struct sof_ipc_ext_data_hdr ext_hdr; + uint32_t num_windows; + struct sof_ipc_window_elem window[]; +} __packed; + +#endif diff --git a/include/sound/sof/pm.h b/include/sound/sof/pm.h new file mode 100644 index 000000000000..8ae3ad45bdf7 --- /dev/null +++ b/include/sound/sof/pm.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_PM_H__ +#define __INCLUDE_SOUND_SOF_PM_H__ + +#include <sound/sof/header.h> + +/* + * PM + */ + +/* PM context element */ +struct sof_ipc_pm_ctx_elem { + struct sof_ipc_hdr hdr; + uint32_t type; + uint32_t size; + uint64_t addr; +} __packed; + +/* + * PM context - SOF_IPC_PM_CTX_SAVE, SOF_IPC_PM_CTX_RESTORE, + * SOF_IPC_PM_CTX_SIZE + */ +struct sof_ipc_pm_ctx { + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t num_elems; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + struct sof_ipc_pm_ctx_elem elems[]; +} __packed; + +/* enable or disable cores - SOF_IPC_PM_CORE_ENABLE */ +struct sof_ipc_pm_core_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t enable_mask; +} __packed; + +#endif diff --git a/include/sound/sof/stream.h b/include/sound/sof/stream.h new file mode 100644 index 000000000000..643f175cb479 --- /dev/null +++ b/include/sound/sof/stream.h @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_STREAM_H__ +#define __INCLUDE_SOUND_SOF_STREAM_H__ + +#include <sound/sof/header.h> + +/* + * Stream configuration. + */ + +#define SOF_IPC_MAX_CHANNELS 8 + +/* common sample rates for use in masks */ +#define SOF_RATE_8000 (1 << 0) /**< 8000Hz */ +#define SOF_RATE_11025 (1 << 1) /**< 11025Hz */ +#define SOF_RATE_12000 (1 << 2) /**< 12000Hz */ +#define SOF_RATE_16000 (1 << 3) /**< 16000Hz */ +#define SOF_RATE_22050 (1 << 4) /**< 22050Hz */ +#define SOF_RATE_24000 (1 << 5) /**< 24000Hz */ +#define SOF_RATE_32000 (1 << 6) /**< 32000Hz */ +#define SOF_RATE_44100 (1 << 7) /**< 44100Hz */ +#define SOF_RATE_48000 (1 << 8) /**< 48000Hz */ +#define SOF_RATE_64000 (1 << 9) /**< 64000Hz */ +#define SOF_RATE_88200 (1 << 10) /**< 88200Hz */ +#define SOF_RATE_96000 (1 << 11) /**< 96000Hz */ +#define SOF_RATE_176400 (1 << 12) /**< 176400Hz */ +#define SOF_RATE_192000 (1 << 13) /**< 192000Hz */ + +/* continuous and non-standard rates for flexibility */ +#define SOF_RATE_CONTINUOUS (1 << 30) /**< range */ +#define SOF_RATE_KNOT (1 << 31) /**< non-continuous */ + +/* generic PCM flags for runtime settings */ +#define SOF_PCM_FLAG_XRUN_STOP (1 << 0) /**< Stop on any XRUN */ + +/* stream PCM frame format */ +enum sof_ipc_frame { + SOF_IPC_FRAME_S16_LE = 0, + SOF_IPC_FRAME_S24_4LE, + SOF_IPC_FRAME_S32_LE, + SOF_IPC_FRAME_FLOAT, + /* other formats here */ +}; + +/* stream buffer format */ +enum sof_ipc_buffer_format { + SOF_IPC_BUFFER_INTERLEAVED, + SOF_IPC_BUFFER_NONINTERLEAVED, + /* other formats here */ +}; + +/* stream direction */ +enum sof_ipc_stream_direction { + SOF_IPC_STREAM_PLAYBACK = 0, + SOF_IPC_STREAM_CAPTURE, +}; + +/* stream ring info */ +struct sof_ipc_host_buffer { + struct sof_ipc_hdr hdr; + uint32_t phy_addr; + uint32_t pages; + uint32_t size; + uint32_t reserved[3]; +} __packed; + +struct sof_ipc_stream_params { + struct sof_ipc_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t direction; /**< enum sof_ipc_stream_direction */ + uint32_t frame_fmt; /**< enum sof_ipc_frame */ + uint32_t buffer_fmt; /**< enum sof_ipc_buffer_format */ + uint32_t rate; + uint16_t stream_tag; + uint16_t channels; + uint16_t sample_valid_bytes; + uint16_t sample_container_bytes; + + /* for notifying host period has completed - 0 means no period IRQ */ + uint32_t host_period_bytes; + + uint32_t reserved[2]; + uint16_t chmap[SOF_IPC_MAX_CHANNELS]; /**< channel map - SOF_CHMAP_ */ +} __packed; + +/* PCM params info - SOF_IPC_STREAM_PCM_PARAMS */ +struct sof_ipc_pcm_params { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; + uint32_t flags; /**< generic PCM flags - SOF_PCM_FLAG_ */ + uint32_t reserved[2]; + struct sof_ipc_stream_params params; +} __packed; + +/* PCM params info reply - SOF_IPC_STREAM_PCM_PARAMS_REPLY */ +struct sof_ipc_pcm_params_reply { + struct sof_ipc_reply rhdr; + uint32_t comp_id; + uint32_t posn_offset; +} __packed; + +/* free stream - SOF_IPC_STREAM_PCM_PARAMS */ +struct sof_ipc_stream { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +/* flags indicating which time stamps are in sync with each other */ +#define SOF_TIME_HOST_SYNC (1 << 0) +#define SOF_TIME_DAI_SYNC (1 << 1) +#define SOF_TIME_WALL_SYNC (1 << 2) +#define SOF_TIME_STAMP_SYNC (1 << 3) + +/* flags indicating which time stamps are valid */ +#define SOF_TIME_HOST_VALID (1 << 8) +#define SOF_TIME_DAI_VALID (1 << 9) +#define SOF_TIME_WALL_VALID (1 << 10) +#define SOF_TIME_STAMP_VALID (1 << 11) + +/* flags indicating time stamps are 64bit else 3use low 32bit */ +#define SOF_TIME_HOST_64 (1 << 16) +#define SOF_TIME_DAI_64 (1 << 17) +#define SOF_TIME_WALL_64 (1 << 18) +#define SOF_TIME_STAMP_64 (1 << 19) + +struct sof_ipc_stream_posn { + struct sof_ipc_reply rhdr; + uint32_t comp_id; /**< host component ID */ + uint32_t flags; /**< SOF_TIME_ */ + uint32_t wallclock_hz; /**< frequency of wallclock in Hz */ + uint32_t timestamp_ns; /**< resolution of timestamp in ns */ + uint64_t host_posn; /**< host DMA position in bytes */ + uint64_t dai_posn; /**< DAI DMA position in bytes */ + uint64_t comp_posn; /**< comp position in bytes */ + uint64_t wallclock; /**< audio wall clock */ + uint64_t timestamp; /**< system time stamp */ + uint32_t xrun_comp_id; /**< comp ID of XRUN component */ + int32_t xrun_size; /**< XRUN size in bytes */ +} __packed; + +#endif diff --git a/include/sound/sof/trace.h b/include/sound/sof/trace.h new file mode 100644 index 000000000000..49f0f310d902 --- /dev/null +++ b/include/sound/sof/trace.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_TRACE_H__ +#define __INCLUDE_SOUND_SOF_TRACE_H__ + +#include <sound/sof/header.h> +#include <sound/sof/stream.h> + +/* + * DMA for Trace + */ + +#define SOF_TRACE_FILENAME_SIZE 32 + +/* DMA for Trace params info - SOF_IPC_DEBUG_DMA_PARAMS */ +struct sof_ipc_dma_trace_params { + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t stream_tag; +} __packed; + +/* DMA for Trace params info - SOF_IPC_DEBUG_DMA_PARAMS */ +struct sof_ipc_dma_trace_posn { + struct sof_ipc_reply rhdr; + uint32_t host_offset; /* Offset of DMA host buffer */ + uint32_t overflow; /* overflow bytes if any */ + uint32_t messages; /* total trace messages */ +} __packed; + +/* + * Commom debug + */ + +/* + * SOF panic codes + */ +#define SOF_IPC_PANIC_MAGIC 0x0dead000 +#define SOF_IPC_PANIC_MAGIC_MASK 0x0ffff000 +#define SOF_IPC_PANIC_CODE_MASK 0x00000fff +#define SOF_IPC_PANIC_MEM (SOF_IPC_PANIC_MAGIC | 0x0) +#define SOF_IPC_PANIC_WORK (SOF_IPC_PANIC_MAGIC | 0x1) +#define SOF_IPC_PANIC_IPC (SOF_IPC_PANIC_MAGIC | 0x2) +#define SOF_IPC_PANIC_ARCH (SOF_IPC_PANIC_MAGIC | 0x3) +#define SOF_IPC_PANIC_PLATFORM (SOF_IPC_PANIC_MAGIC | 0x4) +#define SOF_IPC_PANIC_TASK (SOF_IPC_PANIC_MAGIC | 0x5) +#define SOF_IPC_PANIC_EXCEPTION (SOF_IPC_PANIC_MAGIC | 0x6) +#define SOF_IPC_PANIC_DEADLOCK (SOF_IPC_PANIC_MAGIC | 0x7) +#define SOF_IPC_PANIC_STACK (SOF_IPC_PANIC_MAGIC | 0x8) +#define SOF_IPC_PANIC_IDLE (SOF_IPC_PANIC_MAGIC | 0x9) +#define SOF_IPC_PANIC_WFI (SOF_IPC_PANIC_MAGIC | 0xa) + +/* panic info include filename and line number */ +struct sof_ipc_panic_info { + struct sof_ipc_hdr hdr; + uint32_t code; /* SOF_IPC_PANIC_ */ + char filename[SOF_TRACE_FILENAME_SIZE]; + uint32_t linenum; +} __packed; + +#endif diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c new file mode 100644 index 000000000000..02910d82923f --- /dev/null +++ b/sound/soc/sof/ipc.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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 "ops.h" + +/* + * IPC message default size and timeout (ms). + * TODO: allow platforms to set size and timeout. + */ +#define IPC_TIMEOUT_MS 300 + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id); +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); + +/* + * 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; +}; + +#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; + + 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: SET_VALUE"; + 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: + 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; + default: + str = "unknown GLB command"; break; + } + + if (str2) + 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) +{ + 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(IPC_TIMEOUT_MS)); + + if (ret == 0) { + dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n", + hdr->cmd, hdr->size); + snd_sof_dsp_dbg_dump(ipc->sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_trace_notify_for_error(ipc->sdev); + ret = -ETIMEDOUT; + } else { + /* copy the data returned from DSP */ + ret = snd_sof_dsp_get_reply(sdev, msg); + if (msg->reply_size) + memcpy(reply_data, msg->reply_data, msg->reply_size); + 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); + } + + snd_sof_dsp_cmd_done(sdev, SOF_IPC_DSP_REPLY); + + 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) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + 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); + + if (ipc->disable_ipc_tx) { + ret = -ENODEV; + goto unlock; + } + + /* + * 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; + + /* attach any data */ + if (msg_bytes) + memcpy(msg->msg_data, msg_data, msg_bytes); + + 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) { + /* So far IPC TX never fails, consider making the above void */ + dev_err_ratelimited(sdev->dev, + "error: ipc tx failed with error %d\n", + ret); + goto unlock; + } + + ipc_log_header(sdev->dev, "ipc tx", msg->header); + + /* now wait for completion */ + if (!ret) + ret = tx_wait_done(ipc, msg, reply_data); + +unlock: + mutex_unlock(&ipc->tx_mutex); + + return ret; +} +EXPORT_SYMBOL(sof_ipc_tx_message); + +/* mark IPC message as complete - locks held by caller */ +static void sof_ipc_tx_msg_reply_complete(struct snd_sof_ipc *ipc, + struct snd_sof_ipc_msg *msg) +{ + msg->ipc_complete = true; + wake_up(&msg->waitq); +} + +/* handle reply message from DSP */ +int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + unsigned long flags; + + /* + * 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_irqsave(&sdev->ipc_lock, flags); + + if (msg->ipc_complete) { + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + dev_err(sdev->dev, "error: no reply expected, received 0x%x", + msg_id); + return -EINVAL; + } + + /* wake up and return the error if we have waiters on this message ? */ + sof_ipc_tx_msg_reply_complete(sdev->ipc, msg); + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + + return 0; +} +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_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &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->boot_complete) { + err = sof_ops(sdev)->fw_ready(sdev, cmd); + if (err < 0) { + /* + * this indicates a mismatch in ABI + * between the driver and fw + */ + dev_err(sdev->dev, "error: ABI mismatch %d\n", + err); + } else { + /* firmware boot completed OK */ + sdev->boot_complete = true; + } + + /* 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); + + /* tell DSP we are done */ + snd_sof_dsp_cmd_done(sdev, SOF_IPC_HOST_REPLY); +} +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_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &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 sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + u32 posn_offset; + int direction; + + /* check if we have stream box */ + if (sdev->stream_box.size == 0) { + /* read back full message */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &posn, + sizeof(posn)); + + spcm = snd_sof_find_spcm_comp(sdev, posn.comp_id, &direction); + } else { + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + } + + if (!spcm) { + dev_err(sdev->dev, + "error: period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + /* have stream box read from stream box */ + if (sdev->stream_box.size != 0) { + posn_offset = spcm->posn_offset[direction]; + snd_sof_dsp_mailbox_read(sdev, posn_offset, &posn, + sizeof(posn)); + + dev_dbg(sdev->dev, "posn mailbox: posn offset is 0x%x", + posn_offset); + } + + dev_dbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", + posn.host_posn, posn.dai_posn, posn.wallclock); + + memcpy(&spcm->stream[direction].posn, &posn, sizeof(posn)); + + /* only inform ALSA for period_wakeup mode */ + if (!spcm->stream[direction].substream->runtime->no_period_wakeup) + snd_pcm_period_elapsed(spcm->stream[direction].substream); +} + +/* DSP notifies host of an XRUN within FW */ +static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + u32 posn_offset; + int direction; + + /* check if we have stream MMIO on this platform */ + if (sdev->stream_box.size == 0) { + /* read back full message */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &posn, + sizeof(posn)); + + spcm = snd_sof_find_spcm_comp(sdev, posn.comp_id, &direction); + } else { + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + } + + if (!spcm) { + dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + /* have stream box read from stream box */ + if (sdev->stream_box.size != 0) { + posn_offset = spcm->posn_offset[direction]; + snd_sof_dsp_mailbox_read(sdev, posn_offset, &posn, + sizeof(posn)); + + dev_dbg(sdev->dev, "posn mailbox: posn offset is 0x%x", + posn_offset); + } + + 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(&spcm->stream[direction].posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(spcm->stream[direction].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_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn) +{ + 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); + +/* + * IPC get()/set() for kcontrols. + */ + +int snd_sof_ipc_set_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + int err; + + /* read firmware volume */ + if (scontrol->readback_offset != 0) { + /* we can read value header via mmaped region */ + snd_sof_dsp_block_write(sdev, sdev->mmio_bar, + scontrol->readback_offset, cdata->chanv, + sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems); + + } else { + /* write value via slower IPC */ + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->rhdr.hdr.size = scontrol->size; + cdata->comp_id = scontrol->comp_id; + cdata->num_elems = scontrol->num_channels; + + /* send IPC to the DSP */ + 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: failed to set control %d values\n", + cdata->comp_id); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_set_comp_data); + +int snd_sof_ipc_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + int err; + + /* read firmware byte counters */ + if (scontrol->readback_offset != 0) { + /* we can read values via mmaped region */ + snd_sof_dsp_block_read(sdev, sdev->mmio_bar, + scontrol->readback_offset, cdata->chanv, + sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems); + + } else { + /* read position via slower IPC */ + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->rhdr.hdr.size = scontrol->size; + cdata->comp_id = scontrol->comp_id; + cdata->num_elems = scontrol->num_channels; + + /* send IPC to the DSP */ + 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: failed to get control %d values\n", + cdata->comp_id); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_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 (ready->debug.bits.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->debug.bits.gdb ? "enabled" : "disabled", + ready->debug.bits.locks ? "enabled" : "disabled", + ready->debug.bits.locks_verbose ? "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; + + /* check if mandatory ops required for ipc are defined */ + if (!sof_ops(sdev)->fw_ready) { + dev_err(sdev->dev, "error: ipc mandatory ops not defined\n"); + return NULL; + } + + ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return NULL; + + 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; + + /* 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);
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add support for exposing PCMs to userspace. PCMs are defined by topology and the operations in this patch map to SOF IPC calls.
The .ignore_module_refcount field is set to allow for module load/unload tests. There is no risk of the sof-pci/acpi-dev module being removed while the platform components are in use. This may need to be revisited when DT platforms are supported.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/pcm.c | 723 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 723 insertions(+) create mode 100644 sound/soc/sof/pcm.c
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c new file mode 100644 index 000000000000..6dd94c00f7af --- /dev/null +++ b/sound/soc/sof/pcm.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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 "ops.h" + +#define DRV_NAME "sof-audio-component" + +/* Create DMA buffer page table for DSP */ +static int create_page_table(struct snd_pcm_substream *substream, + unsigned char *dma_area, size_t size) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int stream = substream->stream; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + return snd_sof_create_page_table(sdev, dmab, + spcm->stream[stream].page_table.area, size); +} + +/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_pcm_params pcm; + struct sof_ipc_pcm_params_reply ipc_params_reply; + int posn_offset; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: hw params stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); + + memset(&pcm, 0, sizeof(pcm)); + + /* allocate audio buffer pages */ + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(sdev->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), spcm->pcm.pcm_id); + return ret; + } + + /* create compressed page table for audio firmware */ + ret = create_page_table(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 */ + switch (params_width(params)) { + case 16: + pcm.params.sample_container_bytes = 2; + break; + case 24: + pcm.params.sample_container_bytes = 4; + break; + case 32: + pcm.params.sample_container_bytes = 4; + break; + default: + return -EINVAL; + } + + /* 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(sdev->dev, "error: platform hw params failed\n"); + return ret; + } + + dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: hw params ipc failed for stream %d\n", + pcm.params.stream_tag); + return ret; + } + + /* validate offset */ + posn_offset = ipc_params_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) { + dev_err(sdev->dev, "error: got wrong posn offset 0x%x for PCM %d\n", + posn_offset, spcm->pcm.pcm_id); + return -EINVAL; + } + spcm->posn_offset[substream->stream] = + sdev->stream_box.offset + posn_offset; + + /* save pcm hw_params */ + memcpy(&spcm->params[substream->stream], params, sizeof(*params)); + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + + return ret; +} + +static int sof_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + + snd_pcm_lib_free_pages(substream); + return ret; +} + +static int sof_restore_hw_params(struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, + struct snd_sof_dev *sdev) +{ + snd_pcm_uframes_t host; + u64 host_posn; + int ret; + + /* resume stream */ + host_posn = spcm->stream[substream->stream].posn.host_posn; + host = bytes_to_frames(substream->runtime, host_posn); + dev_dbg(sdev->dev, + "PCM: resume stream %d dir %d DMA position %lu\n", + spcm->pcm.pcm_id, substream->stream, host); + + /* set hw_params */ + ret = sof_pcm_hw_params(substream, + &spcm->params[substream->stream]); + if (ret < 0) { + dev_err(sdev->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_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->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; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + + /* check if the stream hw_params needs to be restored */ + if (spcm->restore_stream[substream->stream]) { + + /* restore hw_params */ + ret = sof_restore_hw_params(substream, spcm, sdev); + if (ret < 0) + return ret; + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + + /* trigger start */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + } else { + + /* trigger pause release */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; + } + break; + case SNDRV_PCM_TRIGGER_START: + /* fall through */ + case SNDRV_PCM_TRIGGER_RESUME: + + /* check if the stream hw_params needs to be restored */ + if (spcm->restore_stream[substream->stream]) { + + /* restore hw_params */ + ret = sof_restore_hw_params(substream, spcm, sdev); + if (ret < 0) + return ret; + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + } + + /* trigger stream */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + /* fallthrough */ + case SNDRV_PCM_TRIGGER_STOP: + + /* Check if stream was marked for restore before suspend */ + if (spcm->restore_stream[substream->stream]) { + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + + /* do not send ipc as the stream hasn't been set up */ + return 0; + } + + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + break; + default: + dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd); + return -EINVAL; + } + + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); +} + +static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + snd_pcm_uframes_t host = 0, dai = 0; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + /* if have dsp ops pointer callback, use that directly */ + if (sof_ops(sdev)->pcm_pointer) + return sof_ops(sdev)->pcm_pointer(sdev, substream); + + spcm = snd_sof_find_spcm_dai(sdev, 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_dbg(sdev->dev, "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", + spcm->pcm.pcm_id, substream->stream, host, dai); + + return host; +} + +static int sof_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_soc_tplg_stream_caps *caps; + int ret; + int err; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + caps = &spcm->pcm.caps[substream->stream]; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: pcm open failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* set any runtime constraints based on topology */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + le32_to_cpu(caps->period_size_min)); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + le32_to_cpu(caps->period_size_min)); + + /* set runtime config */ + runtime->hw.info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + 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); + runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max); + + dev_dbg(sdev->dev, "period min %zd max %zd bytes\n", + runtime->hw.period_bytes_min, + runtime->hw.period_bytes_max); + dev_dbg(sdev->dev, "period count %d max %d\n", + runtime->hw.periods_min, + runtime->hw.periods_max); + dev_dbg(sdev->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; + + ret = snd_sof_pcm_platform_open(sdev, substream); + if (ret < 0) { + dev_err(sdev->dev, "error: pcm open failed %d\n", + ret); + + pm_runtime_mark_last_busy(sdev->dev); + + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: pcm close failed to idle %d\n", + err); + } + + return ret; +} + +static int sof_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + int err; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: close stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + err = snd_sof_pcm_platform_close(sdev, substream); + if (err < 0) { + dev_err(sdev->dev, "error: pcm close failed %d\n", + err); + /* + * keep going, no point in preventing the close + * from happening + */ + } + + pm_runtime_mark_last_busy(sdev->dev); + + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: pcm close failed to idle %d\n", + err); + + return 0; +} + +static struct snd_pcm_ops sof_pcm_ops = { + .open = sof_pcm_open, + .close = sof_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sof_pcm_hw_params, + .hw_free = sof_pcm_hw_free, + .trigger = sof_pcm_trigger, + .pointer = sof_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* + * Pre-allocate playback/capture audio buffer pages. + * no need to explicitly release memory preallocated by sof_pcm_new in pcm_free + * snd_pcm_lib_preallocate_free_for_all() is called by the core. + */ +static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_tplg_stream_caps *caps; + int ret = 0, stream = SNDRV_PCM_STREAM_PLAYBACK; + + /* find SOF PCM for this RTD */ + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + dev_dbg(sdev->dev, "creating new PCM %s\n", spcm->pcm.pcm_name); + + /* 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(sdev->dev, "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to pre-allocate capture audio buffer pages */ + if (!spcm->pcm.capture) + return ret; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate capture audio buffer pages */ + dev_dbg(sdev->dev, "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); + + return ret; +} + +/* 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, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_dai *dai = + snd_sof_find_dai(sdev, (char *)rtd->dai_link->name); + + /* no topology exists for this BE, try a common configuration */ + if (!dai) { + dev_warn(sdev->dev, "warning: no topology found for BE DAI %s config\n", + 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(sdev->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(sdev->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(sdev->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(sdev->dev, + "error: invalid fmt %d for DAI type %d\n", + dai->comp_dai.config.frame_fmt, + dai->dai_config->type); + } + /* TODO: add any other DMIC specific fixups */ + break; + case SOF_DAI_INTEL_HDA: + /* do nothing for HDA dai_link */ + break; + default: + dev_err(sdev->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(sdev, tplg_filename); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP topology %d\n", + ret); + return ret; + } + + /* + * Some platforms in SOF, ex: BYT, may not have their platform PM + * callbacks set. Increment the usage count so as to + * prevent the device entering runtime suspend. + */ + if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) + pm_runtime_get_noresume(sdev->dev); + + return ret; +} + +static void sof_pcm_remove(struct snd_soc_component *component) +{ +} + +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->ops = &sof_pcm_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + pd->compr_ops = &sof_compressed_ops; +#endif + pd->pcm_new = 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"; + + /* do not increase the refcount in core */ + pd->ignore_module_refcount = 1; +}
On Thu, 21 Mar 2019 17:10:07 +0100, Pierre-Louis Bossart wrote:
+/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
....
- /* container size */
- switch (params_width(params)) {
- case 16:
pcm.params.sample_container_bytes = 2;
break;
- case 24:
pcm.params.sample_container_bytes = 4;
break;
- case 32:
pcm.params.sample_container_bytes = 4;
break;
- default:
return -EINVAL;
- }
This can be simply snd_pcm_format_physical_width() / 8.
+static int sof_pcm_open(struct snd_pcm_substream *substream) +{
....
- /* set any runtime constraints based on topology */
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
le32_to_cpu(caps->period_size_min));
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
le32_to_cpu(caps->period_size_min));
Is period_size_min in frames or in bytes? The code below refers as byte size while this refers as frames, so they look inconsistent.
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
- 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);
- runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max);
These refer as bytes...
thanks,
Takashi
On Thu, 2019-04-04 at 12:37 +0200, Takashi Iwai wrote:
/* set runtime config */
runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Not atm, and I don't see this as an ask in the near future either, but it's technically possible given the open firmware.
Liam
On 2019/4/4 下午6:37, Takashi Iwai wrote:
On Thu, 21 Mar 2019 17:10:07 +0100, Pierre-Louis Bossart wrote:
+/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
....
- /* container size */
- switch (params_width(params)) {
- case 16:
pcm.params.sample_container_bytes = 2;
break;
- case 24:
pcm.params.sample_container_bytes = 4;
break;
- case 32:
pcm.params.sample_container_bytes = 4;
break;
- default:
return -EINVAL;
- }
This can be simply snd_pcm_format_physical_width() / 8.
+static int sof_pcm_open(struct snd_pcm_substream *substream) +{
....
- /* set any runtime constraints based on topology */
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
le32_to_cpu(caps->period_size_min));
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
le32_to_cpu(caps->period_size_min));
Is period_size_min in frames or in bytes? The code below refers as byte size while this refers as frames, so they look inconsistent.
Yep, you are right, we should use SNDRV_PCM_HW_PARAM_BUFFER_BYTES and SNDRV_PCM_HW_PARAM_PERIOD_BYTES here, thanks for pointing out.
They are configured(in tplg file) following comments in definition(somewhat confusion comparing to ALSA notion where 'size' means in frames?) of struct snd_soc_tplg_stream_caps: __le32 period_size_min; /* min period size bytes */ __le32 period_size_max; /* max period size bytes */ __le32 buffer_size_min; /* min buffer size bytes */ __le32 buffer_size_max; /* max buffer size bytes */
Thanks, ~Keyon
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
- 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);
- runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max);
These refer as bytes...
thanks,
Takashi _______________________________________________ Sound-open-firmware mailing list Sound-open-firmware@alsa-project.org https://mailman.alsa-project.org/mailman/listinfo/sound-open-firmware
- /* container size */
- switch (params_width(params)) {
- case 16:
pcm.params.sample_container_bytes = 2;
break;
- case 24:
pcm.params.sample_container_bytes = 4;
break;
- case 32:
pcm.params.sample_container_bytes = 4;
break;
- default:
return -EINVAL;
- }
This can be simply snd_pcm_format_physical_width() / 8.
ok
+static int sof_pcm_open(struct snd_pcm_substream *substream) +{
....
- /* set any runtime constraints based on topology */
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
le32_to_cpu(caps->period_size_min));
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
le32_to_cpu(caps->period_size_min));
Is period_size_min in frames or in bytes? The code below refers as byte size while this refers as frames, so they look inconsistent.
need to check, the topology files always mention frames not bytes.
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Ah this is interesting. All previous Intel drivers set this flag, baytrail/haswell/broadwell, atom-sst and skylake. Only Skylake+ devices can restart at exactly the right position, but it's my understanding that no one ever enabled this mode.
Now I see an awful amount of devices that set this INFO_RESUME flag, and I'd be surprised if they all supported a full resume, could it be that there's a requirement that fell between the cracks on this one?
- 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);
- runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max);
These refer as bytes...
will check as well.
On Thu, 04 Apr 2019 15:53:34 +0200, Pierre-Louis Bossart wrote:
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Ah this is interesting. All previous Intel drivers set this flag, baytrail/haswell/broadwell, atom-sst and skylake. Only Skylake+ devices can restart at exactly the right position, but it's my understanding that no one ever enabled this mode.
Now I see an awful amount of devices that set this INFO_RESUME flag, and I'd be surprised if they all supported a full resume, could it be that there's a requirement that fell between the cracks on this one?
The SNDRV_PCM_INFO_RESUME is merely a hint, so far, and user-space would eventually fall back to the default restart procedure even if something fails. That is, that's not that important, but it should be dropped if the general SOF framework doesn't support for it at all. But, the framework itself seems supporting the full resume feature, judging from your and others replies, so we can keep it.
thanks,
Takashi
On Thu, 2019-04-04 at 12:37 +0200, Takashi Iwai wrote:
On Thu, 21 Mar 2019 17:10:07 +0100, Pierre-Louis Bossart wrote:
+/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
....
- /* container size */
- switch (params_width(params)) {
- case 16:
pcm.params.sample_container_bytes = 2;
break;
- case 24:
pcm.params.sample_container_bytes = 4;
break;
- case 32:
pcm.params.sample_container_bytes = 4;
break;
- default:
return -EINVAL;
- }
This can be simply snd_pcm_format_physical_width() / 8.
+static int sof_pcm_open(struct snd_pcm_substream *substream) +{
....
- /* set any runtime constraints based on topology */
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
le32_to_cpu(caps->period_size_min));
- snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
le32_to_cpu(caps->period_size_min));
Is period_size_min in frames or in bytes? The code below refers as byte size while this refers as frames, so they look inconsistent.
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
Thanks, Ranjani
- 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);
- runtime->hw.buffer_bytes_max = le32_to_cpu(caps-
buffer_size_max);
These refer as bytes...
thanks,
Takashi _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org https://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Thu, 04 Apr 2019 21:13:43 +0200, Ranjani Sridharan wrote:
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
So, SOF mandates the re-setup of the hw params in BE at resume? Then the right place to do is in the prepare callback. Without SNDRV_PCM_INFO_RESUME flag, the PCM core won't trigger RESUME, but returns -ENOSYS. Then user-space will do prepare, and start with the normal trigger.
You'd need a flag indicating the BE hw_params resetup, and at the beginning of prepare function, the flag is checked and the (internal) hw_params is done accordingly.
Takashi
On Fri, 2019-04-05 at 14:30 +0200, Takashi Iwai wrote:
On Thu, 04 Apr 2019 21:13:43 +0200, Ranjani Sridharan wrote:
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
So, SOF mandates the re-setup of the hw params in BE at resume?
Yes. That's correct.
Then the right place to do is in the prepare callback. Without SNDRV_PCM_INFO_RESUME flag, the PCM core won't trigger RESUME, but returns -ENOSYS. Then user-space will do prepare, and start with the normal trigger.
You'd need a flag indicating the BE hw_params resetup, and at the beginning of prepare function, the flag is checked and the (internal) hw_params is done accordingly.
This makes sense. Thank you for your help. We will change the flow accordingly.
Thanks, Ranjani
Takashi
On 4/5/19 7:30 AM, Takashi Iwai wrote:
On Thu, 04 Apr 2019 21:13:43 +0200, Ranjani Sridharan wrote:
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
So, SOF mandates the re-setup of the hw params in BE at resume? Then the right place to do is in the prepare callback. Without SNDRV_PCM_INFO_RESUME flag, the PCM core won't trigger RESUME, but returns -ENOSYS. Then user-space will do prepare, and start with the normal trigger.
Takashi, some of us at Intel are a bit nervous about returning an error to force the application to jump to its error-handling code (typically only done for xruns and not always rock-solid) and as a side effect get a prepare ioctl. I checked that e.g. CRAS does support this mode but I wonder how many application developers know about this. I personally didn't.
Moreover, it seems that the default behavior is rather to support RESUME, even if the hardware can't precisely restart from the same position, so we are concerned about exposing new errors when SOF is selected instead of legacy drivers (Baytrail, Atom-SST and Skylake-SST).
In short, is this really your recommendation to remove this INFO_RESUME flag? And if yes, should we demote all existing Intel drivers and remove this flag as well?
Thanks for your feedback -Pierre
On Mon, 08 Apr 2019 21:31:43 +0200, Pierre-Louis Bossart wrote:
On 4/5/19 7:30 AM, Takashi Iwai wrote:
On Thu, 04 Apr 2019 21:13:43 +0200, Ranjani Sridharan wrote:
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
So, SOF mandates the re-setup of the hw params in BE at resume? Then the right place to do is in the prepare callback. Without SNDRV_PCM_INFO_RESUME flag, the PCM core won't trigger RESUME, but returns -ENOSYS. Then user-space will do prepare, and start with the normal trigger.
Takashi, some of us at Intel are a bit nervous about returning an error to force the application to jump to its error-handling code (typically only done for xruns and not always rock-solid) and as a side effect get a prepare ioctl. I checked that e.g. CRAS does support this mode but I wonder how many application developers know about this. I personally didn't.
Essentially all applications are supposed to do so. There are only few drivers that support really the full resume. Some drivers set the flag, but many of them must be incorrect, especially ASoC ones.
Moreover, it seems that the default behavior is rather to support RESUME, even if the hardware can't precisely restart from the same position, so we are concerned about exposing new errors when SOF is selected instead of legacy drivers (Baytrail, Atom-SST and Skylake-SST).
In short, is this really your recommendation to remove this INFO_RESUME flag? And if yes, should we demote all existing Intel drivers and remove this flag as well?
Yes, better to start with less -- especially if this allows us to simplify the code a lot.
Takashi
- /* set runtime config */
- runtime->hw.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
Does SOF support the full resume? That is, the stream gets resumed at the exact position that was suspended. Most devices can't, hence they don't set *_INFO_RESUME flag and let user-space restarting.
Hi Takashi,
Thanks for your comment. The SOF driver definitely cannot guarantee to resume from the exact same position due to the fact that triggers for the host dma and link dma are not synchronized and also IPC scheduling will affect this. So we should remove INOF_RESUME from hw.info.
I do one follow up question for you. When a stream resumes/restarts after suspend, we have the need for restoring the hw_params in the SOF driver prior to handling the START trigger. I noticed that the legacy/skylake driver does not seem to do this though. Do you have any insights on what we might be missing in the SOF driver?
So, SOF mandates the re-setup of the hw params in BE at resume? Then the right place to do is in the prepare callback. Without SNDRV_PCM_INFO_RESUME flag, the PCM core won't trigger RESUME, but returns -ENOSYS. Then user-space will do prepare, and start with the normal trigger.
Takashi, some of us at Intel are a bit nervous about returning an error to force the application to jump to its error-handling code (typically only done for xruns and not always rock-solid) and as a side effect get a prepare ioctl. I checked that e.g. CRAS does support this mode but I wonder how many application developers know about this. I personally didn't.
Essentially all applications are supposed to do so. There are only few drivers that support really the full resume. Some drivers set the flag, but many of them must be incorrect, especially ASoC ones.
Moreover, it seems that the default behavior is rather to support RESUME, even if the hardware can't precisely restart from the same position, so we are concerned about exposing new errors when SOF is selected instead of legacy drivers (Baytrail, Atom-SST and Skylake-SST).
In short, is this really your recommendation to remove this INFO_RESUME flag? And if yes, should we demote all existing Intel drivers and remove this flag as well?
Yes, better to start with less -- especially if this allows us to simplify the code a lot.
ok, thanks for confirming. we'll remove the INFO_RESUME flag in SOF and follow-up with the removal on all other Intel drivers. Thanks for enlightening us on this. -Pierre
ok, thanks for confirming. we'll remove the INFO_RESUME flag in SOF and follow-up with the removal on all other Intel drivers. Thanks for enlightening us on this.
Actually one more question related to the documentation, which reads
"Note that the trigger with SUSPEND can always be called when snd_pcm_suspend_all() is called, regardless of the SNDRV_PCM_INFO_RESUME flag. The RESUME flag affects only the behavior of snd_pcm_resume(). (Thus, in theory, SNDRV_PCM_TRIGGER_RESUME isn’t needed to be handled in the trigger callback when no SNDRV_PCM_INFO_RESUME flag is set. But, it’s better to keep it for compatibility reasons.)"
I could not figure out what the last sentence means. It's my understanding that the resume_trigger will never be called with the code flow below when INFO_RESUME isn't declared. Would you mind clarifying what this compatibility might be? Thanks!
static int snd_pcm_pre_resume(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; if (!(runtime->info & SNDRV_PCM_INFO_RESUME)) return -ENOSYS;
res = ops->pre_action(substream, state); if (res < 0) return res; <<< return means trigger_resume is not called? res = ops->do_action(substream, state);
static const struct action_ops snd_pcm_action_resume = { .pre_action = snd_pcm_pre_resume, .do_action = snd_pcm_do_resume,
On Tue, 09 Apr 2019 16:23:17 +0200, Pierre-Louis Bossart wrote:
ok, thanks for confirming. we'll remove the INFO_RESUME flag in SOF and follow-up with the removal on all other Intel drivers. Thanks for enlightening us on this.
Actually one more question related to the documentation, which reads
"Note that the trigger with SUSPEND can always be called when snd_pcm_suspend_all() is called, regardless of the SNDRV_PCM_INFO_RESUME flag. The RESUME flag affects only the behavior of snd_pcm_resume(). (Thus, in theory, SNDRV_PCM_TRIGGER_RESUME isn’t needed to be handled in the trigger callback when no SNDRV_PCM_INFO_RESUME flag is set. But, it’s better to keep it for compatibility reasons.)"
I could not figure out what the last sentence means. It's my understanding that the resume_trigger will never be called with the code flow below when INFO_RESUME isn't declared. Would you mind clarifying what this compatibility might be? Thanks!
Well, in the above "better to keep it" text -- here "it" was meant as SNDRV_PCM_TRIGGER_RESUME case handling in the trigger callback, not as SNDRV_PCM_INFO_RESUME flag. That is, the above recommends a trigger callback like below would keep SNDRV_PCM_TRIGGER_RESUME although it won't be called practically:
static int foo_trigger(....) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: do_start(); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: do_stop(); break; .... } .... }
This text needs clearly a better rephrasing...
Takashi
On 4/9/19 10:48 AM, Takashi Iwai wrote:
On Tue, 09 Apr 2019 16:23:17 +0200, Pierre-Louis Bossart wrote:
ok, thanks for confirming. we'll remove the INFO_RESUME flag in SOF and follow-up with the removal on all other Intel drivers. Thanks for enlightening us on this.
Actually one more question related to the documentation, which reads
"Note that the trigger with SUSPEND can always be called when snd_pcm_suspend_all() is called, regardless of the SNDRV_PCM_INFO_RESUME flag. The RESUME flag affects only the behavior of snd_pcm_resume(). (Thus, in theory, SNDRV_PCM_TRIGGER_RESUME isn’t needed to be handled in the trigger callback when no SNDRV_PCM_INFO_RESUME flag is set. But, it’s better to keep it for compatibility reasons.)"
I could not figure out what the last sentence means. It's my understanding that the resume_trigger will never be called with the code flow below when INFO_RESUME isn't declared. Would you mind clarifying what this compatibility might be? Thanks!
Well, in the above "better to keep it" text -- here "it" was meant as SNDRV_PCM_TRIGGER_RESUME case handling in the trigger callback, not as SNDRV_PCM_INFO_RESUME flag. That is, the above recommends a trigger callback like below would keep SNDRV_PCM_TRIGGER_RESUME although it won't be called practically:
That's the part that I find odd. we keep the TRIGGER_RESUME but it will never be called, that's an unreachable/untestable switch case, no? Or we should trap it as an error case.
static int foo_trigger(....) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: do_start(); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: do_stop(); break; .... } .... }
This text needs clearly a better rephrasing...
Takashi
On Tue, 09 Apr 2019 18:11:07 +0200, Pierre-Louis Bossart wrote:
On 4/9/19 10:48 AM, Takashi Iwai wrote:
On Tue, 09 Apr 2019 16:23:17 +0200, Pierre-Louis Bossart wrote:
ok, thanks for confirming. we'll remove the INFO_RESUME flag in SOF and follow-up with the removal on all other Intel drivers. Thanks for enlightening us on this.
Actually one more question related to the documentation, which reads
"Note that the trigger with SUSPEND can always be called when snd_pcm_suspend_all() is called, regardless of the SNDRV_PCM_INFO_RESUME flag. The RESUME flag affects only the behavior of snd_pcm_resume(). (Thus, in theory, SNDRV_PCM_TRIGGER_RESUME isn’t needed to be handled in the trigger callback when no SNDRV_PCM_INFO_RESUME flag is set. But, it’s better to keep it for compatibility reasons.)"
I could not figure out what the last sentence means. It's my understanding that the resume_trigger will never be called with the code flow below when INFO_RESUME isn't declared. Would you mind clarifying what this compatibility might be? Thanks!
Well, in the above "better to keep it" text -- here "it" was meant as SNDRV_PCM_TRIGGER_RESUME case handling in the trigger callback, not as SNDRV_PCM_INFO_RESUME flag. That is, the above recommends a trigger callback like below would keep SNDRV_PCM_TRIGGER_RESUME although it won't be called practically:
That's the part that I find odd. we keep the TRIGGER_RESUME but it will never be called, that's an unreachable/untestable switch case, no? Or we should trap it as an error case.
It's just for consistency. But it might look confusing, yeah.
Takashi
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF uses topology to define the DAPM graphs and widgets, DAIs, PCMs and set parameters for init and run time usage. This patch loads topology and maps it to IPC commands that are build the topology on the DSP.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/topology.h | 256 +++ sound/soc/sof/topology.c | 2913 ++++++++++++++++++++++++++++++++++ 2 files changed, 3169 insertions(+) create mode 100644 include/sound/sof/topology.h create mode 100644 sound/soc/sof/topology.c
diff --git a/include/sound/sof/topology.h b/include/sound/sof/topology.h new file mode 100644 index 000000000000..27966998251d --- /dev/null +++ b/include/sound/sof/topology.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_TOPOLOGY_H__ +#define __INCLUDE_SOUND_SOF_TOPOLOGY_H__ + +#include <sound/sof/header.h> + +/* + * Component + */ + +/* types of component */ +enum sof_comp_type { + SOF_COMP_NONE = 0, + SOF_COMP_HOST, + SOF_COMP_DAI, + SOF_COMP_SG_HOST, /**< scatter gather variant */ + SOF_COMP_SG_DAI, /**< scatter gather variant */ + SOF_COMP_VOLUME, + SOF_COMP_MIXER, + SOF_COMP_MUX, + SOF_COMP_SRC, + SOF_COMP_SPLITTER, + SOF_COMP_TONE, + SOF_COMP_SWITCH, + SOF_COMP_BUFFER, + SOF_COMP_EQ_IIR, + SOF_COMP_EQ_FIR, + SOF_COMP_FILEREAD, /**< host test based file IO */ + SOF_COMP_FILEWRITE, /**< host test based file IO */ +}; + +/* XRUN action for component */ +#define SOF_XRUN_STOP 1 /**< stop stream */ +#define SOF_XRUN_UNDER_ZERO 2 /**< send 0s to sink */ +#define SOF_XRUN_OVER_NULL 4 /**< send data to NULL */ + +/* create new generic component - SOF_IPC_TPLG_COMP_NEW */ +struct sof_ipc_comp { + struct sof_ipc_cmd_hdr hdr; + uint32_t id; + enum sof_comp_type type; + uint32_t pipeline_id; + + /* reserved for future use */ + uint32_t reserved[2]; +} __packed; + +/* + * Component Buffers + */ + +/* + * SOF memory capabilities, add new ones at the end + */ +#define SOF_MEM_CAPS_RAM (1 << 0) +#define SOF_MEM_CAPS_ROM (1 << 1) +#define SOF_MEM_CAPS_EXT (1 << 2) /**< external */ +#define SOF_MEM_CAPS_LP (1 << 3) /**< low power */ +#define SOF_MEM_CAPS_HP (1 << 4) /**< high performance */ +#define SOF_MEM_CAPS_DMA (1 << 5) /**< DMA'able */ +#define SOF_MEM_CAPS_CACHE (1 << 6) /**< cacheable */ +#define SOF_MEM_CAPS_EXEC (1 << 7) /**< executable */ + +/* create new component buffer - SOF_IPC_TPLG_BUFFER_NEW */ +struct sof_ipc_buffer { + struct sof_ipc_comp comp; + uint32_t size; /**< buffer size in bytes */ + uint32_t caps; /**< SOF_MEM_CAPS_ */ +} __packed; + +/* generic component config data - must always be after struct sof_ipc_comp */ +struct sof_ipc_comp_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t periods_sink; /**< 0 means variable */ + uint32_t periods_source; /**< 0 means variable */ + uint32_t reserved1; /**< reserved */ + uint32_t frame_fmt; /**< SOF_IPC_FRAME_ */ + uint32_t xrun_action; + + /* reserved for future use */ + uint32_t reserved[2]; +} __packed; + +/* generic host component */ +struct sof_ipc_comp_host { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t direction; /**< SOF_IPC_STREAM_ */ + uint32_t no_irq; /**< don't send periodic IRQ to host/DSP */ + uint32_t dmac_config; /**< DMA engine specific */ +} __packed; + +/* generic DAI component */ +struct sof_ipc_comp_dai { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t direction; /**< SOF_IPC_STREAM_ */ + uint32_t dai_index; /**< index of this type dai */ + uint32_t type; /**< DAI type - SOF_DAI_ */ + uint32_t reserved; /**< reserved */ +} __packed; + +/* generic mixer component */ +struct sof_ipc_comp_mixer { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; +} __packed; + +/* volume ramping types */ +enum sof_volume_ramp { + SOF_VOLUME_LINEAR = 0, + SOF_VOLUME_LOG, + SOF_VOLUME_LINEAR_ZC, + SOF_VOLUME_LOG_ZC, +}; + +/* generic volume component */ +struct sof_ipc_comp_volume { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t channels; + uint32_t min_value; + uint32_t max_value; + uint32_t ramp; /**< SOF_VOLUME_ */ + uint32_t initial_ramp; /**< ramp space in ms */ +} __packed; + +/* generic SRC component */ +struct sof_ipc_comp_src { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + /* either source or sink rate must be non zero */ + uint32_t source_rate; /**< source rate or 0 for variable */ + uint32_t sink_rate; /**< sink rate or 0 for variable */ + uint32_t rate_mask; /**< SOF_RATE_ supported rates */ +} __packed; + +/* generic MUX component */ +struct sof_ipc_comp_mux { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; +} __packed; + +/* generic tone generator component */ +struct sof_ipc_comp_tone { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + int32_t sample_rate; + int32_t frequency; + int32_t amplitude; + int32_t freq_mult; + int32_t ampl_mult; + int32_t length; + int32_t period; + int32_t repeats; + int32_t ramp_step; +} __packed; + +/** \brief Types of EFFECT */ +enum sof_ipc_effect_type { + SOF_EFFECT_NONE = 0, /**< None */ + SOF_EFFECT_INTEL_EQFIR, /**< Intel FIR */ + SOF_EFFECT_INTEL_EQIIR, /**< Intel IIR */ +}; + +/* general purpose EFFECT configuration */ +struct sof_ipc_comp_effect { + struct sof_ipc_hdr hdr; + uint32_t type; /** sof_ipc_effect_type */ +} __packed; + +/* FIR equalizer component */ +struct sof_ipc_comp_eq_fir { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + unsigned char data[0]; +} __packed; + +/* IIR equalizer component */ +struct sof_ipc_comp_eq_iir { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + unsigned char data[0]; +} __packed; + +/* frees components, buffers and pipelines + * SOF_IPC_TPLG_COMP_FREE, SOF_IPC_TPLG_PIPE_FREE, SOF_IPC_TPLG_BUFFER_FREE + */ +struct sof_ipc_free { + struct sof_ipc_cmd_hdr hdr; + uint32_t id; +} __packed; + +struct sof_ipc_comp_reply { + struct sof_ipc_reply rhdr; + uint32_t id; + uint32_t offset; +} __packed; + +/* + * Pipeline + */ + +/* new pipeline - SOF_IPC_TPLG_PIPE_NEW */ +struct sof_ipc_pipe_new { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; /**< component id for pipeline */ + uint32_t pipeline_id; /**< pipeline id */ + uint32_t sched_id; /**< Scheduling component id */ + uint32_t core; /**< core we run on */ + uint32_t deadline; /**< execution completion deadline in us*/ + uint32_t priority; /**< priority level 0 (low) to 10 (max) */ + uint32_t period_mips; /**< worst case instruction count per period */ + uint32_t frames_per_sched;/**< output frames of pipeline, 0 is variable */ + uint32_t xrun_limit_usecs; /**< report xruns greater than limit */ + + /* non zero if timer scheduled, otherwise DAI DMA irq scheduled */ + uint32_t timer_delay; +} __packed; + +/* pipeline construction complete - SOF_IPC_TPLG_PIPE_COMPLETE */ +struct sof_ipc_pipe_ready { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +struct sof_ipc_pipe_free { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +/* connect two components in pipeline - SOF_IPC_TPLG_COMP_CONNECT */ +struct sof_ipc_pipe_comp_connect { + struct sof_ipc_cmd_hdr hdr; + uint32_t source_id; + uint32_t sink_id; +} __packed; + +#endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c new file mode 100644 index 000000000000..d0d1236646c4 --- /dev/null +++ b/sound/soc/sof/topology.c @@ -0,0 +1,2913 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/firmware.h> +#include <sound/tlv.h> +#include <uapi/sound/sof/tokens.h> +#include "sof-priv.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 + +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}, +}; + +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_effect_types { + const char *name; + enum sof_ipc_effect_type type; +}; + +static const struct sof_effect_types sof_effects[] = { + {"EQFIR", SOF_EFFECT_INTEL_EQFIR}, + {"EQIIR", SOF_EFFECT_INTEL_EQIIR}, +}; + +static enum sof_ipc_effect_type find_effect(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_effects); i++) { + if (strcmp(name, sof_effects[i].name) == 0) + return sof_effects[i].type; + } + + return SOF_EFFECT_NONE; +} + +/* + * 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 = + (struct snd_soc_tplg_mixer_control *)hdr; + struct sof_ipc_ctrl_data *cdata; + int tlv[TLV_ITEMS]; + unsigned int i; + int ret; + + /* validate topology data */ + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + /* init the volume get/put data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_ipc_ctrl_value_chan) * + le32_to_cpu(mc->num_channels); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + + /* set cmd for mixer control */ + if (mc->max == 1) { + scontrol->cmd = SOF_CTRL_CMD_SWITCH; + goto out; + } + + scontrol->cmd = SOF_CTRL_CMD_VOLUME; + + /* extract tlv data */ + if (get_tlv_data(kc->tlv.p, tlv) < 0) { + dev_err(sdev->dev, "error: invalid TLV data\n"); + return -EINVAL; + } + + /* set up volume table */ + ret = set_up_volume_table(scontrol, tlv, mc->max + 1); + if (ret < 0) { + dev_err(sdev->dev, "error: setting up volume table\n"); + return ret; + } + + /* set default volume values to 0dB in control */ + cdata = scontrol->control_data; + for (i = 0; i < scontrol->num_channels; i++) { + cdata->chanv[i].channel = i; + cdata->chanv[i].value = VOL_ZERO_DB; + } + +out: + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + return 0; +} + +static int sof_control_load_enum(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_enum_control *ec = + (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 = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_ipc_ctrl_value_chan) * + le32_to_cpu(ec->num_channels); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(ec->num_channels); + + scontrol->cmd = SOF_CTRL_CMD_ENUM; + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n", + scontrol->comp_id, scontrol->num_channels, scontrol->comp_id); + + return 0; +} + +static int sof_control_load_bytes(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_ctrl_data *cdata; + struct snd_soc_tplg_bytes_control *control = + (struct snd_soc_tplg_bytes_control *)hdr; + const int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + + /* init the get/put bytes data */ + scontrol->size = SOF_IPC_MSG_MAX_SIZE; + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + cdata = scontrol->control_data; + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->cmd = SOF_CTRL_CMD_BINARY; + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + if (le32_to_cpu(control->priv.size) > max_size) { + dev_err(sdev->dev, "error: bytes priv data size %d exceeds max %d.\n", + control->priv.size, max_size); + return -EINVAL; + } + + if (le32_to_cpu(control->priv.size) > 0) { + memcpy(cdata->data, control->priv.data, + le32_to_cpu(control->priv.size)); + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + return -EINVAL; + } + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, + cdata->data->abi)) { + dev_err(sdev->dev, + "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + if (cdata->data->size + sizeof(const struct sof_abi_hdr) != + le32_to_cpu(control->priv.size)) { + dev_err(sdev->dev, + "error: Conflict in bytes vs. priv size.\n"); + return -EINVAL; + } + } + return 0; +} + +/* + * Topology Token Parsing. + * New tokens should be added to headers and parsing tables below. + */ + +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_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_effect_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_effect(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_DEADLINE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, deadline), 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_TIMER, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, timer_delay), 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}, +}; + +/* Tone */ +static const struct sof_topology_token tone_tokens[] = { +}; + +/* EFFECT */ +static const struct sof_topology_token effect_tokens[] = { + {SOF_TKN_EFFECT_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, + get_token_effect_type, + offsetof(struct sof_ipc_comp_effect, 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}, +}; + +/* 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}, + +}; + +/* 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}, +}; + +/* + * 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[] = { +}; + +static void 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) +{ + struct snd_soc_tplg_vendor_uuid_elem *elem; + 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, tokens[j].offset, + tokens[j].size); + } + } +} + +static void 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) +{ + struct snd_soc_tplg_vendor_string_elem *elem; + 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, tokens[j].offset, + tokens[j].size); + } + } +} + +static void 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) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_vendor_value_elem *elem; + size_t size = sizeof(struct sof_ipc_dai_dmic_pdm_ctrl); + int i, j; + u32 offset; + u32 *index = NULL; + + /* 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)) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* pdm config array index */ + if (sdev->private) + index = (u32 *)sdev->private; + + /* matched - determine offset */ + switch (tokens[j].token) { + case SOF_TKN_INTEL_DMIC_PDM_CTRL_ID: + + /* inc number of pdm array index */ + if (index) + (*index)++; + /* fallthrough */ + case SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable: + case SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_A: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_B: + case SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE: + case SOF_TKN_INTEL_DMIC_PDM_SKEW: + + /* check if array index is valid */ + if (!index || *index == 0) { + dev_err(sdev->dev, + "error: invalid array offset\n"); + continue; + } else { + /* offset within the pdm config array */ + offset = size * (*index - 1); + } + break; + default: + offset = 0; + break; + } + + /* load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + } + } +} + +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) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + int asize; + + while (priv_size > 0) { + asize = le32_to_cpu(array->size); + + /* validate asize */ + if (asize < 0) { /* FIXME: A zero-size array makes no sense */ + dev_err(sdev->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(sdev->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: + sof_parse_uuid_tokens(scomp, object, tokens, count, + array); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + sof_parse_string_tokens(scomp, object, tokens, count, + array); + 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: + sof_parse_word_tokens(scomp, object, tokens, count, + array); + break; + default: + dev_err(sdev->dev, "error: unknown token type %d\n", + array->type); + return -EINVAL; + } + + /* next array */ + array = (struct snd_soc_tplg_vendor_array *)((u8 *)array + + asize); + } + return 0; +} + +static void sof_dbg_comp_config(struct snd_soc_component *scomp, + struct sof_ipc_comp_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + + dev_dbg(sdev->dev, " config: periods snk %d src %d fmt %d\n", + config->periods_sink, config->periods_source, + config->frame_fmt); +} + +/* 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 = NULL; + struct snd_sof_control *scontrol; + int ret = -EINVAL; + + dev_dbg(sdev->dev, "tplg: load control type %d name : %s\n", + hdr->type, hdr->name); + + scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); + if (!scontrol) + return -ENOMEM; + + scontrol->sdev = sdev; + + 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(sdev->dev, "control type not supported %d:%d:%d\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info); + kfree(scontrol); + return 0; + } + + dobj->private = scontrol; + list_add(&scontrol->list, &sdev->kcontrol_list); + return ret; +} + +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(sdev->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 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_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_card *card = scomp->card; + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + dev_vdbg(sdev->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", + 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: + rtd->cpu_dai->capture_widget = w; + if (dai) + dai->name = rtd->dai_link->name; + dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + case snd_soc_dapm_dai_in: + rtd->cpu_dai->playback_widget = w; + if (dai) + dai->name = rtd->dai_link->name; + dev_dbg(sdev->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(sdev->dev, "error: can't connect DAI %s stream %s\n", + w->name, w->sname); + return -EINVAL; + } + + return 0; +} + +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; + int ret; + + /* configure dai IPC message */ + memset(&comp_dai, 0, sizeof(comp_dai)); + comp_dai.comp.hdr.size = sizeof(comp_dai); + comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + comp_dai.comp.id = swidget->comp_id; + comp_dai.comp.type = SOF_COMP_DAI; + comp_dai.comp.pipeline_id = index; + 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(sdev->dev, "error: parse dai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + 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(sdev->dev, "error: parse dai.cfg tokens failed %d\n", + private->size); + return ret; + } + + dev_dbg(sdev->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, sizeof(comp_dai), r, sizeof(*r)); + + if (ret == 0 && dai) { + dai->sdev = sdev; + memcpy(&dai->comp_dai, &comp_dai, sizeof(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; + + ret = sof_parse_tokens(scomp, buffer, buffer_tokens, + ARRAY_SIZE(buffer_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse buffer tokens failed %d\n", + private->size); + kfree(buffer); + return ret; + } + + dev_dbg(sdev->dev, "buffer %s: size %d caps 0x%x\n", + swidget->widget->name, buffer->size, buffer->caps); + + swidget->private = (void *)buffer; + + ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, + sizeof(*buffer), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: buffer %s load failed\n", + swidget->widget->name); + kfree(buffer); + } + + return ret; +} + +/* bind PCM ID to host component ID */ +static int spcm_bind(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + int dir) +{ + struct snd_sof_widget *host_widget; + + host_widget = snd_sof_find_swidget_sname(sdev, + spcm->pcm.caps[dir].name, + dir); + if (!host_widget) { + dev_err(sdev->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; + int ret; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + /* configure mixer IPC message */ + host->comp.hdr.size = sizeof(*host); + host->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + host->comp.id = swidget->comp_id; + host->comp.type = SOF_COMP_HOST; + host->comp.pipeline_id = index; + 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(sdev->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(sdev->dev, "error: parse host.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "loaded host %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &host->config); + + swidget->private = (void *)host; + + ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host, + sizeof(*host), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(host); + return ret; +} + +/* + * Pipeline Topology + */ +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r) +{ + struct sof_ipc_pm_core_config pm_core_config; + int ret = 0; + + ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, + sizeof(*pipeline), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: load pipeline ipc failure\n"); + return ret; + } + + /* power up the core that this pipeline is scheduled on */ + ret = snd_sof_dsp_core_power_up(sdev, 1 << pipeline->core); + if (ret < 0) { + dev_err(sdev->dev, "error: powering up pipeline schedule core %d\n", + pipeline->core); + return ret; + } + + /* update enabled cores mask */ + sdev->enabled_cores_mask |= 1 << pipeline->core; + + /* + * Now notify DSP that the core that this pipeline is scheduled on + * has been powered up + */ + memset(&pm_core_config, 0, sizeof(pm_core_config)); + pm_core_config.enable_mask = sdev->enabled_cores_mask; + + /* configure CORE_ENABLE ipc message */ + pm_core_config.hdr.size = sizeof(pm_core_config); + pm_core_config.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE; + + /* send ipc */ + 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 enable 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_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_widget *comp_swidget; + 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(sdev, tw->sname); + if (!comp_swidget) { + dev_err(sdev->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(sdev->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", + pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id); + + ret = sof_parse_tokens(scomp, pipeline, sched_tokens, + ARRAY_SIZE(sched_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse pipeline tokens failed %d\n", + private->size); + goto err; + } + + dev_dbg(sdev->dev, "pipeline %s: deadline %d pri %d mips %d core %d frames %d\n", + swidget->widget->name, pipeline->deadline, pipeline->priority, + pipeline->period_mips, pipeline->core, pipeline->frames_per_sched); + + swidget->private = (void *)pipeline; + + /* send ipc's to create pipeline comp and power up schedule core */ + ret = sof_load_pipeline_ipc(sdev, 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; + int ret; + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + /* configure mixer IPC message */ + mixer->comp.hdr.size = sizeof(*mixer); + mixer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + mixer->comp.id = swidget->comp_id; + mixer->comp.type = SOF_COMP_MIXER; + mixer->comp.pipeline_id = index; + 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(sdev->dev, "error: parse mixer.cfg tokens failed %d\n", + private->size); + kfree(mixer); + return ret; + } + + sof_dbg_comp_config(scomp, &mixer->config); + + swidget->private = (void *)mixer; + + ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer, + sizeof(*mixer), 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; + int ret; + + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + /* configure mux IPC message */ + mux->comp.hdr.size = sizeof(*mux); + mux->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + mux->comp.id = swidget->comp_id; + mux->comp.type = SOF_COMP_MUX; + mux->comp.pipeline_id = index; + 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(sdev->dev, "error: parse mux.cfg tokens failed %d\n", + private->size); + kfree(mux); + return ret; + } + + sof_dbg_comp_config(scomp, &mux->config); + + swidget->private = (void *)mux; + + ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux, + sizeof(*mux), 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; + int ret; + + volume = kzalloc(sizeof(*volume), GFP_KERNEL); + if (!volume) + return -ENOMEM; + + if (le32_to_cpu(tw->num_kcontrols) != 1) { + dev_err(sdev->dev, "error: invalid kcontrol count %d for volume\n", + tw->num_kcontrols); + ret = -EINVAL; + goto err; + } + + /* configure dai IPC message */ + volume->comp.hdr.size = sizeof(*volume); + volume->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + volume->comp.id = swidget->comp_id; + volume->comp.type = SOF_COMP_VOLUME; + volume->comp.pipeline_id = index; + 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(sdev->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(sdev->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 = (void *)volume; + + ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume, + sizeof(*volume), 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; + int ret; + + src = kzalloc(sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + /* configure mixer IPC message */ + src->comp.hdr.size = sizeof(*src); + src->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + src->comp.id = swidget->comp_id; + src->comp.type = SOF_COMP_SRC; + src->comp.pipeline_id = index; + 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(sdev->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(sdev->dev, "error: parse src.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "src %s: source rate %d sink rate %d\n", + swidget->widget->name, src->source_rate, src->sink_rate); + sof_dbg_comp_config(scomp, &src->config); + + swidget->private = (void *)src; + + ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src, + sizeof(*src), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(src); + 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; + int ret; + + tone = kzalloc(sizeof(*tone), GFP_KERNEL); + if (!tone) + return -ENOMEM; + + /* configure mixer IPC message */ + tone->comp.hdr.size = sizeof(*tone); + tone->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + tone->comp.id = swidget->comp_id; + tone->comp.type = SOF_COMP_TONE; + tone->comp.pipeline_id = index; + 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(sdev->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(sdev->dev, "error: parse tone.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", + swidget->widget->name, tone->frequency, tone->amplitude); + sof_dbg_comp_config(scomp, &tone->config); + + swidget->private = (void *)tone; + + ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone, + sizeof(*tone), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(tone); + return ret; +} + +static int sof_effect_fir_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) + +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct snd_sof_control *scontrol = NULL; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc = NULL; + struct soc_bytes_ext *sbe; + struct sof_abi_hdr *pdata = NULL; + struct sof_ipc_comp_eq_fir *fir; + size_t ipc_size = 0, fir_data_size = 0; + int ret; + + /* get possible eq controls */ + kc = &widget->kcontrol_news[0]; + if (kc) { + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + } + + /* + * Check if there's eq parameters in control's private member and set + * data size accordingly. If there's no parameters eq will use defaults + * in firmware (which in this case is passthrough). + */ + if (scontrol && scontrol->cmd == SOF_CTRL_CMD_BINARY) { + pdata = scontrol->control_data->data; + if (pdata->size > 0 && pdata->magic == SOF_ABI_MAGIC) + fir_data_size = pdata->size; + } + + ipc_size = sizeof(struct sof_ipc_comp_eq_fir) + + le32_to_cpu(private->size) + + fir_data_size; + + fir = kzalloc(ipc_size, GFP_KERNEL); + if (!fir) + return -ENOMEM; + + /* configure fir IPC message */ + fir->comp.hdr.size = ipc_size; + fir->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + fir->comp.id = swidget->comp_id; + fir->comp.type = SOF_COMP_EQ_FIR; + fir->comp.pipeline_id = index; + fir->config.hdr.size = sizeof(fir->config); + + ret = sof_parse_tokens(scomp, &fir->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse fir.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &fir->config); + + /* we have a private data found in control, so copy it */ + if (fir_data_size > 0) { + memcpy(&fir->data, pdata->data, pdata->size); + fir->size = fir_data_size; + } + + swidget->private = (void *)fir; + + ret = sof_ipc_tx_message(sdev->ipc, fir->comp.hdr.cmd, fir, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(fir); + return ret; +} + +static int sof_effect_iir_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) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc = NULL; + struct soc_bytes_ext *sbe; + struct snd_sof_control *scontrol = NULL; + struct sof_abi_hdr *pdata = NULL; + struct sof_ipc_comp_eq_iir *iir; + size_t ipc_size = 0, iir_data_size = 0; + int ret; + + /* get possible eq controls */ + kc = &widget->kcontrol_news[0]; + if (kc) { + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + } + + /* + * Check if there's eq parameters in control's private member and set + * data size accordingly. If there's no parameters eq will use defaults + * in firmware (which in this case is passthrough). + */ + if (scontrol && scontrol->cmd == SOF_CTRL_CMD_BINARY) { + pdata = scontrol->control_data->data; + if (pdata->size > 0 && pdata->magic == SOF_ABI_MAGIC) + iir_data_size = pdata->size; + } + + ipc_size = sizeof(struct sof_ipc_comp_eq_iir) + + le32_to_cpu(private->size) + + iir_data_size; + + iir = kzalloc(ipc_size, GFP_KERNEL); + if (!iir) + return -ENOMEM; + + /* configure iir IPC message */ + iir->comp.hdr.size = ipc_size; + iir->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + iir->comp.id = swidget->comp_id; + iir->comp.type = SOF_COMP_EQ_IIR; + iir->comp.pipeline_id = index; + iir->config.hdr.size = sizeof(iir->config); + + ret = sof_parse_tokens(scomp, &iir->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse iir.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &iir->config); + + /* we have a private data found in control, so copy it */ + if (iir_data_size > 0) { + memcpy(&iir->data, pdata->data, pdata->size); + iir->size = iir_data_size; + } + + swidget->private = (void *)iir; + + ret = sof_ipc_tx_message(sdev->ipc, iir->comp.hdr.cmd, iir, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(iir); + return ret; +} + +/* + * Effect Topology + */ + +static int sof_widget_load_effect(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_effect config; + int ret; + + /* check we have some tokens - we need at least effect type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: effect tokens not found\n"); + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + + /* get the effect token */ + ret = sof_parse_tokens(scomp, &config, effect_tokens, + ARRAY_SIZE(effect_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse effect tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* now load effect specific data and send IPC */ + switch (config.type) { + case SOF_EFFECT_INTEL_EQFIR: + ret = sof_effect_fir_load(scomp, index, swidget, tw, r); + break; + case SOF_EFFECT_INTEL_EQIIR: + ret = sof_effect_iir_load(scomp, index, swidget, tw, r); + break; + default: + dev_err(sdev->dev, "error: invalid effect type %d\n", + config.type); + ret = -EINVAL; + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: effect loading failed\n"); + return ret; + } + + return 0; +} + +/* 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 = NULL; + int ret = 0; + + swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); + if (!swidget) + return -ENOMEM; + + swidget->sdev = sdev; + 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(sdev->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"); + + /* 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_siggen: + ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_effect: + ret = sof_widget_load_effect(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_warn(sdev->dev, "warning: widget type %d name %s not handled\n", + swidget->id, tw->name); + break; + } + + /* check IPC reply */ + if (ret < 0 || reply.rhdr.error < 0) { + dev_err(sdev->dev, + "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; + } + + 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 = NULL; + 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 = (struct snd_sof_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 = (struct sof_ipc_pipe_new *)swidget->private; + ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); + if (ret < 0) + dev_err(sdev->dev, "error: powering down pipeline schedule core %d\n", + 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(sdev->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_sof_pcm *spcm; + int stream = SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + /* don't need to do anything for BEs atm */ + if (!pcm) + return 0; + + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->sdev = sdev; + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED; + + if (pcm) { + spcm->pcm = *pcm; + dev_dbg(sdev->dev, "tplg: load pcm %s\n", pcm->dai_name); + } + dai_drv->dobj.private = spcm; + list_add(&spcm->list, &sdev->pcm_list); + + /* do we need to allocate playback PCM DMA pages */ + if (!spcm->pcm.playback) + goto capture; + + caps = &spcm->pcm.caps[stream]; + + /* allocate playback page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + + return ret; + } + + /* bind pcm to host comp */ + ret = spcm_bind(sdev, spcm, stream); + if (ret) { + dev_err(sdev->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; + + caps = &spcm->pcm.caps[stream]; + + /* allocate capture page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + goto free_playback_tables; + } + + /* bind pcm to host comp */ + ret = spcm_bind(sdev, spcm, stream); + if (ret) { + dev_err(sdev->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; + } +} + +/* set config for all DAI's with name matching the link name */ +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) { + dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!dai->dai_config) + return -ENOMEM; + + 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; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->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(sdev->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(sdev->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n", + 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(sdev->dev, "error: invalid fsync rate for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + if (config->ssp.tdm_slots < 1 || config->ssp.tdm_slots > 8) { + dev_err(sdev->dev, "error: invalid channel count for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for SSP%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for SSP%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_dai_config *ipc_config; + struct sof_ipc_reply reply; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + u32 size; + int ret, j; + + /* + * config is only used for the common params in dmic_params structure + * that does not include the PDM controller config array + * Set the common params to 0. + */ + 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(sdev->dev, "error: parse dmic tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* + * allocate memory for dmic dai config accounting for the + * variable number of active pdm controllers + * This will be the ipc payload for setting dai config + */ + size = sizeof(*config) + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl) * + config->dmic.num_pdm_active; + + ipc_config = kzalloc(size, GFP_KERNEL); + if (!ipc_config) + return -ENOMEM; + + /* copy the common dai config and dmic params */ + memcpy(ipc_config, config, sizeof(*config)); + + /* + * alloc memory for private member + * Used to track the pdm config array index currently being parsed + */ + sdev->private = kzalloc(sizeof(u32), GFP_KERNEL); + if (!sdev->private) { + kfree(ipc_config); + return -ENOMEM; + } + + /* get DMIC PDM tokens */ + ret = sof_parse_tokens(scomp, &ipc_config->dmic.pdm[0], dmic_pdm_tokens, + ARRAY_SIZE(dmic_pdm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dmic pdm tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + /* set IPC header size */ + ipc_config->hdr.size = size; + + /* debug messages */ + dev_dbg(sdev->dev, "tplg: config DMIC%d driver version %d\n", + ipc_config->dai_index, ipc_config->dmic.driver_ipc_version); + dev_dbg(sdev->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", + ipc_config->dmic.pdmclk_min, ipc_config->dmic.pdmclk_max, + ipc_config->dmic.duty_min); + dev_dbg(sdev->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", + ipc_config->dmic.duty_max, ipc_config->dmic.fifo_fs, + ipc_config->dmic.num_pdm_active); + dev_dbg(sdev->dev, "fifo word length %hd\n", + ipc_config->dmic.fifo_bits); + + for (j = 0; j < ipc_config->dmic.num_pdm_active; j++) { + dev_dbg(sdev->dev, "pdm %hd mic a %hd mic b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].enable_mic_a, + ipc_config->dmic.pdm[j].enable_mic_b); + dev_dbg(sdev->dev, "pdm %hd polarity a %hd polarity b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].polarity_mic_a, + ipc_config->dmic.pdm[j].polarity_mic_b); + dev_dbg(sdev->dev, "pdm %hd clk_edge %hd skew %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].clk_edge, + ipc_config->dmic.pdm[j].skew); + } + + if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) { + /* this takes care of backwards compatible handling of fifo_bits_b */ + ipc_config->dmic.reserved_2 = ipc_config->dmic.fifo_bits; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + ipc_config->hdr.cmd, ipc_config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set DAI config for DMIC%d\n", + config->dai_index); + goto err; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, ipc_config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for DMIC%d\n", + config->dai_index); + +err: + kfree(sdev->private); + kfree(ipc_config); + + return ret; +} + +/* + * for hda link, playback and capture are supported by different dai + * in FW. Here get the dai_index, set dma channel of each dai + * and send config to FW. In FW, each dai sets config by dai_index + */ +static int sof_link_hda_process(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config, + int tx_slot, + int rx_slot) +{ + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + struct snd_sof_dai *sof_dai; + int found = 0; + int ret; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) { + if (sof_dai->comp_dai.direction == + SNDRV_PCM_STREAM_PLAYBACK) { + if (!link->dpcm_playback) + return -EINVAL; + + config->hda.link_dma_ch = tx_slot; + } else { + if (!link->dpcm_capture) + return -EINVAL; + + config->hda.link_dma_ch = rx_slot; + } + + config->dai_index = sof_dai->comp_dai.dai_index; + found = 1; + + /* save config in dai component */ + sof_dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!sof_dai->dai_config) + return -ENOMEM; + + /* 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 direction:%d of HDA dai %d\n", + sof_dai->comp_dai.direction, + config->dai_index); + + return ret; + } + } + } + + /* + * 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_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_dai_link_component dai_component; + struct snd_soc_tplg_private *private = &cfg->priv; + struct snd_soc_dai *dai; + u32 size = sizeof(*config); + u32 tx_num = 0; + u32 tx_slot = 0; + u32 rx_num = 0; + u32 rx_slot = 0; + int ret; + + /* init IPC */ + memset(&dai_component, 0, sizeof(dai_component)); + 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_tokens, + ARRAY_SIZE(hda_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse hda tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s in %s", + dai_component.dai_name, __func__); + return -EINVAL; + } + + if (link->dpcm_playback) + tx_num = 1; + + if (link->dpcm_capture) + rx_num = 1; + + ret = snd_soc_dai_get_channel_map(dai, &tx_num, &tx_slot, + &rx_num, &rx_slot); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get dma channel for HDA%d\n", + config->dai_index); + + return ret; + } + + ret = sof_link_hda_process(sdev, link, config, tx_slot, rx_slot); + if (ret < 0) + dev_err(sdev->dev, "error: failed to process hda dai link %s", + link->name); + + return ret; +} + +/* DAI link - used for any driver specific init */ +static int sof_link_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config config; + struct snd_soc_tplg_hw_config *hw_config; + int ret = 0; + + link->platform_name = dev_name(sdev->dev); + + /* + * Set nonatomic property for FE dai links as their trigger action + * involves IPC's. + */ + if (!link->no_pcm) { + link->nonatomic = true; + + /* nothing more to do for FE dai links */ + return 0; + } + + /* check we have some tokens - we need at least DAI type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: expected tokens for DAI, none found\n"); + 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(sdev->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. + */ + if (!le32_to_cpu(cfg->num_hw_configs) && + config.type != SOF_DAI_INTEL_HDA) { + dev_err(sdev->dev, "error: unexpected DAI config count %d!\n", + le32_to_cpu(cfg->num_hw_configs)); + return -EINVAL; + } + + /* configure dai IPC message */ + hw_config = &cfg->hw_config[0]; + + 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; + default: + dev_err(sdev->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_link_component dai_component; + struct snd_soc_dai *dai; + int ret = 0; + + memset(&dai_component, 0, sizeof(dai_component)); + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s in %s", + dai_component.dai_name, __func__); + return -EINVAL; + } + + /* + * FIXME: this call to hw_free is mainly to release the link DMA ID. + * This is abusing the API and handling SOC internals is not + * recommended. This part will be reworked. + */ + if (dai->driver->ops->hw_free) + ret = dai->driver->ops->hw_free(NULL, dai); + if (ret < 0) + dev_err(sdev->dev, "error: failed to free hda resource for %s\n", + link->name); + + return ret; +} + +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 = NULL; + 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(sdev->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: + /* no resource needs to be released for SSP and DMIC */ + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_unload(sdev, link); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + 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->sdev = sdev; + + 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(sdev->dev, "sink %s control %s source %s\n", + route->sink, route->control ? route->control : "none", + route->source); + + /* source component */ + source_swidget = snd_sof_find_swidget(sdev, (char *)route->source); + if (!source_swidget) { + dev_err(sdev->dev, "error: source %s not found\n", + route->source); + ret = -EINVAL; + goto err; + } + + connect->source_id = source_swidget->comp_id; + + /* sink component */ + sink_swidget = snd_sof_find_swidget(sdev, (char *)route->sink); + if (!sink_swidget) { + dev_err(sdev->dev, "error: sink %s not found\n", + route->sink); + ret = -EINVAL; + goto err; + } + + connect->sink_id = sink_swidget->comp_id; + + /* + * Some virtual routes and widgets may been added in topology for + * compatibility. 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(sdev->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", + route->source, route->sink); + ret = 0; + goto err; + } 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(sdev->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(sdev->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 ret; + } + +err: + kfree(connect); + kfree(sroute); + return ret; +} + +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct sof_ipc_pipe_ready ready; + struct sof_ipc_reply reply; + int ret; + + dev_dbg(sdev->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(sdev, swidget); + break; + default: + break; + } + } +} + +/* manifest - optional to inform component of manifest */ +static int sof_manifest(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man) +{ + /* not currently parsed */ + 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}, +}; + +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_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops) +{ + /* TODO: support linked list of topologies */ + sdev->tplg_ops = ops; + return 0; +} +EXPORT_SYMBOL(snd_sof_init_topology); + +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file) +{ + const struct firmware *fw; + int ret; + + dev_dbg(sdev->dev, "loading topology:%s\n", file); + + ret = request_firmware(&fw, file, sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg request firmware %s failed err: %d\n", + file, ret); + return ret; + } + + ret = snd_soc_tplg_component_load(sdev->component, + &sof_tplg_ops, fw, + SND_SOC_TPLG_INDEX_ALL); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg component load failed %d\n", + ret); + ret = -EINVAL; + } + + release_firmware(fw); + return ret; +} +EXPORT_SYMBOL(snd_sof_load_topology);
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/trace.c | 295 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 sound/soc/sof/trace.c
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c new file mode 100644 index 000000000000..8c95e3453b6f --- /dev/null +++ b/sound/soc/sof/trace.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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_wait_trace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + wait_queue_entry_t wait; + + /* + * 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 (sdev->host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (sdev->host_offset > pos) + return sdev->host_offset - pos; + + /* 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 bytes available for copy */ + if (sdev->host_offset < pos) + return buffer_size - pos; + + return sdev->host_offset - pos; +} + +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, (void *)((u8 *)(dfse->buf) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static const struct file_operations sof_dfs_trace_fops = { + .open = simple_open, + .read = sof_dfsentry_trace_read, + .llseek = default_llseek, +}; + +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; + + dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root, + dfse, &sof_dfs_trace_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, + "error: cannot create debugfs entry for trace\n"); + } + + return 0; +} + +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) +{ + struct sof_ipc_dma_trace_params params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) + return -EINVAL; + + /* set IPC parameters */ + params.hdr.size = sizeof(params); + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG | 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; + + 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; + + /* 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, &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_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_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_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; +} +EXPORT_SYMBOL(snd_sof_release_trace); + +void snd_sof_free_trace(struct snd_sof_dev *sdev) +{ + snd_sof_release_trace(sdev); + + snd_dma_free_pages(&sdev->dmatb); + snd_dma_free_pages(&sdev->dmatp); +} +EXPORT_SYMBOL(snd_sof_free_trace);
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add operation pointers that can be called by core to control a wide variety of DSP targets. The DSP HW drivers will fill in these operations.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/ops.c | 204 ++++++++++++++++++++++ sound/soc/sof/ops.h | 403 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 607 insertions(+) create mode 100644 sound/soc/sof/ops.c create mode 100644 sound/soc/sof/ops.h
diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c new file mode 100644 index 000000000000..ba0b7a66f395 --- /dev/null +++ b/sound/soc/sof/ops.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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; + + 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); + +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) +{ + u32 reg; + unsigned long tout_jiff; + int k = 0, s = interval_us; + + /* + * Split the loop into 2 sleep stages with varying resolution. + * To do it more accurately, the range of wakeups are: + * In case of interval_us = 500, + * Phase 1(first 5ms): min sleep 0.5ms; max sleep 1ms. + * Phase 2(beyond 5ms): min sleep 5ms; max sleep 10ms. + */ + + tout_jiff = jiffies + msecs_to_jiffies(timeout_ms); + do { + reg = snd_sof_dsp_read(sdev, bar, offset); + if ((reg & mask) == target) + break; + + /* Phase 2 after 5ms(500us * 10) */ + if (++k > 10) + s = interval_us * 10; + + usleep_range(s, 2 * s); + } while (time_before(jiffies, tout_jiff)); + + if ((reg & mask) == target) { + dev_dbg(sdev->dev, "FW Poll Status: reg=%#x successful\n", reg); + + return 0; + } + + dev_dbg(sdev->dev, "FW Poll Status: reg=%#x timedout\n", reg); + return -ETIME; +} +EXPORT_SYMBOL(snd_sof_dsp_register_poll); + +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); + snd_sof_dsp_cmd_done(sdev, SOF_IPC_HOST_REPLY); +} +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 000000000000..23b57884c199 --- /dev/null +++ b/sound/soc/sof/ops.h @@ -0,0 +1,403 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 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) + +/* init */ +static inline int snd_sof_probe(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->probe) + return sof_ops(sdev)->probe(sdev); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +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) +{ + if (sof_ops(sdev)->run) + return sof_ops(sdev)->run(sdev); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +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; +} + +/* 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, int state) +{ + if (sof_ops(sdev)->suspend) + return sof_ops(sdev)->suspend(sdev, 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, + int state) +{ + if (sof_ops(sdev)->runtime_suspend) + return sof_ops(sdev)->runtime_suspend(sdev, state); + + 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; +} + +/* 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); +} + +/* 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) +{ + if (sof_ops(sdev)->block_read) { + sof_ops(sdev)->block_read(sdev, bar, offset, dest, bytes); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *src, size_t bytes) +{ + if (sof_ops(sdev)->block_write) { + sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +/* mailbox */ +static inline void snd_sof_dsp_mailbox_read(struct snd_sof_dev *sdev, + u32 offset, void *message, + size_t bytes) +{ + if (sof_ops(sdev)->mailbox_read) { + sof_ops(sdev)->mailbox_read(sdev, offset, message, bytes); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline void snd_sof_dsp_mailbox_write(struct snd_sof_dev *sdev, + u32 offset, void *message, + size_t bytes) +{ + if (sof_ops(sdev)->mailbox_write) { + sof_ops(sdev)->mailbox_write(sdev, offset, message, bytes); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +/* ipc */ +static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + if (sof_ops(sdev)->send_msg) + return sof_ops(sdev)->send_msg(sdev, msg); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +static inline int snd_sof_dsp_get_reply(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + if (sof_ops(sdev)->get_reply) + return sof_ops(sdev)->get_reply(sdev, msg); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +static inline int snd_sof_dsp_cmd_done(struct snd_sof_dev *sdev, + int dir) +{ + if (sof_ops(sdev)->cmd_done) + return sof_ops(sdev)->cmd_done(sdev, dir); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +/* 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 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 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; +} + +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; +} + +/* 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
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The firmware loader exports APIs that can be called by core to load and process multiple different file formats.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/loader.c | 376 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 sound/soc/sof/loader.c
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c new file mode 100644 index 000000000000..657c0dd5c013 --- /dev/null +++ b/sound/soc/sof/loader.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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 "ops.h" + +static int get_ext_windows(struct snd_sof_dev *sdev, + struct sof_ipc_ext_data_hdr *ext_hdr) +{ + struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr; + size_t size; + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + size = sizeof(*w) + sizeof(struct sof_ipc_window_elem) * w->num_windows; + + /* keep a local copy of the data */ + sdev->info_window = kmemdup(w, size, GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + 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 = (struct sof_ipc_ext_data_hdr *)ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + offset += sizeof(*ext_hdr); + snd_sof_dsp_block_read(sdev, bar, offset, + (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_DMA_BUFFER: + break; + case SOF_IPC_EXT_WINDOW: + ret = get_ext_windows(sdev, ext_hdr); + break; + default: + 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 = (struct sof_ipc_ext_data_hdr *)ext_data; + } + + kfree(ext_data); + return ret; +} +EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); + +/* 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; + 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++) { + /* minus header size of block */ + remaining -= sizeof(*block); + if (remaining < block->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + 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_SRAM...SOF_FW_BLK_TYPE_RSRVD14: + continue; /* not handled atm */ + case SOF_FW_BLK_TYPE_IRAM: + case SOF_FW_BLK_TYPE_DRAM: + offset = block->offset; + 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, sdev->mmio_bar, offset, + (void *)((u8 *)block + sizeof(*block)), + block->size); + + /* 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) +{ + struct snd_sof_fw_header *header; + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)fw->data; + + /* 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) +{ + 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; + load_module = sof_ops(sdev)->load_module; + if (!load_module) + return -EINVAL; + + /* parse each module */ + module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header)); + remaining = fw->size - sizeof(*header); + for (count = 0; count < header->num_modules; count++) { + /* minus header size of module */ + remaining -= sizeof(*module); + if (remaining < module->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid module %d\n", count); + return ret; + } + /* 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; + int ret; + + /* set code loading condition to true */ + sdev->code_loading = 1; + + /* Don't request firmware again if firmware is already requested */ + if (plat_data->fw) + return 0; + + fw_filename = devm_kasprintf(sdev->dev, 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); + } + 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); + 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); + 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); + sdev->boot_complete = false; + + /* create 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"); + /* 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 */ + ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete, + 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); + return -EIO; + } + + dev_info(sdev->dev, "firmware boot complete\n"); + + /* 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 */ +} +EXPORT_SYMBOL(snd_sof_fw_unload);
On Thu, 21 Mar 2019 17:10:11 +0100, Pierre-Louis Bossart wrote:
+static int get_ext_windows(struct snd_sof_dev *sdev,
struct sof_ipc_ext_data_hdr *ext_hdr)
+{
- struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr;
In general it's safer to use container_of() than the forced cast, and...
+/* 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 = (struct sof_ipc_ext_data_hdr *)ext_data;
The cast isn't needed from a void pointer. The whole SOF codes have way too many unneeded cast. Let's reduce.
+/* 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;
- 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++) {
/* minus header size of block */
remaining -= sizeof(*block);
if (remaining < block->size) {
dev_err(sdev->dev, "error: not enough data remaining\n");
return -EINVAL;
}
remaining is unsigned, so a negative check doesn't work here. Hence you need the explicit underflow check.
+static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw) +{
- 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;
- load_module = sof_ops(sdev)->load_module;
- if (!load_module)
return -EINVAL;
- /* parse each module */
- module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header));
- remaining = fw->size - sizeof(*header);
The size check should be more strict (e.g. here remaining might become negative).
+int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) +{
- struct snd_sof_pdata *plat_data = sdev->pdata;
- const char *fw_filename;
- int ret;
- /* set code loading condition to true */
- sdev->code_loading = 1;
- /* Don't request firmware again if firmware is already requested */
- if (plat_data->fw)
return 0;
- fw_filename = devm_kasprintf(sdev->dev, 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);
- }
- return ret;
Hrm, any need of devm_kasprintf() here? That is, do we need to keep this string after the call of request_firmware()? It doesn't make sense if we need it only temporarily.
thanks,
Takashi
+static int get_ext_windows(struct snd_sof_dev *sdev,
struct sof_ipc_ext_data_hdr *ext_hdr)
+{
- struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr;
In general it's safer to use container_of() than the forced cast, and...
yes
+/* 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 = (struct sof_ipc_ext_data_hdr *)ext_data;
The cast isn't needed from a void pointer. The whole SOF codes have way too many unneeded cast. Let's reduce.
ok. we've removed quite a few casts but that's obviously not finished.
+/* 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;
- 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++) {
/* minus header size of block */
remaining -= sizeof(*block);
if (remaining < block->size) {
dev_err(sdev->dev, "error: not enough data remaining\n");
return -EINVAL;
}
remaining is unsigned, so a negative check doesn't work here. Hence you need the explicit underflow check.
yes, probably need ssize_t here.
+static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw) +{
- 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;
- load_module = sof_ops(sdev)->load_module;
- if (!load_module)
return -EINVAL;
- /* parse each module */
- module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header));
- remaining = fw->size - sizeof(*header);
The size check should be more strict (e.g. here remaining might become negative).
yes.
+int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) +{
- struct snd_sof_pdata *plat_data = sdev->pdata;
- const char *fw_filename;
- int ret;
- /* set code loading condition to true */
- sdev->code_loading = 1;
- /* Don't request firmware again if firmware is already requested */
- if (plat_data->fw)
return 0;
- fw_filename = devm_kasprintf(sdev->dev, 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);
- }
- return ret;
Hrm, any need of devm_kasprintf() here? That is, do we need to keep this string after the call of request_firmware()? It doesn't make sense if we need it only temporarily.
Good point, this can be simplified.
On Thu, 04 Apr 2019 15:59:46 +0200, Pierre-Louis Bossart wrote:
+/* 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;
- 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++) {
/* minus header size of block */
remaining -= sizeof(*block);
if (remaining < block->size) {
dev_err(sdev->dev, "error: not enough data remaining\n");
return -EINVAL;
}
remaining is unsigned, so a negative check doesn't work here. Hence you need the explicit underflow check.
yes, probably need ssize_t here.
Be careful. If block->size is unsigned, the comparison is also done as unsigned in the code above.
Takashi
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add userspace ABI for audio userspace application IO outside of regular ALSA PCM and kcontrols. This is intended to be used to format coefficients and data for custom processing components.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/uapi/sound/sof/abi.h | 62 ++++++++++ include/uapi/sound/sof/eq.h | 172 +++++++++++++++++++++++++++ include/uapi/sound/sof/fw.h | 78 +++++++++++++ include/uapi/sound/sof/header.h | 27 +++++ include/uapi/sound/sof/manifest.h | 188 ++++++++++++++++++++++++++++++ include/uapi/sound/sof/tokens.h | 103 ++++++++++++++++ include/uapi/sound/sof/tone.h | 21 ++++ include/uapi/sound/sof/trace.h | 97 +++++++++++++++ 8 files changed, 748 insertions(+) create mode 100644 include/uapi/sound/sof/abi.h create mode 100644 include/uapi/sound/sof/eq.h create mode 100644 include/uapi/sound/sof/fw.h create mode 100644 include/uapi/sound/sof/header.h create mode 100644 include/uapi/sound/sof/manifest.h create mode 100644 include/uapi/sound/sof/tokens.h create mode 100644 include/uapi/sound/sof/tone.h create mode 100644 include/uapi/sound/sof/trace.h
diff --git a/include/uapi/sound/sof/abi.h b/include/uapi/sound/sof/abi.h new file mode 100644 index 000000000000..98da1236cd66 --- /dev/null +++ b/include/uapi/sound/sof/abi.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +/** + * SOF ABI versioning is based on Semantic Versioning where we have a given + * MAJOR.MINOR.PATCH version number. See https://semver.org/ + * + * Rules for incrementing or changing version :- + * + * 1) Increment MAJOR version if you make incompatible API changes. MINOR and + * PATCH should be reset to 0. + * + * 2) Increment MINOR version if you add backwards compatible features or + * changes. PATCH should be reset to 0. + * + * 3) Increment PATCH version if you add backwards compatible bug fixes. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_ABI_H__ +#define __INCLUDE_UAPI_SOUND_SOF_ABI_H__ + +/* SOF ABI version major, minor and patch numbers */ +#define SOF_ABI_MAJOR 3 +#define SOF_ABI_MINOR 3 +#define SOF_ABI_PATCH 0 + +/* SOF ABI version number. Format within 32bit word is MMmmmppp */ +#define SOF_ABI_MAJOR_SHIFT 24 +#define SOF_ABI_MAJOR_MASK 0xff +#define SOF_ABI_MINOR_SHIFT 12 +#define SOF_ABI_MINOR_MASK 0xfff +#define SOF_ABI_PATCH_SHIFT 0 +#define SOF_ABI_PATCH_MASK 0xfff + +#define SOF_ABI_VER(major, minor, patch) \ + (((major) << SOF_ABI_MAJOR_SHIFT) | \ + ((minor) << SOF_ABI_MINOR_SHIFT) | \ + ((patch) << SOF_ABI_PATCH_SHIFT)) + +#define SOF_ABI_VERSION_MAJOR(version) \ + (((version) >> SOF_ABI_MAJOR_SHIFT) & SOF_ABI_MAJOR_MASK) +#define SOF_ABI_VERSION_MINOR(version) \ + (((version) >> SOF_ABI_MINOR_SHIFT) & SOF_ABI_MINOR_MASK) +#define SOF_ABI_VERSION_PATCH(version) \ + (((version) >> SOF_ABI_PATCH_SHIFT) & SOF_ABI_PATCH_MASK) + +#define SOF_ABI_VERSION_INCOMPATIBLE(sof_ver, client_ver) \ + (SOF_ABI_VERSION_MAJOR((sof_ver)) != \ + SOF_ABI_VERSION_MAJOR((client_ver)) \ + ) + +#define SOF_ABI_VERSION SOF_ABI_VER(SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH) + +/* SOF ABI magic number "SOF\0". */ +#define SOF_ABI_MAGIC 0x00464F53 + +#endif diff --git a/include/uapi/sound/sof/eq.h b/include/uapi/sound/sof/eq.h new file mode 100644 index 000000000000..666c2b6a3229 --- /dev/null +++ b/include/uapi/sound/sof/eq.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_EQ_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_EQ_H__ + +/* FIR EQ type */ + +#define SOF_EQ_FIR_IDX_SWITCH 0 + +#define SOF_EQ_FIR_MAX_SIZE 4096 /* Max size allowed for coef data in bytes */ + +#define SOF_EQ_FIR_MAX_LENGTH 192 /* Max length for individual filter */ + +#define SOF_EQ_FIR_MAX_RESPONSES 8 /* A blob can define max 8 FIR EQs */ + +/* + * eq_fir_configuration data structure contains this information + * uint32_t size + * This is the number of bytes need to store the received EQ + * configuration. + * uint16_t channels_in_config + * This describes the number of channels in this EQ config data. It + * can be different from PLATFORM_MAX_CHANNELS. + * uint16_t number_of_responses + * 0=no responses, 1=one response defined, 2=two responses defined, etc. + * int16_t data[] + * assign_response[channels_in_config] + * 0 = use first response, 1 = use 2nd response, etc. + * E.g. {0, 0, 0, 0, 1, 1, 1, 1} would apply to channels 0-3 the + * same first defined response and for to channels 4-7 the second. + * coef_data[] + * Repeated data + * { filter_length, output_shift, h[] } + * for every EQ response defined where vector h has filter_length + * number of coefficients. Coefficients in h[] are in Q1.15 format. + * E.g. 16384 (Q1.15) = 0.5. The shifts are number of right shifts. + * + * NOTE: The channels_in_config must be even to have coef_data aligned to + * 32 bit word in RAM. Therefore a mono EQ assign must be duplicated to 2ch + * even if it would never used. Similarly a 5ch EQ assign must be increased + * to 6ch. EQ init will return an error if this is not met. + * + * NOTE: The filter_length must be multiple of four. Therefore the filter must + * be padded from the end with zeros have this condition met. + */ + +struct sof_eq_fir_config { + uint32_t size; + uint16_t channels_in_config; + uint16_t number_of_responses; + + /* reserved */ + uint32_t reserved[4]; + + int16_t data[]; +} __packed; + +struct sof_eq_fir_coef_data { + int16_t length; /* Number of FIR taps */ + int16_t out_shift; /* Amount of right shifts at output */ + + /* reserved */ + uint32_t reserved[4]; + + int16_t coef[]; /* FIR coefficients */ +} __packed; + +/* In the struct above there's two 16 bit words (length, shift) and four + * reserved 32 bit words before the actual FIR coefficients. This information + * is used in parsing of the configuration blob. + */ +#define SOF_EQ_FIR_COEF_NHEADER \ + (sizeof(struct sof_eq_fir_coef_data) / sizeof(int16_t)) + +/* IIR EQ type */ + +#define SOF_EQ_IIR_IDX_SWITCH 0 + +#define SOF_EQ_IIR_MAX_SIZE 1024 /* Max size allowed for coef data in bytes */ + +#define SOF_EQ_IIR_MAX_RESPONSES 8 /* A blob can define max 8 IIR EQs */ + +/* eq_iir_configuration + * uint32_t channels_in_config + * This describes the number of channels in this EQ config data. It + * can be different from PLATFORM_MAX_CHANNELS. + * uint32_t number_of_responses_defined + * 0=no responses, 1=one response defined, 2=two responses defined, etc. + * int32_t data[] + * Data consist of two parts. First is the response assign vector that + * has length of channels_in_config. The latter part is coefficient + * data. + * uint32_t assign_response[channels_in_config] + * -1 = not defined, 0 = use first response, 1 = use 2nd, etc. + * E.g. {0, 0, 0, 0, -1, -1, -1, -1} would apply to channels 0-3 the + * same first defined response and leave channels 4-7 unequalized. + * coefficient_data[] + * <1st EQ> + * uint32_t num_biquads + * uint32_t num_biquads_in_series + * <1st biquad> + * int32_t coef_a2 Q2.30 format + * int32_t coef_a1 Q2.30 format + * int32_t coef_b2 Q2.30 format + * int32_t coef_b1 Q2.30 format + * int32_t coef_b0 Q2.30 format + * int32_t output_shift number of shifts right, shift left is negative + * int32_t output_gain Q2.14 format + * <2nd biquad> + * ... + * <2nd EQ> + * + * Note: A flat response biquad can be made with a section set to + * b0 = 1.0, gain = 1.0, and other parameters set to 0 + * {0, 0, 0, 0, 1073741824, 0, 16484} + */ + +struct sof_eq_iir_config { + uint32_t size; + uint32_t channels_in_config; + uint32_t number_of_responses; + + /* reserved */ + uint32_t reserved[4]; + + int32_t data[]; /* eq_assign[channels], eq 0, eq 1, ... */ +} __packed; + +struct sof_eq_iir_header_df2t { + uint32_t num_sections; + uint32_t num_sections_in_series; + + /* reserved */ + uint32_t reserved[4]; + + int32_t biquads[]; /* Repeated biquad coefficients */ +} __packed; + +struct sof_eq_iir_biquad_df2t { + int32_t a2; /* Q2.30 */ + int32_t a1; /* Q2.30 */ + int32_t b2; /* Q2.30 */ + int32_t b1; /* Q2.30 */ + int32_t b0; /* Q2.30 */ + int32_t output_shift; /* Number of right shifts */ + int32_t output_gain; /* Q2.14 */ +} __packed; + +/* A full 22th order equalizer with 11 biquads cover octave bands 1-11 in + * in the 0 - 20 kHz bandwidth. + */ +#define SOF_EQ_IIR_DF2T_BIQUADS_MAX 11 + +/* The number of int32_t words in sof_eq_iir_header_df2t: + * num_sections, num_sections_in_series, reserved[4] + */ +#define SOF_EQ_IIR_NHEADER_DF2T \ + (sizeof(struct sof_eq_iir_header_df2t) / sizeof(int32_t)) + +/* The number of int32_t words in sof_eq_iir_biquad_df2t: + * a2, a1, b2, b1, b0, output_shift, output_gain + */ +#define SOF_EQ_IIR_NBIQUAD_DF2T \ + (sizeof(struct sof_eq_iir_biquad_df2t) / sizeof(int32_t)) + +#endif diff --git a/include/uapi/sound/sof/fw.h b/include/uapi/sound/sof/fw.h new file mode 100644 index 000000000000..1afca973eb09 --- /dev/null +++ b/include/uapi/sound/sof/fw.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +/* + * Firmware file format . + */ + +#ifndef __INCLUDE_UAPI_SOF_FW_H__ +#define __INCLUDE_UAPI_SOF_FW_H__ + +#define SND_SOF_FW_SIG_SIZE 4 +#define SND_SOF_FW_ABI 1 +#define SND_SOF_FW_SIG "Reef" + +/* + * Firmware module is made up of 1 . N blocks of different types. The + * Block header is used to determine where and how block is to be copied in the + * DSP/host memory space. + */ +enum snd_sof_fw_blk_type { + SOF_FW_BLK_TYPE_INVALID = -1, + SOF_FW_BLK_TYPE_START = 0, + SOF_FW_BLK_TYPE_RSRVD0 = SOF_FW_BLK_TYPE_START, + SOF_FW_BLK_TYPE_IRAM = 1, /* local instruction RAM */ + SOF_FW_BLK_TYPE_DRAM = 2, /* local data RAM */ + SOF_FW_BLK_TYPE_SRAM = 3, /* system RAM */ + SOF_FW_BLK_TYPE_ROM = 4, + SOF_FW_BLK_TYPE_IMR = 5, + SOF_FW_BLK_TYPE_RSRVD6 = 6, + SOF_FW_BLK_TYPE_RSRVD7 = 7, + SOF_FW_BLK_TYPE_RSRVD8 = 8, + SOF_FW_BLK_TYPE_RSRVD9 = 9, + SOF_FW_BLK_TYPE_RSRVD10 = 10, + SOF_FW_BLK_TYPE_RSRVD11 = 11, + SOF_FW_BLK_TYPE_RSRVD12 = 12, + SOF_FW_BLK_TYPE_RSRVD13 = 13, + SOF_FW_BLK_TYPE_RSRVD14 = 14, + /* use SOF_FW_BLK_TYPE_RSVRDX for new block types */ + SOF_FW_BLK_TYPE_NUM +}; + +struct snd_sof_blk_hdr { + enum snd_sof_fw_blk_type type; + uint32_t size; /* bytes minus this header */ + uint32_t offset; /* offset from base */ +} __packed; + +/* + * Firmware file is made up of 1 .. N different modules types. The module + * type is used to determine how to load and parse the module. + */ +enum snd_sof_fw_mod_type { + SOF_FW_BASE = 0, /* base firmware image */ + SOF_FW_MODULE = 1, /* firmware module */ +}; + +struct snd_sof_mod_hdr { + enum snd_sof_fw_mod_type type; + uint32_t size; /* bytes minus this header */ + uint32_t num_blocks; /* number of blocks */ +} __packed; + +/* + * Firmware file header. + */ +struct snd_sof_fw_header { + unsigned char sig[SND_SOF_FW_SIG_SIZE]; /* "Reef" */ + uint32_t file_size; /* size of file minus this header */ + uint32_t num_modules; /* number of modules */ + uint32_t abi; /* version of header format */ +} __packed; + +#endif diff --git a/include/uapi/sound/sof/header.h b/include/uapi/sound/sof/header.h new file mode 100644 index 000000000000..7868990b0d6f --- /dev/null +++ b/include/uapi/sound/sof/header.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_HEADER_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_HEADER_H__ + +/* + * Header for all non IPC ABI data. + * + * Identifies data type, size and ABI. + * Used by any bespoke component data structures or binary blobs. + */ +struct sof_abi_hdr { + uint32_t magic; /**< 'S', 'O', 'F', '\0' */ + uint32_t type; /**< component specific type */ + uint32_t size; /**< size in bytes of data excl. this struct */ + uint32_t abi; /**< SOF ABI version */ + uint32_t reserved[4]; /**< reserved for future use */ + uint32_t data[0]; /**< Component data - opaque to core */ +} __packed; + +#endif diff --git a/include/uapi/sound/sof/manifest.h b/include/uapi/sound/sof/manifest.h new file mode 100644 index 000000000000..2009ee30fad0 --- /dev/null +++ b/include/uapi/sound/sof/manifest.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_MANIFEST_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_MANIFEST_H__ + +/* start offset for base FW module */ +#define SOF_MAN_ELF_TEXT_OFFSET 0x2000 + +/* FW Extended Manifest Header id = $AE1 */ +#define SOF_MAN_EXT_HEADER_MAGIC 0x31454124 + +/* module type load type */ +#define SOF_MAN_MOD_TYPE_BUILTIN 0 +#define SOF_MAN_MOD_TYPE_MODULE 1 + +struct sof_man_module_type { + uint32_t load_type:4; /* SOF_MAN_MOD_TYPE_ */ + uint32_t auto_start:1; + uint32_t domain_ll:1; + uint32_t domain_dp:1; + uint32_t rsvd_:25; +}; + +/* segment flags.type */ +#define SOF_MAN_SEGMENT_TEXT 0 +#define SOF_MAN_SEGMENT_RODATA 1 +#define SOF_MAN_SEGMENT_DATA 1 +#define SOF_MAN_SEGMENT_BSS 2 +#define SOF_MAN_SEGMENT_EMPTY 15 + +union sof_man_segment_flags { + uint32_t ul; + struct { + uint32_t contents:1; + uint32_t alloc:1; + uint32_t load:1; + uint32_t readonly:1; + uint32_t code:1; + uint32_t data:1; + uint32_t _rsvd0:2; + uint32_t type:4; /* MAN_SEGMENT_ */ + uint32_t _rsvd1:4; + uint32_t length:16; /* of segment in pages */ + } r; +} __packed; + +/* + * Module segment descriptor. Used by ROM - Immutable. + */ +struct sof_man_segment_desc { + union sof_man_segment_flags flags; + uint32_t v_base_addr; + uint32_t file_offset; +} __packed; + +/* + * The firmware binary can be split into several modules. + */ + +#define SOF_MAN_MOD_ID_LEN 4 +#define SOF_MAN_MOD_NAME_LEN 8 +#define SOF_MAN_MOD_SHA256_LEN 32 +#define SOF_MAN_MOD_ID {'$', 'A', 'M', 'E'} + +/* + * Each module has an entry in the FW header. Used by ROM - Immutable. + */ +struct sof_man_module { + uint8_t struct_id[SOF_MAN_MOD_ID_LEN]; /* SOF_MAN_MOD_ID */ + uint8_t name[SOF_MAN_MOD_NAME_LEN]; + uint8_t uuid[16]; + struct sof_man_module_type type; + uint8_t hash[SOF_MAN_MOD_SHA256_LEN]; + uint32_t entry_point; + uint16_t cfg_offset; + uint16_t cfg_count; + uint32_t affinity_mask; + uint16_t instance_max_count; /* max number of instances */ + uint16_t instance_bss_size; /* instance (pages) */ + struct sof_man_segment_desc segment[3]; +} __packed; + +/* + * Each module has a configuration in the FW header. Used by ROM - Immutable. + */ +struct sof_man_mod_config { + uint32_t par[4]; /* module parameters */ + uint32_t is_pages; /* actual size of instance .bss (pages) */ + uint32_t cps; /* cycles per second */ + uint32_t ibs; /* input buffer size (bytes) */ + uint32_t obs; /* output buffer size (bytes) */ + uint32_t module_flags; /* flags, reserved for future use */ + uint32_t cpc; /* cycles per single run */ + uint32_t obls; /* output block size, reserved for future use */ +} __packed; + +/* + * FW Manifest Header + */ + +#define SOF_MAN_FW_HDR_FW_NAME_LEN 8 +#define SOF_MAN_FW_HDR_ID {'$', 'A', 'M', '1'} +#define SOF_MAN_FW_HDR_NAME "ADSPFW" +#define SOF_MAN_FW_HDR_FLAGS 0x0 +#define SOF_MAN_FW_HDR_FEATURES 0xff + +/* + * The firmware has a standard header that is checked by the ROM on firmware + * loading. preload_page_count is used by DMA code loader and is entire + * image size on CNL. i.e. CNL: total size of the binary’s .text and .rodata + * Used by ROM - Immutable. + */ +struct sof_man_fw_header { + uint8_t header_id[4]; + uint32_t header_len; + uint8_t name[SOF_MAN_FW_HDR_FW_NAME_LEN]; + /* number of pages of preloaded image loaded by driver */ + uint32_t preload_page_count; + uint32_t fw_image_flags; + uint32_t feature_mask; + uint16_t major_version; + uint16_t minor_version; + uint16_t hotfix_version; + uint16_t build_version; + uint32_t num_module_entries; + uint32_t hw_buf_base_addr; + uint32_t hw_buf_length; + /* target address for binary loading as offset in IMR - must be == base offset */ + uint32_t load_offset; +} __packed; + +/* + * Firmware manifest descriptor. This can contain N modules and N module + * configs. Used by ROM - Immutable. + */ +struct sof_man_fw_desc { + struct sof_man_fw_header header; + + /* Warning - hack for module arrays. For some unknown reason the we + * have a variable size array of struct man_module followed by a + * variable size array of struct mod_config. These should have been + * merged into a variable array of a parent structure. We have to hack + * around this in many places.... + * + * struct sof_man_module man_module[]; + * struct sof_man_mod_config mod_config[]; + */ + +} __packed; + +/* + * Component Descriptor. Used by ROM - Immutable. + */ +struct sof_man_component_desc { + uint32_t reserved[2]; /* all 0 */ + uint32_t version; + uint8_t hash[SOF_MAN_MOD_SHA256_LEN]; + uint32_t base_offset; + uint32_t limit_offset; + uint32_t attributes[4]; +} __packed; + +/* + * Audio DSP extended metadata. Used by ROM - Immutable. + */ +struct sof_man_adsp_meta_file_ext { + uint32_t ext_type; /* always 17 for ADSP extension */ + uint32_t ext_len; + uint32_t imr_type; + uint8_t reserved[16]; /* all 0 */ + struct sof_man_component_desc comp_desc[1]; +} __packed; + +/* + * Module Manifest for rimage module metadata. Not used by ROM. + */ +struct sof_man_module_manifest { + struct sof_man_module module; + uint32_t text_size; +} __packed; + +#endif diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h new file mode 100644 index 000000000000..649c31acce24 --- /dev/null +++ b/include/uapi/sound/sof/tokens.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 + * Keyon Jie yang.jie@linux.intel.com + */ + +/* + * Topology IDs and tokens. + * + * ** MUST BE ALIGNED WITH TOPOLOGY CONFIGURATION TOKEN VALUES ** + */ + +#ifndef __INCLUDE_UAPI_SOF_TOPOLOGY_H__ +#define __INCLUDE_UAPI_SOF_TOPOLOGY_H__ + +/* + * Kcontrol IDs + */ +#define SOF_TPLG_KCTL_VOL_ID 256 +#define SOF_TPLG_KCTL_ENUM_ID 257 +#define SOF_TPLG_KCTL_BYTES_ID 258 +#define SOF_TPLG_KCTL_SWITCH_ID 259 + +/* + * Tokens - must match values in topology configurations + */ + +/* buffers */ +#define SOF_TKN_BUF_SIZE 100 +#define SOF_TKN_BUF_CAPS 101 + +/* DAI */ +/* Token retired with ABI 3.2, do not use for new capabilities + * #define SOF_TKN_DAI_DMAC_CONFIG 153 + */ +#define SOF_TKN_DAI_TYPE 154 +#define SOF_TKN_DAI_INDEX 155 +#define SOF_TKN_DAI_DIRECTION 156 + +/* scheduling */ +#define SOF_TKN_SCHED_DEADLINE 200 +#define SOF_TKN_SCHED_PRIORITY 201 +#define SOF_TKN_SCHED_MIPS 202 +#define SOF_TKN_SCHED_CORE 203 +#define SOF_TKN_SCHED_FRAMES 204 +#define SOF_TKN_SCHED_TIMER 205 + +/* volume */ +#define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 +#define SOF_TKN_VOLUME_RAMP_STEP_MS 251 + +/* SRC */ +#define SOF_TKN_SRC_RATE_IN 300 +#define SOF_TKN_SRC_RATE_OUT 301 + +/* PCM */ +#define SOF_TKN_PCM_DMAC_CONFIG 353 + +/* Generic components */ +#define SOF_TKN_COMP_PERIOD_SINK_COUNT 400 +#define SOF_TKN_COMP_PERIOD_SOURCE_COUNT 401 +#define SOF_TKN_COMP_FORMAT 402 +/* Token retired with ABI 3.2, do not use for new capabilities + * #define SOF_TKN_COMP_PRELOAD_COUNT 403 + */ + +/* SSP */ +#define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +#define SOF_TKN_INTEL_SSP_MCLK_ID 501 +#define SOF_TKN_INTEL_SSP_SAMPLE_BITS 502 +#define SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH 503 +#define SOF_TKN_INTEL_SSP_QUIRKS 504 +#define SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT 505 + +/* DMIC */ +#define SOF_TKN_INTEL_DMIC_DRIVER_VERSION 600 +#define SOF_TKN_INTEL_DMIC_CLK_MIN 601 +#define SOF_TKN_INTEL_DMIC_CLK_MAX 602 +#define SOF_TKN_INTEL_DMIC_DUTY_MIN 603 +#define SOF_TKN_INTEL_DMIC_DUTY_MAX 604 +#define SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE 605 +#define SOF_TKN_INTEL_DMIC_SAMPLE_RATE 608 +#define SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH 609 + +/* DMIC PDM */ +#define SOF_TKN_INTEL_DMIC_PDM_CTRL_ID 700 +#define SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable 701 +#define SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable 702 +#define SOF_TKN_INTEL_DMIC_PDM_POLARITY_A 703 +#define SOF_TKN_INTEL_DMIC_PDM_POLARITY_B 704 +#define SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE 705 +#define SOF_TKN_INTEL_DMIC_PDM_SKEW 706 + +/* Tone */ +#define SOF_TKN_TONE_SAMPLE_RATE 800 + +#define SOF_TKN_EFFECT_TYPE 900 + +#endif diff --git a/include/uapi/sound/sof/tone.h b/include/uapi/sound/sof/tone.h new file mode 100644 index 000000000000..d7c6e5d8317e --- /dev/null +++ b/include/uapi/sound/sof/tone.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. +*/ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_TONE_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_TONE_H__ + +#define SOF_TONE_IDX_FREQUENCY 0 +#define SOF_TONE_IDX_AMPLITUDE 1 +#define SOF_TONE_IDX_FREQ_MULT 2 +#define SOF_TONE_IDX_AMPL_MULT 3 +#define SOF_TONE_IDX_LENGTH 4 +#define SOF_TONE_IDX_PERIOD 5 +#define SOF_TONE_IDX_REPEATS 6 +#define SOF_TONE_IDX_LIN_RAMP_STEP 7 + +#endif diff --git a/include/uapi/sound/sof/trace.h b/include/uapi/sound/sof/trace.h new file mode 100644 index 000000000000..69c6e0845fad --- /dev/null +++ b/include/uapi/sound/sof/trace.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_TRACE_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_TRACE_H__ + +/* + * Host system time. + * + * This property is used by the driver to pass down information about + * current system time. It is expressed in us. + * FW translates timestamps (in log entries, probe pockets) to this time + * domain. + * + * (cavs: SystemTime). + */ +struct system_time { + uint32_t val_l; /* Lower dword of current host time value */ + uint32_t val_u; /* Upper dword of current host time value */ +} __packed; + +/* trace event classes - high 8 bits*/ +#define TRACE_CLASS_IRQ (1 << 24) +#define TRACE_CLASS_IPC (2 << 24) +#define TRACE_CLASS_PIPE (3 << 24) +#define TRACE_CLASS_HOST (4 << 24) +#define TRACE_CLASS_DAI (5 << 24) +#define TRACE_CLASS_DMA (6 << 24) +#define TRACE_CLASS_SSP (7 << 24) +#define TRACE_CLASS_COMP (8 << 24) +#define TRACE_CLASS_WAIT (9 << 24) +#define TRACE_CLASS_LOCK (10 << 24) +#define TRACE_CLASS_MEM (11 << 24) +#define TRACE_CLASS_MIXER (12 << 24) +#define TRACE_CLASS_BUFFER (13 << 24) +#define TRACE_CLASS_VOLUME (14 << 24) +#define TRACE_CLASS_SWITCH (15 << 24) +#define TRACE_CLASS_MUX (16 << 24) +#define TRACE_CLASS_SRC (17 << 24) +#define TRACE_CLASS_TONE (18 << 24) +#define TRACE_CLASS_EQ_FIR (19 << 24) +#define TRACE_CLASS_EQ_IIR (20 << 24) +#define TRACE_CLASS_SA (21 << 24) +#define TRACE_CLASS_DMIC (22 << 24) +#define TRACE_CLASS_POWER (23 << 24) +#define TRACE_CLASS_IDC (24 << 24) +#define TRACE_CLASS_CPU (25 << 24) +#define TRACE_CLASS_EDF (27 << 24) +#define TRACE_CLASS_KPB (28 << 24) +#define TRACE_CLASS_SELECTOR (29 << 24) +#define TRACE_CLASS_SCHEDULE (30 << 24) + +#define LOG_ENABLE 1 /* Enable logging */ +#define LOG_DISABLE 0 /* Disable logging */ + +#define LOG_LEVEL_CRITICAL 1 /* (FDK fatal) */ +#define LOG_LEVEL_VERBOSE 2 + +/* + * Layout of a log fifo. + */ +struct log_buffer_layout { + uint32_t read_ptr; /*read pointer */ + uint32_t write_ptr; /* write pointer */ + uint32_t buffer[0]; /* buffer */ +} __packed; + +/* + * Log buffer status reported by FW. + */ +struct log_buffer_status { + uint32_t core_id; /* ID of core that logged to other half */ +} __packed; + +#define TRACE_ID_LENGTH 12 + +/* + * Log entry header. + * + * The header is followed by an array of arguments (uint32_t[]). + * Number of arguments is specified by the params_num field of log_entry + */ +struct log_entry_header { + uint32_t id_0 : TRACE_ID_LENGTH; /* e.g. Pipeline ID */ + uint32_t id_1 : TRACE_ID_LENGTH; /* e.g. Component ID */ + uint32_t core_id : 8; /* Reporting core's id */ + + uint64_t timestamp; /* Timestamp (in dsp ticks) */ + uint32_t log_entry_address; /* Address of log entry in ELF */ +} __packed; + +#endif
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add support for saving and restoring DSP context in D3 to host DDR.
The suspend callback includes: suspend all pcm's stream that are running, send CTX_SAVE ipc, drop all ipc's, release trace dma and then power off the DSP.
And the resume callback performs the following steps: load FW, run FW, re-initialize trace, restore pipeline, restore the kcontrol values and finally send the ctx restore ipc to the dsp.
The streams that are suspended are resumed by the ALSA resume trigger. If the streams are paused during system suspend, they are marked explicitly so they can be restored during PAUSE_RELEASE.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/pm.c | 373 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 sound/soc/sof/pm.c
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c new file mode 100644 index 000000000000..fd0cf10bf742 --- /dev/null +++ b/sound/soc/sof/pm.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include "ops.h" +#include "sof-priv.h" + +static int sof_restore_kcontrols(struct snd_sof_dev *sdev) +{ + struct snd_sof_control *scontrol = NULL; + 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: + /* fallthrough */ + case SOF_CTRL_CMD_ENUM: + /* fallthrough */ + 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_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd); + 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_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd); + break; + + default: + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "error: failed kcontrol value set for widget: %d\n", + scontrol->comp_id); + + return ret; + } + } + + return 0; +} + +static int sof_restore_pipelines(struct snd_sof_dev *sdev) +{ + struct snd_sof_widget *swidget = NULL; + struct snd_sof_route *sroute = NULL; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_dai *dai; + struct sof_ipc_comp_dai *comp_dai; + struct sof_ipc_cmd_hdr *hdr; + int ret = 0; + + /* restore pipeline components */ + list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + struct sof_ipc_comp_reply r; + + /* skip if there is no private data */ + if (!swidget->private) + continue; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + /* fallthrough */ + case snd_soc_dapm_dai_out: + dai = (struct snd_sof_dai *)swidget->private; + comp_dai = &dai->comp_dai; + ret = sof_ipc_tx_message(sdev->ipc, + comp_dai->comp.hdr.cmd, + comp_dai, sizeof(*comp_dai), + &r, sizeof(r)); + break; + case snd_soc_dapm_scheduler: + + /* + * During suspend, all DSP cores are powered off. + * Therefore upon resume, create the pipeline comp + * and power up the core that the pipeline is + * scheduled on. + */ + pipeline = (struct sof_ipc_pipe_new *)swidget->private; + ret = sof_load_pipeline_ipc(sdev, pipeline, &r); + break; + default: + hdr = (struct sof_ipc_cmd_hdr *)swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, + swidget->private, hdr->size, + &r, sizeof(r)); + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load widget type %d with ID: %d\n", + swidget->widget->id, swidget->comp_id); + + return ret; + } + } + + /* restore pipeline connections */ + list_for_each_entry_reverse(sroute, &sdev->route_list, list) { + struct sof_ipc_pipe_comp_connect *connect; + struct sof_ipc_reply reply; + + /* skip if there's no private data */ + if (!sroute->private) + continue; + + connect = sroute->private; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load route sink %s control %s source %s\n", + sroute->route->sink, + sroute->route->control ? sroute->route->control + : "none", + sroute->route->source); + + return ret; + } + } + + /* restore dai links */ + list_for_each_entry_reverse(dai, &sdev->dai_list, list) { + struct sof_ipc_reply reply; + struct sof_ipc_dai_config *config = dai->dai_config; + + if (!config) { + dev_err(sdev->dev, "error: no config for DAI %s\n", + dai->name); + continue; + } + + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, + config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set dai config for %s\n", + dai->name); + + return ret; + } + } + + /* complete pipeline */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(sdev, swidget); + break; + default: + break; + } + } + + /* restore pipeline kcontrols */ + ret = sof_restore_kcontrols(sdev); + if (ret < 0) + dev_err(sdev->dev, + "error: restoring kcontrols after resume\n"); + + return ret; +} + +static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) +{ + 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)); +} + +static void sof_set_restore_stream(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + + /* suspend all running streams */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + + spcm->restore_stream[0] = 1; + spcm->restore_stream[1] = 1; + + } +} + +#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); + int ret = 0; + + /* do nothing if dsp resume callbacks are not set */ + if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) + 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; + } + + /* 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; + } + + /* boot the firmware */ + 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); + 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_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); + int ret = 0; + + /* do nothing if dsp suspend callback is not set */ + if (!sof_ops(sdev)->suspend) + return 0; + + /* release trace */ + snd_sof_release_trace(sdev); + + /* set restore_stream for all streams during system suspend */ + if (!runtime_suspend) + sof_set_restore_stream(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_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + if (ret < 0) { + dev_err(sdev->dev, + "error: ctx_save ipc error during suspend %d\n", + ret); + return ret; + } + + /* power down all DSP cores */ + if (runtime_suspend) + ret = snd_sof_dsp_runtime_suspend(sdev, 0); + else + ret = snd_sof_dsp_suspend(sdev, 0); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to power down DSP during suspend %d\n", + ret); + + return ret; +} + +int snd_sof_runtime_suspend(struct device *dev) +{ + return sof_suspend(dev, true); +} +EXPORT_SYMBOL(snd_sof_runtime_suspend); + +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);
On Thu, 21 Mar 2019 17:10:13 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add support for saving and restoring DSP context in D3 to host DDR.
The suspend callback includes: suspend all pcm's stream that are running, send CTX_SAVE ipc, drop all ipc's, release trace dma and then power off the DSP.
And the resume callback performs the following steps: load FW, run FW, re-initialize trace, restore pipeline, restore the kcontrol values and finally send the ctx restore ipc to the dsp.
The streams that are suspended are resumed by the ALSA resume trigger. If the streams are paused during system suspend, they are marked explicitly so they can be restored during PAUSE_RELEASE.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
sound/soc/sof/pm.c | 373 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 sound/soc/sof/pm.c
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c new file mode 100644 index 000000000000..fd0cf10bf742 --- /dev/null +++ b/sound/soc/sof/pm.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +//
+#include "ops.h" +#include "sof-priv.h"
+static int sof_restore_kcontrols(struct snd_sof_dev *sdev) +{
- struct snd_sof_control *scontrol = NULL;
- 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:
/* fallthrough */
case SOF_CTRL_CMD_ENUM:
/* fallthrough */
case SOF_CTRL_CMD_SWITCH:
In such an expression, fallthrough marker is superfluous.
switch (foo) { case A: case B: case C: ....
thanks,
Takashi
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add a simple "fallback" machine driver that can be used to enable SOF on boards with no codec device. This machine driver can also be forced for debug/development.
Signed-off-by: Keyon Jie yang.jie@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof.h | 6 +++ sound/soc/sof/nocodec.c | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 sound/soc/sof/nocodec.c
diff --git a/include/sound/sof.h b/include/sound/sof.h index 54f65ec33a6c..4640566b54fe 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -12,6 +12,7 @@ #define __INCLUDE_SOUND_SOF_H
#include <linux/pci.h> +#include <sound/soc.h> #include <sound/soc-acpi.h>
struct snd_sof_dsp_ops; @@ -91,4 +92,9 @@ struct sof_dev_desc { const struct sof_arch_ops *arch_ops; };
+int sof_nocodec_setup(struct device *dev, + struct snd_sof_pdata *sof_pdata, + struct snd_soc_acpi_mach *mach, + const struct sof_dev_desc *desc, + const struct snd_sof_dsp_ops *ops); #endif diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c new file mode 100644 index 000000000000..4a66abe065c9 --- /dev/null +++ b/sound/soc/sof/nocodec.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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 */ +}; + +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) +{ + int i; + + if (!ops || !links || !card) + return -EINVAL; + + /* set up BE dai_links */ + for (i = 0; i < link_num; i++) { + links[i].name = devm_kasprintf(dev, GFP_KERNEL, + "NoCodec-%d", i); + if (!links[i].name) + return -ENOMEM; + + links[i].id = i; + links[i].no_pcm = 1; + links[i].cpu_dai_name = ops->drv[i].name; + links[i].platform_name = dev_name(dev); + links[i].codec_dai_name = "snd-soc-dummy-dai"; + links[i].codec_name = "snd-soc-dummy"; + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + } + + card->dai_link = links; + card->num_links = link_num; + + return 0; +} + +int sof_nocodec_setup(struct device *dev, + struct snd_sof_pdata *sof_pdata, + struct snd_soc_acpi_mach *mach, + const struct sof_dev_desc *desc, + const struct snd_sof_dsp_ops *ops) +{ + struct snd_soc_dai_link *links; + int ret = 0; + + if (!mach) + return -EINVAL; + + sof_pdata->drv_name = "sof-nocodec"; + + mach->drv_name = "sof-nocodec"; + sof_pdata->fw_filename = desc->nocodec_fw_filename; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + + /* create dummy BE dai_links */ + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * + ops->num_drv, GFP_KERNEL); + if (!links) + return -ENOMEM; + + ret = sof_nocodec_bes_setup(dev, ops, links, ops->num_drv, + &sof_nocodec_card); + return ret; +} +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");
Add common directory for xtensa architecture
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/xtensa.h | 44 +++++++++++ sound/soc/sof/xtensa/Kconfig | 2 + sound/soc/sof/xtensa/Makefile | 5 ++ sound/soc/sof/xtensa/core.c | 138 ++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 include/sound/sof/xtensa.h create mode 100644 sound/soc/sof/xtensa/Kconfig create mode 100644 sound/soc/sof/xtensa/Makefile create mode 100644 sound/soc/sof/xtensa/core.c
diff --git a/include/sound/sof/xtensa.h b/include/sound/sof/xtensa.h new file mode 100644 index 000000000000..a7189984000d --- /dev/null +++ b/include/sound/sof/xtensa.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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. + */ + +#ifndef __INCLUDE_SOUND_SOF_XTENSA_H__ +#define __INCLUDE_SOUND_SOF_XTENSA_H__ + +#include <sound/sof/header.h> + +/* + * Architecture specific debug + */ + +/* Xtensa Firmware Oops data */ +struct sof_ipc_dsp_oops_xtensa { + struct sof_ipc_hdr hdr; + uint32_t exccause; + uint32_t excvaddr; + uint32_t ps; + uint32_t epc1; + uint32_t epc2; + uint32_t epc3; + uint32_t epc4; + uint32_t epc5; + uint32_t epc6; + uint32_t epc7; + uint32_t eps2; + uint32_t eps3; + uint32_t eps4; + uint32_t eps5; + uint32_t eps6; + uint32_t eps7; + uint32_t depc; + uint32_t intenable; + uint32_t interrupt; + uint32_t sar; + uint32_t stack; +} __packed; + +#endif diff --git a/sound/soc/sof/xtensa/Kconfig b/sound/soc/sof/xtensa/Kconfig new file mode 100644 index 000000000000..8a9343b85146 --- /dev/null +++ b/sound/soc/sof/xtensa/Kconfig @@ -0,0 +1,2 @@ +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 000000000000..cc89c7472a38 --- /dev/null +++ b/sound/soc/sof/xtensa/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: (GPL-2.0 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 000000000000..c3ad23a85b99 --- /dev/null +++ b/sound/soc/sof/xtensa/core.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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->stack; + /* 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(sof_xtensa_arch_ops); + +MODULE_DESCRIPTION("SOF Xtensa DSP support"); +MODULE_LICENSE("Dual BSD/GPL");
Helpers to set-up back-ends, create platform devices and common IO/block read/write operations
Signed-off-by: Keyon Jie yang.jie@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/utils.c | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 sound/soc/sof/utils.c
diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c new file mode 100644 index 000000000000..fbefddde34d2 --- /dev/null +++ b/sound/soc/sof/utils.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 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 <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 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);
On Thu, 21 Mar 2019 17:10:16 +0100, Pierre-Louis Bossart wrote:
+/*
- Register IO
- The sof_io_xyz() wrappers are typically referenced in snd_sof_dsp_ops
- structures and and cannot be inlined.
Too many and.
Takashi
participants (6)
-
Keyon Jie
-
Liam Girdwood
-
Mark Brown
-
Pierre-Louis Bossart
-
Ranjani Sridharan
-
Takashi Iwai