diff options
Diffstat (limited to 'sound/soc/intel/avs')
57 files changed, 19524 insertions, 0 deletions
diff --git a/sound/soc/intel/avs/Makefile b/sound/soc/intel/avs/Makefile new file mode 100644 index 000000000000..576dc0da381d --- /dev/null +++ b/sound/soc/intel/avs/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only + +snd-soc-avs-y := dsp.o ipc.o messages.o utils.o core.o loader.o \ + topology.o path.o pcm.o board_selection.o control.o \ + sysfs.o +snd-soc-avs-y += cldma.o +snd-soc-avs-y += skl.o apl.o cnl.o icl.o tgl.o mtl.o lnl.o ptl.o + +snd-soc-avs-y += trace.o +# tell define_trace.h where to find the trace header +CFLAGS_trace.o := -I$(src) + +ifneq ($(CONFIG_DEBUG_FS),) +snd-soc-avs-y += probes.o debugfs.o +endif + +obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o + +# Machine support +obj-$(CONFIG_SND_SOC) += boards/ diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c new file mode 100644 index 000000000000..b922eeaba843 --- /dev/null +++ b/sound/soc/intel/avs/apl.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/devcoredump.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" +#include "path.h" +#include "registers.h" +#include "topology.h" + +static irqreturn_t avs_apl_dsp_interrupt(struct avs_dev *adev) +{ + u32 adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS); + irqreturn_t ret = IRQ_NONE; + + if (adspis == UINT_MAX) + return ret; + + if (adspis & AVS_ADSP_ADSPIS_IPC) { + avs_skl_ipc_interrupt(adev); + ret = IRQ_HANDLED; + } + + return ret; +} + +#ifdef CONFIG_DEBUG_FS +int avs_apl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities) +{ + struct avs_apl_log_state_info *info; + u32 size, num_cores = adev->hw_cfg.dsp_cores; + int ret, i; + + if (fls_long(resource_mask) > num_cores) + return -EINVAL; + size = struct_size(info, logs_core, num_cores); + info = kzalloc(size, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->aging_timer_period = aging_period; + info->fifo_full_timer_period = fifo_full_period; + info->core_mask = resource_mask; + if (enable) + for_each_set_bit(i, &resource_mask, num_cores) { + info->logs_core[i].enable = enable; + info->logs_core[i].min_priority = *priorities++; + } + else + for_each_set_bit(i, &resource_mask, num_cores) + info->logs_core[i].enable = enable; + + ret = avs_ipc_set_enable_logs(adev, (u8 *)info, size); + kfree(info); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} +#endif + +int avs_apl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct avs_apl_log_buffer_layout layout; + void __iomem *addr, *buf; + + addr = avs_log_buffer_addr(adev, msg->log.core); + if (!addr) + return -ENXIO; + + memcpy_fromio(&layout, addr, sizeof(layout)); + + if (!avs_logging_fw(adev)) + /* consume the logs regardless of consumer presence */ + goto update_read_ptr; + + buf = avs_apl_log_payload_addr(addr); + + if (layout.read_ptr > layout.write_ptr) { + avs_dump_fw_log(adev, buf + layout.read_ptr, + avs_apl_log_payload_size(adev) - layout.read_ptr); + layout.read_ptr = 0; + } + avs_dump_fw_log_wakeup(adev, buf + layout.read_ptr, layout.write_ptr - layout.read_ptr); + +update_read_ptr: + writel(layout.write_ptr, addr); + return 0; +} + +static int avs_apl_wait_log_entry(struct avs_dev *adev, u32 core, + struct avs_apl_log_buffer_layout *layout) +{ + unsigned long timeout; + void __iomem *addr; + + addr = avs_log_buffer_addr(adev, core); + if (!addr) + return -ENXIO; + + timeout = jiffies + msecs_to_jiffies(10); + + do { + memcpy_fromio(layout, addr, sizeof(*layout)); + if (layout->read_ptr != layout->write_ptr) + return 0; + usleep_range(500, 1000); + } while (!time_after(jiffies, timeout)); + + return -ETIMEDOUT; +} + +/* reads log header and tests its type */ +#define avs_apl_is_entry_stackdump(addr) ((readl(addr) >> 30) & 0x1) + +int avs_apl_coredump(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct avs_apl_log_buffer_layout layout; + void __iomem *addr, *buf; + size_t dump_size; + u32 offset = 0; + u8 *dump, *pos; + + dump_size = AVS_FW_REGS_SIZE + msg->ext.coredump.stack_dump_size; + dump = vzalloc(dump_size); + if (!dump) + return -ENOMEM; + + memcpy_fromio(dump, avs_sram_addr(adev, AVS_FW_REGS_WINDOW), AVS_FW_REGS_SIZE); + + if (!msg->ext.coredump.stack_dump_size) + goto exit; + + /* Dump the registers even if an external error prevents gathering the stack. */ + addr = avs_log_buffer_addr(adev, msg->ext.coredump.core_id); + if (!addr) + goto exit; + + buf = avs_apl_log_payload_addr(addr); + memcpy_fromio(&layout, addr, sizeof(layout)); + if (!avs_apl_is_entry_stackdump(buf + layout.read_ptr)) { + union avs_notify_msg lbs_msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + + /* + * DSP awaits the remaining logs to be + * gathered before dumping stack + */ + lbs_msg.log.core = msg->ext.coredump.core_id; + avs_log_buffer_status_locked(adev, &lbs_msg); + } + + pos = dump + AVS_FW_REGS_SIZE; + /* gather the stack */ + do { + u32 count; + + if (avs_apl_wait_log_entry(adev, msg->ext.coredump.core_id, &layout)) + break; + + if (layout.read_ptr > layout.write_ptr) { + count = avs_apl_log_payload_size(adev) - layout.read_ptr; + memcpy_fromio(pos + offset, buf + layout.read_ptr, count); + layout.read_ptr = 0; + offset += count; + } + count = layout.write_ptr - layout.read_ptr; + memcpy_fromio(pos + offset, buf + layout.read_ptr, count); + offset += count; + + /* update read pointer */ + writel(layout.write_ptr, addr); + } while (offset < msg->ext.coredump.stack_dump_size); + +exit: + dev_coredumpv(adev->dev, dump, dump_size, GFP_KERNEL); + + return 0; +} + +static bool avs_apl_lp_streaming(struct avs_dev *adev) +{ + struct avs_path *path; + + spin_lock(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) { + struct avs_tplg_modcfg_ext *cfg; + + cfg = mod->template->cfg_ext; + + /* only copiers have gateway attributes */ + if (!guid_equal(&cfg->type, &AVS_COPIER_MOD_UUID)) + continue; + /* non-gateway copiers do not prevent PG */ + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + + if (!mod->gtw_attrs.lp_buffer_alloc) { + spin_unlock(&adev->path_list_lock); + return false; + } + } + } + } + spin_unlock(&adev->path_list_lock); + + return true; +} + +bool avs_apl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake) +{ + /* wake in all cases */ + if (wake) + return true; + + /* + * If no pipelines are running, allow for d0ix schedule. + * If all gateways have lp=1, allow for d0ix schedule. + * If any gateway with lp=0 is allocated, abort scheduling d0ix. + * + * Note: for cAVS 1.5+ and 1.8, D0IX is LP-firmware transition, + * not the power-gating mechanism known from cAVS 2.0. + */ + return avs_apl_lp_streaming(adev); +} + +int avs_apl_set_d0ix(struct avs_dev *adev, bool enable) +{ + bool streaming = false; + int ret; + + if (enable) + /* Either idle or all gateways with lp=1. */ + streaming = !list_empty(&adev->path_list); + + ret = avs_ipc_set_d0ix(adev, enable, streaming); + return AVS_IPC_RET(ret); +} + +const struct avs_dsp_ops avs_apl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .dsp_interrupt = avs_apl_dsp_interrupt, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_hda_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .log_buffer_offset = avs_skl_log_buffer_offset, + .log_buffer_status = avs_apl_log_buffer_status, + .coredump = avs_apl_coredump, + .d0ix_toggle = avs_apl_d0ix_toggle, + .set_d0ix = avs_apl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(apl) +}; diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h new file mode 100644 index 000000000000..0f8ddd0e9e5f --- /dev/null +++ b/sound/soc/intel/avs/avs.h @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_H +#define __SOUND_SOC_INTEL_AVS_H + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/kfifo.h> +#include <sound/hda_codec.h> +#include <sound/hda_register.h> +#include <sound/soc-component.h> +#include "messages.h" +#include "registers.h" + +struct avs_dev; +struct avs_tplg; +struct avs_tplg_library; +struct avs_ipc_msg; + +#ifdef CONFIG_ACPI +#define AVS_S0IX_SUPPORTED \ + (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) +#else +#define AVS_S0IX_SUPPORTED false +#endif + +/* + * struct avs_dsp_ops - Platform-specific DSP operations + * + * @power: Power on or off DSP cores + * @reset: Enter or exit reset state on DSP cores + * @stall: Stall or run DSP cores + * @irq_handler: Top half of IPC servicing + * @irq_thread: Bottom half of IPC servicing + * @int_control: Enable or disable IPC interrupts + */ +struct avs_dsp_ops { + int (* const power)(struct avs_dev *, u32, bool); + int (* const reset)(struct avs_dev *, u32, bool); + int (* const stall)(struct avs_dev *, u32, bool); + irqreturn_t (* const dsp_interrupt)(struct avs_dev *); + void (* const int_control)(struct avs_dev *, bool); + int (* const load_basefw)(struct avs_dev *, struct firmware *); + int (* const load_lib)(struct avs_dev *, struct firmware *, u32); + int (* const transfer_mods)(struct avs_dev *, bool, struct avs_module_entry *, u32); + int (* const config_basefw)(struct avs_dev *); + int (* const enable_logs)(struct avs_dev *, enum avs_log_enable, u32, u32, unsigned long, + u32 *); + int (* const log_buffer_offset)(struct avs_dev *, u32); + int (* const log_buffer_status)(struct avs_dev *, union avs_notify_msg *); + int (* const coredump)(struct avs_dev *, union avs_notify_msg *); + bool (* const d0ix_toggle)(struct avs_dev *, struct avs_ipc_msg *, bool); + int (* const set_d0ix)(struct avs_dev *, bool); +}; + +#define avs_dsp_op(adev, op, ...) \ + ((adev)->spec->dsp_ops->op(adev, ## __VA_ARGS__)) + +extern const struct avs_dsp_ops avs_skl_dsp_ops; +extern const struct avs_dsp_ops avs_apl_dsp_ops; +extern const struct avs_dsp_ops avs_cnl_dsp_ops; +extern const struct avs_dsp_ops avs_icl_dsp_ops; +extern const struct avs_dsp_ops avs_tgl_dsp_ops; +extern const struct avs_dsp_ops avs_ptl_dsp_ops; + +#define AVS_PLATATTR_CLDMA BIT_ULL(0) +#define AVS_PLATATTR_IMR BIT_ULL(1) +#define AVS_PLATATTR_ACE BIT_ULL(2) +#define AVS_PLATATTR_ALTHDA BIT_ULL(3) + +#define avs_platattr_test(adev, attr) \ + ((adev)->spec->attributes & AVS_PLATATTR_##attr) + +struct avs_sram_spec { + const u32 base_offset; + const u32 window_size; +}; + +struct avs_hipc_spec { + const u32 req_offset; + const u32 req_ext_offset; + const u32 req_busy_mask; + const u32 ack_offset; + const u32 ack_done_mask; + const u32 rsp_offset; + const u32 rsp_busy_mask; + const u32 ctl_offset; + const u32 sts_offset; +}; + +/* Platform specific descriptor */ +struct avs_spec { + const char *name; + + const struct avs_dsp_ops *const dsp_ops; + struct avs_fw_version min_fw_version; /* anything below is rejected */ + + const u32 core_init_mask; /* used during DSP boot */ + const u64 attributes; /* bitmask of AVS_PLATATTR_* */ + const struct avs_sram_spec *sram; + const struct avs_hipc_spec *hipc; +}; + +struct avs_fw_entry { + const char *name; + const struct firmware *fw; + + struct list_head node; +}; + +/* + * struct avs_dev - Intel HD-Audio driver data + * + * @dev: PCI device + * @dsp_ba: DSP bar address + * @spec: platform-specific descriptor + * @fw_cfg: Firmware configuration, obtained through FW_CONFIG message + * @hw_cfg: Hardware configuration, obtained through HW_CONFIG message + * @mods_info: Available module-types, obtained through MODULES_INFO message + * @mod_idas: Module instance ID pool, one per module-type + * @modres_mutex: For synchronizing any @mods_info updates + * @ppl_ida: Pipeline instance ID pool + * @fw_list: List of libraries loaded, including base firmware + */ +struct avs_dev { + struct hda_bus base; + struct device *dev; + + void __iomem *dsp_ba; + const struct avs_spec *spec; + struct avs_ipc *ipc; + + struct avs_fw_cfg fw_cfg; + struct avs_hw_cfg hw_cfg; + struct avs_mods_info *mods_info; + struct ida **mod_idas; + struct mutex modres_mutex; + void *modcfg_buf; /* module configuration buffer */ + struct ida ppl_ida; + struct list_head fw_list; + int *core_refs; /* reference count per core */ + char **lib_names; + int num_lp_paths; + atomic_t l1sen_counter; /* controls whether L1SEN should be disabled */ + + struct completion fw_ready; + struct work_struct probe_work; + + struct list_head comp_list; + struct mutex comp_list_mutex; + struct list_head path_list; + spinlock_t path_list_lock; + struct mutex path_mutex; + + spinlock_t trace_lock; /* serialize debug window I/O between each LOG_BUFFER_STATUS */ +#ifdef CONFIG_DEBUG_FS + struct kfifo trace_fifo; + wait_queue_head_t trace_waitq; + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 logged_resources; /* context dependent: core or library */ + struct dentry *debugfs_root; + /* probes */ + struct hdac_ext_stream *extractor; + unsigned int num_probe_streams; +#endif +}; + +/* from hda_bus to avs_dev */ +#define hda_to_avs(hda) container_of(hda, struct avs_dev, base) +/* from hdac_bus to avs_dev */ +#define hdac_to_avs(hdac) hda_to_avs(to_hda_bus(hdac)) +/* from device to avs_dev */ +#define to_avs_dev(dev) \ +({ \ + struct hdac_bus *__bus = dev_get_drvdata(dev); \ + hdac_to_avs(__bus); \ +}) + +int avs_dsp_core_power(struct avs_dev *adev, u32 core_mask, bool power); +int avs_dsp_core_reset(struct avs_dev *adev, u32 core_mask, bool reset); +int avs_dsp_core_stall(struct avs_dev *adev, u32 core_mask, bool stall); +int avs_dsp_core_enable(struct avs_dev *adev, u32 core_mask); +int avs_dsp_core_disable(struct avs_dev *adev, u32 core_mask); + +/* Inter Process Communication */ + +struct avs_ipc_msg { + union { + u64 header; + union avs_global_msg glb; + union avs_reply_msg rsp; + }; + void *data; + size_t size; +}; + +/* + * struct avs_ipc - DSP IPC context + * + * @dev: PCI device + * @rx: Reply message cache + * @default_timeout_ms: default message timeout in MS + * @ready: whether firmware is ready and communication is open + * @rx_completed: whether RX for previously sent TX has been received + * @rx_lock: for serializing manipulation of rx_* fields + * @msg_lock: for synchronizing request handling + * @done_completion: DONE-part of IPC i.e. ROM and ACKs from FW + * @busy_completion: BUSY-part of IPC i.e. receiving responses from FW + */ +struct avs_ipc { + struct device *dev; + + struct avs_ipc_msg rx; + u32 default_timeout_ms; + bool ready; + atomic_t recovering; + + bool rx_completed; + spinlock_t rx_lock; + struct mutex msg_mutex; + struct completion done_completion; + struct completion busy_completion; + + struct work_struct recovery_work; + struct delayed_work d0ix_work; + atomic_t d0ix_disable_depth; + bool in_d0ix; +}; + +#define AVS_EIPC EREMOTEIO +/* + * IPC handlers may return positive value (firmware error code) what denotes + * successful HOST <-> DSP communication yet failure to process specific request. + * + * Below macro converts returned value to linux kernel error code. + * All IPC callers MUST use it as soon as firmware error code is consumed. + */ +#define AVS_IPC_RET(ret) \ + (((ret) <= 0) ? (ret) : -AVS_EIPC) + +void avs_dsp_process_response(struct avs_dev *adev, u64 header); +int avs_dsp_send_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, const char *name); +int avs_dsp_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, const char *name); +/* Two variants below are for messages that control DSP power states. */ +int avs_dsp_send_pm_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0, + const char *name); +int avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, bool wake_d0i0, const char *name); +int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout, + const char *name); +int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request, const char *name); +void avs_dsp_interrupt_control(struct avs_dev *adev, bool enable); +int avs_ipc_init(struct avs_ipc *ipc, struct device *dev); +void avs_ipc_block(struct avs_ipc *ipc); + +int avs_dsp_disable_d0ix(struct avs_dev *adev); +int avs_dsp_enable_d0ix(struct avs_dev *adev); + +int avs_mtl_core_power(struct avs_dev *adev, u32 core_mask, bool power); +int avs_mtl_core_reset(struct avs_dev *adev, u32 core_mask, bool power); +int avs_mtl_core_stall(struct avs_dev *adev, u32 core_mask, bool stall); +int avs_lnl_core_stall(struct avs_dev *adev, u32 core_mask, bool stall); +void avs_mtl_interrupt_control(struct avs_dev *adev, bool enable); +void avs_skl_ipc_interrupt(struct avs_dev *adev); +irqreturn_t avs_cnl_dsp_interrupt(struct avs_dev *adev); +irqreturn_t avs_mtl_dsp_interrupt(struct avs_dev *adev); +int avs_apl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities); +int avs_icl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities); +int avs_skl_log_buffer_offset(struct avs_dev *adev, u32 core); +int avs_icl_log_buffer_offset(struct avs_dev *adev, u32 core); +int avs_apl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg); +int avs_apl_coredump(struct avs_dev *adev, union avs_notify_msg *msg); +bool avs_apl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake); +bool avs_icl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake); +int avs_apl_set_d0ix(struct avs_dev *adev, bool enable); +int avs_icl_set_d0ix(struct avs_dev *adev, bool enable); + +/* Firmware resources management */ + +int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry); +int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_module_entry *entry); +int avs_get_module_id(struct avs_dev *adev, const guid_t *uuid); +bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id); + +int avs_module_info_init(struct avs_dev *adev, bool purge); +void avs_module_info_free(struct avs_dev *adev); +int avs_module_id_alloc(struct avs_dev *adev, u16 module_id); +void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id); +int avs_request_firmware(struct avs_dev *adev, const struct firmware **fw_p, const char *name); +void avs_release_last_firmware(struct avs_dev *adev); +void avs_release_firmwares(struct avs_dev *adev); + +int avs_dsp_init_module(struct avs_dev *adev, u16 module_id, u8 ppl_instance_id, + u8 core_id, u8 domain, void *param, u32 param_size, + u8 *instance_id); +void avs_dsp_delete_module(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_instance_id, u8 core_id); +int avs_dsp_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + bool lp, u16 attributes, u8 *instance_id); +int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id); + +/* Firmware loading */ + +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable); +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable); +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable); + +int avs_dsp_load_libraries(struct avs_dev *adev, struct avs_tplg_library *libs, u32 num_libs); +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge); +int avs_dsp_first_boot_firmware(struct avs_dev *adev); + +int avs_cldma_load_basefw(struct avs_dev *adev, struct firmware *fw); +int avs_cldma_load_library(struct avs_dev *adev, struct firmware *lib, u32 id); +int avs_cldma_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods); +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw); +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib, u32 id); +int avs_hda_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods); + +int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + +/* Soc component members */ + +struct avs_soc_component { + struct snd_soc_component base; + struct avs_tplg *tplg; + + struct list_head node; +}; + +#define to_avs_soc_component(comp) \ + container_of(comp, struct avs_soc_component, base) + +extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +int avs_register_dmic_component(struct avs_dev *adev, const char *name); +int avs_register_i2s_component(struct avs_dev *adev, const char *name, unsigned long port_mask, + unsigned long *tdms); +int avs_register_hda_component(struct avs_dev *adev, const char *name); +int avs_register_component(struct device *dev, const char *name, + struct snd_soc_component_driver *drv, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais); + +int avs_register_all_boards(struct avs_dev *adev); +void avs_unregister_all_boards(struct avs_dev *adev); + +int avs_parse_sched_cfg(struct avs_dev *adev, const char *buf, size_t len); + +/* Filesystems integration */ + +extern const struct attribute_group *avs_attr_groups[]; + +#endif /* __SOUND_SOC_INTEL_AVS_H */ diff --git a/sound/soc/intel/avs/board_selection.c b/sound/soc/intel/avs/board_selection.c new file mode 100644 index 000000000000..52e6266a7cb8 --- /dev/null +++ b/sound/soc/intel/avs/board_selection.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/pci.h> +#include <acpi/nhlt.h> +#include <linux/platform_device.h> +#include <sound/hda_codec.h> +#include <sound/hda_register.h> +#include <sound/soc-acpi.h> +#include <sound/soc-component.h> +#include "avs.h" +#include "debug.h" +#include "pcm.h" +#include "utils.h" + +static char *i2s_test; +module_param(i2s_test, charp, 0444); +MODULE_PARM_DESC(i2s_test, "Use I2S test-board instead of ACPI, i2s_test=ssp0tdm,ssp1tdm,... 0 to ignore port"); + +bool obsolete_card_names = IS_ENABLED(CONFIG_SND_SOC_INTEL_AVS_CARDNAME_OBSOLETE); +module_param_named(obsolete_card_names, obsolete_card_names, bool, 0444); +MODULE_PARM_DESC(obsolete_card_names, "Use obsolete card names 0=no, 1=yes"); + +static const struct dmi_system_id kbl_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Skylake Y LPDDR3 RVP3"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "AmberLake Y"), + }, + }, + {} +}; + +static const struct dmi_system_id kblr_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Kabylake R DDR4 RVP"), + }, + }, + {} +}; + +static struct snd_soc_acpi_mach *dmi_match_quirk(void *arg) +{ + struct snd_soc_acpi_mach *mach = arg; + struct dmi_system_id *dmi_table; + + dmi_table = (struct dmi_system_id *)mach->quirk_data; + + if (!dmi_table || dmi_first_match(dmi_table)) + return mach; + return NULL; +} + +#define AVS_SSP(x) (BIT(x)) +#define AVS_SSP_RANGE(a, b) (GENMASK(b, a)) + +/* supported I2S board codec configurations */ +static struct snd_soc_acpi_mach avs_skl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt286", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt286-tplg.bin", + }, + { + .id = "10508825", + .drv_name = "avs_nau8825", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "nau8825-tplg.bin", + }, + { + .id = "INT343B", + .drv_name = "avs_ssm4567", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "ssm4567-tplg.bin", + }, + { + .id = "MX98357A", + .drv_name = "avs_max98357a", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98357a-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_kbl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt286", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .quirk_data = &kbl_dmi_table, + .machine_quirk = dmi_match_quirk, + .tplg_filename = "rt286-tplg.bin", + }, + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .quirk_data = &kblr_dmi_table, + .machine_quirk = dmi_match_quirk, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "MX98927", + .drv_name = "avs_max98927", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98927-tplg.bin", + }, + { + .id = "10EC5514", + .drv_name = "avs_rt5514", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .pdata = (struct avs_mach_pdata[]){ { .tdms = (unsigned long[]){ 0x2 } } }, + .tplg_filename = "rt5514-tplg.bin", + }, + { + .id = "10EC5663", + .drv_name = "avs_rt5663", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "rt5663-tplg.bin", + }, + { + .id = "MX98373", + .drv_name = "avs_max98373", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98373-tplg.bin", + }, + { + .id = "MX98357A", + .drv_name = "avs_max98357a", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "max98357a-tplg.bin", + }, + { + .id = "DLGS7219", + .drv_name = "avs_da7219", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "da7219-tplg.bin", + }, + { + .id = "ESSX8336", + .drv_name = "avs_es8336", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "es8336-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_apl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(5), + }, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "INT34C3", + .drv_name = "avs_tdf8532", + .mach_params = { + .i2s_link_mask = AVS_SSP_RANGE(0, 5), + }, + .pdata = (struct avs_mach_pdata[]){ { + .tdms = (unsigned long[]){ 0x1, 0x1, 0x14, 0x1, 0x1, 0x1 } + } }, + .tplg_filename = "tdf8532-tplg.bin", + }, + { + .id = "MX98357A", + .drv_name = "avs_max98357a", + .mach_params = { + .i2s_link_mask = AVS_SSP(5), + }, + .tplg_filename = "max98357a-tplg.bin", + }, + { + .id = "DLGS7219", + .drv_name = "avs_da7219", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "da7219-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_gml_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(2), + }, + .tplg_filename = "rt298-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_cnl_i2s_machines[] = { + { + .id = "INT34C2", + .drv_name = "avs_rt274", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt274-tplg.bin", + }, + { + .id = "10EC5682", + .drv_name = "avs_rt5682", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "rt5682-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_icl_i2s_machines[] = { + { + .id = "INT343A", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "INT34C2", + .drv_name = "avs_rt274", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt274-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_tgl_i2s_machines[] = { + { + .id = "INT34C2", + .drv_name = "avs_rt274", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt274-tplg.bin", + }, + { + .id = "10EC0298", + .drv_name = "avs_rt298", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt298-tplg.bin", + }, + { + .id = "10EC1308", + .drv_name = "avs_rt1308", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "rt1308-tplg.bin", + }, + { + .id = "10EC5640", + .uid = "1", + .drv_name = "avs_rt5640", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "rt5640-tplg.bin", + }, + { + .id = "10EC5640", + .uid = "3", + .drv_name = "avs_rt5640", + .mach_params = { + .i2s_link_mask = AVS_SSP(1), + }, + .tplg_filename = "rt5640-tplg.bin", + }, + { + .id = "10EC5640", + .uid = "2", + .drv_name = "avs_rt5640", + .mach_params = { + .i2s_link_mask = AVS_SSP(2), + }, + .tplg_filename = "rt5640-tplg.bin", + }, + { + .id = "ESSX8336", + .drv_name = "avs_es8336", + .mach_params = { + .i2s_link_mask = AVS_SSP(0), + }, + .tplg_filename = "es8336-tplg.bin", + }, + {}, +}; + +static struct snd_soc_acpi_mach avs_mbl_i2s_machines[] = { + { + .id = "PCM3168A", + .drv_name = "avs_pcm3168a", + .mach_params = { + .i2s_link_mask = AVS_SSP(0) | AVS_SSP(2), + }, + .tplg_filename = "pcm3168a-tplg.bin", + }, + {} +}; + +struct avs_acpi_boards { + int id; + struct snd_soc_acpi_mach *machs; +}; + +#define AVS_MACH_ENTRY(_id, _mach) \ + { .id = PCI_DEVICE_ID_INTEL_##_id, .machs = (_mach), } + +/* supported I2S boards per platform */ +static const struct avs_acpi_boards i2s_boards[] = { + AVS_MACH_ENTRY(HDA_SKL_LP, avs_skl_i2s_machines), + AVS_MACH_ENTRY(HDA_KBL_LP, avs_kbl_i2s_machines), + AVS_MACH_ENTRY(HDA_APL, avs_apl_i2s_machines), + AVS_MACH_ENTRY(HDA_GML, avs_gml_i2s_machines), + AVS_MACH_ENTRY(HDA_CNL_LP, avs_cnl_i2s_machines), + AVS_MACH_ENTRY(HDA_CNL_H, avs_cnl_i2s_machines), + AVS_MACH_ENTRY(HDA_CML_LP, avs_cnl_i2s_machines), + AVS_MACH_ENTRY(HDA_ICL_LP, avs_icl_i2s_machines), + AVS_MACH_ENTRY(HDA_TGL_LP, avs_tgl_i2s_machines), + AVS_MACH_ENTRY(HDA_EHL_0, avs_tgl_i2s_machines), + AVS_MACH_ENTRY(HDA_ADL_N, avs_mbl_i2s_machines), + AVS_MACH_ENTRY(HDA_ADL_P, avs_tgl_i2s_machines), + AVS_MACH_ENTRY(HDA_RPL_P_0, avs_tgl_i2s_machines), + AVS_MACH_ENTRY(HDA_RPL_M, avs_mbl_i2s_machines), + AVS_MACH_ENTRY(HDA_FCL, avs_tgl_i2s_machines), + { }, +}; + +static struct snd_soc_acpi_mach *avs_get_i2s_machines(struct avs_dev *adev) +{ + int id, i; + + id = adev->base.pci->device; + for (i = 0; i < ARRAY_SIZE(i2s_boards); i++) + if (i2s_boards[i].id == id) + return i2s_boards[i].machs; + return NULL; +} + +/* Platform devices spawned by AVS driver are removed with this hook. */ +static void avs_unregister_board(void *pdev) +{ + platform_device_unregister(pdev); +} + +static struct platform_device *avs_register_board(struct avs_dev *adev, const char *name, + const void *data, size_t size) +{ + struct platform_device *pdev; + int ret; + + pdev = platform_device_register_data(NULL, name, PLATFORM_DEVID_AUTO, data, size); + if (IS_ERR(pdev)) + return pdev; + + ret = devm_add_action_or_reset(adev->dev, avs_unregister_board, pdev); + if (ret) + return ERR_PTR(ret); + + return pdev; +} + +static struct platform_device *avs_register_board_pdata(struct avs_dev *adev, const char *name, + struct snd_soc_acpi_mach *mach, + struct hda_codec *codec, + unsigned long *tdms, char *codec_name) +{ + struct avs_mach_pdata *pdata; + + pdata = devm_kzalloc(adev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->codec = codec; + pdata->tdms = tdms; + pdata->codec_name = codec_name; + pdata->obsolete_card_names = obsolete_card_names; + mach->pdata = pdata; + + return avs_register_board(adev, name, mach, sizeof(*mach)); +} + +static int __maybe_unused avs_register_probe_board(struct avs_dev *adev) +{ + struct platform_device *pdev; + + pdev = avs_register_board(adev, "avs_probe_mb", NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return avs_register_probe_component(adev, dev_name(&pdev->dev)); +} + +static int avs_register_dmic_board(struct avs_dev *adev) +{ + static struct snd_soc_acpi_mach mach = { + .tplg_filename = "dmic-tplg.bin", + }; + struct platform_device *pdev; + char *codec_name; + + if (!acpi_nhlt_find_endpoint(ACPI_NHLT_LINKTYPE_PDM, -1, -1, -1)) { + dev_dbg(adev->dev, "no DMIC endpoints present\n"); + return 0; + } + + /* DMIC present in Intel PCH is enumerated statically. */ + pdev = avs_register_board(adev, "dmic-codec", NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + codec_name = devm_kstrdup(adev->dev, dev_name(&pdev->dev), GFP_KERNEL); + if (!codec_name) + return -ENOMEM; + + pdev = avs_register_board_pdata(adev, "avs_dmic", &mach, NULL, NULL, codec_name); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return avs_register_dmic_component(adev, dev_name(&pdev->dev)); +} + +static int avs_register_i2s_test_board(struct avs_dev *adev, int ssp_port, int tdm_slot) +{ + struct snd_soc_acpi_mach mach = {{0}}; + struct platform_device *pdev; + unsigned long *tdms; + + tdms = devm_kcalloc(adev->dev, ssp_port + 1, sizeof(*tdms), GFP_KERNEL); + mach.tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL, + AVS_STRING_FMT("i2s", "-test-tplg.bin", + ssp_port, tdm_slot)); + if (!tdms || !mach.tplg_filename) + return -ENOMEM; + + tdms[ssp_port] = BIT(tdm_slot); + mach.drv_name = "avs_i2s_test"; + mach.mach_params.i2s_link_mask = AVS_SSP(ssp_port); + + pdev = avs_register_board_pdata(adev, mach.drv_name, &mach, NULL, tdms, NULL); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return avs_register_i2s_component(adev, dev_name(&pdev->dev), AVS_SSP(ssp_port), tdms); +} + +static int avs_register_i2s_test_boards(struct avs_dev *adev) +{ + int max_ssps = adev->hw_cfg.i2s_caps.ctrl_count; + int ssp_port, tdm_slot, ret; + unsigned long tdm_slots; + u32 *array, num_elems; + + if (!i2s_test) + return 0; + + ret = parse_int_array(i2s_test, strlen(i2s_test), (int **)&array); + if (ret) { + dev_err(adev->dev, "failed to parse i2s_test parameter\n"); + return ret; + } + + num_elems = *array; + if (num_elems > max_ssps) { + dev_err(adev->dev, "board supports only %d SSP, %d specified\n", + max_ssps, num_elems); + return -EINVAL; + } + + for (ssp_port = 0; ssp_port < num_elems; ssp_port++) { + tdm_slots = array[1 + ssp_port]; + for_each_set_bit(tdm_slot, &tdm_slots, 16) { + ret = avs_register_i2s_test_board(adev, ssp_port, tdm_slot); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach *mach) +{ + u32 i2s_mask = mach->mach_params.i2s_link_mask; + struct platform_device *pdev; + unsigned long *tdms = NULL; + + if (mach->pdata) + tdms = ((struct avs_mach_pdata *)mach->pdata)->tdms; + + pdev = avs_register_board_pdata(adev, mach->drv_name, mach, NULL, tdms, NULL); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return avs_register_i2s_component(adev, dev_name(&pdev->dev), i2s_mask, tdms); +} + +static int avs_register_i2s_boards(struct avs_dev *adev) +{ + int num_ssps = adev->hw_cfg.i2s_caps.ctrl_count; + struct snd_soc_acpi_mach *machs; + struct snd_soc_acpi_mach *mach; + int ret; + + if (!acpi_nhlt_find_endpoint(ACPI_NHLT_LINKTYPE_SSP, -1, -1, -1)) { + dev_dbg(adev->dev, "no I2S endpoints present\n"); + return 0; + } + + machs = avs_get_i2s_machines(adev); + if (!machs) { + dev_dbg(adev->dev, "no I2S endpoints supported\n"); + return 0; + } + + for (mach = machs; mach->id[0]; mach++) { + if (!acpi_dev_present(mach->id, mach->uid, -1)) + continue; + + if (fls(mach->mach_params.i2s_link_mask) > num_ssps) { + dev_err(adev->dev, "Platform supports %d SSPs but board %s requires SSP%ld\n", + num_ssps, mach->drv_name, + (unsigned long)__fls(mach->mach_params.i2s_link_mask)); + continue; + } + if (mach->machine_quirk) + if (!mach->machine_quirk(mach)) + continue; + + ret = avs_register_i2s_board(adev, mach); + if (ret < 0) + dev_warn(adev->dev, "register i2s %s failed: %d\n", mach->drv_name, ret); + } + + return 0; +} + +static int avs_register_hda_board(struct avs_dev *adev, struct hda_codec *codec) +{ + struct hdac_device *hdev = &codec->core; + struct snd_soc_acpi_mach mach = {{0}}; + struct platform_device *pdev; + + mach.tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL, "hda-%08x-tplg.bin", + hdev->vendor_id); + if (!mach.tplg_filename) + return -ENOMEM; + + pdev = avs_register_board_pdata(adev, "avs_hdaudio", &mach, codec, NULL, NULL); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return avs_register_hda_component(adev, dev_name(&pdev->dev)); +} + +static int avs_register_hda_boards(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct hdac_device *hdev; + int ret; + + if (!bus->num_codecs) { + dev_dbg(adev->dev, "no HDA endpoints present\n"); + return 0; + } + + list_for_each_entry(hdev, &bus->codec_list, list) { + struct hda_codec *codec; + + codec = dev_to_hda_codec(&hdev->dev); + + ret = avs_register_hda_board(adev, codec); + if (ret < 0) + dev_warn(adev->dev, "register hda-%08x failed: %d\n", + codec->core.vendor_id, ret); + } + + return 0; +} + +int avs_register_all_boards(struct avs_dev *adev) +{ + int ret; + +#ifdef CONFIG_DEBUG_FS + ret = avs_register_probe_board(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate PROBE endpoints failed: %d\n", ret); +#endif + + ret = avs_register_dmic_board(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate DMIC endpoints failed: %d\n", + ret); + + ret = avs_register_i2s_test_boards(adev); + if (ret) + dev_dbg(adev->dev, "enumerate I2S TEST endpoints failed: %d\n", ret); + + ret = avs_register_i2s_boards(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate I2S endpoints failed: %d\n", + ret); + + ret = avs_register_hda_boards(adev); + if (ret < 0) + dev_warn(adev->dev, "enumerate HDA endpoints failed: %d\n", + ret); + + return 0; +} + +void avs_unregister_all_boards(struct avs_dev *adev) +{ + snd_soc_unregister_component(adev->dev); +} diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig new file mode 100644 index 000000000000..82f50207bb2f --- /dev/null +++ b/sound/soc/intel/avs/boards/Kconfig @@ -0,0 +1,199 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Intel AVS Machine drivers" + depends on SND_SOC_INTEL_AVS + +comment "Available DSP configurations" + +config SND_SOC_INTEL_AVS_CARDNAME_OBSOLETE + bool "Use obsolete card names" + default n + help + Use obsolete names for some of avs cards. This option should be + used if your system depends on old card names, for example having + not up to date UCM files. + +config SND_SOC_INTEL_AVS_MACH_DA7219 + tristate "da7219 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_DA7219 + help + This adds support for AVS with DA7219 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_DMIC + tristate "DMIC generic board" + select SND_SOC_DMIC + help + This adds support for AVS with Digital Mic array configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_ES8336 + tristate "es8336 I2S board" + depends on X86 && I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_ES8316 + help + This adds support for AVS with ES8336 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_HDAUDIO + tristate "HD-Audio generic board" + select SND_SOC_HDA + help + This adds support for AVS with HDAudio codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_I2S_TEST + tristate "I2S test board" + help + This adds support for I2S test-board which can be used to verify + transfer over I2S interface with SSP loopback scenarios. + +config SND_SOC_INTEL_AVS_MACH_MAX98927 + tristate "max98927 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98927 + help + This adds support for AVS with MAX98927 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_MAX98357A + tristate "max98357A I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98357A + help + This adds support for AVS with MAX98357A I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_MAX98373 + tristate "max98373 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98373 + help + This adds support for AVS with MAX98373 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_NAU8825 + tristate "nau8825 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_NAU8825 + help + This adds support for ASoC machine driver with NAU8825 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_PCM3168A + tristate "pcm3168a I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_PCM3168A_I2C + help + This adds support for AVS with PCM3168A I2C codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_PROBE + tristate "Probing (data) board" + depends on DEBUG_FS + select SND_HWDEP + help + This adds support for data probing board which can be used to + gather data from runtime stream over compress operations. + +config SND_SOC_INTEL_AVS_MACH_RT274 + tristate "rt274 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT274 + help + This adds support for ASoC machine driver with RT274 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT286 + tristate "rt286 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT286 + help + This adds support for ASoC machine driver with RT286 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT298 + tristate "rt298 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT298 + help + This adds support for ASoC machine driver with RT298 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT5514 + tristate "rt5514 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5514 + help + This adds support for ASoC machine driver with RT5514 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT5640 + tristate "rt5640 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5640 + help + This adds support for ASoC machine board connecting AVS with RT5640, + components representing Intel AudioDSP and Realtek 5640 codec respectively. + The codec chip is present on I2C bus and the streaming occurs over I2S + interface. + Say Y or m if you have such a device. + +config SND_SOC_INTEL_AVS_MACH_RT5663 + tristate "rt5663 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5663 + help + This adds support for ASoC machine driver with RT5663 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_RT5682 + tristate "rt5682 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5682_I2C + help + This adds support for ASoC machine driver with RT5682 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_AVS_MACH_SSM4567 + tristate "ssm4567 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_SSM4567 + help + This adds support for ASoC machine driver with SSM4567 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +endmenu diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile new file mode 100644 index 000000000000..46ef1babda34 --- /dev/null +++ b/sound/soc/intel/avs/boards/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only + +snd-soc-avs-da7219-y := da7219.o +snd-soc-avs-dmic-y := dmic.o +snd-soc-avs-es8336-y := es8336.o +snd-soc-avs-hdaudio-y := hdaudio.o +snd-soc-avs-i2s-test-y := i2s_test.o +snd-soc-avs-max98927-y := max98927.o +snd-soc-avs-max98357a-y := max98357a.o +snd-soc-avs-max98373-y := max98373.o +snd-soc-avs-nau8825-y := nau8825.o +snd-soc-avs-pcm3168a-y := pcm3168a.o +snd-soc-avs-probe-y := probe.o +snd-soc-avs-rt274-y := rt274.o +snd-soc-avs-rt286-y := rt286.o +snd-soc-avs-rt298-y := rt298.o +snd-soc-avs-rt5514-y := rt5514.o +snd-soc-avs-rt5640-y := rt5640.o +snd-soc-avs-rt5663-y := rt5663.o +snd-soc-avs-rt5682-y := rt5682.o +snd-soc-avs-ssm4567-y := ssm4567.o + +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219) += snd-soc-avs-da7219.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_ES8336) += snd-soc-avs-es8336.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_I2S_TEST) += snd-soc-avs-i2s-test.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98927) += snd-soc-avs-max98927.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A) += snd-soc-avs-max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98373) += snd-soc-avs-max98373.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825) += snd-soc-avs-nau8825.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_PCM3168A) += snd-soc-avs-pcm3168a.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_PROBE) += snd-soc-avs-probe.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5514) += snd-soc-avs-rt5514.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5640) += snd-soc-avs-rt5640.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5663) += snd-soc-avs-rt5663.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682) += snd-soc-avs-rt5682.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567) += snd-soc-avs-ssm4567.o diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c new file mode 100644 index 000000000000..2b17abcbd2bc --- /dev/null +++ b/sound/soc/intel/avs/boards/da7219.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/module.h> +#include <linux/platform_data/x86/soc.h> +#include <linux/platform_device.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <uapi/linux/input-event-codes.h> +#include "../../../codecs/da7219.h" +#include "../utils.h" + +#define DA7219_DAI_NAME "da7219-hifi" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Line Out"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, DA7219_DAI_NAME); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found. Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM, + 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"MIC", NULL, "Headset Mic"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, + { "Line Out", NULL, "Platform Clock" }, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Line Out", + .mask = SND_JACK_LINEOUT, + }, +}; + +static int avs_da7219_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins; + int clk_freq; + int ret; + + jack = snd_soc_card_get_drvdata(card); + if (soc_intel_is_apl()) + clk_freq = 19200000; + else /* kbl */ + clk_freq = 24576000; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, clk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + num_pins = ARRAY_SIZE(card_headset_pins); + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3 | SND_JACK_LINEOUT, + jack, pins, num_pins); + if (ret) { + dev_err(card->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + return snd_soc_component_set_jack(component, jack, NULL); +} + +static void avs_da7219_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int +avs_da7219_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-DLGS7219:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, DA7219_DAI_NAME); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_da7219_be_fixup; + dl->init = avs_da7219_codec_init; + dl->exit = avs_da7219_codec_exit; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_da7219_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_da7219"; + } else { + card->driver_name = "avs_da7219"; + card->long_name = card->name = "AVS I2S DA7219"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_da7219_driver_ids[] = { + { + .name = "avs_da7219", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +static struct platform_driver avs_da7219_driver = { + .probe = avs_da7219_probe, + .driver = { + .name = "avs_da7219", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_da7219_driver_ids, +}; + +module_platform_driver(avs_da7219_driver); + +MODULE_DESCRIPTION("Intel da7219 machine driver"); +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c new file mode 100644 index 000000000000..bf6f580a5164 --- /dev/null +++ b/sound/soc/intel/avs/boards/dmic.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/device.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../utils.h" + +SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin"))); +SND_SOC_DAILINK_DEF(dmic_wov_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC WoV Pin"))); + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + {"DMic", NULL, "SoC DMIC"}, +}; + +static int avs_create_dai_links(struct device *dev, const char *codec_name, + struct snd_soc_dai_link **links, int *num_links) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + const int num_dl = 2; + + dl = devm_kcalloc(dev, num_dl, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->codecs) + return -ENOMEM; + + dl->codecs->name = devm_kstrdup(dev, codec_name, GFP_KERNEL); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "dmic-hifi"); + if (!dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl[0].num_cpus = 1; + dl[0].num_codecs = 1; + dl[0].platforms = platform; + dl[0].num_platforms = 1; + dl[0].nonatomic = 1; + dl[0].no_pcm = 1; + dl[0].capture_only = 1; + memcpy(&dl[1], &dl[0], sizeof(*dl)); + + dl[0].name = "DMIC"; + dl[0].cpus = dmic_pin; + dl[0].id = 0; + dl[1].name = "DMIC WoV"; + dl[1].cpus = dmic_wov_pin; + dl[1].id = 1; + dl[1].ignore_suspend = 1; + + *links = dl; + *num_links = num_dl; + return 0; +} + +static int avs_dmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + int ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = avs_create_dai_links(dev, pdata->codec_name, &card->dai_link, &card->num_links); + if (ret) + return ret; + + if (pdata->obsolete_card_names) { + card->name = "avs_dmic"; + } else { + card->driver_name = "avs_dmic"; + card->long_name = card->name = "AVS DMIC"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_dmic_driver_ids[] = { + { + .name = "avs_dmic", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +static struct platform_driver avs_dmic_driver = { + .probe = avs_dmic_probe, + .driver = { + .name = "avs_dmic", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_dmic_driver_ids, +}; + +module_platform_driver(avs_dmic_driver); + +MODULE_DESCRIPTION("Intel DMIC machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c new file mode 100644 index 000000000000..301cfb3cf15b --- /dev/null +++ b/sound/soc/intel/avs/boards/es8336.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2023 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/processor.h> +#include <linux/slab.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <asm/cpu_device_id.h> +#include "../utils.h" + +#define ES8336_CODEC_DAI "ES8316 HiFi" + +struct avs_card_drvdata { + struct snd_soc_jack jack; + struct gpio_desc *gpiod; +}; + +static const struct acpi_gpio_params enable_gpio = { 0, 0, true }; + +static const struct acpi_gpio_mapping speaker_gpios[] = { + { "speaker-enable-gpios", &enable_gpio, 1, ACPI_GPIO_QUIRK_ONLY_GPIOIO }, + { } +}; + +static int avs_es8336_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct avs_card_drvdata *data; + bool speaker_en; + + data = snd_soc_card_get_drvdata(card); + /* As enable_gpio has active_low=true, logic is inverted. */ + speaker_en = !SND_SOC_DAPM_EVENT_ON(event); + + gpiod_set_value_cansleep(data->gpiod, speaker_en); + return 0; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + + SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, + avs_es8336_speaker_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + + /* + * There is no separate speaker output instead the speakers are muxed to + * the HP outputs. The mux is controlled by the "Speaker Power" widget. + */ + {"Speaker", NULL, "HPOL"}, + {"Speaker", NULL, "HPOR"}, + {"Speaker", NULL, "Speaker Power"}, + + /* Mic route map */ + {"MIC1", NULL, "Internal Mic"}, + {"MIC2", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_es8336_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = runtime->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct snd_soc_jack_pin *pins; + struct avs_card_drvdata *data; + struct gpio_desc *gpiod; + int num_pins, ret; + + data = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0, + &data->jack, pins, num_pins); + if (ret) + return ret; + + ret = devm_acpi_dev_add_driver_gpios(codec_dai->dev, speaker_gpios); + if (ret) + dev_warn(codec_dai->dev, "Unable to add GPIO mapping table\n"); + + gpiod = gpiod_get_optional(codec_dai->dev, "speaker-enable", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(codec_dai->dev, PTR_ERR(gpiod), "Get gpiod failed: %ld\n", + PTR_ERR(gpiod)); + + data->gpiod = gpiod; + snd_jack_set_key(data->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_soc_component_set_jack(component, &data->jack, NULL); + + snd_soc_dapm_set_idle_bias(dapm, false); + + return 0; +} + +static void avs_es8336_codec_exit(struct snd_soc_pcm_runtime *runtime) +{ + struct avs_card_drvdata *data = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + + snd_soc_component_set_jack(codec_dai->component, NULL, NULL); + gpiod_put(data->gpiod); +} + +static int avs_es8336_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + int clk_freq; + int ret; + + switch (boot_cpu_data.x86_vfm) { + case INTEL_KABYLAKE_L: + case INTEL_KABYLAKE: + clk_freq = 24000000; + break; + default: + clk_freq = 19200000; + break; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 1, clk_freq, SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(runtime->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_es8336_ops = { + .hw_params = avs_es8336_hw_params, +}; + +static int avs_es8336_be_fixup(struct snd_soc_pcm_runtime *runtime, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_3LE); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-ESSX8336:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, ES8336_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_es8336_codec_init; + dl->exit = avs_es8336_codec_exit; + dl->be_hw_params_fixup = avs_es8336_be_fixup; + dl->ops = &avs_es8336_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, ES8336_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, ES8336_CODEC_DAI); + struct avs_card_drvdata *data = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, &data->jack, NULL); +} + +static int avs_es8336_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct avs_card_drvdata *data; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!data || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_es8336"; + } else { + card->driver_name = "avs_es8336"; + card->long_name = card->name = "AVS I2S ES8336"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, data); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_es8336_driver_ids[] = { + { + .name = "avs_es8336", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +static struct platform_driver avs_es8336_driver = { + .probe = avs_es8336_probe, + .driver = { + .name = "avs_es8336", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_es8336_driver_ids, +}; + +module_platform_driver(avs_es8336_driver); + +MODULE_DESCRIPTION("Intel es8336 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c new file mode 100644 index 000000000000..aec769e2396c --- /dev/null +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/hda.h" +#include "../utils.h" + +static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_link **links) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + struct hda_pcm *pcm; + const char *cname = dev_name(&codec->core.dev); + int i; + + dl = devm_kcalloc(dev, pcm_count, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = dev_name(dev); + pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); + + for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { + dl[i].name = devm_kasprintf(dev, GFP_KERNEL, "%s link%d", cname, i); + if (!dl[i].name) + return -ENOMEM; + + dl[i].id = i; + dl[i].nonatomic = 1; + dl[i].no_pcm = 1; + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + if (!dl[i].codecs || !dl[i].cpus) + return -ENOMEM; + + dl[i].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d", cname, i); + if (!dl[i].cpus->dai_name) + return -ENOMEM; + + dl[i].codecs->name = devm_kstrdup_const(dev, cname, GFP_KERNEL); + if (!dl[i].codecs->name) + return -ENOMEM; + + dl[i].codecs->dai_name = pcm->name; + dl[i].num_codecs = 1; + dl[i].num_cpus = 1; + } + + *links = dl; + return 0; +} + +/* Should be aligned with SectionPCM's name from topology */ +#define FEDAI_NAME_PREFIX "HDMI" + +static struct snd_pcm * +avs_card_hdmi_pcm_at(struct snd_soc_card *card, int hdmi_idx) +{ + struct snd_soc_pcm_runtime *rtd; + int dir = SNDRV_PCM_STREAM_PLAYBACK; + + for_each_card_rtds(card, rtd) { + struct snd_pcm *spcm; + int ret, n; + + spcm = rtd->pcm ? rtd->pcm->streams[dir].pcm : NULL; + if (!spcm || !strstr(spcm->id, FEDAI_NAME_PREFIX)) + continue; + + ret = sscanf(spcm->id, FEDAI_NAME_PREFIX "%d", &n); + if (ret != 1) + continue; + if (n == hdmi_idx) + return rtd->pcm; + } + + return NULL; +} + +static int avs_card_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); + struct avs_mach_pdata *pdata = mach->pdata; + struct hda_codec *codec = pdata->codec; + struct hda_pcm *hpcm; + /* Topology pcm indexing is 1-based */ + int i = 1; + + list_for_each_entry(hpcm, &codec->pcm_list_head, list) { + struct snd_pcm *spcm; + + spcm = avs_card_hdmi_pcm_at(card, i); + if (spcm) { + hpcm->pcm = spcm; + hpcm->device = spcm->device; + dev_info(card->dev, "%s: mapping HDMI converter %d to PCM %d (%p)\n", + __func__, i, hpcm->device, spcm); + } else { + hpcm->pcm = NULL; + hpcm->device = SNDRV_PCM_INVALID_DEVICE; + dev_warn(card->dev, "%s: no PCM in topology for HDMI converter %d\n", + __func__, i); + } + i++; + } + + return hda_codec_probe_complete(codec); +} + +static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) +{ + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_dai_link *links = NULL; + struct snd_soc_card *card = rtm->card; + struct hda_codec *codec; + struct hda_pcm *pcm; + int ret, pcm_count = 0; + + mach = dev_get_platdata(card->dev); + pdata = mach->pdata; + codec = pdata->codec; + + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + ret = avs_create_dai_links(card->dev, codec, pcm_count, &links); + if (ret < 0) { + dev_err(card->dev, "create links failed: %d\n", ret); + return ret; + } + + ret = snd_soc_add_pcm_runtimes(card, links, pcm_count); + if (ret < 0) { + dev_err(card->dev, "add links failed: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_dai_link probing_link = { + .name = "probing-LINK", + .id = -1, + .nonatomic = 1, + .no_pcm = 1, + .cpus = &snd_soc_dummy_dlc, + .num_cpus = 1, + .init = avs_probing_link_init, +}; + +static int avs_hdaudio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *binder; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + struct hda_codec *codec; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + codec = pdata->codec; + + /* codec may be unloaded before card's probe() fires */ + if (!device_is_registered(&codec->core.dev)) + return -ENODEV; + + binder = devm_kmemdup(dev, &probing_link, sizeof(probing_link), GFP_KERNEL); + if (!binder) + return -ENOMEM; + + binder->platforms = devm_kzalloc(dev, sizeof(*binder->platforms), GFP_KERNEL); + binder->codecs = devm_kzalloc(dev, sizeof(*binder->codecs), GFP_KERNEL); + if (!binder->platforms || !binder->codecs) + return -ENOMEM; + + binder->codecs->name = devm_kstrdup_const(dev, dev_name(&codec->core.dev), GFP_KERNEL); + if (!binder->codecs->name) + return -ENOMEM; + + binder->platforms->name = dev_name(dev); + binder->num_platforms = 1; + binder->codecs->dai_name = "codec-probing-DAI"; + binder->num_codecs = 1; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = devm_kasprintf(dev, GFP_KERNEL, "hdaudioB%dD%d", codec->bus->core.idx, + codec->core.addr); + if (!card->name) + return -ENOMEM; + } else { + card->driver_name = "avs_hdaudio"; + if (hda_codec_is_display(codec)) + card->long_name = card->name = "AVS HDMI"; + else + card->long_name = card->name = "AVS HD-Audio"; + } + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = binder; + card->num_links = 1; + card->fully_routed = true; + if (hda_codec_is_display(codec)) + card->late_probe = avs_card_late_probe; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_hdaudio_driver_ids[] = { + { + .name = "avs_hdaudio", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +static struct platform_driver avs_hdaudio_driver = { + .probe = avs_hdaudio_probe, + .driver = { + .name = "avs_hdaudio", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_hdaudio_driver_ids, +}; + +module_platform_driver(avs_hdaudio_driver) + +MODULE_DESCRIPTION("Intel HD-Audio machine driver"); +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c new file mode 100644 index 000000000000..9a6b89ffdf14 --- /dev/null +++ b/sound/soc/intel/avs/boards/i2s_test.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "../utils.h" + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + if (!dl->name || !dl->cpus) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs = &snd_soc_dummy_dlc; + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_i2s_test_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + if (!avs_mach_singular_ssp(mach)) { + dev_err(dev, "Invalid SSP configuration\n"); + return -EINVAL; + } + ssp_port = avs_mach_ssp_port(mach); + + if (!avs_mach_singular_tdm(mach, ssp_port)) { + dev_err(dev, "Invalid TDM configuration\n"); + return -EINVAL; + } + tdm_slot = avs_mach_ssp_tdm(mach, ssp_port); + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("ssp", "-loopback", ssp_port, tdm_slot)); + } else { + card->driver_name = "avs_i2s_test"; + card->long_name = card->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("AVS I2S TEST-", "", + ssp_port, tdm_slot)); + } + if (!card->name) + return -ENOMEM; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d\n", ret); + return ret; + } + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_i2s_test_driver_ids[] = { + { + .name = "avs_i2s_test", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +static struct platform_driver avs_i2s_test_driver = { + .probe = avs_i2s_test_probe, + .driver = { + .name = "avs_i2s_test", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_i2s_test_driver_ids, +}; + +module_platform_driver(avs_i2s_test_driver); + +MODULE_DESCRIPTION("Intel i2s test machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c new file mode 100644 index 000000000000..e9a87804f918 --- /dev/null +++ b/sound/soc/intel/avs/boards/max98357a.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "../utils.h" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Spk", NULL, "Speaker" }, +}; + +static int +avs_max98357a_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "MX98357A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "HiFi"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_max98357a_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->playback_only = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_max98357a_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_max98357a"; + } else { + card->driver_name = "avs_max98357a"; + card->long_name = card->name = "AVS I2S MAX98357A"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_max98357a_driver_ids[] = { + { + .name = "avs_max98357a", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +static struct platform_driver avs_max98357a_driver = { + .probe = avs_max98357a_probe, + .driver = { + .name = "avs_max98357a", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_max98357a_driver_ids, +}; + +module_platform_driver(avs_max98357a_driver) + +MODULE_DESCRIPTION("Intel max98357a machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c new file mode 100644 index 000000000000..8b45b643ca29 --- /dev/null +++ b/sound/soc/intel/avs/boards/max98373.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "../utils.h" + +#define MAX98373_DEV0_NAME "i2c-MX98373:00" +#define MAX98373_DEV1_NAME "i2c-MX98373:01" +#define MAX98373_CODEC_NAME "max98373-aif1" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; + +static int +avs_max98373_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int avs_max98373_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int ret, i; + + for_each_rtd_codec_dais(runtime, i, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX98373_DEV0_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret); + return ret; + } + } + if (!strcmp(codec_dai->component->name, MAX98373_DEV1_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV1 TDM slot err:%d\n", ret); + return ret; + } + } + } + + return 0; +} + +static const struct snd_soc_ops avs_max98373_ops = { + .hw_params = avs_max98373_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kcalloc(dev, 2, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV0_NAME); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV1_NAME); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_max98373_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->ignore_pmdown_time = 1; + dl->ops = &avs_max98373_ops; + + *dai_link = dl; + + return 0; +} + +static int avs_max98373_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_max98373"; + } else { + card->driver_name = "avs_max98373"; + card->long_name = card->name = "AVS I2S MAX98373"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_max98373_driver_ids[] = { + { + .name = "avs_max98373", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +static struct platform_driver avs_max98373_driver = { + .probe = avs_max98373_probe, + .driver = { + .name = "avs_max98373", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_max98373_driver_ids, +}; + +module_platform_driver(avs_max98373_driver) + +MODULE_DESCRIPTION("Intel max98373 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c new file mode 100644 index 000000000000..db073125fa4d --- /dev/null +++ b/sound/soc/intel/avs/boards/max98927.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include "../utils.h" + +#define MAX98927_DEV0_NAME "i2c-MX98927:00" +#define MAX98927_DEV1_NAME "i2c-MX98927:01" +#define MAX98927_CODEC_NAME "max98927-aif1" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX98927_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX98927_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; + +static int +avs_max98927_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int avs_max98927_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int ret = 0; + int i; + + for_each_rtd_codec_dais(runtime, i, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX98927_DEV0_NAME)) + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + else if (!strcmp(codec_dai->component->name, MAX98927_DEV1_NAME)) + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + + if (ret < 0) { + dev_err(runtime->dev, "hw_params for %s failed: %d\n", + codec_dai->component->name, ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_ops avs_max98927_ops = { + .hw_params = avs_max98927_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kcalloc(dev, 2, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, MAX98927_DEV0_NAME); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98927_CODEC_NAME); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, MAX98927_DEV1_NAME); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98927_CODEC_NAME); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_max98927_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->ignore_pmdown_time = 1; + dl->ops = &avs_max98927_ops; + + *dai_link = dl; + + return 0; +} + +static int avs_max98927_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_max98927"; + } else { + card->driver_name = "avs_max98927"; + card->long_name = card->name = "AVS I2S MAX98927"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_max98927_driver_ids[] = { + { + .name = "avs_max98927", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +static struct platform_driver avs_max98927_driver = { + .probe = avs_max98927_probe, + .driver = { + .name = "avs_max98927", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_max98927_driver_ids, +}; + +module_platform_driver(avs_max98927_driver) + +MODULE_DESCRIPTION("Intel max98927 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c new file mode 100644 index 000000000000..d44edacbfc9a --- /dev/null +++ b/sound/soc/intel/avs/boards/nau8825.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" +#include "../utils.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" + +static int +avs_nau8825_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_MCLK, 24000000, + SND_SOC_CLOCK_IN); + else + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "Set sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_nau8825_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + { "MIC", NULL, "Headset Mic" }, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_nau8825_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + /* + * 4 buttons here map to the google Reference headset. + * The use of these buttons can be decided by the user space. + */ + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, + jack, pins, num_pins); + if (ret) + return ret; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + return snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, jack, NULL); +} + +static void avs_nau8825_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int +avs_nau8825_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_nau8825_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); + break; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + } + + return ret; +} + + +static const struct snd_soc_ops avs_nau8825_ops = { + .trigger = avs_nau8825_trigger, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10508825:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, SKL_NUVOTON_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_nau8825_codec_init; + dl->exit = avs_nau8825_codec_exit; + dl->be_hw_params_fixup = avs_nau8825_be_fixup; + dl->ops = &avs_nau8825_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + int stream = SNDRV_PCM_STREAM_PLAYBACK; + + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (snd_soc_dai_stream_active(codec_dai, stream) && + snd_soc_dai_get_widget(codec_dai, stream)->active) + snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_nau8825_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_nau8825"; + } else { + card->driver_name = "avs_nau8825"; + card->long_name = card->name = "AVS I2S NAU8825"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_nau8825_driver_ids[] = { + { + .name = "avs_nau8825", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +static struct platform_driver avs_nau8825_driver = { + .probe = avs_nau8825_probe, + .driver = { + .name = "avs_nau8825", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_nau8825_driver_ids, +}; + +module_platform_driver(avs_nau8825_driver) + +MODULE_DESCRIPTION("Intel nau8825 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c new file mode 100644 index 000000000000..b5bebadbbcb2 --- /dev/null +++ b/sound/soc/intel/avs/boards/pcm3168a.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2024-2025 Intel Corporation +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../utils.h" + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("CPB Stereo HP 1", NULL), + SND_SOC_DAPM_HP("CPB Stereo HP 2", NULL), + SND_SOC_DAPM_HP("CPB Stereo HP 3", NULL), + SND_SOC_DAPM_LINE("CPB Line Out", NULL), + SND_SOC_DAPM_MIC("CPB Stereo Mic 1", NULL), + SND_SOC_DAPM_MIC("CPB Stereo Mic 2", NULL), + SND_SOC_DAPM_LINE("CPB Line In", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + { "CPB Stereo HP 1", NULL, "AOUT1L" }, + { "CPB Stereo HP 1", NULL, "AOUT1R" }, + { "CPB Stereo HP 2", NULL, "AOUT2L" }, + { "CPB Stereo HP 2", NULL, "AOUT2R" }, + { "CPB Stereo HP 3", NULL, "AOUT3L" }, + { "CPB Stereo HP 3", NULL, "AOUT3R" }, + { "CPB Line Out", NULL, "AOUT4L" }, + { "CPB Line Out", NULL, "AOUT4R" }, + + { "AIN1L", NULL, "CPB Stereo Mic 1" }, + { "AIN1R", NULL, "CPB Stereo Mic 1" }, + { "AIN2L", NULL, "CPB Stereo Mic 2" }, + { "AIN2R", NULL, "CPB Stereo Mic 2" }, + { "AIN3L", NULL, "CPB Line In" }, + { "AIN3R", NULL, "CPB Line In" }, +}; + +static int avs_pcm3168a_be_fixup(struct snd_soc_pcm_runtime *runtime, + struct snd_pcm_hw_params *params) +{ + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* Set SSP to 24 bit. */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +SND_SOC_DAILINK_DEF(pcm3168a_dac, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-PCM3168A:00", "pcm3168a-dac"))); +SND_SOC_DAILINK_DEF(pcm3168a_adc, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-PCM3168A:00", "pcm3168a-adc"))); +SND_SOC_DAILINK_DEF(cpu_ssp0, DAILINK_COMP_ARRAY(COMP_CPU("SSP0 Pin"))); +SND_SOC_DAILINK_DEF(cpu_ssp2, DAILINK_COMP_ARRAY(COMP_CPU("SSP2 Pin"))); + +static int avs_create_dai_links(struct device *dev, struct snd_soc_dai_link **links, int *num_links) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + const int num_dl = 2; + + dl = devm_kcalloc(dev, num_dl, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = dev_name(dev); + dl[0].num_cpus = 1; + dl[0].num_codecs = 1; + dl[0].platforms = platform; + dl[0].num_platforms = 1; + dl[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; + dl[0].be_hw_params_fixup = avs_pcm3168a_be_fixup; + dl[0].nonatomic = 1; + dl[0].no_pcm = 1; + memcpy(&dl[1], &dl[0], sizeof(*dl)); + + dl[0].name = "SSP0-Codec-dac"; + dl[0].cpus = cpu_ssp0; + dl[0].codecs = pcm3168a_dac; + dl[1].name = "SSP2-Codec-adc"; + dl[1].cpus = cpu_ssp2; + dl[1].codecs = pcm3168a_adc; + + *links = dl; + *num_links = num_dl; + return 0; +} + +static int avs_pcm3168a_probe(struct platform_device *pdev) +{ + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + int ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = avs_create_dai_links(dev, &card->dai_link, &card->num_links); + if (ret) + return ret; + + if (pdata->obsolete_card_names) { + card->name = "avs_pcm3168a"; + } else { + card->driver_name = "avs_pcm3168a"; + card->long_name = card->name = "AVS I2S PCM3168A"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_pcm3168a_driver_ids[] = { + { + .name = "avs_pcm3168a", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +static struct platform_driver avs_pcm3168a_driver = { + .probe = avs_pcm3168a_probe, + .driver = { + .name = "avs_pcm3168a", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_pcm3168a_driver_ids, +}; + +module_platform_driver(avs_pcm3168a_driver); + +MODULE_DESCRIPTION("Intel pcm3168a machine driver"); +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c new file mode 100644 index 000000000000..73884f8a535c --- /dev/null +++ b/sound/soc/intel/avs/boards/probe.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/device.h> +#include <linux/module.h> +#include <sound/soc.h> + +static int avs_create_dai_links(struct device *dev, struct snd_soc_dai_link **links, int *num_links) +{ + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + if (!dl) + return -ENOMEM; + + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->platforms = devm_kzalloc(dev, sizeof(*dl->platforms), GFP_KERNEL); + if (!dl->cpus || !dl->platforms) + return -ENOMEM; + + dl->name = "Compress Probe Capture"; + dl->cpus->dai_name = "Probe Extraction CPU DAI"; + dl->num_cpus = 1; + dl->codecs = &snd_soc_dummy_dlc; + dl->num_codecs = 1; + dl->platforms->name = dev_name(dev); + dl->num_platforms = 1; + dl->nonatomic = 1; + + *links = dl; + *num_links = 1; + return 0; +} + +static int avs_probe_mb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = avs_create_dai_links(dev, &card->dai_link, &card->num_links); + if (ret) + return ret; + + card->driver_name = "avs_probe_mb"; + card->long_name = card->name = "AVS PROBE"; + card->dev = dev; + card->owner = THIS_MODULE; + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +static struct platform_driver avs_probe_mb_driver = { + .probe = avs_probe_mb_probe, + .driver = { + .name = "avs_probe_mb", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_probe_mb_driver_ids, +}; + +module_platform_driver(avs_probe_mb_driver); + +MODULE_DESCRIPTION("Intel probe machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c new file mode 100644 index 000000000000..a689f4c80867 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt274.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt274.h" +#include "../utils.h" + +#define AVS_RT274_FREQ_OUT 24000000 +#define AVS_RT274_BE_FIXUP_RATE 48000 +#define RT274_CODEC_DAI "rt274-aif1" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), +}; + +static int +avs_rt274_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); + if (!codec_dai) + return -EINVAL; + + /* Codec needs clock for Jack detection and button press */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT274_SCLK_S_PLL2, AVS_RT274_FREQ_OUT, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "set codec sysclk failed: %d\n", ret); + return ret; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + int ratio = 100; + + snd_soc_dai_set_bclk_ratio(codec_dai, ratio); + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT274_PLL2_S_BCLK, + AVS_RT274_BE_FIXUP_RATE * ratio, AVS_RT274_FREQ_OUT); + if (ret) { + dev_err(codec_dai->dev, "failed to enable PLL2: %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_rt274_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC", NULL, "Mic Jack"}, + + {"Headphone Jack", NULL, "Platform Clock"}, + {"MIC", NULL, "Platform Clock"}, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt274_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET, jack, pins, + num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(card->dev, "can't set codec pcm format %d\n", ret); + return ret; + } + + snd_soc_dapm_set_idle_bias(dapm, false); + + return 0; +} + +static void avs_rt274_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int avs_rt274_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = AVS_RT274_BE_FIXUP_RATE; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT34C2:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT274_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt274_codec_init; + dl->exit = avs_rt274_codec_exit; + dl->be_hw_params_fixup = avs_rt274_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt274_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt274"; + } else { + card->driver_name = "avs_rt274"; + card->long_name = card->name = "AVS I2S ALC274"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +static struct platform_driver avs_rt274_driver = { + .probe = avs_rt274_probe, + .driver = { + .name = "avs_rt274", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt274_driver_ids, +}; + +module_platform_driver(avs_rt274_driver); + +MODULE_DESCRIPTION("Intel rt274 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c new file mode 100644 index 000000000000..4c9ac545555a --- /dev/null +++ b/sound/soc/intel/avs/boards/rt286.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt286.h" +#include "../utils.h" + +#define RT286_CODEC_DAI "rt286-aif1" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt286_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0, + jack, pins, num_pins); + if (ret) + return ret; + + return snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, jack, NULL); +} + +static void avs_rt286_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int avs_rt286_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt286_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt286_ops = { + .hw_params = avs_rt286_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT286_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt286_codec_init; + dl->exit = avs_rt286_codec_exit; + dl->be_hw_params_fixup = avs_rt286_be_fixup; + dl->ops = &avs_rt286_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT286_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT286_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt286_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt286"; + } else { + card->driver_name = "avs_rt286"; + card->long_name = card->name = "AVS I2S ALC286"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +static struct platform_driver avs_rt286_driver = { + .probe = avs_rt286_probe, + .driver = { + .name = "avs_rt286", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt286_driver_ids, +}; + +module_platform_driver(avs_rt286_driver); + +MODULE_DESCRIPTION("Intel rt286 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c new file mode 100644 index 000000000000..2d7a7748d577 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt298.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/dmi.h> +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt298.h" +#include "../utils.h" + +#define RT298_CODEC_DAI "rt298-aif1" + +static const struct dmi_system_id kblr_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Kabylake R DDR4 RVP"), + }, + }, + {} +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt298_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0, + jack, pins, num_pins); + if (ret) + return ret; + + return snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, jack, NULL); +} + +static void avs_rt298_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int avs_rt298_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt298_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + unsigned int clk_freq; + int ret; + + if (dmi_first_match(kblr_dmi_table)) + clk_freq = 24000000; + else + clk_freq = 19200000; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL, clk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt298_ops = { + .hw_params = avs_rt298_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT298_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + if (dmi_first_match(kblr_dmi_table)) + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + else + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt298_codec_init; + dl->exit = avs_rt298_codec_exit; + dl->be_hw_params_fixup = avs_rt298_be_fixup; + dl->ops = &avs_rt298_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT298_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT298_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt298_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt298"; + } else { + card->driver_name = "avs_rt298"; + card->long_name = card->name = "AVS I2S ALC298"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +static struct platform_driver avs_rt298_driver = { + .probe = avs_rt298_probe, + .driver = { + .name = "avs_rt298", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt298_driver_ids, +}; + +module_platform_driver(avs_rt298_driver); + +MODULE_DESCRIPTION("Intel rt298 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c new file mode 100644 index 000000000000..22139eaad83a --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5514.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2023 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/clk.h> +#include <linux/input.h> +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt5514.h" +#include "../utils.h" + +#define RT5514_CODEC_DAI "rt5514-aif1" + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* DMIC */ + { "DMIC1L", NULL, "DMIC" }, + { "DMIC1R", NULL, "DMIC" }, + { "DMIC2L", NULL, "DMIC" }, + { "DMIC2R", NULL, "DMIC" }, +}; + +static int avs_rt5514_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(runtime->card); + int ret = snd_soc_dapm_ignore_suspend(dapm, "DMIC"); + + if (ret) + dev_err(runtime->dev, "DMIC - Ignore suspend failed = %d\n", ret); + + return ret; +} + +static int avs_rt5514_be_fixup(struct snd_soc_pcm_runtime *runtime, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + rate->min = rate->max = 48000; + channels->min = channels->max = 4; + + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static int avs_rt5514_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5514_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "set sysclk err: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt5514_ops = { + .hw_params = avs_rt5514_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5514:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT5514_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt5514_codec_init; + dl->be_hw_params_fixup = avs_rt5514_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->capture_only = 1; + dl->ops = &avs_rt5514_ops; + + *dai_link = dl; + + return 0; +} + +static int avs_rt5514_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt5514"; + } else { + card->driver_name = "avs_rt5514"; + card->long_name = card->name = "AVS I2S ALC5514"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +static struct platform_driver avs_rt5514_driver = { + .probe = avs_rt5514_probe, + .driver = { + .name = "avs_rt5514", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt5514_driver_ids, +}; + +module_platform_driver(avs_rt5514_driver); + +MODULE_DESCRIPTION("Intel rt5514 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c new file mode 100644 index 000000000000..2990d32f2301 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5640.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022-2025 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt5640.h" +#include "../utils.h" + +#define AVS_RT5640_MCLK_HZ 19200000 +#define RT5640_CODEC_DAI "rt5640-aif1" + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + { "Headphone Jack", NULL, "HPOR" }, + { "Headphone Jack", NULL, "HPOL" }, + { "IN2P", NULL, "Mic Jack" }, + { "IN2P", NULL, "MICBIAS1" }, + { "Speaker", NULL, "SPOLP" }, + { "Speaker", NULL, "SPOLN" }, + { "Speaker", NULL, "SPORP" }, + { "Speaker", NULL, "SPORN" }, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt5640_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_card *card = runtime->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET, jack, pins, + num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(codec_dai->component, jack, NULL); + snd_soc_dapm_set_idle_bias(dapm, false); + + return 0; +} + +static void avs_rt5640_codec_exit(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + + snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_rt5640_be_fixup(struct snd_soc_pcm_runtime *runtime, + struct snd_pcm_hw_params *params) +{ + struct snd_mask *fmask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* Format 24/32 is MSB-aligned for HDAudio and LSB-aligned for I2S. */ + if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) + snd_mask_set_format(fmask, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_MCLK, AVS_RT5640_MCLK_HZ, + params_rate(params) * 512); + if (ret < 0) { + dev_err(runtime->dev, "Set codec PLL failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(runtime->dev, "Set codec SCLK failed: %d\n", ret); + return ret; + } + + ret = rt5640_sel_asrc_clk_src(codec_dai->component, + RT5640_DA_STEREO_FILTER | RT5640_AD_STEREO_FILTER | + RT5640_DA_MONO_L_FILTER | RT5640_DA_MONO_R_FILTER | + RT5640_AD_MONO_L_FILTER | RT5640_AD_MONO_R_FILTER, + RT5640_CLK_SEL_ASRC); + if (ret) + dev_err(runtime->dev, "Set codec ASRC failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt5640_ops = { + .hw_params = avs_rt5640_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_acpi_mach *mach, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + u32 uid = 0; + int ret; + + if (mach->uid) { + ret = kstrtou32(mach->uid, 0, &uid); + if (ret) + return ret; + uid--; /* 0-based indexing. */ + } + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5640:0%d", uid); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT5640_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt5640_codec_init; + dl->exit = avs_rt5640_codec_exit; + dl->be_hw_params_fixup = avs_rt5640_be_fixup; + dl->ops = &avs_rt5640_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT5640_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT5640_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt5640_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct device *dev = &pdev->dev; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, mach, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (mach->uid) { + card->name = devm_kasprintf(dev, GFP_KERNEL, "AVS I2S ALC5640.%s", mach->uid); + if (!card->name) + return -ENOMEM; + } else { + card->name = "AVS I2S ALC5640"; + } + card->driver_name = "avs_rt5640"; + card->long_name = card->name; + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +static struct platform_driver avs_rt5640_driver = { + .probe = avs_rt5640_probe, + .driver = { + .name = "avs_rt5640", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt5640_driver_ids, +}; + +module_platform_driver(avs_rt5640_driver); + +MODULE_DESCRIPTION("Intel rt5640 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c new file mode 100644 index 000000000000..68fea325376a --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5663.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022-2023 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/clk.h> +#include <linux/input.h> +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt5663.h" +#include "../utils.h" + +#define RT5663_CODEC_DAI "rt5663-aif" + +struct rt5663_private { + struct snd_soc_jack jack; +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route card_routes[] = { + /* HP jack connectors */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* Mic jacks */ + { "IN1P", NULL, "Headset Mic" }, + { "IN1N", NULL, "Headset Mic" }, +}; + +static const struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt5663_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct rt5663_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = &priv->jack; + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup_array(card->dev, card_headset_pins, num_pins, + sizeof(card_headset_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, jack, + pins, num_pins); + if (ret) + return ret; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, jack, NULL); + + return 0; +} + +static void avs_rt5663_codec_exit(struct snd_soc_pcm_runtime *runtime) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, NULL, NULL); +} + +static int +avs_rt5663_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_rt5663_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret; + + /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */ + rt5663_sel_asrc_clk_src(codec_dai->component, + RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER, + RT5663_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5663_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN); + + return ret; +} + +static const struct snd_soc_ops avs_rt5663_ops = { + .hw_params = avs_rt5663_hw_params, +}; + + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5663:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, RT5663_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt5663_codec_init; + dl->exit = avs_rt5663_codec_exit; + dl->be_hw_params_fixup = avs_rt5663_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->ops = &avs_rt5663_ops; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT5663_CODEC_DAI); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT5663_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt5663_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct rt5663_private *priv; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!priv || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt5663"; + } else { + card->driver_name = "avs_rt5663"; + card->long_name = card->name = "AVS I2S ALC5663"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, priv); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +static struct platform_driver avs_rt5663_driver = { + .probe = avs_rt5663_probe, + .driver = { + .name = "avs_rt5663", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt5663_driver_ids, +}; + +module_platform_driver(avs_rt5663_driver); + +MODULE_DESCRIPTION("Intel rt5663 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c new file mode 100644 index 000000000000..81863728da1d --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5682.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/clk.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rt5682.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../common/soc-intel-quirks.h" +#include "../../../codecs/rt5682.h" +#include "../utils.h" + +#define AVS_RT5682_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define AVS_RT5682_SSP_CODEC_MASK (GENMASK(2, 0)) +#define AVS_RT5682_MCLK_EN BIT(3) +#define AVS_RT5682_MCLK_24MHZ BIT(4) +#define AVS_RT5682_CODEC_DAI_NAME "rt5682-aif1" + +/* Default: MCLK on, MCLK 19.2M, SSP0 */ +static unsigned long avs_rt5682_quirk = AVS_RT5682_MCLK_EN | AVS_RT5682_SSP_CODEC(0); + +static int avs_rt5682_quirk_cb(const struct dmi_system_id *id) +{ + avs_rt5682_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id avs_rt5682_quirk_table[] = { + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "WhiskeyLake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_MCLK_24MHZ | + AVS_RT5682_SSP_CODEC(1)), + }, + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_SSP_CODEC(0)), + }, + {} +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "IN1P", NULL, "Headset Mic" }, +}; + +static const struct snd_soc_jack_pin card_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt5682_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_jack_pins); + + pins = devm_kmemdup_array(card->dev, card_jack_pins, num_pins, + sizeof(card_jack_pins[0]), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + /* Need to enable ASRC function for 24MHz mclk rate */ + if ((avs_rt5682_quirk & AVS_RT5682_MCLK_EN) && + (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ)) { + rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, RT5682_CLK_SEL_I2S1_ASRC); + } + + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, jack, + pins, num_pins); + if (ret) { + dev_err(card->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + if (ret) { + dev_err(card->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return 0; +}; + +static void avs_rt5682_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); +} + +static int +avs_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + int pll_source, freq_in, freq_out; + int ret; + + if (avs_rt5682_quirk & AVS_RT5682_MCLK_EN) { + pll_source = RT5682_PLL1_S_MCLK; + if (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ) + freq_in = 24000000; + else + freq_in = 19200000; + } else { + pll_source = RT5682_PLL1_S_BCLK1; + freq_in = params_rate(params) * 50; + } + + freq_out = params_rate(params) * 512; + + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL1, pll_source, freq_in, freq_out); + if (ret < 0) + dev_err(runtime->dev, "Set PLL failed: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, freq_out, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "Set sysclk failed: %d\n", ret); + + /* slot_width should be equal or larger than data length. */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, params_width(params)); + if (ret < 0) + dev_err(runtime->dev, "Set TDM slot failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt5682_ops = { + .hw_params = avs_rt5682_hw_params, +}; + +static int +avs_rt5682_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5682:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, AVS_RT5682_CODEC_DAI_NAME); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_rt5682_codec_init; + dl->exit = avs_rt5682_codec_exit; + dl->be_hw_params_fixup = avs_rt5682_be_fixup; + dl->ops = &avs_rt5682_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, AVS_RT5682_CODEC_DAI_NAME); + + return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, AVS_RT5682_CODEC_DAI_NAME); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return snd_soc_component_set_jack(codec_dai->component, jack, NULL); +} + +static int avs_rt5682_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct snd_soc_jack *jack; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + if (pdev->id_entry && pdev->id_entry->driver_data) + avs_rt5682_quirk = (unsigned long)pdev->id_entry->driver_data; + + dmi_check_system(avs_rt5682_quirk_table); + dev_dbg(dev, "avs_rt5682_quirk = %lx\n", avs_rt5682_quirk); + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!jack || !card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_rt5682"; + } else { + card->driver_name = "avs_rt5682"; + card->long_name = card->name = "AVS I2S ALC5682"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +static struct platform_driver avs_rt5682_driver = { + .probe = avs_rt5682_probe, + .driver = { + .name = "avs_rt5682", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_rt5682_driver_ids, +}; + +module_platform_driver(avs_rt5682_driver) + +MODULE_DESCRIPTION("Intel rt5682 machine driver"); +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c new file mode 100644 index 000000000000..ae0e6e27a8b8 --- /dev/null +++ b/sound/soc/intel/avs/boards/ssm4567.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" +#include "../utils.h" + +#define SKL_SSM_CODEC_DAI "ssm4567-hifi" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:01"), + .name_prefix = "Right", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Speaker"), + SOC_DAPM_PIN_SWITCH("Right Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Speaker", NULL), + SND_SOC_DAPM_SPK("Right Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Left Speaker", NULL, "Left OUT"}, + {"Right Speaker", NULL, "Right OUT"}, +}; + +static int avs_ssm4567_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + + /* Slot 1 for left */ + ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(runtime, 0), 0x01, 0x01, 2, 48); + if (ret < 0) + return ret; + + /* Slot 2 for right */ + ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(runtime, 1), 0x02, 0x02, 2, 48); + if (ret < 0) + return ret; + + return 0; +} + +static int +avs_ssm4567_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + dl->codecs = devm_kcalloc(dev, 2, sizeof(*dl->codecs), GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:00"); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:01"); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + platform->name = dev_name(dev); + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->init = avs_ssm4567_codec_init; + dl->be_hw_params_fixup = avs_ssm4567_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->ignore_pmdown_time = 1; + + *dai_link = dl; + + return 0; +} + +static int avs_ssm4567_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ssp_port, tdm_slot, ret; + + mach = dev_get_platdata(dev); + pdata = mach->pdata; + + ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); + if (ret) + return ret; + + ret = avs_create_dai_link(dev, ssp_port, tdm_slot, &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + if (pdata->obsolete_card_names) { + card->name = "avs_ssm4567"; + } else { + card->driver_name = "avs_ssm4567"; + card->long_name = card->name = "AVS I2S SSM4567"; + } + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + card->num_controls = ARRAY_SIZE(card_controls); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); + card->fully_routed = true; + + return devm_snd_soc_register_deferrable_card(dev, card); +} + +static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +static struct platform_driver avs_ssm4567_driver = { + .probe = avs_ssm4567_probe, + .driver = { + .name = "avs_ssm4567", + .pm = &snd_soc_pm_ops, + }, + .id_table = avs_ssm4567_driver_ids, +}; + +module_platform_driver(avs_ssm4567_driver) + +MODULE_DESCRIPTION("Intel ssm4567 machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/cldma.c b/sound/soc/intel/avs/cldma.c new file mode 100644 index 000000000000..61326d7059b1 --- /dev/null +++ b/sound/soc/intel/avs/cldma.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/pci.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> +#include "cldma.h" +#include "registers.h" + +/* Stream Registers */ +#define AZX_CL_SD_BASE 0x80 +#define AZX_SD_CTL_STRM_MASK GENMASK(23, 20) +#define AZX_SD_CTL_STRM(s) (((s)->stream_tag << 20) & AZX_SD_CTL_STRM_MASK) +#define AZX_SD_BDLPL_BDLPLBA_MASK GENMASK(31, 7) +#define AZX_SD_BDLPL_BDLPLBA(lb) ((lb) & AZX_SD_BDLPL_BDLPLBA_MASK) + +/* Software Position Based FIFO Capability Registers */ +#define AZX_CL_SPBFCS 0x20 +#define AZX_REG_CL_SPBFCTL (AZX_CL_SPBFCS + 0x4) +#define AZX_REG_CL_SD_SPIB (AZX_CL_SPBFCS + 0x8) + +#define AVS_CL_OP_INTERVAL_US 3 +#define AVS_CL_OP_TIMEOUT_US 300 +#define AVS_CL_IOC_TIMEOUT_MS 300 +#define AVS_CL_STREAM_INDEX 0 + +struct hda_cldma { + struct device *dev; + struct hdac_bus *bus; + void __iomem *dsp_ba; + + unsigned int buffer_size; + unsigned int num_periods; + unsigned char stream_tag; + void __iomem *sd_addr; + + struct snd_dma_buffer dmab_data; + struct snd_dma_buffer dmab_bdl; + struct delayed_work memcpy_work; + struct completion completion; + + /* runtime */ + void *position; + unsigned int remaining; + unsigned int sd_status; +}; + +static void cldma_memcpy_work(struct work_struct *work); + +struct hda_cldma code_loader = { + .stream_tag = AVS_CL_STREAM_INDEX + 1, + .memcpy_work = __DELAYED_WORK_INITIALIZER(code_loader.memcpy_work, cldma_memcpy_work, 0), + .completion = COMPLETION_INITIALIZER(code_loader.completion), +}; + +void hda_cldma_fill(struct hda_cldma *cl) +{ + unsigned int size, offset; + + if (cl->remaining > cl->buffer_size) + size = cl->buffer_size; + else + size = cl->remaining; + + offset = snd_hdac_stream_readl(cl, CL_SD_SPIB); + if (offset + size > cl->buffer_size) { + unsigned int ss; + + ss = cl->buffer_size - offset; + memcpy(cl->dmab_data.area + offset, cl->position, ss); + offset = 0; + size -= ss; + cl->position += ss; + cl->remaining -= ss; + } + + memcpy(cl->dmab_data.area + offset, cl->position, size); + cl->position += size; + cl->remaining -= size; + + snd_hdac_stream_writel(cl, CL_SD_SPIB, offset + size); +} + +static void cldma_memcpy_work(struct work_struct *work) +{ + struct hda_cldma *cl = container_of(work, struct hda_cldma, memcpy_work.work); + int ret; + + ret = hda_cldma_start(cl); + if (ret < 0) { + dev_err(cl->dev, "cldma set RUN failed: %d\n", ret); + return; + } + + while (true) { + ret = wait_for_completion_timeout(&cl->completion, + msecs_to_jiffies(AVS_CL_IOC_TIMEOUT_MS)); + if (!ret) { + dev_err(cl->dev, "cldma IOC timeout\n"); + break; + } + + if (!(cl->sd_status & SD_INT_COMPLETE)) { + dev_err(cl->dev, "cldma transfer error, SD status: 0x%08x\n", + cl->sd_status); + break; + } + + if (!cl->remaining) + break; + + reinit_completion(&cl->completion); + hda_cldma_fill(cl); + /* enable CLDMA interrupt */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, + AVS_ADSP_ADSPIC_CLDMA); + } +} + +void hda_cldma_transfer(struct hda_cldma *cl, unsigned long start_delay) +{ + if (!cl->remaining) + return; + + reinit_completion(&cl->completion); + /* fill buffer with the first chunk before scheduling run */ + hda_cldma_fill(cl); + + schedule_delayed_work(&cl->memcpy_work, start_delay); +} + +int hda_cldma_start(struct hda_cldma *cl) +{ + unsigned int reg; + + /* enable interrupts */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, + AVS_ADSP_ADSPIC_CLDMA); + snd_hdac_stream_updateb(cl, SD_CTL, SD_INT_MASK | SD_CTL_DMA_START, + SD_INT_MASK | SD_CTL_DMA_START); + + /* await DMA engine start */ + return snd_hdac_stream_readb_poll(cl, SD_CTL, reg, reg & SD_CTL_DMA_START, + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); +} + +int hda_cldma_stop(struct hda_cldma *cl) +{ + unsigned int reg; + int ret; + + /* disable interrupts */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, 0); + snd_hdac_stream_updateb(cl, SD_CTL, SD_INT_MASK | SD_CTL_DMA_START, 0); + + /* await DMA engine stop */ + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, !(reg & SD_CTL_DMA_START), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + cancel_delayed_work_sync(&cl->memcpy_work); + + return ret; +} + +int hda_cldma_reset(struct hda_cldma *cl) +{ + unsigned int reg; + int ret; + + ret = hda_cldma_stop(cl); + if (ret < 0) { + dev_err(cl->dev, "cldma stop failed: %d\n", ret); + return ret; + } + + snd_hdac_stream_updateb(cl, SD_CTL, SD_CTL_STREAM_RESET, SD_CTL_STREAM_RESET); + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, (reg & SD_CTL_STREAM_RESET), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + if (ret < 0) { + dev_err(cl->dev, "cldma set SRST failed: %d\n", ret); + return ret; + } + + snd_hdac_stream_updateb(cl, SD_CTL, SD_CTL_STREAM_RESET, 0); + ret = snd_hdac_stream_readb_poll(cl, SD_CTL, reg, !(reg & SD_CTL_STREAM_RESET), + AVS_CL_OP_INTERVAL_US, AVS_CL_OP_TIMEOUT_US); + if (ret < 0) { + dev_err(cl->dev, "cldma unset SRST failed: %d\n", ret); + return ret; + } + + return 0; +} + +void hda_cldma_set_data(struct hda_cldma *cl, void *data, unsigned int size) +{ + /* setup runtime */ + cl->position = data; + cl->remaining = size; +} + +static void cldma_setup_bdle(struct hda_cldma *cl, u32 bdle_size) +{ + struct snd_dma_buffer *dmab = &cl->dmab_data; + __le32 *bdl = (__le32 *)cl->dmab_bdl.area; + int remaining = cl->buffer_size; + int offset = 0; + + cl->num_periods = 0; + + while (remaining > 0) { + phys_addr_t addr; + int chunk; + + addr = snd_sgbuf_get_addr(dmab, offset); + bdl[0] = cpu_to_le32(lower_32_bits(addr)); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + chunk = snd_sgbuf_get_chunk_size(dmab, offset, bdle_size); + bdl[2] = cpu_to_le32(chunk); + + remaining -= chunk; + /* set IOC only for the last entry */ + bdl[3] = (remaining > 0) ? 0 : cpu_to_le32(0x01); + + bdl += 4; + offset += chunk; + cl->num_periods++; + } +} + +void hda_cldma_setup(struct hda_cldma *cl) +{ + dma_addr_t bdl_addr = cl->dmab_bdl.addr; + + cldma_setup_bdle(cl, cl->buffer_size / 2); + + snd_hdac_stream_writel(cl, SD_BDLPL, AZX_SD_BDLPL_BDLPLBA(lower_32_bits(bdl_addr))); + snd_hdac_stream_writel(cl, SD_BDLPU, upper_32_bits(bdl_addr)); + + snd_hdac_stream_writel(cl, SD_CBL, cl->buffer_size); + snd_hdac_stream_writeb(cl, SD_LVI, cl->num_periods - 1); + + snd_hdac_stream_updatel(cl, SD_CTL, AZX_SD_CTL_STRM_MASK, AZX_SD_CTL_STRM(cl)); + /* enable spib */ + snd_hdac_stream_writel(cl, CL_SPBFCTL, 1); +} + +void hda_cldma_interrupt(struct hda_cldma *cl) +{ + /* disable CLDMA interrupt */ + snd_hdac_adsp_updatel(cl, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_CLDMA, 0); + + cl->sd_status = snd_hdac_stream_readb(cl, SD_STS); + dev_dbg(cl->dev, "%s sd_status: 0x%08x\n", __func__, cl->sd_status); + + complete(&cl->completion); +} + +int hda_cldma_init(struct hda_cldma *cl, struct hdac_bus *bus, void __iomem *dsp_ba, + unsigned int buffer_size) +{ + int ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, bus->dev, buffer_size, &cl->dmab_data); + if (ret < 0) + return ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, bus->dev, BDL_SIZE, &cl->dmab_bdl); + if (ret < 0) { + snd_dma_free_pages(&cl->dmab_data); + return ret; + } + + cl->dev = bus->dev; + cl->bus = bus; + cl->dsp_ba = dsp_ba; + cl->buffer_size = buffer_size; + cl->sd_addr = dsp_ba + AZX_CL_SD_BASE; + + return 0; +} + +void hda_cldma_free(struct hda_cldma *cl) +{ + snd_dma_free_pages(&cl->dmab_data); + snd_dma_free_pages(&cl->dmab_bdl); +} diff --git a/sound/soc/intel/avs/cldma.h b/sound/soc/intel/avs/cldma.h new file mode 100644 index 000000000000..7f9b2b1c566e --- /dev/null +++ b/sound/soc/intel/avs/cldma.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation + * + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_CLDMA_H +#define __SOUND_SOC_INTEL_AVS_CLDMA_H + +#include <linux/sizes.h> + +#define AVS_CL_DEFAULT_BUFFER_SIZE SZ_128K + +struct hda_cldma; +extern struct hda_cldma code_loader; + +void hda_cldma_fill(struct hda_cldma *cl); +void hda_cldma_transfer(struct hda_cldma *cl, unsigned long start_delay); + +int hda_cldma_start(struct hda_cldma *cl); +int hda_cldma_stop(struct hda_cldma *cl); +int hda_cldma_reset(struct hda_cldma *cl); + +void hda_cldma_set_data(struct hda_cldma *cl, void *data, unsigned int size); +void hda_cldma_setup(struct hda_cldma *cl); +void hda_cldma_interrupt(struct hda_cldma *cl); +int hda_cldma_init(struct hda_cldma *cl, struct hdac_bus *bus, void __iomem *dsp_ba, + unsigned int buffer_size); +void hda_cldma_free(struct hda_cldma *cl); + +#endif diff --git a/sound/soc/intel/avs/cnl.c b/sound/soc/intel/avs/cnl.c new file mode 100644 index 000000000000..5b5359e9128b --- /dev/null +++ b/sound/soc/intel/avs/cnl.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2024 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" +#include "registers.h" + +static void avs_cnl_ipc_interrupt(struct avs_dev *adev) +{ + const struct avs_spec *spec = adev->spec; + u32 hipc_ack, hipc_rsp; + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, 0); + + hipc_ack = snd_hdac_adsp_readl(adev, spec->hipc->ack_offset); + hipc_rsp = snd_hdac_adsp_readl(adev, spec->hipc->rsp_offset); + + /* DSP acked host's request. */ + if (hipc_ack & spec->hipc->ack_done_mask) { + complete(&adev->ipc->done_completion); + + /* Tell DSP it has our attention. */ + snd_hdac_adsp_updatel(adev, spec->hipc->ack_offset, spec->hipc->ack_done_mask, + spec->hipc->ack_done_mask); + } + + /* DSP sent new response to process. */ + if (hipc_rsp & spec->hipc->rsp_busy_mask) { + union avs_reply_msg msg; + u32 hipctda; + + msg.primary = snd_hdac_adsp_readl(adev, CNL_ADSP_REG_HIPCTDR); + msg.ext.val = snd_hdac_adsp_readl(adev, CNL_ADSP_REG_HIPCTDD); + + avs_dsp_process_response(adev, msg.val); + + /* Tell DSP we accepted its message. */ + snd_hdac_adsp_updatel(adev, CNL_ADSP_REG_HIPCTDR, + CNL_ADSP_HIPCTDR_BUSY, CNL_ADSP_HIPCTDR_BUSY); + /* Ack this response. */ + snd_hdac_adsp_updatel(adev, CNL_ADSP_REG_HIPCTDA, + CNL_ADSP_HIPCTDA_DONE, CNL_ADSP_HIPCTDA_DONE); + /* HW might have been clock gated, give some time for change to propagate. */ + snd_hdac_adsp_readl_poll(adev, CNL_ADSP_REG_HIPCTDA, hipctda, + !(hipctda & CNL_ADSP_HIPCTDA_DONE), 10, 1000); + } + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY); +} + +irqreturn_t avs_cnl_dsp_interrupt(struct avs_dev *adev) +{ + u32 adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS); + irqreturn_t ret = IRQ_NONE; + + if (adspis == UINT_MAX) + return ret; + + if (adspis & AVS_ADSP_ADSPIS_IPC) { + avs_cnl_ipc_interrupt(adev); + ret = IRQ_HANDLED; + } + + return ret; +} + +const struct avs_dsp_ops avs_cnl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .dsp_interrupt = avs_cnl_dsp_interrupt, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_hda_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .log_buffer_offset = avs_skl_log_buffer_offset, + .log_buffer_status = avs_apl_log_buffer_status, + .coredump = avs_apl_coredump, + .d0ix_toggle = avs_apl_d0ix_toggle, + .set_d0ix = avs_apl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(apl) +}; diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c new file mode 100644 index 000000000000..a8f05de338e0 --- /dev/null +++ b/sound/soc/intel/avs/control.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// Cezary Rojewski <cezary.rojewski@intel.com> +// + +#include <linux/cleanup.h> +#include <sound/soc.h> +#include "avs.h" +#include "control.h" +#include "messages.h" +#include "path.h" + +static struct avs_dev *avs_get_kcontrol_adev(struct snd_kcontrol *kcontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); + struct device *dev = snd_soc_dapm_to_dev(dapm); + + return to_avs_dev(dev); +} + +static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 id) +{ + struct avs_path *path; + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + + spin_lock(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { + guid_t *type = &mod->template->cfg_ext->type; + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && + mod->template->ctl_id == id) { + spin_unlock(&adev->path_list_lock); + return mod; + } + } + } + } + spin_unlock(&adev->path_list_lock); + + return NULL; +} + +int avs_control_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + struct avs_control_data *ctl_data = mc->dobj.private; + struct avs_path_module *active_module; + struct avs_volume_cfg *dspvols; + struct avs_dev *adev; + size_t num_dspvols; + int ret, i; + + adev = avs_get_kcontrol_adev(kctl); + + /* Prevent access to modules while path is being constructed. */ + guard(mutex)(&adev->path_mutex); + + active_module = avs_get_volume_module(adev, ctl_data->id); + if (active_module) { + ret = avs_ipc_peakvol_get_volume(adev, active_module->module_id, + active_module->instance_id, &dspvols, + &num_dspvols); + if (ret) + return AVS_IPC_RET(ret); + + /* Do not copy more than the control can store. */ + num_dspvols = min_t(u32, num_dspvols, SND_SOC_TPLG_MAX_CHAN); + for (i = 0; i < num_dspvols; i++) + ctl_data->values[i] = dspvols[i].target_volume; + kfree(dspvols); + } + + memcpy(uctl->value.integer.value, ctl_data->values, sizeof(ctl_data->values)); + return 0; +} + +int avs_control_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct avs_path_module *active_module; + struct avs_control_data *ctl_data; + struct soc_mixer_control *mc; + struct avs_dev *adev; + long *input; + int ret, i; + + mc = (struct soc_mixer_control *)kctl->private_value; + ctl_data = mc->dobj.private; + adev = avs_get_kcontrol_adev(kctl); + input = uctl->value.integer.value; + i = 0; + + /* mc->num_channels can be 0. */ + do { + if (input[i] < mc->min || input[i] > mc->max) + return -EINVAL; + } while (++i < mc->num_channels); + + if (!memcmp(ctl_data->values, input, sizeof(ctl_data->values))) + return 0; + + /* Prevent access to modules while path is being constructed. */ + guard(mutex)(&adev->path_mutex); + + active_module = avs_get_volume_module(adev, ctl_data->id); + if (active_module) { + ret = avs_peakvol_set_volume(adev, active_module, mc, input); + if (ret) + return ret; + } + + memcpy(ctl_data->values, input, sizeof(ctl_data->values)); + return 1; +} + +int avs_control_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = max_t(u32, 1, mc->num_channels); + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mc->max; + return 0; +} + +int avs_control_mute_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + struct avs_control_data *ctl_data = mc->dobj.private; + struct avs_path_module *active_module; + struct avs_mute_cfg *dspmutes; + struct avs_dev *adev; + size_t num_dspmutes; + int ret, i; + + adev = avs_get_kcontrol_adev(kctl); + + /* Prevent access to modules while path is being constructed. */ + guard(mutex)(&adev->path_mutex); + + active_module = avs_get_volume_module(adev, ctl_data->id); + if (active_module) { + ret = avs_ipc_peakvol_get_mute(adev, active_module->module_id, + active_module->instance_id, &dspmutes, + &num_dspmutes); + if (ret) + return AVS_IPC_RET(ret); + + /* Do not copy more than the control can store. */ + num_dspmutes = min_t(u32, num_dspmutes, SND_SOC_TPLG_MAX_CHAN); + for (i = 0; i < num_dspmutes; i++) + ctl_data->values[i] = !dspmutes[i].mute; + kfree(dspmutes); + } + + memcpy(uctl->value.integer.value, ctl_data->values, sizeof(ctl_data->values)); + return 0; +} + +int avs_control_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl) +{ + struct avs_path_module *active_module; + struct avs_control_data *ctl_data; + struct soc_mixer_control *mc; + struct avs_dev *adev; + long *input; + int ret, i; + + mc = (struct soc_mixer_control *)kctl->private_value; + ctl_data = mc->dobj.private; + adev = avs_get_kcontrol_adev(kctl); + input = uctl->value.integer.value; + i = 0; + + /* mc->num_channels can be 0. */ + do { + if (input[i] < mc->min || input[i] > mc->max) + return -EINVAL; + } while (++i < mc->num_channels); + + if (!memcmp(ctl_data->values, input, sizeof(ctl_data->values))) + return 0; + + /* Prevent access to modules while path is being constructed. */ + guard(mutex)(&adev->path_mutex); + + active_module = avs_get_volume_module(adev, ctl_data->id); + if (active_module) { + ret = avs_peakvol_set_mute(adev, active_module, mc, input); + if (ret) + return ret; + } + + memcpy(ctl_data->values, input, sizeof(ctl_data->values)); + return 1; +} + +int avs_control_mute_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = max_t(u32, 1, mc->num_channels); + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mc->max; + return 0; +} diff --git a/sound/soc/intel/avs/control.h b/sound/soc/intel/avs/control.h new file mode 100644 index 000000000000..08b2919e4629 --- /dev/null +++ b/sound/soc/intel/avs/control.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation + * + * Authors: Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + * Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_CTRL_H +#define __SOUND_SOC_INTEL_AVS_CTRL_H + +#include <sound/control.h> +#include <uapi/sound/asoc.h> + +struct avs_control_data { + u32 id; + long values[SND_SOC_TPLG_MAX_CHAN]; +}; + +int avs_control_volume_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl); +int avs_control_volume_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl); +int avs_control_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo); +int avs_control_mute_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl); +int avs_control_mute_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uctl); +int avs_control_mute_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo); + +#endif diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c new file mode 100644 index 000000000000..6e0e65584c7f --- /dev/null +++ b/sound/soc/intel/avs/core.c @@ -0,0 +1,959 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// +// Special thanks to: +// Krzysztof Hejmowski <krzysztof.hejmowski@intel.com> +// Michal Sienkiewicz <michal.sienkiewicz@intel.com> +// Filip Proborszcz +// +// for sharing Intel AudioDSP expertise and helping shape the very +// foundation of this driver +// + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <acpi/nhlt.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/hda_register.h> +#include <sound/hdaudio.h> +#include <sound/hdaudio_ext.h> +#include <sound/intel-dsp-config.h> +#include "../../codecs/hda.h" +#include "avs.h" +#include "cldma.h" +#include "debug.h" +#include "messages.h" +#include "pcm.h" + +static u32 pgctl_mask = AZX_PGCTL_LSRMD_MASK; +module_param(pgctl_mask, uint, 0444); +MODULE_PARM_DESC(pgctl_mask, "PCI PGCTL policy override"); + +static u32 cgctl_mask = AZX_CGCTL_MISCBDCGE_MASK; +module_param(cgctl_mask, uint, 0444); +MODULE_PARM_DESC(cgctl_mask, "PCI CGCTL policy override"); + +static void +avs_hda_update_config_dword(struct hdac_bus *bus, u32 reg, u32 mask, u32 value) +{ + struct pci_dev *pci = to_pci_dev(bus->dev); + u32 data; + + pci_read_config_dword(pci, reg, &data); + data &= ~mask; + data |= (value & mask); + pci_write_config_dword(pci, reg, data); +} + +void avs_hda_power_gating_enable(struct avs_dev *adev, bool enable) +{ + u32 value = enable ? 0 : pgctl_mask; + + if (!avs_platattr_test(adev, ACE)) + avs_hda_update_config_dword(&adev->base.core, AZX_PCIREG_PGCTL, pgctl_mask, value); +} + +static void avs_hdac_clock_gating_enable(struct hdac_bus *bus, bool enable) +{ + struct avs_dev *adev = hdac_to_avs(bus); + u32 value = enable ? cgctl_mask : 0; + + if (!avs_platattr_test(adev, ACE)) + avs_hda_update_config_dword(bus, AZX_PCIREG_CGCTL, cgctl_mask, value); +} + +void avs_hda_clock_gating_enable(struct avs_dev *adev, bool enable) +{ + avs_hdac_clock_gating_enable(&adev->base.core, enable); +} + +void avs_hda_l1sen_enable(struct avs_dev *adev, bool enable) +{ + if (avs_platattr_test(adev, ACE)) + return; + if (enable) { + if (atomic_inc_and_test(&adev->l1sen_counter)) + snd_hdac_chip_updatel(&adev->base.core, VS_EM2, AZX_VS_EM2_L1SEN, + AZX_VS_EM2_L1SEN); + } else { + if (atomic_dec_return(&adev->l1sen_counter) == -1) + snd_hdac_chip_updatel(&adev->base.core, VS_EM2, AZX_VS_EM2_L1SEN, 0); + } +} + +static int avs_hdac_bus_init_streams(struct hdac_bus *bus) +{ + unsigned int cp_streams, pb_streams; + unsigned int gcap; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + + snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); + snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + + return snd_hdac_bus_alloc_stream_pages(bus); +} + +static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +{ + struct avs_dev *adev = hdac_to_avs(bus); + struct hdac_ext_link *hlink; + bool ret; + + avs_hdac_clock_gating_enable(bus, false); + ret = snd_hdac_bus_init_chip(bus, full_reset); + + /* Reset stream-to-link mapping */ + list_for_each_entry(hlink, &bus->hlink_list, list) + writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); + + avs_hdac_clock_gating_enable(bus, true); + + /* Set DUM bit to address incorrect position reporting for capture + * streams. In order to do so, CTRL needs to be out of reset state + */ + if (!avs_platattr_test(adev, ACE)) + snd_hdac_chip_updatel(bus, VS_EM2, AZX_VS_EM2_DUM, AZX_VS_EM2_DUM); + + return ret; +} + +static int probe_codec(struct hdac_bus *bus, int addr) +{ + struct hda_codec *codec; + unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + unsigned int res = -1; + int ret; + + mutex_lock(&bus->cmd_mutex); + snd_hdac_bus_send_cmd(bus, cmd); + snd_hdac_bus_get_response(bus, addr, &res); + mutex_unlock(&bus->cmd_mutex); + if (res == -1) + return -EIO; + + dev_dbg(bus->dev, "codec #%d probed OK: 0x%x\n", addr, res); + + codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "hdaudioB%dD%d", bus->idx, addr); + if (IS_ERR(codec)) { + dev_err(bus->dev, "init codec failed: %ld\n", PTR_ERR(codec)); + return PTR_ERR(codec); + } + /* + * Allow avs_core suspend by forcing suspended state on all + * of its codec child devices. Component interested in + * dealing with hda codecs directly takes pm responsibilities + */ + pm_runtime_set_suspended(hda_codec_dev(codec)); + + /* configure effectively creates new ASoC component */ + ret = snd_hda_codec_configure(codec); + if (ret < 0) { + dev_warn(bus->dev, "failed to config codec #%d: %d\n", addr, ret); + return ret; + } + + return 0; +} + +static void avs_hdac_bus_probe_codecs(struct hdac_bus *bus) +{ + int ret, c; + + /* First try to probe all given codec slots */ + for (c = 0; c < HDA_MAX_CODECS; c++) { + if (!(bus->codec_mask & BIT(c))) + continue; + + ret = probe_codec(bus, c); + /* Ignore codecs with no supporting driver. */ + if (!ret || ret == -ENODEV) + continue; + + /* + * Some BIOSen give you wrong codec addresses + * that don't exist + */ + dev_warn(bus->dev, "Codec #%d probe error; disabling it...\n", c); + bus->codec_mask &= ~BIT(c); + /* + * More badly, accessing to a non-existing + * codec often screws up the controller bus, + * and disturbs the further communications. + * Thus if an error occurs during probing, + * better to reset the controller bus to get + * back to the sanity state. + */ + snd_hdac_bus_stop_chip(bus); + avs_hdac_bus_init_chip(bus, true); + } +} + +static void avs_hda_probe_work(struct work_struct *work) +{ + struct avs_dev *adev = container_of(work, struct avs_dev, probe_work); + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_link *hlink; + int ret; + + pm_runtime_set_active(bus->dev); /* clear runtime_error flag */ + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); + avs_hdac_bus_init_chip(bus, true); + avs_hdac_bus_probe_codecs(bus); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + /* with all codecs probed, links can be powered down */ + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_ext_bus_link_put(bus, hlink); + + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + avs_debugfs_init(adev); + + ret = avs_dsp_first_boot_firmware(adev); + if (ret < 0) + return; + + acpi_nhlt_get_gbl_table(); + + avs_register_all_boards(adev); + + /* configure PM */ + pm_runtime_set_autosuspend_delay(bus->dev, 2000); + pm_runtime_use_autosuspend(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + pm_runtime_allow(bus->dev); +} + +static void hdac_stream_update_pos(struct hdac_stream *stream, u64 buffer_size) +{ + u64 prev_pos, pos, num_bytes; + + div64_u64_rem(stream->curr_pos, buffer_size, &prev_pos); + pos = snd_hdac_stream_get_pos_posbuf(stream); + + if (pos < prev_pos) + num_bytes = (buffer_size - prev_pos) + pos; + else + num_bytes = pos - prev_pos; + + stream->curr_pos += num_bytes; +} + +/* called from IRQ */ +static void hdac_update_stream(struct hdac_bus *bus, struct hdac_stream *stream) +{ + if (stream->substream) { + avs_period_elapsed(stream->substream); + } else if (stream->cstream) { + u64 buffer_size = stream->cstream->runtime->buffer_size; + + hdac_stream_update_pos(stream, buffer_size); + snd_compr_fragment_elapsed(stream->cstream); + } +} + +static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) +{ + irqreturn_t ret = IRQ_NONE; + u32 status; + + status = snd_hdac_chip_readl(bus, INTSTS); + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + + spin_lock_irq(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + ret = IRQ_HANDLED; + } + + spin_unlock_irq(&bus->reg_lock); + return ret; +} + +static irqreturn_t avs_hda_irq_handler(int irq, void *dev_id) +{ + struct hdac_bus *bus = dev_id; + u32 intsts; + + intsts = snd_hdac_chip_readl(bus, INTSTS); + if (intsts == UINT_MAX || !(intsts & AZX_INT_GLOBAL_EN)) + return IRQ_NONE; + + /* Mask GIE, unmasked in irq_thread(). */ + snd_hdac_chip_updatel(bus, INTCTL, AZX_INT_GLOBAL_EN, 0); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t avs_hda_irq_thread(int irq, void *dev_id) +{ + struct hdac_bus *bus = dev_id; + u32 status; + + status = snd_hdac_chip_readl(bus, INTSTS); + if (status & ~AZX_INT_GLOBAL_EN) + avs_hda_interrupt(bus); + + /* Unmask GIE, masked in irq_handler(). */ + snd_hdac_chip_updatel(bus, INTCTL, AZX_INT_GLOBAL_EN, AZX_INT_GLOBAL_EN); + + return IRQ_HANDLED; +} + +static irqreturn_t avs_dsp_irq_handler(int irq, void *dev_id) +{ + struct avs_dev *adev = dev_id; + + return avs_hda_irq_handler(irq, &adev->base.core); +} + +static irqreturn_t avs_dsp_irq_thread(int irq, void *dev_id) +{ + struct avs_dev *adev = dev_id; + struct hdac_bus *bus = &adev->base.core; + u32 status; + + status = readl(bus->ppcap + AZX_REG_PP_PPSTS); + if (status & AZX_PPCTL_PIE) + avs_dsp_op(adev, dsp_interrupt); + + /* Unmask GIE, masked in irq_handler(). */ + snd_hdac_chip_updatel(bus, INTCTL, AZX_INT_GLOBAL_EN, AZX_INT_GLOBAL_EN); + + return IRQ_HANDLED; +} + +static int avs_hdac_acquire_irq(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct pci_dev *pci = to_pci_dev(bus->dev); + int ret; + + /* request one and check that we only got one interrupt */ + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_INTX); + if (ret != 1) { + dev_err(adev->dev, "Failed to allocate IRQ vector: %d\n", ret); + return ret; + } + + ret = pci_request_irq(pci, 0, avs_hda_irq_handler, avs_hda_irq_thread, bus, + KBUILD_MODNAME); + if (ret < 0) { + dev_err(adev->dev, "Failed to request stream IRQ handler: %d\n", ret); + goto free_vector; + } + + ret = pci_request_irq(pci, 0, avs_dsp_irq_handler, avs_dsp_irq_thread, adev, + KBUILD_MODNAME); + if (ret < 0) { + dev_err(adev->dev, "Failed to request IPC IRQ handler: %d\n", ret); + goto free_stream_irq; + } + + return 0; + +free_stream_irq: + pci_free_irq(pci, 0, bus); +free_vector: + pci_free_irq_vectors(pci); + return ret; +} + +static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct pci_device_id *id) +{ + struct hda_bus *bus = &adev->base; + struct avs_ipc *ipc; + struct device *dev = &pci->dev; + int ret; + + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; + + bus->core.use_posbuf = 1; + bus->core.bdl_pos_adj = 0; + bus->core.sync_write = 1; + bus->pci = pci; + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + + ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + ret = avs_ipc_init(ipc, dev); + if (ret < 0) + return ret; + + adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!adev->modcfg_buf) + return -ENOMEM; + + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; + adev->hw_cfg.dsp_cores = hweight_long(AVS_MAIN_CORE_MASK); + INIT_WORK(&adev->probe_work, avs_hda_probe_work); + INIT_LIST_HEAD(&adev->comp_list); + INIT_LIST_HEAD(&adev->path_list); + INIT_LIST_HEAD(&adev->fw_list); + init_completion(&adev->fw_ready); + spin_lock_init(&adev->path_list_lock); + mutex_init(&adev->modres_mutex); + mutex_init(&adev->comp_list_mutex); + mutex_init(&adev->path_mutex); + + return 0; +} + +static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct hdac_bus *bus; + struct avs_dev *adev; + struct device *dev = &pci->dev; + int ret; + + ret = snd_intel_dsp_driver_probe(pci); + switch (ret) { + case SND_INTEL_DSP_DRIVER_ANY: + case SND_INTEL_DSP_DRIVER_SST: + case SND_INTEL_DSP_DRIVER_AVS: + break; + default: + return -ENODEV; + } + + ret = pcim_enable_device(pci); + if (ret < 0) + return ret; + + adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + bus = &adev->base.core; + + ret = avs_bus_init(adev, pci, id); + if (ret < 0) { + dev_err(dev, "failed to init avs bus: %d\n", ret); + return ret; + } + + ret = pcim_request_all_regions(pci, "AVS HDAudio"); + if (ret < 0) + return ret; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); + return -ENXIO; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); + if (!adev->dsp_ba) { + dev_err(bus->dev, "ioremap error\n"); + ret = -ENXIO; + goto err_remap_bar4; + } + + snd_hdac_bus_parse_capabilities(bus); + if (bus->mlcap) + snd_hdac_ext_bus_get_ml_capabilities(bus); + + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(dev, UINT_MAX); + + ret = avs_hdac_bus_init_streams(bus); + if (ret < 0) { + dev_err(dev, "failed to init streams: %d\n", ret); + goto err_init_streams; + } + + ret = avs_hdac_acquire_irq(adev); + if (ret < 0) { + dev_err(bus->dev, "failed to acquire irq: %d\n", ret); + goto err_acquire_irq; + } + + pci_set_master(pci); + pci_set_drvdata(pci, bus); + device_disable_async_suspend(dev); + + ret = snd_hdac_i915_init(bus); + if (ret == -EPROBE_DEFER) + goto err_i915_init; + else if (ret < 0) + dev_info(bus->dev, "i915 init unsuccessful: %d\n", ret); + + schedule_work(&adev->probe_work); + + return 0; + +err_i915_init: + pci_free_irq(pci, 0, adev); + pci_free_irq(pci, 0, bus); + pci_free_irq_vectors(pci); + pci_clear_master(pci); + pci_set_drvdata(pci, NULL); +err_acquire_irq: + snd_hdac_bus_free_stream_pages(bus); + snd_hdac_ext_stream_free_all(bus); +err_init_streams: + iounmap(adev->dsp_ba); +err_remap_bar4: + iounmap(bus->remap_addr); + return ret; +} + +static void avs_pci_shutdown(struct pci_dev *pci) +{ + struct hdac_bus *bus = pci_get_drvdata(pci); + struct avs_dev *adev = hdac_to_avs(bus); + + cancel_work_sync(&adev->probe_work); + avs_ipc_block(adev->ipc); + + snd_hdac_stop_streams(bus); + avs_dsp_op(adev, int_control, false); + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + snd_hdac_ext_bus_link_power_down_all(bus); + + snd_hdac_bus_stop_chip(bus); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + pci_free_irq(pci, 0, adev); + pci_free_irq(pci, 0, bus); + pci_free_irq_vectors(pci); +} + +static void avs_pci_remove(struct pci_dev *pci) +{ + struct hdac_device *hdev, *save; + struct hdac_bus *bus = pci_get_drvdata(pci); + struct avs_dev *adev = hdac_to_avs(bus); + + cancel_work_sync(&adev->probe_work); + avs_ipc_block(adev->ipc); + + avs_unregister_all_boards(adev); + + acpi_nhlt_put_gbl_table(); + avs_debugfs_exit(adev); + + if (avs_platattr_test(adev, CLDMA)) + hda_cldma_free(&code_loader); + + snd_hdac_stop_streams_and_chip(bus); + avs_dsp_op(adev, int_control, false); + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + + /* it is safe to remove all codecs from the system now */ + list_for_each_entry_safe(hdev, save, &bus->codec_list, list) + snd_hda_codec_unregister(hdac_to_hda_codec(hdev)); + + snd_hdac_bus_free_stream_pages(bus); + snd_hdac_ext_stream_free_all(bus); + /* reverse ml_capabilities */ + snd_hdac_ext_link_free_all(bus); + snd_hdac_ext_bus_exit(bus); + + avs_dsp_core_disable(adev, GENMASK(adev->hw_cfg.dsp_cores - 1, 0)); + snd_hdac_ext_bus_ppcap_enable(bus, false); + + /* snd_hdac_stop_streams_and_chip does that already? */ + snd_hdac_bus_stop_chip(bus); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + if (bus->audio_component) + snd_hdac_i915_exit(bus); + + avs_module_info_free(adev); + pci_free_irq(pci, 0, adev); + pci_free_irq(pci, 0, bus); + pci_free_irq_vectors(pci); + iounmap(bus->remap_addr); + iounmap(adev->dsp_ba); + + /* Firmware is not needed anymore */ + avs_release_firmwares(adev); + + /* pm_runtime_forbid() can rpm_resume() which we do not want */ + pm_runtime_disable(&pci->dev); + pm_runtime_forbid(&pci->dev); + pm_runtime_enable(&pci->dev); + pm_runtime_get_noresume(&pci->dev); +} + +static int avs_suspend_standby(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct pci_dev *pci = adev->base.pci; + + if (bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); + + snd_hdac_ext_bus_link_power_down_all(bus); + + enable_irq_wake(pci->irq); + pci_save_state(pci); + + return 0; +} + +static int avs_suspend_common(struct avs_dev *adev, bool low_power) +{ + struct hdac_bus *bus = &adev->base.core; + int ret; + + flush_work(&adev->probe_work); + if (low_power && adev->num_lp_paths) + return avs_suspend_standby(adev); + + snd_hdac_ext_bus_link_power_down_all(bus); + + ret = avs_ipc_set_dx(adev, AVS_MAIN_CORE_MASK, false); + /* + * pm_runtime is blocked on DSP failure but system-wide suspend is not. + * Do not block entire system from suspending if that's the case. + */ + if (ret && ret != -EPERM) { + dev_err(adev->dev, "set dx failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + avs_ipc_block(adev->ipc); + avs_dsp_op(adev, int_control, false); + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + + ret = avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + if (ret < 0) { + dev_err(adev->dev, "core_mask %ld disable failed: %d\n", AVS_MAIN_CORE_MASK, ret); + return ret; + } + + snd_hdac_ext_bus_ppcap_enable(bus, false); + /* disable LP SRAM retention */ + avs_hda_power_gating_enable(adev, false); + snd_hdac_bus_stop_chip(bus); + /* disable CG when putting controller to reset */ + avs_hdac_clock_gating_enable(bus, false); + snd_hdac_bus_enter_link_reset(bus); + avs_hdac_clock_gating_enable(bus, true); + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); + + return 0; +} + +static int avs_resume_standby(struct avs_dev *adev) +{ + struct hdac_bus *bus = &adev->base.core; + struct pci_dev *pci = adev->base.pci; + + pci_restore_state(pci); + disable_irq_wake(pci->irq); + + snd_hdac_ext_bus_link_power_up_all(bus); + + if (bus->cmd_dma_state) + snd_hdac_bus_init_cmd_io(bus); + + return 0; +} + +static int avs_resume_common(struct avs_dev *adev, bool low_power, bool purge) +{ + struct hdac_bus *bus = &adev->base.core; + int ret; + + if (low_power && adev->num_lp_paths) + return avs_resume_standby(adev); + + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); + avs_hdac_bus_init_chip(bus, true); + + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + + ret = avs_dsp_boot_firmware(adev, purge); + if (ret < 0) { + dev_err(adev->dev, "firmware boot failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int avs_suspend(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev), true); +} + +static int avs_resume(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), true, true); +} + +static int avs_runtime_suspend(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev), true); +} + +static int avs_runtime_resume(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), true, false); +} + +static int avs_freeze(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev), false); +} +static int avs_thaw(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), false, true); +} + +static int avs_poweroff(struct device *dev) +{ + return avs_suspend_common(to_avs_dev(dev), false); +} + +static int avs_restore(struct device *dev) +{ + return avs_resume_common(to_avs_dev(dev), false, true); +} + +static const struct dev_pm_ops avs_dev_pm = { + .suspend = avs_suspend, + .resume = avs_resume, + .freeze = avs_freeze, + .thaw = avs_thaw, + .poweroff = avs_poweroff, + .restore = avs_restore, + RUNTIME_PM_OPS(avs_runtime_suspend, avs_runtime_resume, NULL) +}; + +static const struct avs_sram_spec skl_sram_spec = { + .base_offset = SKL_ADSP_SRAM_BASE_OFFSET, + .window_size = SKL_ADSP_SRAM_WINDOW_SIZE, +}; + +static const struct avs_sram_spec apl_sram_spec = { + .base_offset = APL_ADSP_SRAM_BASE_OFFSET, + .window_size = APL_ADSP_SRAM_WINDOW_SIZE, +}; + +static const struct avs_sram_spec mtl_sram_spec = { + .base_offset = MTL_ADSP_SRAM_BASE_OFFSET, + .window_size = MTL_ADSP_SRAM_WINDOW_SIZE, +}; + +static const struct avs_hipc_spec skl_hipc_spec = { + .req_offset = SKL_ADSP_REG_HIPCI, + .req_ext_offset = SKL_ADSP_REG_HIPCIE, + .req_busy_mask = SKL_ADSP_HIPCI_BUSY, + .ack_offset = SKL_ADSP_REG_HIPCIE, + .ack_done_mask = SKL_ADSP_HIPCIE_DONE, + .rsp_offset = SKL_ADSP_REG_HIPCT, + .rsp_busy_mask = SKL_ADSP_HIPCT_BUSY, + .ctl_offset = SKL_ADSP_REG_HIPCCTL, + .sts_offset = SKL_ADSP_SRAM_BASE_OFFSET, +}; + +static const struct avs_hipc_spec apl_hipc_spec = { + .req_offset = SKL_ADSP_REG_HIPCI, + .req_ext_offset = SKL_ADSP_REG_HIPCIE, + .req_busy_mask = SKL_ADSP_HIPCI_BUSY, + .ack_offset = SKL_ADSP_REG_HIPCIE, + .ack_done_mask = SKL_ADSP_HIPCIE_DONE, + .rsp_offset = SKL_ADSP_REG_HIPCT, + .rsp_busy_mask = SKL_ADSP_HIPCT_BUSY, + .ctl_offset = SKL_ADSP_REG_HIPCCTL, + .sts_offset = APL_ADSP_SRAM_BASE_OFFSET, +}; + +static const struct avs_hipc_spec cnl_hipc_spec = { + .req_offset = CNL_ADSP_REG_HIPCIDR, + .req_ext_offset = CNL_ADSP_REG_HIPCIDD, + .req_busy_mask = CNL_ADSP_HIPCIDR_BUSY, + .ack_offset = CNL_ADSP_REG_HIPCIDA, + .ack_done_mask = CNL_ADSP_HIPCIDA_DONE, + .rsp_offset = CNL_ADSP_REG_HIPCTDR, + .rsp_busy_mask = CNL_ADSP_HIPCTDR_BUSY, + .ctl_offset = CNL_ADSP_REG_HIPCCTL, + .sts_offset = APL_ADSP_SRAM_BASE_OFFSET, +}; + +static const struct avs_hipc_spec lnl_hipc_spec = { + .req_offset = MTL_REG_HfIPCxIDR, + .req_ext_offset = MTL_REG_HfIPCxIDD, + .req_busy_mask = MTL_HfIPCxIDR_BUSY, + .ack_offset = MTL_REG_HfIPCxIDA, + .ack_done_mask = MTL_HfIPCxIDA_DONE, + .rsp_offset = MTL_REG_HfIPCxTDR, + .rsp_busy_mask = MTL_HfIPCxTDR_BUSY, + .ctl_offset = MTL_REG_HfIPCxCTL, + .sts_offset = LNL_REG_HfDFR(0), +}; + +static const struct avs_spec skl_desc = { + .name = "skl", + .min_fw_version = { 9, 21, 0, 4732 }, + .dsp_ops = &avs_skl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_CLDMA, + .sram = &skl_sram_spec, + .hipc = &skl_hipc_spec, +}; + +static const struct avs_spec apl_desc = { + .name = "apl", + .min_fw_version = { 9, 22, 1, 4323 }, + .dsp_ops = &avs_apl_dsp_ops, + .core_init_mask = 3, + .attributes = AVS_PLATATTR_IMR, + .sram = &apl_sram_spec, + .hipc = &apl_hipc_spec, +}; + +static const struct avs_spec cnl_desc = { + .name = "cnl", + .min_fw_version = { 10, 23, 0, 5314 }, + .dsp_ops = &avs_cnl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_IMR, + .sram = &apl_sram_spec, + .hipc = &cnl_hipc_spec, +}; + +static const struct avs_spec icl_desc = { + .name = "icl", + .min_fw_version = { 10, 23, 0, 5040 }, + .dsp_ops = &avs_icl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_IMR, + .sram = &apl_sram_spec, + .hipc = &cnl_hipc_spec, +}; + +static const struct avs_spec jsl_desc = { + .name = "jsl", + .min_fw_version = { 10, 26, 0, 5872 }, + .dsp_ops = &avs_icl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_IMR, + .sram = &apl_sram_spec, + .hipc = &cnl_hipc_spec, +}; + +#define AVS_TGL_BASED_SPEC(sname, min) \ +static const struct avs_spec sname##_desc = { \ + .name = #sname, \ + .min_fw_version = { 10, min, 0, 5646 }, \ + .dsp_ops = &avs_tgl_dsp_ops, \ + .core_init_mask = 1, \ + .attributes = AVS_PLATATTR_IMR, \ + .sram = &apl_sram_spec, \ + .hipc = &cnl_hipc_spec, \ +} + +AVS_TGL_BASED_SPEC(lkf, 28); +AVS_TGL_BASED_SPEC(tgl, 29); +AVS_TGL_BASED_SPEC(ehl, 30); +AVS_TGL_BASED_SPEC(adl, 35); +AVS_TGL_BASED_SPEC(adl_n, 35); + +static const struct avs_spec fcl_desc = { + .name = "fcl", + .min_fw_version = { 0 }, + .dsp_ops = &avs_ptl_dsp_ops, + .core_init_mask = 1, + .attributes = AVS_PLATATTR_IMR | AVS_PLATATTR_ACE | AVS_PLATATTR_ALTHDA, + .sram = &mtl_sram_spec, + .hipc = &lnl_hipc_spec, +}; + +static const struct pci_device_id avs_ids[] = { + { PCI_DEVICE_DATA(INTEL, HDA_SKL_LP, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_SKL, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_KBL_LP, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_KBL, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_KBL_H, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CML_S, &skl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_APL, &apl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_GML, &apl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CNL_LP, &cnl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CNL_H, &cnl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CML_LP, &cnl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CML_H, &cnl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RKL_S, &cnl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ICL_LP, &icl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ICL_N, &icl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ICL_H, &icl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_JSL_N, &jsl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_LKF, &lkf_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_TGL_LP, &tgl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_TGL_H, &tgl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_CML_R, &tgl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_EHL_0, &ehl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_EHL_3, &ehl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_S, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_P, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_PS, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_M, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_PX, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_ADL_N, &adl_n_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RPL_S, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RPL_P_0, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RPL_P_1, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RPL_M, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_RPL_PX, &adl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_FCL, &fcl_desc) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, avs_ids); + +static struct pci_driver avs_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = avs_ids, + .probe = avs_pci_probe, + .remove = avs_pci_remove, + .shutdown = avs_pci_shutdown, + .dev_groups = avs_attr_groups, + .driver = { + .pm = pm_ptr(&avs_dev_pm), + }, +}; +module_pci_driver(avs_pci_driver); + +MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); +MODULE_AUTHOR("Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>"); +MODULE_DESCRIPTION("Intel cAVS sound driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("intel/avs/skl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/apl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/cnl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/icl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/jsl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/lkf/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/tgl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/ehl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/adl/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/avs/adl_n/dsp_basefw.bin"); +MODULE_FIRMWARE("intel/fcl/dsp_basefw.bin"); diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h new file mode 100644 index 000000000000..94fe8729a5c1 --- /dev/null +++ b/sound/soc/intel/avs/debug.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2024-2025 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H +#define __SOUND_SOC_INTEL_AVS_DEBUG_H + +#include "messages.h" +#include "registers.h" + +struct avs_dev; + +#define avs_log_buffer_size(adev) \ + ((adev)->fw_cfg.trace_log_bytes / (adev)->hw_cfg.dsp_cores) + +#define avs_log_buffer_addr(adev, core) \ +({ \ + s32 __offset = avs_dsp_op(adev, log_buffer_offset, core); \ + (__offset < 0) ? NULL : \ + (avs_sram_addr(adev, AVS_DEBUG_WINDOW) + __offset); \ +}) + +static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&adev->trace_lock, flags); + ret = avs_dsp_op(adev, log_buffer_status, msg); + spin_unlock_irqrestore(&adev->trace_lock, flags); + + return ret; +} + +struct avs_apl_log_buffer_layout { + u32 read_ptr; + u32 write_ptr; + u8 buffer[]; +} __packed; +static_assert(sizeof(struct avs_apl_log_buffer_layout) == 8); + +#define avs_apl_log_payload_size(adev) \ + (avs_log_buffer_size(adev) - sizeof(struct avs_apl_log_buffer_layout)) + +#define avs_apl_log_payload_addr(addr) \ + (addr + sizeof(struct avs_apl_log_buffer_layout)) + +#ifdef CONFIG_DEBUG_FS +int avs_register_probe_component(struct avs_dev *adev, const char *name); + +#define AVS_SET_ENABLE_LOGS_OP(name) \ + .enable_logs = avs_##name##_enable_logs + +bool avs_logging_fw(struct avs_dev *adev); +void avs_dump_fw_log(struct avs_dev *adev, const void __iomem *src, unsigned int len); +void avs_dump_fw_log_wakeup(struct avs_dev *adev, const void __iomem *src, unsigned int len); + +void avs_debugfs_init(struct avs_dev *adev); +void avs_debugfs_exit(struct avs_dev *adev); + +#else +static inline int avs_register_probe_component(struct avs_dev *adev, const char *name) +{ + return -EOPNOTSUPP; +} + +#define AVS_SET_ENABLE_LOGS_OP(name) + +static inline bool avs_logging_fw(struct avs_dev *adev) +{ + return false; +} + +static inline void avs_dump_fw_log(struct avs_dev *adev, const void __iomem *src, unsigned int len) +{ +} + +static inline void avs_dump_fw_log_wakeup(struct avs_dev *adev, const void __iomem *src, + unsigned int len) +{ +} + +static inline void avs_debugfs_init(struct avs_dev *adev) { } +static inline void avs_debugfs_exit(struct avs_dev *adev) { } +#endif + +#endif diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c new file mode 100644 index 000000000000..701c247227bf --- /dev/null +++ b/sound/soc/intel/avs/debugfs.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/kfifo.h> +#include <linux/wait.h> +#include <linux/sched/signal.h> +#include <linux/string_helpers.h> +#include <sound/soc.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" + +static unsigned int __kfifo_fromio(struct kfifo *fifo, const void __iomem *src, unsigned int len) +{ + struct __kfifo *__fifo = &fifo->kfifo; + unsigned int l, off; + + len = min(len, kfifo_avail(fifo)); + off = __fifo->in & __fifo->mask; + l = min(len, kfifo_size(fifo) - off); + + memcpy_fromio(__fifo->data + off, src, l); + memcpy_fromio(__fifo->data, src + l, len - l); + /* Make sure data copied from SRAM is visible to all CPUs. */ + smp_mb(); + __fifo->in += len; + + return len; +} + +bool avs_logging_fw(struct avs_dev *adev) +{ + return kfifo_initialized(&adev->trace_fifo); +} + +void avs_dump_fw_log(struct avs_dev *adev, const void __iomem *src, unsigned int len) +{ + __kfifo_fromio(&adev->trace_fifo, src, len); +} + +void avs_dump_fw_log_wakeup(struct avs_dev *adev, const void __iomem *src, unsigned int len) +{ + avs_dump_fw_log(adev, src, len); + wake_up(&adev->trace_waitq); +} + +static ssize_t fw_regs_read(struct file *file, char __user *to, size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + char *buf; + int ret; + + buf = kzalloc(AVS_FW_REGS_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy_fromio(buf, avs_sram_addr(adev, AVS_FW_REGS_WINDOW), AVS_FW_REGS_SIZE); + + ret = simple_read_from_buffer(to, count, ppos, buf, AVS_FW_REGS_SIZE); + kfree(buf); + return ret; +} + +static const struct file_operations fw_regs_fops = { + .open = simple_open, + .read = fw_regs_read, +}; + +static ssize_t debug_window_read(struct file *file, char __user *to, size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + size_t size; + char *buf; + int ret; + + size = adev->hw_cfg.dsp_cores * AVS_WINDOW_CHUNK_SIZE; + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy_fromio(buf, avs_sram_addr(adev, AVS_DEBUG_WINDOW), size); + + ret = simple_read_from_buffer(to, count, ppos, buf, size); + kfree(buf); + return ret; +} + +static const struct file_operations debug_window_fops = { + .open = simple_open, + .read = debug_window_read, +}; + +static ssize_t probe_points_read(struct file *file, char __user *to, size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + struct avs_probe_point_desc *desc; + size_t num_desc, len = 0; + char *buf; + int i, ret; + + /* Prevent chaining, send and dump IPC value just once. */ + if (*ppos) + return 0; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = avs_ipc_probe_get_points(adev, &desc, &num_desc); + if (ret) { + ret = AVS_IPC_RET(ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) { + ret = scnprintf(buf + len, PAGE_SIZE - len, + "Id: %#010x Purpose: %d Node id: %#x\n", + desc[i].id.value, desc[i].purpose, desc[i].node_id.val); + len += ret; + } + + ret = simple_read_from_buffer(to, count, ppos, buf, len); + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t probe_points_write(struct file *file, const char __user *from, size_t count, + loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + struct avs_probe_point_desc *desc; + u32 *array, num_elems; + size_t bytes; + int ret; + + ret = parse_int_array_user(from, count, (int **)&array); + if (ret) + return ret; + + num_elems = *array; + bytes = sizeof(*array) * num_elems; + if (bytes % sizeof(*desc)) { + ret = -EINVAL; + goto exit; + } + + desc = (struct avs_probe_point_desc *)&array[1]; + ret = avs_ipc_probe_connect_points(adev, desc, bytes / sizeof(*desc)); + if (ret) + ret = AVS_IPC_RET(ret); + else + ret = count; +exit: + kfree(array); + return ret; +} + +static const struct file_operations probe_points_fops = { + .open = simple_open, + .read = probe_points_read, + .write = probe_points_write, +}; + +static ssize_t probe_points_disconnect_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + union avs_probe_point_id *id; + u32 *array, num_elems; + size_t bytes; + int ret; + + ret = parse_int_array_user(from, count, (int **)&array); + if (ret) + return ret; + + num_elems = *array; + bytes = sizeof(*array) * num_elems; + if (bytes % sizeof(*id)) { + ret = -EINVAL; + goto exit; + } + + id = (union avs_probe_point_id *)&array[1]; + ret = avs_ipc_probe_disconnect_points(adev, id, bytes / sizeof(*id)); + if (ret) + ret = AVS_IPC_RET(ret); + else + ret = count; +exit: + kfree(array); + return ret; +} + +static const struct file_operations probe_points_disconnect_fops = { + .open = simple_open, + .write = probe_points_disconnect_write, + .llseek = default_llseek, +}; + +static ssize_t strace_read(struct file *file, char __user *to, size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + struct kfifo *fifo = &adev->trace_fifo; + unsigned int copied; + + if (kfifo_is_empty(fifo)) { + DEFINE_WAIT(wait); + + prepare_to_wait(&adev->trace_waitq, &wait, TASK_INTERRUPTIBLE); + if (!signal_pending(current)) + schedule(); + finish_wait(&adev->trace_waitq, &wait); + } + + if (kfifo_to_user(fifo, to, count, &copied)) + return -EFAULT; + *ppos += copied; + return copied; +} + +static int strace_open(struct inode *inode, struct file *file) +{ + struct avs_dev *adev = inode->i_private; + int ret; + + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + + if (kfifo_initialized(&adev->trace_fifo)) + return -EBUSY; + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) + return ret; + + file->private_data = adev; + return 0; +} + +static int strace_release(struct inode *inode, struct file *file) +{ + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; + unsigned long flags, i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + + spin_lock_irqsave(&adev->trace_lock, flags); + + /* Gather any remaining logs. */ + for_each_set_bit(i, &resource_mask, num_cores) { + msg.log.core = i; + avs_dsp_op(adev, log_buffer_status, &msg); + } + + kfifo_free(&adev->trace_fifo); + + spin_unlock_irqrestore(&adev->trace_lock, flags); + + module_put(adev->dev->driver->owner); + return 0; +} + +static const struct file_operations strace_fops = { + .llseek = default_llseek, + .read = strace_read, + .open = strace_open, + .release = strace_release, +}; + +#define DISABLE_TIMERS UINT_MAX + +static int enable_logs(struct avs_dev *adev, u32 resource_mask, u32 *priorities) +{ + int ret; + + /* Logging demands D0i0 state from DSP. */ + if (!adev->logged_resources) { + pm_runtime_get_sync(adev->dev); + + ret = avs_dsp_disable_d0ix(adev); + if (ret) + goto err_d0ix; + } + + ret = avs_ipc_set_system_time(adev); + if (ret && ret != AVS_IPC_NOT_SUPPORTED) { + ret = AVS_IPC_RET(ret); + goto err_ipc; + } + + ret = avs_dsp_op(adev, enable_logs, AVS_LOG_ENABLE, adev->aging_timer_period, + adev->fifo_full_timer_period, resource_mask, priorities); + if (ret) + goto err_ipc; + + adev->logged_resources |= resource_mask; + return 0; + +err_ipc: + if (!adev->logged_resources) { + avs_dsp_enable_d0ix(adev); +err_d0ix: + pm_runtime_put_autosuspend(adev->dev); + } + + return ret; +} + +static int disable_logs(struct avs_dev *adev, u32 resource_mask) +{ + int ret; + + /* Check if there's anything to do. */ + if (!adev->logged_resources) + return 0; + + ret = avs_dsp_op(adev, enable_logs, AVS_LOG_DISABLE, DISABLE_TIMERS, DISABLE_TIMERS, + resource_mask, NULL); + + /* + * If IPC fails causing recovery, logged_resources is already zero + * so unsetting bits is still safe. + */ + adev->logged_resources &= ~resource_mask; + + /* If that's the last resource, allow for D3. */ + if (!adev->logged_resources) { + avs_dsp_enable_d0ix(adev); + pm_runtime_put_autosuspend(adev->dev); + } + + return ret; +} + +static ssize_t trace_control_read(struct file *file, char __user *to, size_t count, loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + char buf[64]; + int len; + + len = snprintf(buf, sizeof(buf), "0x%08x\n", adev->logged_resources); + + return simple_read_from_buffer(to, count, ppos, buf, len); +} + +static ssize_t trace_control_write(struct file *file, const char __user *from, size_t count, + loff_t *ppos) +{ + struct avs_dev *adev = file->private_data; + u32 *array, num_elems; + u32 resource_mask; + int ret; + + ret = parse_int_array_user(from, count, (int **)&array); + if (ret) + return ret; + + num_elems = *array; + if (!num_elems) { + ret = -EINVAL; + goto free_array; + } + + /* + * Disable if just resource mask is provided - no log priority flags. + * + * Enable input format: mask, prio1, .., prioN + * Where 'N' equals number of bits set in the 'mask'. + */ + resource_mask = array[1]; + if (num_elems == 1) { + ret = disable_logs(adev, resource_mask); + } else { + if (num_elems != (hweight_long(resource_mask) + 1)) { + ret = -EINVAL; + goto free_array; + } + + ret = enable_logs(adev, resource_mask, &array[2]); + } + + if (!ret) + ret = count; +free_array: + kfree(array); + return ret; +} + +static const struct file_operations trace_control_fops = { + .llseek = default_llseek, + .read = trace_control_read, + .write = trace_control_write, + .open = simple_open, +}; + +void avs_debugfs_init(struct avs_dev *adev) +{ + init_waitqueue_head(&adev->trace_waitq); + spin_lock_init(&adev->trace_lock); + + adev->debugfs_root = debugfs_create_dir("avs", snd_soc_debugfs_root); + + /* Initialize timer periods with recommended defaults. */ + adev->aging_timer_period = 10; + adev->fifo_full_timer_period = 10; + + debugfs_create_file("strace", 0444, adev->debugfs_root, adev, &strace_fops); + debugfs_create_file("trace_control", 0644, adev->debugfs_root, adev, &trace_control_fops); + debugfs_create_file("fw_regs", 0444, adev->debugfs_root, adev, &fw_regs_fops); + debugfs_create_file("debug_window", 0444, adev->debugfs_root, adev, &debug_window_fops); + + debugfs_create_u32("trace_aging_period", 0644, adev->debugfs_root, + &adev->aging_timer_period); + debugfs_create_u32("trace_fifo_full_period", 0644, adev->debugfs_root, + &adev->fifo_full_timer_period); + + debugfs_create_file("probe_points", 0644, adev->debugfs_root, adev, &probe_points_fops); + debugfs_create_file("probe_points_disconnect", 0200, adev->debugfs_root, adev, + &probe_points_disconnect_fops); +} + +void avs_debugfs_exit(struct avs_dev *adev) +{ + debugfs_remove_recursive(adev->debugfs_root); +} diff --git a/sound/soc/intel/avs/dsp.c b/sound/soc/intel/avs/dsp.c new file mode 100644 index 000000000000..464bd6859182 --- /dev/null +++ b/sound/soc/intel/avs/dsp.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/string_choices.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "registers.h" +#include "trace.h" + +#define AVS_ADSPCS_DELAY_US 1000 + +int avs_dsp_core_power(struct avs_dev *adev, u32 core_mask, bool power) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "power", power); + + mask = AVS_ADSPCS_SPA_MASK(core_mask); + value = power ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + /* Delay the polling to avoid false positives. */ + usleep_range(AVS_ADSPCS_DELAY_US, 2 * AVS_ADSPCS_DELAY_US); + + mask = AVS_ADSPCS_CPA_MASK(core_mask); + value = power ? mask : 0; + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) + dev_err(adev->dev, "core_mask %d power %s failed: %d\n", + core_mask, str_on_off(power), ret); + + return ret; +} + +int avs_dsp_core_reset(struct avs_dev *adev, u32 core_mask, bool reset) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "reset", reset); + + mask = AVS_ADSPCS_CRST_MASK(core_mask); + value = reset ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) + dev_err(adev->dev, "core_mask %d %s reset failed: %d\n", + core_mask, reset ? "enter" : "exit", ret); + + return ret; +} + +int avs_dsp_core_stall(struct avs_dev *adev, u32 core_mask, bool stall) +{ + u32 value, mask, reg; + int ret; + + value = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPCS); + trace_avs_dsp_core_op(value, core_mask, "stall", stall); + + mask = AVS_ADSPCS_CSTALL_MASK(core_mask); + value = stall ? mask : 0; + + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPCS, mask, value); + + ret = snd_hdac_adsp_readl_poll(adev, AVS_ADSP_REG_ADSPCS, + reg, (reg & mask) == value, + AVS_ADSPCS_INTERVAL_US, + AVS_ADSPCS_TIMEOUT_US); + if (ret) { + dev_err(adev->dev, "core_mask %d %sstall failed: %d\n", + core_mask, stall ? "" : "un", ret); + return ret; + } + + /* Give HW time to propagate the change. */ + usleep_range(AVS_ADSPCS_DELAY_US, 2 * AVS_ADSPCS_DELAY_US); + return 0; +} + +int avs_dsp_core_enable(struct avs_dev *adev, u32 core_mask) +{ + int ret; + + ret = avs_dsp_op(adev, power, core_mask, true); + if (ret) + return ret; + + ret = avs_dsp_op(adev, reset, core_mask, false); + if (ret) + return ret; + + return avs_dsp_op(adev, stall, core_mask, false); +} + +int avs_dsp_core_disable(struct avs_dev *adev, u32 core_mask) +{ + /* No error checks to allow for complete DSP shutdown. */ + avs_dsp_op(adev, stall, core_mask, true); + avs_dsp_op(adev, reset, core_mask, true); + + return avs_dsp_op(adev, power, core_mask, false); +} + +static int avs_dsp_enable(struct avs_dev *adev, u32 core_mask) +{ + u32 mask; + int ret; + + ret = avs_dsp_core_enable(adev, core_mask); + if (ret < 0) + return ret; + + mask = core_mask & ~AVS_MAIN_CORE_MASK; + if (!mask) + /* + * without main core, fw is dead anyway + * so setting D0 for it is futile. + */ + return 0; + + ret = avs_ipc_set_dx(adev, mask, true); + return AVS_IPC_RET(ret); +} + +static int avs_dsp_disable(struct avs_dev *adev, u32 core_mask) +{ + int ret; + + ret = avs_ipc_set_dx(adev, core_mask, false); + if (ret) + return AVS_IPC_RET(ret); + + return avs_dsp_core_disable(adev, core_mask); +} + +static int avs_dsp_get_core(struct avs_dev *adev, u32 core_id) +{ + u32 mask; + int ret; + + mask = BIT_MASK(core_id); + if (mask == AVS_MAIN_CORE_MASK) + /* nothing to do for main core */ + return 0; + if (core_id >= adev->hw_cfg.dsp_cores) { + ret = -EINVAL; + goto err; + } + + adev->core_refs[core_id]++; + if (adev->core_refs[core_id] == 1) { + /* + * No cores other than main-core can be running for DSP + * to achieve d0ix. Conscious SET_D0IX IPC failure is permitted, + * simply d0ix power state will no longer be attempted. + */ + ret = avs_dsp_disable_d0ix(adev); + if (ret && ret != -AVS_EIPC) + goto err_disable_d0ix; + + ret = avs_dsp_enable(adev, mask); + if (ret) + goto err_enable_dsp; + } + + return 0; + +err_enable_dsp: + avs_dsp_enable_d0ix(adev); +err_disable_d0ix: + adev->core_refs[core_id]--; +err: + dev_err(adev->dev, "get core %d failed: %d\n", core_id, ret); + return ret; +} + +static int avs_dsp_put_core(struct avs_dev *adev, u32 core_id) +{ + u32 mask; + int ret; + + mask = BIT_MASK(core_id); + if (mask == AVS_MAIN_CORE_MASK) + /* nothing to do for main core */ + return 0; + if (core_id >= adev->hw_cfg.dsp_cores) { + ret = -EINVAL; + goto err; + } + + adev->core_refs[core_id]--; + if (!adev->core_refs[core_id]) { + ret = avs_dsp_disable(adev, mask); + if (ret) + goto err; + + /* Match disable_d0ix in avs_dsp_get_core(). */ + avs_dsp_enable_d0ix(adev); + } + + return 0; +err: + dev_err(adev->dev, "put core %d failed: %d\n", core_id, ret); + return ret; +} + +int avs_dsp_init_module(struct avs_dev *adev, u16 module_id, u8 ppl_instance_id, + u8 core_id, u8 domain, void *param, u32 param_size, + u8 *instance_id) +{ + struct avs_module_entry mentry; + bool was_loaded = false; + int ret, id; + + id = avs_module_id_alloc(adev, module_id); + if (id < 0) + return id; + + ret = avs_get_module_id_entry(adev, module_id, &mentry); + if (ret) + goto err_mod_entry; + + ret = avs_dsp_get_core(adev, core_id); + if (ret) + goto err_mod_entry; + + /* Load code into memory if this is the first instance. */ + if (!id && !avs_module_entry_is_loaded(&mentry)) { + ret = avs_dsp_op(adev, transfer_mods, true, &mentry, 1); + if (ret) { + dev_err(adev->dev, "load modules failed: %d\n", ret); + goto err_mod_entry; + } + was_loaded = true; + } + + ret = avs_ipc_init_instance(adev, module_id, id, ppl_instance_id, + core_id, domain, param, param_size); + if (ret) { + ret = AVS_IPC_RET(ret); + goto err_ipc; + } + + *instance_id = id; + return 0; + +err_ipc: + if (was_loaded) + avs_dsp_op(adev, transfer_mods, false, &mentry, 1); + avs_dsp_put_core(adev, core_id); +err_mod_entry: + avs_module_id_free(adev, module_id, id); + return ret; +} + +void avs_dsp_delete_module(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_instance_id, u8 core_id) +{ + struct avs_module_entry mentry; + int ret; + + /* Modules not owned by any pipeline need to be freed explicitly. */ + if (ppl_instance_id == INVALID_PIPELINE_ID) + avs_ipc_delete_instance(adev, module_id, instance_id); + + avs_module_id_free(adev, module_id, instance_id); + + ret = avs_get_module_id_entry(adev, module_id, &mentry); + /* Unload occupied memory if this was the last instance. */ + if (!ret && mentry.type.load_type == AVS_MODULE_LOAD_TYPE_LOADABLE) { + if (avs_is_module_ida_empty(adev, module_id)) { + ret = avs_dsp_op(adev, transfer_mods, false, &mentry, 1); + if (ret) + dev_err(adev->dev, "unload modules failed: %d\n", ret); + } + } + + avs_dsp_put_core(adev, core_id); +} + +int avs_dsp_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + bool lp, u16 attributes, u8 *instance_id) +{ + struct avs_fw_cfg *fw_cfg = &adev->fw_cfg; + int ret, id; + + id = ida_alloc_max(&adev->ppl_ida, fw_cfg->max_ppl_count - 1, GFP_KERNEL); + if (id < 0) + return id; + + ret = avs_ipc_create_pipeline(adev, req_size, priority, id, lp, attributes); + if (ret) { + ida_free(&adev->ppl_ida, id); + return AVS_IPC_RET(ret); + } + + *instance_id = id; + return 0; +} + +int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id) +{ + int ret; + + ret = avs_ipc_delete_pipeline(adev, instance_id); + if (ret) + ret = AVS_IPC_RET(ret); + + ida_free(&adev->ppl_ida, instance_id); + return ret; +} diff --git a/sound/soc/intel/avs/icl.c b/sound/soc/intel/avs/icl.c new file mode 100644 index 000000000000..d655e727bebd --- /dev/null +++ b/sound/soc/intel/avs/icl.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2024 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/slab.h> +#include <sound/hdaudio.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" + +#define ICL_VS_LTRP_GB_ICCMAX 95 + +#ifdef CONFIG_DEBUG_FS +int avs_icl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities) +{ + struct avs_icl_log_state_info *info; + u32 size, num_libs = adev->fw_cfg.max_libs_count; + int i, ret; + + if (fls_long(resource_mask) > num_libs) + return -EINVAL; + size = struct_size(info, logs_priorities_mask, num_libs); + info = kzalloc(size, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->aging_timer_period = aging_period; + info->fifo_full_timer_period = fifo_full_period; + info->enable = enable; + if (enable) + for_each_set_bit(i, &resource_mask, num_libs) + info->logs_priorities_mask[i] = *priorities++; + + ret = avs_ipc_set_enable_logs(adev, (u8 *)info, size); + kfree(info); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} +#endif + +union avs_icl_memwnd2_slot_type { + u32 val; + struct { + u32 resource_id:8; + u32 type:24; + }; +} __packed; +static_assert(sizeof(union avs_icl_memwnd2_slot_type) == 4); + +struct avs_icl_memwnd2_desc { + u32 resource_id; + union avs_icl_memwnd2_slot_type slot_id; + u32 vma; +} __packed; +static_assert(sizeof(struct avs_icl_memwnd2_desc) == 12); + +#define AVS_ICL_MEMWND2_SLOTS_COUNT 15 + +struct avs_icl_memwnd2 { + union { + struct avs_icl_memwnd2_desc slot_desc[AVS_ICL_MEMWND2_SLOTS_COUNT]; + u8 rsvd[SZ_4K]; + }; + u8 slot_array[AVS_ICL_MEMWND2_SLOTS_COUNT][SZ_4K]; +} __packed; +static_assert(sizeof(struct avs_icl_memwnd2) == 65536); + +#define AVS_ICL_SLOT_UNUSED \ + ((union avs_icl_memwnd2_slot_type) { 0x00000000U }) +#define AVS_ICL_SLOT_CRITICAL_LOG \ + ((union avs_icl_memwnd2_slot_type) { 0x54524300U }) +#define AVS_ICL_SLOT_DEBUG_LOG \ + ((union avs_icl_memwnd2_slot_type) { 0x474f4c00U }) +#define AVS_ICL_SLOT_GDB_STUB \ + ((union avs_icl_memwnd2_slot_type) { 0x42444700U }) +#define AVS_ICL_SLOT_BROKEN \ + ((union avs_icl_memwnd2_slot_type) { 0x44414544U }) + +static int avs_icl_slot_offset(struct avs_dev *adev, union avs_icl_memwnd2_slot_type slot_type) +{ + struct avs_icl_memwnd2_desc desc[AVS_ICL_MEMWND2_SLOTS_COUNT]; + int i; + + memcpy_fromio(&desc, avs_sram_addr(adev, AVS_DEBUG_WINDOW), sizeof(desc)); + + for (i = 0; i < AVS_ICL_MEMWND2_SLOTS_COUNT; i++) + if (desc[i].slot_id.val == slot_type.val) + return offsetof(struct avs_icl_memwnd2, slot_array) + i * SZ_4K; + return -ENXIO; +} + +int avs_icl_log_buffer_offset(struct avs_dev *adev, u32 core) +{ + union avs_icl_memwnd2_slot_type slot_type = AVS_ICL_SLOT_DEBUG_LOG; + int ret; + + slot_type.resource_id = core; + ret = avs_icl_slot_offset(adev, slot_type); + if (ret < 0) + dev_dbg(adev->dev, "No slot offset found for: %x\n", + slot_type.val); + + return ret; +} + +bool avs_icl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake) +{ + /* Full-power when starting DMA engines. */ + if (tx->glb.set_ppl_state.state == AVS_PPL_STATE_RUNNING) + return true; + + /* Payload-less IPCs do not take part in d0ix toggling. */ + return tx->size; +} + +int avs_icl_set_d0ix(struct avs_dev *adev, bool enable) +{ + int ret; + + ret = avs_ipc_set_d0ix(adev, enable, false); + return AVS_IPC_RET(ret); +} + +int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw) +{ + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_stream *host_stream; + struct snd_pcm_substream substream; + struct snd_dma_buffer dmab; + unsigned int sd_fmt; + u8 ltrp_gb; + int ret; + + /* + * ICCMAX: + * + * For ICL+ platforms, as per HW recommendation LTRP_GB is set to 95us + * during FW load. Its original value shall be restored once load completes. + * + * To avoid DMI/OPIO L1 entry during the load procedure, additional CAPTURE + * stream is allocated and set to run. + */ + + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + + host_stream = snd_hdac_ext_stream_assign(bus, &substream, HDAC_EXT_STREAM_TYPE_HOST); + if (!host_stream) + return -EBUSY; + + ltrp_gb = snd_hdac_chip_readb(bus, VS_LTRP) & AZX_REG_VS_LTRP_GB_MASK; + /* Carries no real data, use default format. */ + sd_fmt = snd_hdac_stream_format(1, 32, 48000); + + ret = snd_hdac_dsp_prepare(hdac_stream(host_stream), sd_fmt, fw->size, &dmab); + if (ret < 0) + goto release_stream; + + snd_hdac_chip_updateb(bus, VS_LTRP, AZX_REG_VS_LTRP_GB_MASK, ICL_VS_LTRP_GB_ICCMAX); + + spin_lock(&bus->reg_lock); + snd_hdac_stream_start(hdac_stream(host_stream)); + spin_unlock(&bus->reg_lock); + + ret = avs_hda_load_basefw(adev, fw); + + spin_lock(&bus->reg_lock); + snd_hdac_stream_stop(hdac_stream(host_stream)); + spin_unlock(&bus->reg_lock); + + snd_hdac_dsp_cleanup(hdac_stream(host_stream), &dmab); + +release_stream: + snd_hdac_ext_stream_release(host_stream, HDAC_EXT_STREAM_TYPE_HOST); + snd_hdac_chip_updateb(bus, VS_LTRP, AZX_REG_VS_LTRP_GB_MASK, ltrp_gb); + + return ret; +} + +const struct avs_dsp_ops avs_icl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .dsp_interrupt = avs_cnl_dsp_interrupt, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_icl_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .log_buffer_offset = avs_icl_log_buffer_offset, + .log_buffer_status = avs_apl_log_buffer_status, + .coredump = avs_apl_coredump, + .d0ix_toggle = avs_icl_d0ix_toggle, + .set_d0ix = avs_icl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(icl) +}; diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c new file mode 100644 index 000000000000..c0feb9edd7f6 --- /dev/null +++ b/sound/soc/intel/avs/ipc.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" +#include "registers.h" +#include "trace.h" + +#define AVS_IPC_TIMEOUT_MS 300 +#define AVS_D0IX_DELAY_MS 300 + +static int +avs_dsp_set_d0ix(struct avs_dev *adev, bool enable) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + /* Is transition required? */ + if (ipc->in_d0ix == enable) + return 0; + + ret = avs_dsp_op(adev, set_d0ix, enable); + if (ret) { + /* Prevent further d0ix attempts on conscious IPC failure. */ + if (ret == -AVS_EIPC) + atomic_inc(&ipc->d0ix_disable_depth); + + ipc->in_d0ix = false; + return ret; + } + + ipc->in_d0ix = enable; + return 0; +} + +static void avs_dsp_schedule_d0ix(struct avs_dev *adev, struct avs_ipc_msg *tx) +{ + if (atomic_read(&adev->ipc->d0ix_disable_depth)) + return; + + mod_delayed_work(system_power_efficient_wq, &adev->ipc->d0ix_work, + msecs_to_jiffies(AVS_D0IX_DELAY_MS)); +} + +static void avs_dsp_d0ix_work(struct work_struct *work) +{ + struct avs_ipc *ipc = container_of(work, struct avs_ipc, d0ix_work.work); + + avs_dsp_set_d0ix(to_avs_dev(ipc->dev), true); +} + +static int avs_dsp_wake_d0i0(struct avs_dev *adev, struct avs_ipc_msg *tx) +{ + struct avs_ipc *ipc = adev->ipc; + + if (!atomic_read(&ipc->d0ix_disable_depth)) { + cancel_delayed_work_sync(&ipc->d0ix_work); + return avs_dsp_set_d0ix(adev, false); + } + + return 0; +} + +int avs_dsp_disable_d0ix(struct avs_dev *adev) +{ + struct avs_ipc *ipc = adev->ipc; + + /* Prevent PG only on the first disable. */ + if (atomic_inc_return(&ipc->d0ix_disable_depth) == 1) { + cancel_delayed_work_sync(&ipc->d0ix_work); + return avs_dsp_set_d0ix(adev, false); + } + + return 0; +} + +int avs_dsp_enable_d0ix(struct avs_dev *adev) +{ + struct avs_ipc *ipc = adev->ipc; + + if (atomic_dec_and_test(&ipc->d0ix_disable_depth)) + queue_delayed_work(system_power_efficient_wq, &ipc->d0ix_work, + msecs_to_jiffies(AVS_D0IX_DELAY_MS)); + return 0; +} + +static void avs_dsp_recovery(struct avs_dev *adev) +{ + struct avs_soc_component *acomp; + unsigned int core_mask; + int ret; + + mutex_lock(&adev->comp_list_mutex); + /* disconnect all running streams */ + list_for_each_entry(acomp, &adev->comp_list, node) { + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_card *card; + + card = acomp->base.card; + if (!card) + continue; + + for_each_card_rtds(card, rtd) { + struct snd_pcm *pcm; + int dir; + + pcm = rtd->pcm; + if (!pcm || rtd->dai_link->no_pcm) + continue; + + for_each_pcm_streams(dir) { + struct snd_pcm_substream *substream; + + substream = pcm->streams[dir].substream; + if (!substream || !substream->runtime) + continue; + + /* No need for _irq() as we are in nonatomic context. */ + snd_pcm_stream_lock(substream); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + snd_pcm_stream_unlock(substream); + } + } + } + mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); + avs_dsp_core_disable(adev, core_mask); + + /* attempt dsp reboot */ + ret = avs_dsp_boot_firmware(adev, true); + if (ret < 0) + dev_err(adev->dev, "dsp reboot failed: %d\n", ret); + + pm_runtime_enable(adev->dev); + pm_request_autosuspend(adev->dev); + + atomic_set(&adev->ipc->recovering, 0); +} + +static void avs_dsp_recovery_work(struct work_struct *work) +{ + struct avs_ipc *ipc = container_of(work, struct avs_ipc, recovery_work); + + avs_dsp_recovery(to_avs_dev(ipc->dev)); +} + +static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg *msg) +{ + struct avs_ipc *ipc = adev->ipc; + + /* Account for the double-exception case. */ + ipc->ready = false; + + if (!atomic_add_unless(&ipc->recovering, 1, 1)) { + dev_err(adev->dev, "dsp recovery is already in progress\n"); + return; + } + + dev_crit(adev->dev, "communication severed, rebooting dsp..\n"); + + /* Avoid deadlock as the exception may be the response to SET_D0IX. */ + if (current_work() != &ipc->d0ix_work.work) + cancel_delayed_work_sync(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); + + /* Process received notification. */ + avs_dsp_op(adev, coredump, msg); + + schedule_work(&ipc->recovery_work); +} + +static void avs_dsp_receive_rx(struct avs_dev *adev, u64 header) +{ + struct avs_ipc *ipc = adev->ipc; + union avs_reply_msg msg = AVS_MSG(header); + u32 sts, lec; + + sts = snd_hdac_adsp_readl(adev, AVS_FW_REG_STATUS(adev)); + lec = snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev)); + trace_avs_ipc_reply_msg(header, sts, lec); + + ipc->rx.header = header; + /* Abort copying payload if request processing was unsuccessful. */ + if (!msg.status) { + /* update size in case of LARGE_CONFIG_GET */ + if (msg.msg_target == AVS_MOD_MSG && + msg.global_msg_type == AVS_MOD_LARGE_CONFIG_GET) + ipc->rx.size = min_t(u32, AVS_MAILBOX_SIZE, + msg.ext.large_config.data_off_size); + + memcpy_fromio(ipc->rx.data, avs_uplink_addr(adev), ipc->rx.size); + trace_avs_msg_payload(ipc->rx.data, ipc->rx.size); + } +} + +static void avs_dsp_process_notification(struct avs_dev *adev, u64 header) +{ + struct avs_notify_mod_data mod_data; + union avs_notify_msg msg = AVS_MSG(header); + size_t data_size = 0; + void *data = NULL; + u32 sts, lec; + + sts = snd_hdac_adsp_readl(adev, AVS_FW_REG_STATUS(adev)); + lec = snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev)); + trace_avs_ipc_notify_msg(header, sts, lec); + + /* Ignore spurious notifications until handshake is established. */ + if (!adev->ipc->ready && msg.notify_msg_type != AVS_NOTIFY_FW_READY) { + dev_dbg(adev->dev, "FW not ready, skip notification: 0x%08x\n", msg.primary); + return; + } + + /* Calculate notification payload size. */ + switch (msg.notify_msg_type) { + case AVS_NOTIFY_FW_READY: + break; + + case AVS_NOTIFY_PHRASE_DETECTED: + data_size = sizeof(struct avs_notify_voice_data); + break; + + case AVS_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct avs_notify_res_data); + break; + + case AVS_NOTIFY_LOG_BUFFER_STATUS: + case AVS_NOTIFY_EXCEPTION_CAUGHT: + break; + + case AVS_NOTIFY_MODULE_EVENT: + /* To know the total payload size, header needs to be read first. */ + memcpy_fromio(&mod_data, avs_uplink_addr(adev), sizeof(mod_data)); + data_size = sizeof(mod_data) + mod_data.data_size; + break; + + default: + dev_info(adev->dev, "unknown notification: 0x%08x\n", msg.primary); + break; + } + + if (data_size) { + data = kmalloc(data_size, GFP_KERNEL); + if (!data) + return; + + memcpy_fromio(data, avs_uplink_addr(adev), data_size); + trace_avs_msg_payload(data, data_size); + } + + /* Perform notification-specific operations. */ + switch (msg.notify_msg_type) { + case AVS_NOTIFY_FW_READY: + dev_dbg(adev->dev, "FW READY 0x%08x\n", msg.primary); + adev->ipc->ready = true; + complete(&adev->fw_ready); + break; + + case AVS_NOTIFY_LOG_BUFFER_STATUS: + avs_log_buffer_status_locked(adev, &msg); + break; + + case AVS_NOTIFY_EXCEPTION_CAUGHT: + avs_dsp_exception_caught(adev, &msg); + break; + + default: + break; + } + + kfree(data); +} + +void avs_dsp_process_response(struct avs_dev *adev, u64 header) +{ + struct avs_ipc *ipc = adev->ipc; + + /* + * Response may either be solicited - a reply for a request that has + * been sent beforehand - or unsolicited (notification). + */ + if (avs_msg_is_reply(header)) { + /* Response processing is invoked from IRQ thread. */ + spin_lock_irq(&ipc->rx_lock); + avs_dsp_receive_rx(adev, header); + ipc->rx_completed = true; + spin_unlock_irq(&ipc->rx_lock); + } else { + avs_dsp_process_notification(adev, header); + } + + complete(&ipc->busy_completion); +} + +static bool avs_ipc_is_busy(struct avs_ipc *ipc) +{ + struct avs_dev *adev = to_avs_dev(ipc->dev); + const struct avs_spec *const spec = adev->spec; + u32 hipc_rsp; + + hipc_rsp = snd_hdac_adsp_readl(adev, spec->hipc->rsp_offset); + return hipc_rsp & spec->hipc->rsp_busy_mask; +} + +static int avs_ipc_wait_busy_completion(struct avs_ipc *ipc, int timeout) +{ + u32 repeats_left = 128; /* to avoid infinite looping */ + int ret; + +again: + ret = wait_for_completion_timeout(&ipc->busy_completion, msecs_to_jiffies(timeout)); + + /* DSP could be unresponsive at this point. */ + if (!ipc->ready) + return -EPERM; + + if (!ret) { + if (!avs_ipc_is_busy(ipc)) + return -ETIMEDOUT; + /* + * Firmware did its job, either notification or reply + * has been received - now wait until it's processed. + */ + wait_for_completion_killable(&ipc->busy_completion); + } + + /* Ongoing notification's bottom-half may cause early wakeup */ + spin_lock(&ipc->rx_lock); + if (!ipc->rx_completed) { + if (repeats_left) { + /* Reply delayed due to notification. */ + repeats_left--; + reinit_completion(&ipc->busy_completion); + spin_unlock(&ipc->rx_lock); + goto again; + } + + spin_unlock(&ipc->rx_lock); + return -ETIMEDOUT; + } + + spin_unlock(&ipc->rx_lock); + return 0; +} + +static void avs_ipc_msg_init(struct avs_ipc *ipc, struct avs_ipc_msg *reply) +{ + lockdep_assert_held(&ipc->rx_lock); + + ipc->rx.header = 0; + ipc->rx.size = reply ? reply->size : 0; + ipc->rx_completed = false; + + reinit_completion(&ipc->done_completion); + reinit_completion(&ipc->busy_completion); +} + +static void avs_dsp_send_tx(struct avs_dev *adev, struct avs_ipc_msg *tx, bool read_fwregs) +{ + const struct avs_spec *const spec = adev->spec; + u32 sts = UINT_MAX; + u32 lec = UINT_MAX; + + tx->header |= spec->hipc->req_busy_mask; + if (read_fwregs) { + sts = snd_hdac_adsp_readl(adev, AVS_FW_REG_STATUS(adev)); + lec = snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev)); + } + + trace_avs_request(tx, sts, lec); + + if (tx->size) + memcpy_toio(avs_downlink_addr(adev), tx->data, tx->size); + snd_hdac_adsp_writel(adev, spec->hipc->req_ext_offset, tx->header >> 32); + snd_hdac_adsp_writel(adev, spec->hipc->req_offset, tx->header & UINT_MAX); +} + +static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, const char *name) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + if (!ipc->ready) + return -EPERM; + + mutex_lock(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); + spin_unlock(&ipc->rx_lock); + + ret = avs_ipc_wait_busy_completion(ipc, timeout); + if (ret) { + if (ret == -ETIMEDOUT) { + union avs_notify_msg msg = AVS_NOTIFICATION(EXCEPTION_CAUGHT); + + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } + goto exit; + } + + ret = ipc->rx.rsp.status; + /* + * If IPC channel is blocked e.g.: due to ongoing recovery, + * -EPERM error code is expected and thus it's not an actual error. + * + * Unsupported IPCs are of no harm either. + */ + if (ret == -EPERM || ret == AVS_IPC_NOT_SUPPORTED) + dev_dbg(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + else if (ret) + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + + if (reply) { + reply->header = ipc->rx.header; + reply->size = ipc->rx.size; + if (reply->data && ipc->rx.size) + memcpy(reply->data, ipc->rx.data, reply->size); + } + +exit: + mutex_unlock(&ipc->msg_mutex); + return ret; +} + +static int avs_dsp_send_msg_sequence(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0, + bool schedule_d0ix, const char *name) +{ + int ret; + + trace_avs_d0ix("wake", wake_d0i0, request->header); + if (wake_d0i0) { + ret = avs_dsp_wake_d0i0(adev, request); + if (ret) + return ret; + } + + ret = avs_dsp_do_send_msg(adev, request, reply, timeout, name); + if (ret) + return ret; + + trace_avs_d0ix("schedule", schedule_d0ix, request->header); + if (schedule_d0ix) + avs_dsp_schedule_d0ix(adev, request); + + return 0; +} + +int avs_dsp_send_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, const char *name) +{ + bool wake_d0i0 = avs_dsp_op(adev, d0ix_toggle, request, true); + bool schedule_d0ix = avs_dsp_op(adev, d0ix_toggle, request, false); + + return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, schedule_d0ix, + name); +} + +int avs_dsp_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, const char *name) +{ + return avs_dsp_send_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms, name); +} + +int avs_dsp_send_pm_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, int timeout, bool wake_d0i0, + const char *name) +{ + return avs_dsp_send_msg_sequence(adev, request, reply, timeout, wake_d0i0, false, name); +} + +int avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request, + struct avs_ipc_msg *reply, bool wake_d0i0, const char *name) +{ + return avs_dsp_send_pm_msg_timeout(adev, request, reply, adev->ipc->default_timeout_ms, + wake_d0i0, name); +} + +static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout, + const char *name) +{ + struct avs_ipc *ipc = adev->ipc; + int ret; + + mutex_lock(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); + /* + * with hw still stalled, memory windows may not be + * configured properly so avoid accessing SRAM + */ + avs_dsp_send_tx(adev, request, false); + spin_unlock(&ipc->rx_lock); + + /* ROM messages must be sent before main core is unstalled */ + ret = avs_dsp_op(adev, stall, AVS_MAIN_CORE_MASK, false); + if (!ret) { + ret = wait_for_completion_timeout(&ipc->done_completion, msecs_to_jiffies(timeout)); + ret = ret ? 0 : -ETIMEDOUT; + } + if (ret) + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + + mutex_unlock(&ipc->msg_mutex); + + return ret; +} + +int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev, struct avs_ipc_msg *request, int timeout, + const char *name) +{ + return avs_dsp_do_send_rom_msg(adev, request, timeout, name); +} + +int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request, const char *name) +{ + return avs_dsp_send_rom_msg_timeout(adev, request, adev->ipc->default_timeout_ms, name); +} + +void avs_dsp_interrupt_control(struct avs_dev *adev, bool enable) +{ + const struct avs_spec *const spec = adev->spec; + u32 value, mask; + + /* + * No particular bit setting order. All of these are required + * to have a functional SW <-> FW communication. + */ + value = enable ? AVS_ADSP_ADSPIC_IPC : 0; + snd_hdac_adsp_updatel(adev, AVS_ADSP_REG_ADSPIC, AVS_ADSP_ADSPIC_IPC, value); + + mask = AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY; + value = enable ? mask : 0; + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, mask, value); +} + +int avs_ipc_init(struct avs_ipc *ipc, struct device *dev) +{ + ipc->rx.data = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!ipc->rx.data) + return -ENOMEM; + + ipc->dev = dev; + ipc->ready = false; + ipc->default_timeout_ms = AVS_IPC_TIMEOUT_MS; + INIT_WORK(&ipc->recovery_work, avs_dsp_recovery_work); + INIT_DELAYED_WORK(&ipc->d0ix_work, avs_dsp_d0ix_work); + init_completion(&ipc->done_completion); + init_completion(&ipc->busy_completion); + spin_lock_init(&ipc->rx_lock); + mutex_init(&ipc->msg_mutex); + + return 0; +} + +void avs_ipc_block(struct avs_ipc *ipc) +{ + ipc->ready = false; + cancel_work_sync(&ipc->recovery_work); + cancel_delayed_work_sync(&ipc->d0ix_work); + ipc->in_d0ix = false; +} diff --git a/sound/soc/intel/avs/lnl.c b/sound/soc/intel/avs/lnl.c new file mode 100644 index 000000000000..4fbc62bfd6c5 --- /dev/null +++ b/sound/soc/intel/avs/lnl.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2021-2025 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "registers.h" + +int avs_lnl_core_stall(struct avs_dev *adev, u32 core_mask, bool stall) +{ + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_link *hlink; + int ret; + + ret = avs_mtl_core_stall(adev, core_mask, stall); + + /* On unstall, route interrupts from the links to the DSP firmware. */ + if (!ret && !stall) + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, AZX_ML_LCTL_OFLEN, + AZX_ML_LCTL_OFLEN); + return ret; +} diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c new file mode 100644 index 000000000000..353e343b1d28 --- /dev/null +++ b/sound/soc/intel/avs/loader.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <sound/hdaudio.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "cldma.h" +#include "messages.h" +#include "registers.h" +#include "topology.h" + +#define AVS_ROM_STS_MASK 0xFF +#define AVS_ROM_INIT_DONE 0x1 +#define SKL_ROM_BASEFW_ENTERED 0xF +#define APL_ROM_FW_ENTERED 0x5 +#define AVS_ROM_INIT_POLLING_US 5 +#define SKL_ROM_INIT_TIMEOUT_US 1000000 +#define APL_ROM_INIT_TIMEOUT_US 300000 +#define APL_ROM_INIT_RETRIES 3 + +#define AVS_FW_INIT_POLLING_US 500 +#define AVS_FW_INIT_TIMEOUT_MS 3000 +#define AVS_FW_INIT_TIMEOUT_US (AVS_FW_INIT_TIMEOUT_MS * 1000) + +#define AVS_CLDMA_START_DELAY_MS 100 + +#define AVS_ROOT_DIR "intel/avs" +#define AVS_BASEFW_FILENAME "dsp_basefw.bin" +#define AVS_EXT_MANIFEST_MAGIC 0x31454124 +#define SKL_MANIFEST_MAGIC 0x00000006 +#define SKL_ADSPFW_OFFSET 0x284 +#define APL_MANIFEST_MAGIC 0x44504324 +#define APL_ADSPFW_OFFSET 0x2000 + +/* Occasionally, engineering (release candidate) firmware is provided for testing. */ +static bool debug_ignore_fw_version; +module_param_named(ignore_fw_version, debug_ignore_fw_version, bool, 0444); +MODULE_PARM_DESC(ignore_fw_version, "Ignore firmware version check 0=no (default), 1=yes"); + +#define AVS_LIB_NAME_SIZE 8 + +struct avs_fw_manifest { + u32 id; + u32 len; + char name[AVS_LIB_NAME_SIZE]; + u32 preload_page_count; + u32 img_flags; + u32 feature_mask; + struct avs_fw_version version; +} __packed; +static_assert(sizeof(struct avs_fw_manifest) == 36); + +struct avs_fw_ext_manifest { + u32 id; + u32 len; + u16 version_major; + u16 version_minor; + u32 entries; +} __packed; +static_assert(sizeof(struct avs_fw_ext_manifest) == 16); + +static int avs_fw_ext_manifest_strip(struct firmware *fw) +{ + struct avs_fw_ext_manifest *man; + + if (fw->size < sizeof(*man)) + return -EINVAL; + + man = (struct avs_fw_ext_manifest *)fw->data; + if (man->id == AVS_EXT_MANIFEST_MAGIC) { + fw->data += man->len; + fw->size -= man->len; + } + + return 0; +} + +static int avs_fw_manifest_offset(struct firmware *fw) +{ + /* Header type found in first DWORD of fw binary. */ + u32 magic = *(u32 *)fw->data; + + switch (magic) { + case SKL_MANIFEST_MAGIC: + return SKL_ADSPFW_OFFSET; + case APL_MANIFEST_MAGIC: + return APL_ADSPFW_OFFSET; + default: + return -EINVAL; + } +} + +static int avs_fw_manifest_strip_verify(struct avs_dev *adev, struct firmware *fw, + const struct avs_fw_version *min) +{ + struct avs_fw_manifest *man; + int offset, ret; + + ret = avs_fw_ext_manifest_strip(fw); + if (ret) + return ret; + + offset = avs_fw_manifest_offset(fw); + if (offset < 0) + return offset; + + if (fw->size < offset + sizeof(*man)) + return -EINVAL; + if (!min) + return 0; + + man = (struct avs_fw_manifest *)(fw->data + offset); + if (man->version.major != min->major || + man->version.minor != min->minor || + man->version.hotfix != min->hotfix || + man->version.build < min->build) { + dev_warn(adev->dev, "bad FW version %d.%d.%d.%d, expected %d.%d.%d.%d or newer\n", + man->version.major, man->version.minor, + man->version.hotfix, man->version.build, + min->major, min->minor, min->hotfix, min->build); + + if (!debug_ignore_fw_version) + return -EINVAL; + } + + return 0; +} + +int avs_cldma_load_basefw(struct avs_dev *adev, struct firmware *fw) +{ + struct hda_cldma *cl = &code_loader; + unsigned int reg; + int ret; + + ret = avs_dsp_op(adev, power, AVS_MAIN_CORE_MASK, true); + if (ret < 0) + return ret; + + ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + return ret; + + ret = hda_cldma_reset(cl); + if (ret < 0) { + dev_err(adev->dev, "cldma reset failed: %d\n", ret); + return ret; + } + hda_cldma_setup(cl); + + ret = avs_dsp_op(adev, stall, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + return ret; + + reinit_completion(&adev->fw_ready); + avs_dsp_op(adev, int_control, true); + + /* await ROM init */ + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_INIT_DONE) == AVS_ROM_INIT_DONE, + AVS_ROM_INIT_POLLING_US, SKL_ROM_INIT_TIMEOUT_US); + if (ret < 0) { + dev_err(adev->dev, "rom init failed: %d, status: 0x%08x, lec: 0x%08x\n", + ret, reg, snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return ret; + } + + hda_cldma_set_data(cl, (void *)fw->data, fw->size); + /* transfer firmware */ + hda_cldma_transfer(cl, 0); + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_STS_MASK) == SKL_ROM_BASEFW_ENTERED, + AVS_FW_INIT_POLLING_US, AVS_FW_INIT_TIMEOUT_US); + hda_cldma_stop(cl); + if (ret < 0) { + dev_err(adev->dev, "transfer fw failed: %d, status: 0x%08x, lec: 0x%08x\n", + ret, reg, snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return ret; + } + + return 0; +} + +int avs_cldma_load_library(struct avs_dev *adev, struct firmware *lib, u32 id) +{ + struct hda_cldma *cl = &code_loader; + int ret; + + hda_cldma_set_data(cl, (void *)lib->data, lib->size); + /* transfer modules manifest */ + hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS)); + + /* DMA id ignored as there is only ever one code-loader DMA */ + ret = avs_ipc_load_library(adev, 0, id); + hda_cldma_stop(cl); + + if (ret) { + ret = AVS_IPC_RET(ret); + dev_err(adev->dev, "transfer lib %d failed: %d\n", id, ret); + } + + return ret; +} + +static int avs_cldma_load_module(struct avs_dev *adev, struct avs_module_entry *mentry) +{ + struct hda_cldma *cl = &code_loader; + const struct firmware *mod; + char *mod_name; + int ret; + + mod_name = kasprintf(GFP_KERNEL, "%s/%s/dsp_mod_%pUL.bin", AVS_ROOT_DIR, + adev->spec->name, mentry->uuid.b); + if (!mod_name) + return -ENOMEM; + + ret = avs_request_firmware(adev, &mod, mod_name); + kfree(mod_name); + if (ret < 0) + return ret; + + avs_hda_power_gating_enable(adev, false); + avs_hda_clock_gating_enable(adev, false); + avs_hda_l1sen_enable(adev, false); + + hda_cldma_set_data(cl, (void *)mod->data, mod->size); + hda_cldma_transfer(cl, msecs_to_jiffies(AVS_CLDMA_START_DELAY_MS)); + ret = avs_ipc_load_modules(adev, &mentry->module_id, 1); + hda_cldma_stop(cl); + + avs_hda_l1sen_enable(adev, true); + avs_hda_clock_gating_enable(adev, true); + avs_hda_power_gating_enable(adev, true); + + if (ret) { + dev_err(adev->dev, "load module %d failed: %d\n", mentry->module_id, ret); + avs_release_last_firmware(adev); + return AVS_IPC_RET(ret); + } + + return 0; +} + +int avs_cldma_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods) +{ + u16 *mod_ids; + int ret, i; + + /* Either load to DSP or unload them to free space. */ + if (load) { + for (i = 0; i < num_mods; i++) { + ret = avs_cldma_load_module(adev, &mods[i]); + if (ret) + return ret; + } + + return 0; + } + + mod_ids = kcalloc(num_mods, sizeof(u16), GFP_KERNEL); + if (!mod_ids) + return -ENOMEM; + + for (i = 0; i < num_mods; i++) + mod_ids[i] = mods[i].module_id; + + ret = avs_ipc_unload_modules(adev, mod_ids, num_mods); + kfree(mod_ids); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +static int +avs_hda_init_rom(struct avs_dev *adev, unsigned int dma_id, bool purge) +{ + const struct avs_spec *const spec = adev->spec; + unsigned int corex_mask, reg; + int ret; + + corex_mask = spec->core_init_mask & ~AVS_MAIN_CORE_MASK; + + ret = avs_dsp_op(adev, power, spec->core_init_mask, true); + if (ret < 0) + goto err; + + ret = avs_dsp_op(adev, reset, AVS_MAIN_CORE_MASK, false); + if (ret < 0) + goto err; + + reinit_completion(&adev->fw_ready); + avs_dsp_op(adev, int_control, true); + + /* set boot config */ + ret = avs_ipc_set_boot_config(adev, dma_id, purge); + if (ret) { + ret = AVS_IPC_RET(ret); + goto err; + } + + /* await ROM init */ + ret = snd_hdac_adsp_readl_poll(adev, spec->hipc->sts_offset, reg, + (reg & 0xF) == AVS_ROM_INIT_DONE || + (reg & 0xF) == APL_ROM_FW_ENTERED, + AVS_ROM_INIT_POLLING_US, APL_ROM_INIT_TIMEOUT_US); + if (ret < 0) { + dev_err(adev->dev, "rom init failed: %d, status: 0x%08x, lec: 0x%08x\n", + ret, reg, snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + goto err; + } + + /* power down non-main cores */ + if (corex_mask) { + ret = avs_dsp_op(adev, power, corex_mask, false); + if (ret < 0) + goto err; + } + + return 0; + +err: + avs_dsp_core_disable(adev, spec->core_init_mask); + return ret; +} + +static int avs_imr_load_basefw(struct avs_dev *adev) +{ + int ret; + + /* DMA id ignored when flashing from IMR as no transfer occurs. */ + ret = avs_hda_init_rom(adev, 0, false); + if (ret < 0) + return ret; + + ret = wait_for_completion_timeout(&adev->fw_ready, + msecs_to_jiffies(AVS_FW_INIT_TIMEOUT_MS)); + if (!ret) { + dev_err(adev->dev, "firmware ready timeout, status: 0x%08x, lec: 0x%08x\n", + snd_hdac_adsp_readl(adev, AVS_FW_REG_STATUS(adev)), + snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + return -ETIMEDOUT; + } + + return 0; +} + +int avs_hda_load_basefw(struct avs_dev *adev, struct firmware *fw) +{ + struct snd_pcm_substream substream; + struct snd_dma_buffer dmab; + struct hdac_ext_stream *estream; + struct hdac_stream *hstream; + struct hdac_bus *bus = &adev->base.core; + unsigned int sdfmt, reg; + int ret, i; + + /* configure hda dma */ + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + estream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_HOST); + if (!estream) + return -ENODEV; + hstream = hdac_stream(estream); + + /* code loading performed with default format */ + sdfmt = snd_hdac_stream_format(1, 32, 48000); + ret = snd_hdac_dsp_prepare(hstream, sdfmt, fw->size, &dmab); + if (ret < 0) + goto release_stream; + + /* enable SPIB for hda stream */ + snd_hdac_stream_spbcap_enable(bus, true, hstream->index); + ret = snd_hdac_stream_set_spib(bus, hstream, fw->size); + if (ret) + goto cleanup_resources; + + memcpy(dmab.area, fw->data, fw->size); + + for (i = 0; i < APL_ROM_INIT_RETRIES; i++) { + unsigned int dma_id = hstream->stream_tag - 1; + + ret = avs_hda_init_rom(adev, dma_id, true); + if (!ret) + break; + dev_info(adev->dev, "#%d rom init failed: %d\n", i + 1, ret); + } + if (ret < 0) + goto cleanup_resources; + + /* transfer firmware */ + snd_hdac_dsp_trigger(hstream, true); + ret = snd_hdac_adsp_readl_poll(adev, AVS_FW_REG_STATUS(adev), reg, + (reg & AVS_ROM_STS_MASK) == APL_ROM_FW_ENTERED, + AVS_FW_INIT_POLLING_US, AVS_FW_INIT_TIMEOUT_US); + snd_hdac_dsp_trigger(hstream, false); + if (ret < 0) { + dev_err(adev->dev, "transfer fw failed: %d, status: 0x%08x, lec: 0x%08x\n", + ret, reg, snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + } + +cleanup_resources: + /* disable SPIB for hda stream */ + snd_hdac_stream_spbcap_enable(bus, false, hstream->index); + snd_hdac_stream_set_spib(bus, hstream, 0); + + snd_hdac_dsp_cleanup(hstream, &dmab); +release_stream: + snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST); + + return ret; +} + +int avs_hda_load_library(struct avs_dev *adev, struct firmware *lib, u32 id) +{ + struct snd_pcm_substream substream; + struct snd_dma_buffer dmab; + struct hdac_ext_stream *estream; + struct hdac_stream *stream; + struct hdac_bus *bus = &adev->base.core; + unsigned int sdfmt; + int ret; + + /* configure hda dma */ + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + estream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_HOST); + if (!estream) + return -ENODEV; + stream = hdac_stream(estream); + + /* code loading performed with default format */ + sdfmt = snd_hdac_stream_format(1, 32, 48000); + ret = snd_hdac_dsp_prepare(stream, sdfmt, lib->size, &dmab); + if (ret < 0) + goto release_stream; + + /* enable SPIB for hda stream */ + snd_hdac_stream_spbcap_enable(bus, true, stream->index); + snd_hdac_stream_set_spib(bus, stream, lib->size); + + memcpy(dmab.area, lib->data, lib->size); + + /* transfer firmware */ + snd_hdac_dsp_trigger(stream, true); + ret = avs_ipc_load_library(adev, stream->stream_tag - 1, id); + snd_hdac_dsp_trigger(stream, false); + if (ret) { + dev_err(adev->dev, "transfer lib %d failed: %d\n", id, ret); + ret = AVS_IPC_RET(ret); + } + + /* disable SPIB for hda stream */ + snd_hdac_stream_spbcap_enable(bus, false, stream->index); + snd_hdac_stream_set_spib(bus, stream, 0); + + snd_hdac_dsp_cleanup(stream, &dmab); +release_stream: + snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST); + + return ret; +} + +int avs_hda_transfer_modules(struct avs_dev *adev, bool load, + struct avs_module_entry *mods, u32 num_mods) +{ + /* + * All platforms without CLDMA are equipped with IMR, + * and thus the module transferring is offloaded to DSP. + */ + return 0; +} + +int avs_dsp_load_libraries(struct avs_dev *adev, struct avs_tplg_library *libs, u32 num_libs) +{ + int start, id, i = 0; + int ret; + + /* Calculate the id to assign for the next lib. */ + for (id = 0; id < adev->fw_cfg.max_libs_count; id++) + if (adev->lib_names[id][0] == '\0') + break; + if (id + num_libs >= adev->fw_cfg.max_libs_count) + return -EINVAL; + + start = id; + while (i < num_libs) { + struct avs_fw_manifest *man; + const struct firmware *fw; + struct firmware stripped_fw; + char *filename; + int j; + + filename = kasprintf(GFP_KERNEL, "%s/%s/%s", AVS_ROOT_DIR, adev->spec->name, + libs[i].name); + if (!filename) + return -ENOMEM; + + /* + * If any call after this one fails, requested firmware is not released with + * avs_release_last_firmware() as failing to load code results in need for reload + * of entire driver module. And then avs_release_firmwares() is in place already. + */ + ret = avs_request_firmware(adev, &fw, filename); + kfree(filename); + if (ret < 0) + return ret; + + stripped_fw = *fw; + ret = avs_fw_manifest_strip_verify(adev, &stripped_fw, NULL); + if (ret) { + dev_err(adev->dev, "invalid library data: %d\n", ret); + return ret; + } + + ret = avs_fw_manifest_offset(&stripped_fw); + if (ret < 0) + return ret; + man = (struct avs_fw_manifest *)(stripped_fw.data + ret); + + /* Don't load anything that's already in DSP memory. */ + for (j = 0; j < id; j++) + if (!strncmp(adev->lib_names[j], man->name, AVS_LIB_NAME_SIZE)) + goto next_lib; + + ret = avs_dsp_op(adev, load_lib, &stripped_fw, id); + if (ret) + return ret; + + strscpy(adev->lib_names[id], man->name, AVS_LIB_NAME_SIZE); + id++; +next_lib: + i++; + } + + return start == id ? 1 : 0; +} + +static int avs_dsp_load_basefw(struct avs_dev *adev) +{ + const struct avs_fw_version *min_req; + const struct avs_spec *const spec = adev->spec; + const struct firmware *fw; + struct firmware stripped_fw; + char *filename; + int ret; + + filename = kasprintf(GFP_KERNEL, "%s/%s/%s", AVS_ROOT_DIR, spec->name, AVS_BASEFW_FILENAME); + if (!filename) + return -ENOMEM; + + ret = avs_request_firmware(adev, &fw, filename); + kfree(filename); + if (ret < 0) { + dev_err(adev->dev, "request firmware failed: %d\n", ret); + return ret; + } + + stripped_fw = *fw; + min_req = &adev->spec->min_fw_version; + + ret = avs_fw_manifest_strip_verify(adev, &stripped_fw, min_req); + if (ret < 0) { + dev_err(adev->dev, "invalid firmware data: %d\n", ret); + goto release_fw; + } + + ret = avs_dsp_op(adev, load_basefw, &stripped_fw); + if (ret < 0) { + dev_err(adev->dev, "basefw load failed: %d\n", ret); + goto release_fw; + } + + ret = wait_for_completion_timeout(&adev->fw_ready, + msecs_to_jiffies(AVS_FW_INIT_TIMEOUT_MS)); + if (!ret) { + dev_err(adev->dev, "firmware ready timeout, status: 0x%08x, lec: 0x%08x\n", + snd_hdac_adsp_readl(adev, AVS_FW_REG_STATUS(adev)), + snd_hdac_adsp_readl(adev, AVS_FW_REG_ERROR(adev))); + avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + ret = -ETIMEDOUT; + goto release_fw; + } + + return 0; + +release_fw: + avs_release_last_firmware(adev); + return ret; +} + +static int avs_load_firmware(struct avs_dev *adev, bool purge) +{ + struct avs_soc_component *acomp; + int ret, i; + + /* Forgo full boot if flash from IMR succeeds. */ + if (!purge && avs_platattr_test(adev, IMR)) { + ret = avs_imr_load_basefw(adev); + if (!ret) + return 0; + + dev_dbg(adev->dev, "firmware flash from imr failed: %d\n", ret); + } + + /* Full boot, clear cached data except for basefw (slot 0). */ + for (i = 1; i < adev->fw_cfg.max_libs_count; i++) + memset(adev->lib_names[i], 0, AVS_LIB_NAME_SIZE); + + avs_hda_power_gating_enable(adev, false); + avs_hda_clock_gating_enable(adev, false); + avs_hda_l1sen_enable(adev, false); + + ret = avs_dsp_load_basefw(adev); + if (ret) + goto reenable_gating; + + mutex_lock(&adev->comp_list_mutex); + list_for_each_entry(acomp, &adev->comp_list, node) { + struct avs_tplg *tplg = acomp->tplg; + + ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); + if (ret < 0) + break; + } + mutex_unlock(&adev->comp_list_mutex); + +reenable_gating: + avs_hda_l1sen_enable(adev, true); + avs_hda_clock_gating_enable(adev, true); + avs_hda_power_gating_enable(adev, true); + + if (ret < 0) + return ret; + + /* With all code loaded, refresh module information. */ + ret = avs_module_info_init(adev, true); + if (ret) { + dev_err(adev->dev, "init module info failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int avs_config_basefw(struct avs_dev *adev) +{ + int ret; + + if (adev->spec->dsp_ops->config_basefw) { + ret = avs_dsp_op(adev, config_basefw); + if (ret) + return ret; + } + + return 0; +} + +int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge) +{ + int ret; + + ret = avs_load_firmware(adev, purge); + if (ret) + return ret; + + return avs_config_basefw(adev); +} + +static int avs_dsp_alloc_resources(struct avs_dev *adev) +{ + struct hdac_ext_link *link; + int ret, i; + + ret = avs_ipc_get_hw_config(adev, &adev->hw_cfg); + if (ret) + return AVS_IPC_RET(ret); + + ret = avs_ipc_get_fw_config(adev, &adev->fw_cfg); + if (ret) + return AVS_IPC_RET(ret); + + /* If hw allows, read capabilities directly from it. */ + if (avs_platattr_test(adev, ALTHDA)) { + link = snd_hdac_ext_bus_get_hlink_by_id(&adev->base.core, + AZX_REG_ML_LEPTR_ID_INTEL_SSP); + if (link) + adev->hw_cfg.i2s_caps.ctrl_count = link->slcount; + } + + adev->core_refs = devm_kcalloc(adev->dev, adev->hw_cfg.dsp_cores, + sizeof(*adev->core_refs), GFP_KERNEL); + adev->lib_names = devm_kcalloc(adev->dev, adev->fw_cfg.max_libs_count, + sizeof(*adev->lib_names), GFP_KERNEL); + if (!adev->core_refs || !adev->lib_names) + return -ENOMEM; + + for (i = 0; i < adev->fw_cfg.max_libs_count; i++) { + adev->lib_names[i] = devm_kzalloc(adev->dev, AVS_LIB_NAME_SIZE, GFP_KERNEL); + if (!adev->lib_names[i]) + return -ENOMEM; + } + + /* basefw always occupies slot 0 */ + strscpy(adev->lib_names[0], "BASEFW", AVS_LIB_NAME_SIZE); + + ida_init(&adev->ppl_ida); + return 0; +} + +int avs_dsp_first_boot_firmware(struct avs_dev *adev) +{ + int ret; + + if (avs_platattr_test(adev, CLDMA)) { + ret = hda_cldma_init(&code_loader, &adev->base.core, + adev->dsp_ba, AVS_CL_DEFAULT_BUFFER_SIZE); + if (ret < 0) { + dev_err(adev->dev, "cldma init failed: %d\n", ret); + return ret; + } + } + + ret = avs_dsp_core_disable(adev, AVS_MAIN_CORE_MASK); + if (ret < 0) + return ret; + + ret = avs_dsp_boot_firmware(adev, true); + if (ret < 0) { + dev_err(adev->dev, "firmware boot failed: %d\n", ret); + return ret; + } + + return avs_dsp_alloc_resources(adev); +} diff --git a/sound/soc/intel/avs/messages.c b/sound/soc/intel/avs/messages.c new file mode 100644 index 000000000000..a5ba27983091 --- /dev/null +++ b/sound/soc/intel/avs/messages.c @@ -0,0 +1,904 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/slab.h> +#include "avs.h" +#include "messages.h" + +#define AVS_CL_TIMEOUT_MS 5000 + +int avs_ipc_set_boot_config(struct avs_dev *adev, u32 dma_id, u32 purge) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(ROM_CONTROL); + struct avs_ipc_msg request = {{0}}; + + msg.boot_cfg.rom_ctrl_msg_type = AVS_ROM_SET_BOOT_CONFIG; + msg.boot_cfg.dma_id = dma_id; + msg.boot_cfg.purge_request = purge; + request.header = msg.val; + + return avs_dsp_send_rom_msg(adev, &request, "set boot config"); +} + +int avs_ipc_load_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(LOAD_MULTIPLE_MODULES); + struct avs_ipc_msg request; + + msg.load_multi_mods.mod_cnt = num_mod_ids; + request.header = msg.val; + request.data = mod_ids; + request.size = sizeof(*mod_ids) * num_mod_ids; + + return avs_dsp_send_msg_timeout(adev, &request, NULL, AVS_CL_TIMEOUT_MS, + "load multiple modules"); +} + +int avs_ipc_unload_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(UNLOAD_MULTIPLE_MODULES); + struct avs_ipc_msg request; + + msg.load_multi_mods.mod_cnt = num_mod_ids; + request.header = msg.val; + request.data = mod_ids; + request.size = sizeof(*mod_ids) * num_mod_ids; + + return avs_dsp_send_msg(adev, &request, NULL, "unload multiple modules"); +} + +int avs_ipc_load_library(struct avs_dev *adev, u32 dma_id, u32 lib_id) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(LOAD_LIBRARY); + struct avs_ipc_msg request = {{0}}; + + msg.load_lib.dma_id = dma_id; + msg.load_lib.lib_id = lib_id; + request.header = msg.val; + + return avs_dsp_send_msg_timeout(adev, &request, NULL, AVS_CL_TIMEOUT_MS, "load library"); +} + +int avs_ipc_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + u8 instance_id, bool lp, u16 attributes) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(CREATE_PIPELINE); + struct avs_ipc_msg request = {{0}}; + + msg.create_ppl.ppl_mem_size = req_size; + msg.create_ppl.ppl_priority = priority; + msg.create_ppl.instance_id = instance_id; + msg.ext.create_ppl.lp = lp; + msg.ext.create_ppl.attributes = attributes; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "create pipeline"); +} + +int avs_ipc_delete_pipeline(struct avs_dev *adev, u8 instance_id) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(DELETE_PIPELINE); + struct avs_ipc_msg request = {{0}}; + + msg.ppl.instance_id = instance_id; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "delete pipeline"); +} + +int avs_ipc_set_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state state) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(SET_PIPELINE_STATE); + struct avs_ipc_msg request = {{0}}; + + msg.set_ppl_state.ppl_id = instance_id; + msg.set_ppl_state.state = state; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "set pipeline state"); +} + +int avs_ipc_get_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state *state) +{ + union avs_global_msg msg = AVS_GLOBAL_REQUEST(GET_PIPELINE_STATE); + struct avs_ipc_msg request = {{0}}; + struct avs_ipc_msg reply = {{0}}; + int ret; + + msg.get_ppl_state.ppl_id = instance_id; + request.header = msg.val; + + ret = avs_dsp_send_msg(adev, &request, &reply, "get pipeline state"); + if (!ret) + *state = reply.rsp.ext.get_ppl_state.state; + return ret; +} + +/* + * avs_ipc_init_instance - Initialize module instance + * + * @adev: Driver context + * @module_id: Module-type id + * @instance_id: Unique module instance id + * @ppl_id: Parent pipeline id + * @core_id: DSP core to allocate module on + * @domain: Processing domain (low latency or data processing) + * @param: Module-type specific configuration + * @param_size: Size of @param in bytes + * + * Argument verification, as well as pipeline state checks are done by the + * firmware. + * + * Note: @ppl_id and @core_id are independent of each other as single pipeline + * can be composed of module instances located on different DSP cores. + */ +int avs_ipc_init_instance(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_id, u8 core_id, u8 domain, + void *param, u32 param_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(INIT_INSTANCE); + struct avs_ipc_msg request; + + msg.module_id = module_id; + msg.instance_id = instance_id; + /* firmware expects size provided in dwords */ + msg.ext.init_instance.param_block_size = DIV_ROUND_UP(param_size, sizeof(u32)); + msg.ext.init_instance.ppl_instance_id = ppl_id; + msg.ext.init_instance.core_id = core_id; + msg.ext.init_instance.proc_domain = domain; + + request.header = msg.val; + request.data = param; + request.size = param_size; + + return avs_dsp_send_msg(adev, &request, NULL, "init instance"); +} + +/* + * avs_ipc_delete_instance - Delete module instance + * + * @adev: Driver context + * @module_id: Module-type id + * @instance_id: Unique module instance id + * + * Argument verification, as well as pipeline state checks are done by the + * firmware. + * + * Note: only standalone modules i.e. without a parent pipeline shall be + * deleted using this IPC message. In all other cases, pipeline owning the + * modules performs cleanup automatically when it is deleted. + */ +int avs_ipc_delete_instance(struct avs_dev *adev, u16 module_id, u8 instance_id) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(DELETE_INSTANCE); + struct avs_ipc_msg request = {{0}}; + + msg.module_id = module_id; + msg.instance_id = instance_id; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "delete instance"); +} + +/* + * avs_ipc_bind - Bind two module instances + * + * @adev: Driver context + * @module_id: Source module-type id + * @instance_id: Source module instance id + * @dst_module_id: Sink module-type id + * @dst_instance_id: Sink module instance id + * @dst_queue: Sink module pin to bind @src_queue with + * @src_queue: Source module pin to bind @dst_queue with + */ +int avs_ipc_bind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(BIND); + struct avs_ipc_msg request = {{0}}; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.bind_unbind.dst_module_id = dst_module_id; + msg.ext.bind_unbind.dst_instance_id = dst_instance_id; + msg.ext.bind_unbind.dst_queue = dst_queue; + msg.ext.bind_unbind.src_queue = src_queue; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "bind modules"); +} + +/* + * avs_ipc_unbind - Unbind two module instances + * + * @adev: Driver context + * @module_id: Source module-type id + * @instance_id: Source module instance id + * @dst_module_id: Sink module-type id + * @dst_instance_id: Sink module instance id + * @dst_queue: Sink module pin to unbind @src_queue from + * @src_queue: Source module pin to unbind @dst_queue from + */ +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(UNBIND); + struct avs_ipc_msg request = {{0}}; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.bind_unbind.dst_module_id = dst_module_id; + msg.ext.bind_unbind.dst_instance_id = dst_instance_id; + msg.ext.bind_unbind.dst_queue = dst_queue; + msg.ext.bind_unbind.src_queue = src_queue; + request.header = msg.val; + + return avs_dsp_send_msg(adev, &request, NULL, "unbind modules"); +} + +static int __avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, bool init_block, bool final_block, + u8 *request_data, size_t request_size, size_t off_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(LARGE_CONFIG_SET); + struct avs_ipc_msg request; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.large_config.data_off_size = off_size; + msg.ext.large_config.large_param_id = param_id; + msg.ext.large_config.final_block = final_block; + msg.ext.large_config.init_block = init_block; + + request.header = msg.val; + request.data = request_data; + request.size = request_size; + + return avs_dsp_send_msg(adev, &request, NULL, "large config set"); +} + +int avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, + u8 instance_id, u8 param_id, + u8 *request, size_t request_size) +{ + size_t remaining, tx_size; + bool final; + int ret; + + remaining = request_size; + tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining); + final = (tx_size == remaining); + + /* Initial request states total payload size. */ + ret = __avs_ipc_set_large_config(adev, module_id, instance_id, + param_id, 1, final, request, tx_size, + request_size); + if (ret) + return ret; + + remaining -= tx_size; + + /* Loop the rest only when payload exceeds mailbox's size. */ + while (remaining) { + size_t offset; + + offset = request_size - remaining; + tx_size = min_t(size_t, AVS_MAILBOX_SIZE, remaining); + final = (tx_size == remaining); + + ret = __avs_ipc_set_large_config(adev, module_id, instance_id, + param_id, 0, final, + request + offset, tx_size, + offset); + if (ret) + return ret; + + remaining -= tx_size; + } + + return 0; +} + +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, u8 *request_data, size_t request_size, + u8 **reply_data, size_t *reply_size) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(LARGE_CONFIG_GET); + struct avs_ipc_msg request; + struct avs_ipc_msg reply = {{0}}; + void *buf; + int ret; + + reply.data = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!reply.data) + return -ENOMEM; + + msg.module_id = module_id; + msg.instance_id = instance_id; + msg.ext.large_config.data_off_size = request_size; + msg.ext.large_config.large_param_id = param_id; + /* final_block is always 0 on request. Updated by fw on reply. */ + msg.ext.large_config.final_block = 0; + msg.ext.large_config.init_block = 1; + + request.header = msg.val; + request.data = request_data; + request.size = request_size; + reply.size = AVS_MAILBOX_SIZE; + + ret = avs_dsp_send_msg(adev, &request, &reply, "large config get"); + if (ret) { + kfree(reply.data); + return ret; + } + + buf = krealloc(reply.data, reply.size, GFP_KERNEL); + if (!buf) { + kfree(reply.data); + return -ENOMEM; + } + + *reply_data = buf; + *reply_size = reply.size; + + return 0; +} + +int avs_ipc_set_dx(struct avs_dev *adev, u32 core_mask, bool powerup) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(SET_DX); + struct avs_ipc_msg request; + struct avs_dxstate_info dx; + + dx.core_mask = core_mask; + dx.dx_mask = powerup ? core_mask : 0; + request.header = msg.val; + request.data = &dx; + request.size = sizeof(dx); + + return avs_dsp_send_pm_msg(adev, &request, NULL, true, "set dx"); +} + +/* + * avs_ipc_set_d0ix - Set power gating policy (entering D0IX substates) + * + * @enable_pg: Whether to enable or disable power gating + * @streaming: Whether a stream is running when transitioning + */ +int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming) +{ + union avs_module_msg msg = AVS_MODULE_REQUEST(SET_D0IX); + struct avs_ipc_msg request = {{0}}; + + msg.ext.set_d0ix.wake = enable_pg; + msg.ext.set_d0ix.streaming = streaming; + msg.ext.set_d0ix.prevent_pg = !enable_pg; + + request.header = msg.val; + + return avs_dsp_send_pm_msg(adev, &request, NULL, false, "set d0ix"); +} + +int avs_ipc_get_fw_config(struct avs_dev *adev, struct avs_fw_cfg *cfg) +{ + struct avs_tlv *tlv; + size_t payload_size; + size_t offset = 0; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_FIRMWARE_CONFIG, NULL, 0, + &payload, &payload_size); + if (ret) + goto err; + /* Non-zero payload expected for FIRMWARE_CONFIG. */ + if (!payload_size) { + ret = -EREMOTEIO; + goto err; + } + + while (offset < payload_size) { + tlv = (struct avs_tlv *)(payload + offset); + + switch (tlv->type) { + case AVS_FW_CFG_FW_VERSION: + memcpy(&cfg->fw_version, tlv->value, sizeof(cfg->fw_version)); + break; + + case AVS_FW_CFG_MEMORY_RECLAIMED: + cfg->memory_reclaimed = *tlv->value; + break; + + case AVS_FW_CFG_SLOW_CLOCK_FREQ_HZ: + cfg->slow_clock_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_FAST_CLOCK_FREQ_HZ: + cfg->fast_clock_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_ALH_SUPPORT_LEVEL: + cfg->alh_support = *tlv->value; + break; + + case AVS_FW_CFG_IPC_DL_MAILBOX_BYTES: + cfg->ipc_dl_mailbox_bytes = *tlv->value; + break; + + case AVS_FW_CFG_IPC_UL_MAILBOX_BYTES: + cfg->ipc_ul_mailbox_bytes = *tlv->value; + break; + + case AVS_FW_CFG_TRACE_LOG_BYTES: + cfg->trace_log_bytes = *tlv->value; + break; + + case AVS_FW_CFG_MAX_PPL_COUNT: + cfg->max_ppl_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_ASTATE_COUNT: + cfg->max_astate_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_MODULE_PIN_COUNT: + cfg->max_module_pin_count = *tlv->value; + break; + + case AVS_FW_CFG_MODULES_COUNT: + cfg->modules_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_MOD_INST_COUNT: + cfg->max_mod_inst_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_LL_TASKS_PER_PRI_COUNT: + cfg->max_ll_tasks_per_pri_count = *tlv->value; + break; + + case AVS_FW_CFG_LL_PRI_COUNT: + cfg->ll_pri_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_DP_TASKS_COUNT: + cfg->max_dp_tasks_count = *tlv->value; + break; + + case AVS_FW_CFG_MAX_LIBS_COUNT: + cfg->max_libs_count = *tlv->value; + break; + + case AVS_FW_CFG_XTAL_FREQ_HZ: + cfg->xtal_freq_hz = *tlv->value; + break; + + case AVS_FW_CFG_POWER_GATING_POLICY: + cfg->power_gating_policy = *tlv->value; + break; + + /* Known but not useful to us. */ + case AVS_FW_CFG_DMA_BUFFER_CONFIG: + case AVS_FW_CFG_SCHEDULER_CONFIG: + case AVS_FW_CFG_CLOCKS_CONFIG: + case AVS_FW_CFG_RESERVED: + break; + + default: + dev_info(adev->dev, "Unrecognized fw param: %d\n", tlv->type); + break; + } + + offset += sizeof(*tlv) + tlv->length; + } + + /* No longer needed, free it as it's owned by the get_large_config() caller. */ + kfree(payload); +err: + if (ret) + dev_err(adev->dev, "get fw cfg failed: %d\n", ret); + return ret; +} + +int avs_ipc_set_fw_config(struct avs_dev *adev, size_t num_tlvs, ...) +{ + struct avs_tlv *tlv; + void *payload; + size_t offset; + va_list args; + int ret, i; + + payload = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + va_start(args, num_tlvs); + for (offset = i = 0; i < num_tlvs && offset < AVS_MAILBOX_SIZE - sizeof(*tlv); i++) { + tlv = (struct avs_tlv *)(payload + offset); + tlv->type = va_arg(args, u32); + tlv->length = va_arg(args, u32); + + offset += sizeof(*tlv) + tlv->length; + if (offset > AVS_MAILBOX_SIZE) + break; + + memcpy(tlv->value, va_arg(args, u8*), tlv->length); + } + + if (i == num_tlvs) + ret = avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_FIRMWARE_CONFIG, payload, offset); + else + ret = -ERANGE; + + va_end(args); + kfree(payload); + if (ret) + dev_err(adev->dev, "set fw cfg failed: %d\n", ret); + return ret; +} + +int avs_ipc_get_hw_config(struct avs_dev *adev, struct avs_hw_cfg *cfg) +{ + struct avs_tlv *tlv; + size_t payload_size; + size_t size, offset = 0; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_HARDWARE_CONFIG, NULL, 0, + &payload, &payload_size); + if (ret) + goto err; + /* Non-zero payload expected for HARDWARE_CONFIG. */ + if (!payload_size) { + ret = -EREMOTEIO; + goto err; + } + + while (offset < payload_size) { + tlv = (struct avs_tlv *)(payload + offset); + + switch (tlv->type) { + case AVS_HW_CFG_AVS_VER: + cfg->avs_version = *tlv->value; + break; + + case AVS_HW_CFG_DSP_CORES: + cfg->dsp_cores = *tlv->value; + break; + + case AVS_HW_CFG_MEM_PAGE_BYTES: + cfg->mem_page_bytes = *tlv->value; + break; + + case AVS_HW_CFG_TOTAL_PHYS_MEM_PAGES: + cfg->total_phys_mem_pages = *tlv->value; + break; + + case AVS_HW_CFG_I2S_CAPS: + cfg->i2s_caps.i2s_version = tlv->value[0]; + size = tlv->value[1]; + cfg->i2s_caps.ctrl_count = size; + if (!size) + break; + + /* Multiply to get entire array size. */ + size *= sizeof(*cfg->i2s_caps.ctrl_base_addr); + cfg->i2s_caps.ctrl_base_addr = devm_kmemdup(adev->dev, + &tlv->value[2], + size, GFP_KERNEL); + if (!cfg->i2s_caps.ctrl_base_addr) { + ret = -ENOMEM; + goto exit; + } + break; + + case AVS_HW_CFG_GATEWAY_COUNT: + cfg->gateway_count = *tlv->value; + break; + + case AVS_HW_CFG_HP_EBB_COUNT: + cfg->hp_ebb_count = *tlv->value; + break; + + case AVS_HW_CFG_LP_EBB_COUNT: + cfg->lp_ebb_count = *tlv->value; + break; + + case AVS_HW_CFG_EBB_SIZE_BYTES: + cfg->ebb_size_bytes = *tlv->value; + break; + + case AVS_HW_CFG_GPDMA_CAPS: + break; + + default: + dev_info(adev->dev, "Unrecognized hw config: %d\n", tlv->type); + break; + } + + offset += sizeof(*tlv) + tlv->length; + } + +exit: + /* No longer needed, free it as it's owned by the get_large_config() caller. */ + kfree(payload); +err: + if (ret) + dev_err(adev->dev, "get hw cfg failed: %d\n", ret); + return ret; +} + +int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info) +{ + size_t payload_size; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_MODULES_INFO, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + /* Non-zero payload expected for MODULES_INFO. */ + if (!payload_size) + return -EREMOTEIO; + + *info = (struct avs_mods_info *)payload; + return 0; +} + +int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id, + u8 instance_id, u32 sink_id, + const struct avs_audio_format *src_fmt, + const struct avs_audio_format *sink_fmt) +{ + struct avs_copier_sink_format cpr_fmt; + + cpr_fmt.sink_id = sink_id; + /* Firmware expects driver to resend copier's input format. */ + cpr_fmt.src_fmt = *src_fmt; + cpr_fmt.sink_fmt = *sink_fmt; + + return avs_ipc_set_large_config(adev, module_id, instance_id, + AVS_COPIER_SET_SINK_FORMAT, + (u8 *)&cpr_fmt, sizeof(cpr_fmt)); +} + +int avs_ipc_peakvol_get_volume(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg **vols, size_t *num_vols) +{ + size_t payload_size; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, module_id, instance_id, AVS_PEAKVOL_VOLUME, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + + /* Non-zero payload expected for PEAKVOL_VOLUME. */ + if (!payload_size) + return -EREMOTEIO; + + *vols = (struct avs_volume_cfg *)payload; + *num_vols = payload_size / sizeof(**vols); + + return 0; +} + +int avs_ipc_peakvol_set_volume(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg *vol) +{ + return avs_ipc_set_large_config(adev, module_id, instance_id, AVS_PEAKVOL_VOLUME, + (u8 *)vol, sizeof(*vol)); +} + +int avs_ipc_peakvol_set_volumes(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg *vols, size_t num_vols) +{ + struct avs_tlv *tlv; + size_t offset; + size_t size; + u8 *payload; + int ret, i; + + size = num_vols * sizeof(*vols); + size += num_vols * sizeof(*tlv); + if (size > AVS_MAILBOX_SIZE) + return -EINVAL; + + payload = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + for (offset = i = 0; i < num_vols; i++) { + tlv = (struct avs_tlv *)(payload + offset); + + tlv->type = AVS_PEAKVOL_VOLUME; + tlv->length = sizeof(*vols); + memcpy(tlv->value, &vols[i], tlv->length); + + offset += sizeof(*tlv) + tlv->length; + } + + ret = avs_ipc_set_large_config(adev, module_id, instance_id, AVS_VENDOR_CONFIG, payload, + size); + kfree(payload); + return ret; +} + +int avs_ipc_peakvol_get_mute(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg **mutes, size_t *num_mutes) +{ + size_t payload_size; + u8 *payload; + int ret; + + ret = avs_ipc_get_large_config(adev, module_id, instance_id, AVS_PEAKVOL_MUTE, NULL, 0, + &payload, &payload_size); + if (ret) + return ret; + + /* Non-zero payload expected for PEAKVOL_MUTE. */ + if (!payload_size) + return -EREMOTEIO; + + *mutes = (struct avs_mute_cfg *)payload; + *num_mutes = payload_size / sizeof(**mutes); + + return 0; +} + +int avs_ipc_peakvol_set_mute(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg *mute) +{ + return avs_ipc_set_large_config(adev, module_id, instance_id, AVS_PEAKVOL_MUTE, + (u8 *)mute, sizeof(*mute)); +} + +int avs_ipc_peakvol_set_mutes(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg *mutes, size_t num_mutes) +{ + struct avs_tlv *tlv; + size_t offset; + size_t size; + u8 *payload; + int ret, i; + + size = num_mutes * sizeof(*mutes); + size += num_mutes * sizeof(*tlv); + if (size > AVS_MAILBOX_SIZE) + return -EINVAL; + + payload = kzalloc(AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + for (offset = i = 0; i < num_mutes; i++) { + tlv = (struct avs_tlv *)(payload + offset); + + tlv->type = AVS_PEAKVOL_MUTE; + tlv->length = sizeof(*mutes); + memcpy(tlv->value, &mutes[i], tlv->length); + + offset += sizeof(*tlv) + tlv->length; + } + + ret = avs_ipc_set_large_config(adev, module_id, instance_id, AVS_VENDOR_CONFIG, payload, + size); + kfree(payload); + return ret; +} + +#ifdef CONFIG_DEBUG_FS +int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size) +{ + return avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_ENABLE_LOGS, log_info, size); +} + +int avs_ipc_set_system_time(struct avs_dev *adev) +{ + struct avs_sys_time sys_time; + u64 us; + + /* firmware expects UTC time in micro seconds */ + us = ktime_to_us(ktime_get()); + sys_time.val_l = us & UINT_MAX; + sys_time.val_u = us >> 32; + + return avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID, + AVS_BASEFW_SYSTEM_TIME, (u8 *)&sys_time, sizeof(sys_time)); +} + +int avs_ipc_probe_get_dma(struct avs_dev *adev, struct avs_probe_dma **dmas, size_t *num_dmas) +{ + size_t payload_size; + u32 module_id; + u8 *payload; + int ret; + + module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + ret = avs_ipc_get_large_config(adev, module_id, AVS_PROBE_INST_ID, AVS_PROBE_INJECTION_DMA, + NULL, 0, &payload, &payload_size); + if (ret) + return ret; + + *dmas = (struct avs_probe_dma *)payload; + *num_dmas = payload_size / sizeof(**dmas); + + return 0; +} + +int avs_ipc_probe_attach_dma(struct avs_dev *adev, struct avs_probe_dma *dmas, size_t num_dmas) +{ + u32 module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + return avs_ipc_set_large_config(adev, module_id, AVS_PROBE_INST_ID, AVS_PROBE_INJECTION_DMA, + (u8 *)dmas, array_size(sizeof(*dmas), num_dmas)); +} + +int avs_ipc_probe_detach_dma(struct avs_dev *adev, union avs_connector_node_id *node_ids, + size_t num_node_ids) +{ + u32 module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + return avs_ipc_set_large_config(adev, module_id, AVS_PROBE_INST_ID, + AVS_PROBE_INJECTION_DMA_DETACH, (u8 *)node_ids, + array_size(sizeof(*node_ids), num_node_ids)); +} + +int avs_ipc_probe_get_points(struct avs_dev *adev, struct avs_probe_point_desc **descs, + size_t *num_descs) +{ + size_t payload_size; + u32 module_id; + u8 *payload; + int ret; + + module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + ret = avs_ipc_get_large_config(adev, module_id, AVS_PROBE_INST_ID, AVS_PROBE_POINTS, NULL, + 0, &payload, &payload_size); + if (ret) + return ret; + + *descs = (struct avs_probe_point_desc *)payload; + *num_descs = payload_size / sizeof(**descs); + + return 0; +} + +int avs_ipc_probe_connect_points(struct avs_dev *adev, struct avs_probe_point_desc *descs, + size_t num_descs) +{ + u32 module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + return avs_ipc_set_large_config(adev, module_id, AVS_PROBE_INST_ID, AVS_PROBE_POINTS, + (u8 *)descs, array_size(sizeof(*descs), num_descs)); +} + +int avs_ipc_probe_disconnect_points(struct avs_dev *adev, union avs_probe_point_id *ids, + size_t num_ids) +{ + u32 module_id = avs_get_module_id(adev, &AVS_PROBE_MOD_UUID); + + return avs_ipc_set_large_config(adev, module_id, AVS_PROBE_INST_ID, + AVS_PROBE_POINTS_DISCONNECT, (u8 *)ids, + array_size(sizeof(*ids), num_ids)); +} +#endif diff --git a/sound/soc/intel/avs/messages.h b/sound/soc/intel/avs/messages.h new file mode 100644 index 000000000000..55c04b0142ae --- /dev/null +++ b/sound/soc/intel/avs/messages.h @@ -0,0 +1,1035 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_MSGS_H +#define __SOUND_SOC_INTEL_AVS_MSGS_H + +#include <linux/sizes.h> + +struct avs_dev; + +#define AVS_MAILBOX_SIZE SZ_4K + +enum avs_msg_target { + AVS_FW_GEN_MSG = 0, + AVS_MOD_MSG = 1 +}; + +enum avs_msg_direction { + AVS_MSG_REQUEST = 0, + AVS_MSG_REPLY = 1 +}; + +enum avs_global_msg_type { + AVS_GLB_ROM_CONTROL = 1, + AVS_GLB_LOAD_MULTIPLE_MODULES = 15, + AVS_GLB_UNLOAD_MULTIPLE_MODULES = 16, + AVS_GLB_CREATE_PIPELINE = 17, + AVS_GLB_DELETE_PIPELINE = 18, + AVS_GLB_SET_PIPELINE_STATE = 19, + AVS_GLB_GET_PIPELINE_STATE = 20, + AVS_GLB_LOAD_LIBRARY = 24, + AVS_GLB_NOTIFICATION = 27, +}; + +union avs_global_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 rsvd:24; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + /* set boot config */ + struct { + u32 rom_ctrl_msg_type:9; + u32 dma_id:5; + u32 purge_request:1; + } boot_cfg; + /* module loading */ + struct { + u32 mod_cnt:8; + } load_multi_mods; + /* pipeline management */ + struct { + u32 ppl_mem_size:11; + u32 ppl_priority:5; + u32 instance_id:8; + } create_ppl; + struct { + u32 rsvd:16; + u32 instance_id:8; + } ppl; /* generic ppl request */ + struct { + u32 state:16; + u32 ppl_id:8; + } set_ppl_state; + struct { + u32 ppl_id:8; + } get_ppl_state; + /* library loading */ + struct { + u32 dma_id:5; + u32 rsvd:11; + u32 lib_id:4; + } load_lib; + }; + union { + u32 val; + /* pipeline management */ + struct { + u32 lp:1; /* low power flag */ + u32 rsvd:3; + u32 attributes:16; /* additional scheduling flags */ + } create_ppl; + } ext; + }; +} __packed; +static_assert(sizeof(union avs_global_msg) == 8); + +struct avs_tlv { + u32 type; + u32 length; + u32 value[]; +} __packed; +static_assert(sizeof(struct avs_tlv) == 8); + +#define avs_tlv_size(tlv) struct_size(tlv, value, (tlv)->length / 4) + +enum avs_module_msg_type { + AVS_MOD_INIT_INSTANCE = 0, + AVS_MOD_LARGE_CONFIG_GET = 3, + AVS_MOD_LARGE_CONFIG_SET = 4, + AVS_MOD_BIND = 5, + AVS_MOD_UNBIND = 6, + AVS_MOD_SET_DX = 7, + AVS_MOD_SET_D0IX = 8, + AVS_MOD_DELETE_INSTANCE = 11, +}; + +union avs_module_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 module_id:16; + u32 instance_id:8; + u32 module_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + }; + union { + u32 val; + struct { + u32 param_block_size:16; + u32 ppl_instance_id:8; + u32 core_id:4; + u32 proc_domain:1; + } init_instance; + struct { + u32 data_off_size:20; + u32 large_param_id:8; + u32 final_block:1; + u32 init_block:1; + } large_config; + struct { + u32 dst_module_id:16; + u32 dst_instance_id:8; + u32 dst_queue:3; + u32 src_queue:3; + } bind_unbind; + struct { + /* pre-IceLake */ + u32 wake:1; + u32 streaming:1; + /* IceLake and onwards */ + u32 prevent_pg:1; + u32 prevent_local_cg:1; + } set_d0ix; + } ext; + }; +} __packed; +static_assert(sizeof(union avs_module_msg) == 8); + +#define AVS_IPC_NOT_SUPPORTED 15 + +union avs_reply_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 status:24; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + }; + union { + u32 val; + /* module loading */ + struct { + u32 err_mod_id:16; + } load_multi_mods; + /* pipeline management */ + struct { + u32 state:5; + } get_ppl_state; + /* module management */ + struct { + u32 data_off_size:20; + u32 large_param_id:8; + u32 final_block:1; + u32 init_block:1; + } large_config; + } ext; + }; +} __packed; +static_assert(sizeof(union avs_reply_msg) == 8); + +enum avs_notify_msg_type { + AVS_NOTIFY_PHRASE_DETECTED = 4, + AVS_NOTIFY_RESOURCE_EVENT = 5, + AVS_NOTIFY_LOG_BUFFER_STATUS = 6, + AVS_NOTIFY_FW_READY = 8, + AVS_NOTIFY_EXCEPTION_CAUGHT = 10, + AVS_NOTIFY_MODULE_EVENT = 12, +}; + +union avs_notify_msg { + u64 val; + struct { + union { + u32 primary; + struct { + u32 rsvd:16; + u32 notify_msg_type:8; + u32 global_msg_type:5; + u32 msg_direction:1; + u32 msg_target:1; + }; + struct { + u16 rsvd:12; + u16 core:4; + } log; + }; + union { + u32 val; + struct { + u32 core_id:2; + u32 stack_dump_size:16; + } coredump; + } ext; + }; +} __packed; +static_assert(sizeof(union avs_notify_msg) == 8); + +#define AVS_MSG(hdr) { .val = hdr } + +#define AVS_GLOBAL_REQUEST(msg_type) \ +{ \ + .global_msg_type = AVS_GLB_##msg_type, \ + .msg_direction = AVS_MSG_REQUEST, \ + .msg_target = AVS_FW_GEN_MSG, \ +} + +#define AVS_MODULE_REQUEST(msg_type) \ +{ \ + .module_msg_type = AVS_MOD_##msg_type, \ + .msg_direction = AVS_MSG_REQUEST, \ + .msg_target = AVS_MOD_MSG, \ +} + +#define AVS_NOTIFICATION(msg_type) \ +{ \ + .notify_msg_type = AVS_NOTIFY_##msg_type,\ + .global_msg_type = AVS_GLB_NOTIFICATION,\ + .msg_direction = AVS_MSG_REPLY, \ + .msg_target = AVS_FW_GEN_MSG, \ +} + +#define avs_msg_is_reply(hdr) \ +({ \ + union avs_reply_msg __msg = AVS_MSG(hdr); \ + __msg.msg_direction == AVS_MSG_REPLY && \ + __msg.global_msg_type != AVS_GLB_NOTIFICATION; \ +}) + +/* Notification types */ + +struct avs_notify_voice_data { + u16 kpd_score; + u16 reserved; +} __packed; +static_assert(sizeof(struct avs_notify_voice_data) == 4); + +struct avs_notify_res_data { + u32 resource_type; + u32 resource_id; + u32 event_type; + u32 reserved; + u32 data[6]; +} __packed; +static_assert(sizeof(struct avs_notify_res_data) == 40); + +struct avs_notify_mod_data { + u32 module_instance_id; + u32 event_id; + u32 data_size; + u32 data[]; +} __packed; +static_assert(sizeof(struct avs_notify_mod_data) == 12); + +/* ROM messages */ +enum avs_rom_control_msg_type { + AVS_ROM_SET_BOOT_CONFIG = 0, +}; + +int avs_ipc_set_boot_config(struct avs_dev *adev, u32 dma_id, u32 purge); + +/* Code loading messages */ +int avs_ipc_load_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids); +int avs_ipc_unload_modules(struct avs_dev *adev, u16 *mod_ids, u32 num_mod_ids); +int avs_ipc_load_library(struct avs_dev *adev, u32 dma_id, u32 lib_id); + +/* Pipeline management messages */ +enum avs_pipeline_state { + AVS_PPL_STATE_INVALID, + AVS_PPL_STATE_UNINITIALIZED, + AVS_PPL_STATE_RESET, + AVS_PPL_STATE_PAUSED, + AVS_PPL_STATE_RUNNING, +}; + +int avs_ipc_create_pipeline(struct avs_dev *adev, u16 req_size, u8 priority, + u8 instance_id, bool lp, u16 attributes); +int avs_ipc_delete_pipeline(struct avs_dev *adev, u8 instance_id); +int avs_ipc_set_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state state); +int avs_ipc_get_pipeline_state(struct avs_dev *adev, u8 instance_id, + enum avs_pipeline_state *state); + +/* Module management messages */ +int avs_ipc_init_instance(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 ppl_id, u8 core_id, u8 domain, + void *param, u32 param_size); +int avs_ipc_delete_instance(struct avs_dev *adev, u16 module_id, u8 instance_id); +int avs_ipc_bind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue); +int avs_ipc_unbind(struct avs_dev *adev, u16 module_id, u8 instance_id, + u16 dst_module_id, u8 dst_instance_id, + u8 dst_queue, u8 src_queue); +int avs_ipc_set_large_config(struct avs_dev *adev, u16 module_id, + u8 instance_id, u8 param_id, + u8 *request, size_t request_size); +int avs_ipc_get_large_config(struct avs_dev *adev, u16 module_id, u8 instance_id, + u8 param_id, u8 *request_data, size_t request_size, + u8 **reply_data, size_t *reply_size); + +/* DSP cores and domains power management messages */ +struct avs_dxstate_info { + u32 core_mask; /* which cores are subject for power transition */ + u32 dx_mask; /* bit[n]=1 core n goes to D0, bit[n]=0 it goes to D3 */ +} __packed; +static_assert(sizeof(struct avs_dxstate_info) == 8); + +int avs_ipc_set_dx(struct avs_dev *adev, u32 core_mask, bool powerup); +int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming); + +/* Base-firmware runtime parameters */ + +#define AVS_BASEFW_MOD_ID 0 +#define AVS_BASEFW_INST_ID 0 + +enum avs_basefw_runtime_param { + AVS_BASEFW_ENABLE_LOGS = 6, + AVS_BASEFW_FIRMWARE_CONFIG = 7, + AVS_BASEFW_HARDWARE_CONFIG = 8, + AVS_BASEFW_MODULES_INFO = 9, + AVS_BASEFW_LIBRARIES_INFO = 16, + AVS_BASEFW_SYSTEM_TIME = 20, +}; + +enum avs_log_enable { + AVS_LOG_DISABLE = 0, + AVS_LOG_ENABLE = 1 +}; + +enum avs_skl_log_priority { + AVS_SKL_LOG_CRITICAL = 1, + AVS_SKL_LOG_HIGH, + AVS_SKL_LOG_MEDIUM, + AVS_SKL_LOG_LOW, + AVS_SKL_LOG_VERBOSE, +}; + +struct avs_skl_log_state { + u32 enable; + u32 min_priority; +} __packed; +static_assert(sizeof(struct avs_skl_log_state) == 8); + +struct avs_skl_log_state_info { + u32 core_mask; + struct avs_skl_log_state logs_core[]; +} __packed; +static_assert(sizeof(struct avs_skl_log_state_info) == 4); + +struct avs_apl_log_state_info { + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 core_mask; + struct avs_skl_log_state logs_core[]; +} __packed; +static_assert(sizeof(struct avs_apl_log_state_info) == 12); + +enum avs_icl_log_priority { + AVS_ICL_LOG_CRITICAL = 0, + AVS_ICL_LOG_HIGH, + AVS_ICL_LOG_MEDIUM, + AVS_ICL_LOG_LOW, + AVS_ICL_LOG_VERBOSE, +}; + +enum avs_icl_log_source { + AVS_ICL_LOG_INFRA = 0, + AVS_ICL_LOG_HAL, + AVS_ICL_LOG_MODULE, + AVS_ICL_LOG_AUDIO, + AVS_ICL_LOG_SENSING, + AVS_ICL_LOG_ULP_INFRA, +}; + +struct avs_icl_log_state_info { + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 enable; + u32 logs_priorities_mask[]; +} __packed; +static_assert(sizeof(struct avs_icl_log_state_info) == 12); + +int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size); + +struct avs_fw_version { + u16 major; + u16 minor; + u16 hotfix; + u16 build; +}; + +enum avs_fw_cfg_params { + AVS_FW_CFG_FW_VERSION = 0, + AVS_FW_CFG_MEMORY_RECLAIMED, + AVS_FW_CFG_SLOW_CLOCK_FREQ_HZ, + AVS_FW_CFG_FAST_CLOCK_FREQ_HZ, + AVS_FW_CFG_DMA_BUFFER_CONFIG, + AVS_FW_CFG_ALH_SUPPORT_LEVEL, + AVS_FW_CFG_IPC_DL_MAILBOX_BYTES, + AVS_FW_CFG_IPC_UL_MAILBOX_BYTES, + AVS_FW_CFG_TRACE_LOG_BYTES, + AVS_FW_CFG_MAX_PPL_COUNT, + AVS_FW_CFG_MAX_ASTATE_COUNT, + AVS_FW_CFG_MAX_MODULE_PIN_COUNT, + AVS_FW_CFG_MODULES_COUNT, + AVS_FW_CFG_MAX_MOD_INST_COUNT, + AVS_FW_CFG_MAX_LL_TASKS_PER_PRI_COUNT, + AVS_FW_CFG_LL_PRI_COUNT, + AVS_FW_CFG_MAX_DP_TASKS_COUNT, + AVS_FW_CFG_MAX_LIBS_COUNT, + AVS_FW_CFG_SCHEDULER_CONFIG, + AVS_FW_CFG_XTAL_FREQ_HZ, + AVS_FW_CFG_CLOCKS_CONFIG, + AVS_FW_CFG_RESERVED, + AVS_FW_CFG_POWER_GATING_POLICY, + AVS_FW_CFG_ASSERT_MODE, + AVS_FW_CFG_RESERVED2, + AVS_FW_CFG_BUS_HARDWARE_ID, +}; + +struct avs_fw_cfg { + struct avs_fw_version fw_version; + u32 memory_reclaimed; + u32 slow_clock_freq_hz; + u32 fast_clock_freq_hz; + u32 alh_support; + u32 ipc_dl_mailbox_bytes; + u32 ipc_ul_mailbox_bytes; + u32 trace_log_bytes; + u32 max_ppl_count; + u32 max_astate_count; + u32 max_module_pin_count; + u32 modules_count; + u32 max_mod_inst_count; + u32 max_ll_tasks_per_pri_count; + u32 ll_pri_count; + u32 max_dp_tasks_count; + u32 max_libs_count; + u32 xtal_freq_hz; + u32 power_gating_policy; +}; + +struct avs_bus_hwid { + u32 device; + u32 subsystem; + u8 revision; +}; + +int avs_ipc_get_fw_config(struct avs_dev *adev, struct avs_fw_cfg *cfg); +int avs_ipc_set_fw_config(struct avs_dev *adev, size_t num_tlvs, ...); + +enum avs_hw_cfg_params { + AVS_HW_CFG_AVS_VER, + AVS_HW_CFG_DSP_CORES, + AVS_HW_CFG_MEM_PAGE_BYTES, + AVS_HW_CFG_TOTAL_PHYS_MEM_PAGES, + AVS_HW_CFG_I2S_CAPS, + AVS_HW_CFG_GPDMA_CAPS, + AVS_HW_CFG_GATEWAY_COUNT, + AVS_HW_CFG_HP_EBB_COUNT, + AVS_HW_CFG_LP_EBB_COUNT, + AVS_HW_CFG_EBB_SIZE_BYTES, +}; + +enum avs_iface_version { + AVS_AVS_VER_1_5 = 0x10005, + AVS_AVS_VER_1_8 = 0x10008, +}; + +enum avs_i2s_version { + AVS_I2S_VER_15_SKYLAKE = 0x00000, + AVS_I2S_VER_15_BROXTON = 0x10000, + AVS_I2S_VER_15_BROXTON_P = 0x20000, + AVS_I2S_VER_18_KBL_CNL = 0x30000, +}; + +struct avs_i2s_caps { + u32 i2s_version; + u32 ctrl_count; + u32 *ctrl_base_addr; +}; + +struct avs_hw_cfg { + u32 avs_version; + u32 dsp_cores; + u32 mem_page_bytes; + u32 total_phys_mem_pages; + struct avs_i2s_caps i2s_caps; + u32 gateway_count; + u32 hp_ebb_count; + u32 lp_ebb_count; + u32 ebb_size_bytes; +}; + +int avs_ipc_get_hw_config(struct avs_dev *adev, struct avs_hw_cfg *cfg); + +#define AVS_MODULE_LOAD_TYPE_BUILTIN 0 +#define AVS_MODULE_LOAD_TYPE_LOADABLE 1 +#define AVS_MODULE_STATE_LOADED BIT(0) + +struct avs_module_type { + u32 load_type:4; + u32 auto_start:1; + u32 domain_ll:1; + u32 domain_dp:1; + u32 lib_code:1; + u32 rsvd:24; +} __packed; +static_assert(sizeof(struct avs_module_type) == 4); + +union avs_segment_flags { + u32 ul; + struct { + u32 contents:1; + u32 alloc:1; + u32 load:1; + u32 readonly:1; + u32 code:1; + u32 data:1; + u32 rsvd_1:2; + u32 type:4; + u32 rsvd_2:4; + u32 length:16; + }; +} __packed; +static_assert(sizeof(union avs_segment_flags) == 4); + +struct avs_segment_desc { + union avs_segment_flags flags; + u32 v_base_addr; + u32 file_offset; +} __packed; +static_assert(sizeof(struct avs_segment_desc) == 12); + +struct avs_module_entry { + u16 module_id; + u16 state_flags; + u8 name[8]; + guid_t uuid; + struct avs_module_type type; + u8 hash[32]; + u32 entry_point; + u16 cfg_offset; + u16 cfg_count; + u32 affinity_mask; + u16 instance_max_count; + u16 instance_bss_size; + struct avs_segment_desc segments[3]; +} __packed; +static_assert(sizeof(struct avs_module_entry) == 116); + +struct avs_mods_info { + u32 count; + struct avs_module_entry entries[]; +} __packed; +static_assert(sizeof(struct avs_mods_info) == 4); + +static inline bool avs_module_entry_is_loaded(struct avs_module_entry *mentry) +{ + return mentry->type.load_type == AVS_MODULE_LOAD_TYPE_BUILTIN || + mentry->state_flags & AVS_MODULE_STATE_LOADED; +} + +int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info); + +struct avs_sys_time { + u32 val_l; + u32 val_u; +} __packed; +static_assert(sizeof(struct avs_sys_time) == 8); + +int avs_ipc_set_system_time(struct avs_dev *adev); + +/* Module configuration */ + +#define AVS_MIXIN_MOD_UUID \ + GUID_INIT(0x39656EB2, 0x3B71, 0x4049, 0x8D, 0x3F, 0xF9, 0x2C, 0xD5, 0xC4, 0x3C, 0x09) + +#define AVS_MIXOUT_MOD_UUID \ + GUID_INIT(0x3C56505A, 0x24D7, 0x418F, 0xBD, 0xDC, 0xC1, 0xF5, 0xA3, 0xAC, 0x2A, 0xE0) + +#define AVS_COPIER_MOD_UUID \ + GUID_INIT(0x9BA00C83, 0xCA12, 0x4A83, 0x94, 0x3C, 0x1F, 0xA2, 0xE8, 0x2F, 0x9D, 0xDA) + +#define AVS_PEAKVOL_MOD_UUID \ + GUID_INIT(0x8A171323, 0x94A3, 0x4E1D, 0xAF, 0xE9, 0xFE, 0x5D, 0xBA, 0xa4, 0xC3, 0x93) + +#define AVS_GAIN_MOD_UUID \ + GUID_INIT(0x61BCA9A8, 0x18D0, 0x4A18, 0x8E, 0x7B, 0x26, 0x39, 0x21, 0x98, 0x04, 0xB7) + +#define AVS_KPBUFF_MOD_UUID \ + GUID_INIT(0xA8A0CB32, 0x4A77, 0x4DB1, 0x85, 0xC7, 0x53, 0xD7, 0xEE, 0x07, 0xBC, 0xE6) + +#define AVS_MICSEL_MOD_UUID \ + GUID_INIT(0x32FE92C1, 0x1E17, 0x4FC2, 0x97, 0x58, 0xC7, 0xF3, 0x54, 0x2E, 0x98, 0x0A) + +#define AVS_MUX_MOD_UUID \ + GUID_INIT(0x64CE6E35, 0x857A, 0x4878, 0xAC, 0xE8, 0xE2, 0xA2, 0xF4, 0x2e, 0x30, 0x69) + +#define AVS_UPDWMIX_MOD_UUID \ + GUID_INIT(0x42F8060C, 0x832F, 0x4DBF, 0xB2, 0x47, 0x51, 0xE9, 0x61, 0x99, 0x7b, 0x35) + +#define AVS_SRCINTC_MOD_UUID \ + GUID_INIT(0xE61BB28D, 0x149A, 0x4C1F, 0xB7, 0x09, 0x46, 0x82, 0x3E, 0xF5, 0xF5, 0xAE) + +#define AVS_PROBE_MOD_UUID \ + GUID_INIT(0x7CAD0808, 0xAB10, 0xCD23, 0xEF, 0x45, 0x12, 0xAB, 0x34, 0xCD, 0x56, 0xEF) + +#define AVS_AEC_MOD_UUID \ + GUID_INIT(0x46CB87FB, 0xD2C9, 0x4970, 0x96, 0xD2, 0x6D, 0x7E, 0x61, 0x4B, 0xB6, 0x05) + +#define AVS_ASRC_MOD_UUID \ + GUID_INIT(0x66B4402D, 0xB468, 0x42F2, 0x81, 0xA7, 0xB3, 0x71, 0x21, 0x86, 0x3D, 0xD4) + +#define AVS_INTELWOV_MOD_UUID \ + GUID_INIT(0xEC774FA9, 0x28D3, 0x424A, 0x90, 0xE4, 0x69, 0xF9, 0x84, 0xF1, 0xEE, 0xB7) + +#define AVS_WOVHOSTM_MOD_UUID \ + GUID_INIT(0xF9ED62B7, 0x092E, 0x4A90, 0x8F, 0x4D, 0x82, 0xDA, 0xA8, 0xB3, 0x8F, 0x3B) + +/* channel map */ +enum avs_channel_index { + AVS_CHANNEL_LEFT = 0, + AVS_CHANNEL_RIGHT = 1, + AVS_CHANNEL_CENTER = 2, + AVS_CHANNEL_LEFT_SURROUND = 3, + AVS_CHANNEL_CENTER_SURROUND = 3, + AVS_CHANNEL_RIGHT_SURROUND = 4, + AVS_CHANNEL_LFE = 7, + AVS_CHANNEL_INVALID = 0xF, +}; + +enum avs_channel_config { + AVS_CHANNEL_CONFIG_MONO = 0, + AVS_CHANNEL_CONFIG_STEREO = 1, + AVS_CHANNEL_CONFIG_2_1 = 2, + AVS_CHANNEL_CONFIG_3_0 = 3, + AVS_CHANNEL_CONFIG_3_1 = 4, + AVS_CHANNEL_CONFIG_QUATRO = 5, + AVS_CHANNEL_CONFIG_4_0 = 6, + AVS_CHANNEL_CONFIG_5_0 = 7, + AVS_CHANNEL_CONFIG_5_1 = 8, + AVS_CHANNEL_CONFIG_DUAL_MONO = 9, + AVS_CHANNEL_CONFIG_I2S_DUAL_STEREO_0 = 10, + AVS_CHANNEL_CONFIG_I2S_DUAL_STEREO_1 = 11, + AVS_CHANNEL_CONFIG_7_1 = 12, + AVS_CHANNEL_CONFIG_INVALID +}; + +enum avs_interleaving { + AVS_INTERLEAVING_PER_CHANNEL = 0, + AVS_INTERLEAVING_PER_SAMPLE = 1, +}; + +enum avs_sample_type { + AVS_SAMPLE_TYPE_INT_MSB = 0, + AVS_SAMPLE_TYPE_INT_LSB = 1, + AVS_SAMPLE_TYPE_INT_SIGNED = 2, + AVS_SAMPLE_TYPE_INT_UNSIGNED = 3, + AVS_SAMPLE_TYPE_FLOAT = 4, +}; + +#define AVS_COEFF_CHANNELS_MAX 8 +#define AVS_ALL_CHANNELS_MASK UINT_MAX +#define AVS_CHANNELS_MAX 16 + +struct avs_audio_format { + u32 sampling_freq; + u32 bit_depth; + u32 channel_map; + u32 channel_config; + u32 interleaving; + u32 num_channels:8; + u32 valid_bit_depth:8; + u32 sample_type:8; + u32 reserved:8; +} __packed; +static_assert(sizeof(struct avs_audio_format) == 24); + +struct avs_modcfg_base { + u32 cpc; + u32 ibs; + u32 obs; + u32 is_pages; + struct avs_audio_format audio_fmt; +} __packed; +static_assert(sizeof(struct avs_modcfg_base) == 40); + +struct avs_pin_format { + u32 pin_index; + u32 iobs; + struct avs_audio_format audio_fmt; +} __packed; +static_assert(sizeof(struct avs_pin_format) == 32); + +struct avs_modcfg_ext { + struct avs_modcfg_base base; + u16 num_input_pins; + u16 num_output_pins; + u8 reserved[12]; + /* input pin formats followed by output ones */ + struct avs_pin_format pin_fmts[]; +} __packed; +static_assert(sizeof(struct avs_modcfg_ext) == 56); + +enum avs_dma_type { + AVS_DMA_HDA_HOST_OUTPUT = 0, + AVS_DMA_HDA_HOST_INPUT = 1, + AVS_DMA_HDA_LINK_OUTPUT = 8, + AVS_DMA_HDA_LINK_INPUT = 9, + AVS_DMA_DMIC_LINK_INPUT = 11, + AVS_DMA_I2S_LINK_OUTPUT = 12, + AVS_DMA_I2S_LINK_INPUT = 13, +}; + +union avs_virtual_index { + u8 val; + struct { + u8 time_slot:4; + u8 instance:4; + } i2s; + struct { + u8 queue_id:3; + u8 time_slot:2; + u8 instance:3; + } dmic; +} __packed; +static_assert(sizeof(union avs_virtual_index) == 1); + +union avs_connector_node_id { + u32 val; + struct { + u32 vindex:8; + u32 dma_type:5; + u32 rsvd:19; + }; +} __packed; +static_assert(sizeof(union avs_connector_node_id) == 4); + +#define INVALID_PIPELINE_ID 0xFF +#define INVALID_NODE_ID \ + ((union avs_connector_node_id) { UINT_MAX }) + +union avs_gtw_attributes { + u32 val; + struct { + u32 lp_buffer_alloc:1; + u32 rsvd:31; + }; +} __packed; +static_assert(sizeof(union avs_gtw_attributes) == 4); + +#define AVS_GTW_DMA_CONFIG_ID 0x1000 +#define AVS_DMA_METHOD_HDA 1 + +struct avs_dma_device_stream_channel_map { + u32 device_address; + u32 channel_map; +} __packed; +static_assert(sizeof(struct avs_dma_device_stream_channel_map) == 8); + +struct avs_dma_stream_channel_map { + u32 device_count; + struct avs_dma_device_stream_channel_map map[16]; +} __packed; +static_assert(sizeof(struct avs_dma_stream_channel_map) == 132); + +struct avs_dma_cfg { + u8 dma_method; + u8 pre_allocated; + u16 rsvd; + u32 dma_channel_id; + u32 stream_id; + struct avs_dma_stream_channel_map map; + u32 config_size; + u8 config[] __counted_by(config_size); +} __packed; +static_assert(sizeof(struct avs_dma_cfg) == 148); + +struct avs_copier_gtw_cfg { + union avs_connector_node_id node_id; + u32 dma_buffer_size; + u32 config_length; + union { + union avs_gtw_attributes attrs; + DECLARE_FLEX_ARRAY(u32, blob); + } config; +} __packed; +static_assert(sizeof(struct avs_copier_gtw_cfg) == 16); + +struct avs_copier_cfg { + struct avs_modcfg_base base; + struct avs_audio_format out_fmt; + u32 feature_mask; + struct avs_copier_gtw_cfg gtw_cfg; +} __packed; +static_assert(sizeof(struct avs_copier_cfg) == 84); + +struct avs_volume_cfg { + u32 channel_id; + u32 target_volume; + u32 curve_type; + u32 reserved; /* alignment */ + u64 curve_duration; +} __packed; +static_assert(sizeof(struct avs_volume_cfg) == 24); + +struct avs_mute_cfg { + u32 channel_id; + u32 mute; + u32 curve_type; + u32 reserved; /* alignment */ + u64 curve_duration; +} __packed; +static_assert(sizeof(struct avs_mute_cfg) == 24); + +struct avs_peakvol_cfg { + struct avs_modcfg_base base; + struct avs_volume_cfg vols[]; +} __packed; +static_assert(sizeof(struct avs_peakvol_cfg) == 40); + +struct avs_micsel_cfg { + struct avs_modcfg_base base; + struct avs_audio_format out_fmt; +} __packed; +static_assert(sizeof(struct avs_micsel_cfg) == 64); + +struct avs_mux_cfg { + struct avs_modcfg_base base; + struct avs_audio_format ref_fmt; + struct avs_audio_format out_fmt; +} __packed; +static_assert(sizeof(struct avs_mux_cfg) == 88); + +struct avs_updown_mixer_cfg { + struct avs_modcfg_base base; + u32 out_channel_config; + u32 coefficients_select; + s32 coefficients[AVS_COEFF_CHANNELS_MAX]; + u32 channel_map; +} __packed; +static_assert(sizeof(struct avs_updown_mixer_cfg) == 84); + +struct avs_src_cfg { + struct avs_modcfg_base base; + u32 out_freq; +} __packed; +static_assert(sizeof(struct avs_src_cfg) == 44); + +struct avs_probe_gtw_cfg { + union avs_connector_node_id node_id; + u32 dma_buffer_size; +} __packed; +static_assert(sizeof(struct avs_probe_gtw_cfg) == 8); + +struct avs_probe_cfg { + struct avs_modcfg_base base; + struct avs_probe_gtw_cfg gtw_cfg; +} __packed; +static_assert(sizeof(struct avs_probe_cfg) == 48); + +struct avs_aec_cfg { + struct avs_modcfg_base base; + struct avs_audio_format ref_fmt; + struct avs_audio_format out_fmt; + u32 cpc_lp_mode; +} __packed; +static_assert(sizeof(struct avs_aec_cfg) == 92); + +struct avs_asrc_cfg { + struct avs_modcfg_base base; + u32 out_freq; + u32 mode:2; + u32 rsvd2:2; + u32 disable_jitter_buffer:1; + u32 rsvd3:27; +} __packed; +static_assert(sizeof(struct avs_asrc_cfg) == 48); + +struct avs_wov_cfg { + struct avs_modcfg_base base; + u32 cpc_lp_mode; +} __packed; +static_assert(sizeof(struct avs_wov_cfg) == 44); + +struct avs_whm_cfg { + struct avs_modcfg_base base; + /* Audio format for output pin 0 */ + struct avs_audio_format ref_fmt; + struct avs_audio_format out_fmt; + u32 wake_tick_period; + struct avs_copier_gtw_cfg gtw_cfg; +} __packed; +static_assert(sizeof(struct avs_whm_cfg) == 108); + +/* Module runtime parameters */ + +#define AVS_VENDOR_CONFIG 0xFF + +enum avs_copier_runtime_param { + AVS_COPIER_SET_SINK_FORMAT = 2, +}; + +struct avs_copier_sink_format { + u32 sink_id; + struct avs_audio_format src_fmt; + struct avs_audio_format sink_fmt; +} __packed; +static_assert(sizeof(struct avs_copier_sink_format) == 52); + +int avs_ipc_copier_set_sink_format(struct avs_dev *adev, u16 module_id, + u8 instance_id, u32 sink_id, + const struct avs_audio_format *src_fmt, + const struct avs_audio_format *sink_fmt); + +enum avs_peakvol_runtime_param { + AVS_PEAKVOL_VOLUME = 0, + AVS_PEAKVOL_MUTE = 3, +}; + +enum avs_audio_curve_type { + AVS_AUDIO_CURVE_NONE = 0, + AVS_AUDIO_CURVE_WINDOWS_FADE = 1, +}; + +int avs_ipc_peakvol_get_volume(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg **vols, size_t *num_vols); +int avs_ipc_peakvol_set_volume(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg *vol); +int avs_ipc_peakvol_set_volumes(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_volume_cfg *vols, size_t num_vols); +int avs_ipc_peakvol_get_mute(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg **mutes, size_t *num_mutes); +int avs_ipc_peakvol_set_mute(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg *mute); +int avs_ipc_peakvol_set_mutes(struct avs_dev *adev, u16 module_id, u8 instance_id, + struct avs_mute_cfg *mutes, size_t num_mutes); + +#define AVS_PROBE_INST_ID 0 + +enum avs_probe_runtime_param { + AVS_PROBE_INJECTION_DMA = 1, + AVS_PROBE_INJECTION_DMA_DETACH, + AVS_PROBE_POINTS, + AVS_PROBE_POINTS_DISCONNECT, +}; + +struct avs_probe_dma { + union avs_connector_node_id node_id; + u32 dma_buffer_size; +} __packed; +static_assert(sizeof(struct avs_probe_dma) == 8); + +enum avs_probe_type { + AVS_PROBE_TYPE_INPUT = 0, + AVS_PROBE_TYPE_OUTPUT, + AVS_PROBE_TYPE_INTERNAL +}; + +union avs_probe_point_id { + u32 value; + struct { + u32 module_id:16; + u32 instance_id:8; + u32 type:2; + u32 index:6; + } id; +} __packed; +static_assert(sizeof(union avs_probe_point_id) == 4); + +enum avs_connection_purpose { + AVS_CONNECTION_PURPOSE_EXTRACT = 0, + AVS_CONNECTION_PURPOSE_INJECT, + AVS_CONNECTION_PURPOSE_INJECT_REEXTRACT, +}; + +struct avs_probe_point_desc { + union avs_probe_point_id id; + u32 purpose; + union avs_connector_node_id node_id; +} __packed; +static_assert(sizeof(struct avs_probe_point_desc) == 12); + +int avs_ipc_probe_get_dma(struct avs_dev *adev, struct avs_probe_dma **dmas, size_t *num_dmas); +int avs_ipc_probe_attach_dma(struct avs_dev *adev, struct avs_probe_dma *dmas, size_t num_dmas); +int avs_ipc_probe_detach_dma(struct avs_dev *adev, union avs_connector_node_id *node_ids, + size_t num_node_ids); +int avs_ipc_probe_get_points(struct avs_dev *adev, struct avs_probe_point_desc **descs, + size_t *num_descs); +int avs_ipc_probe_connect_points(struct avs_dev *adev, struct avs_probe_point_desc *descs, + size_t num_descs); +int avs_ipc_probe_disconnect_points(struct avs_dev *adev, union avs_probe_point_id *ids, + size_t num_ids); + +#endif /* __SOUND_SOC_INTEL_AVS_MSGS_H */ diff --git a/sound/soc/intel/avs/mtl.c b/sound/soc/intel/avs/mtl.c new file mode 100644 index 000000000000..d8bdd03275d7 --- /dev/null +++ b/sound/soc/intel/avs/mtl.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2021-2025 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "registers.h" +#include "trace.h" + +#define MTL_HfDSSGBL_BASE 0x1000 +#define MTL_REG_HfDSSCS (MTL_HfDSSGBL_BASE + 0x0) +#define MTL_HfDSSCS_SPA BIT(16) +#define MTL_HfDSSCS_CPA BIT(24) + +#define MTL_DSPCS_BASE 0x178D00 +#define MTL_REG_DSPCCTL (MTL_DSPCS_BASE + 0x4) +#define MTL_DSPCCTL_SPA BIT(0) +#define MTL_DSPCCTL_CPA BIT(8) +#define MTL_DSPCCTL_OSEL GENMASK(25, 24) +#define MTL_DSPCCTL_OSEL_HOST BIT(25) + +#define MTL_HfINT_BASE 0x1100 +#define MTL_REG_HfINTIPPTR (MTL_HfINT_BASE + 0x8) +#define MTL_REG_HfHIPCIE (MTL_HfINT_BASE + 0x40) +#define MTL_HfINTIPPTR_PTR GENMASK(20, 0) +#define MTL_HfHIPCIE_IE BIT(0) + +#define MTL_DWICTL_INTENL_IE BIT(0) +#define MTL_DWICTL_FINALSTATUSL_IPC BIT(0) /* same as ADSPIS_IPC */ + +static int avs_mtl_core_power_on(struct avs_dev *adev) +{ + u32 reg; + int ret; + + /* Power up DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfDSSCS, MTL_HfDSSCS_SPA, MTL_HfDSSCS_SPA); + trace_avs_dsp_core_op(1, AVS_MAIN_CORE_MASK, "power dsp", true); + + ret = snd_hdac_adsp_readl_poll(adev, MTL_REG_HfDSSCS, reg, + (reg & MTL_HfDSSCS_CPA) == MTL_HfDSSCS_CPA, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); + if (ret) { + dev_err(adev->dev, "power on domain dsp failed: %d\n", ret); + return ret; + } + + /* Prevent power gating of DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfPWRCTL, MTL_HfPWRCTL_WPDSPHPxPG, + MTL_HfPWRCTL_WPDSPHPxPG); + trace_avs_dsp_core_op(1, AVS_MAIN_CORE_MASK, "prevent dsp PG", true); + + ret = snd_hdac_adsp_readl_poll(adev, MTL_REG_HfPWRSTS, reg, + (reg & MTL_HfPWRSTS_DSPHPxPGS) == MTL_HfPWRSTS_DSPHPxPGS, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); + + /* Set ownership to HOST. */ + snd_hdac_adsp_updatel(adev, MTL_REG_DSPCCTL, MTL_DSPCCTL_OSEL, MTL_DSPCCTL_OSEL_HOST); + return ret; +} + +static int avs_mtl_core_power_off(struct avs_dev *adev) +{ + u32 reg; + + /* Allow power gating of DSP domain. No STS polling as HOST is only one of its users. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfPWRCTL, MTL_HfPWRCTL_WPDSPHPxPG, 0); + trace_avs_dsp_core_op(0, AVS_MAIN_CORE_MASK, "allow dsp pg", false); + + /* Power down DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfDSSCS, MTL_HfDSSCS_SPA, 0); + trace_avs_dsp_core_op(0, AVS_MAIN_CORE_MASK, "power dsp", false); + + return snd_hdac_adsp_readl_poll(adev, MTL_REG_HfDSSCS, reg, + (reg & MTL_HfDSSCS_CPA) == 0, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); +} + +int avs_mtl_core_power(struct avs_dev *adev, u32 core_mask, bool power) +{ + core_mask &= AVS_MAIN_CORE_MASK; + if (!core_mask) + return 0; + + if (power) + return avs_mtl_core_power_on(adev); + return avs_mtl_core_power_off(adev); +} + +int avs_mtl_core_reset(struct avs_dev *adev, u32 core_mask, bool power) +{ + /* No logical equivalent on ACE 1.x. */ + return 0; +} + +int avs_mtl_core_stall(struct avs_dev *adev, u32 core_mask, bool stall) +{ + u32 value, reg; + int ret; + + core_mask &= AVS_MAIN_CORE_MASK; + if (!core_mask) + return 0; + + value = snd_hdac_adsp_readl(adev, MTL_REG_DSPCCTL); + trace_avs_dsp_core_op(value, core_mask, "stall", stall); + if (value == UINT_MAX) + return 0; + + value = stall ? 0 : MTL_DSPCCTL_SPA; + snd_hdac_adsp_updatel(adev, MTL_REG_DSPCCTL, MTL_DSPCCTL_SPA, value); + + value = stall ? 0 : MTL_DSPCCTL_CPA; + ret = snd_hdac_adsp_readl_poll(adev, MTL_REG_DSPCCTL, + reg, (reg & MTL_DSPCCTL_CPA) == value, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); + if (ret) + dev_err(adev->dev, "core_mask %d %sstall failed: %d\n", + core_mask, stall ? "" : "un", ret); + return ret; +} + +static void avs_mtl_ipc_interrupt(struct avs_dev *adev) +{ + const struct avs_spec *spec = adev->spec; + u32 hipc_ack, hipc_rsp; + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, 0); + + hipc_ack = snd_hdac_adsp_readl(adev, spec->hipc->ack_offset); + hipc_rsp = snd_hdac_adsp_readl(adev, spec->hipc->rsp_offset); + + /* DSP acked host's request. */ + if (hipc_ack & spec->hipc->ack_done_mask) { + complete(&adev->ipc->done_completion); + + /* Tell DSP it has our attention. */ + snd_hdac_adsp_updatel(adev, spec->hipc->ack_offset, spec->hipc->ack_done_mask, + spec->hipc->ack_done_mask); + } + + /* DSP sent new response to process. */ + if (hipc_rsp & spec->hipc->rsp_busy_mask) { + union avs_reply_msg msg; + + msg.primary = snd_hdac_adsp_readl(adev, MTL_REG_HfIPCxTDR); + msg.ext.val = snd_hdac_adsp_readl(adev, MTL_REG_HfIPCxTDD); + + avs_dsp_process_response(adev, msg.val); + + /* Tell DSP we accepted its message. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxTDR, + MTL_HfIPCxTDR_BUSY, MTL_HfIPCxTDR_BUSY); + /* Ack this response. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxTDA, MTL_HfIPCxTDA_BUSY, 0); + } + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY); +} + +irqreturn_t avs_mtl_dsp_interrupt(struct avs_dev *adev) +{ + u32 adspis = snd_hdac_adsp_readl(adev, MTL_DWICTL_REG_FINALSTATUSL); + irqreturn_t ret = IRQ_NONE; + + if (adspis == UINT_MAX) + return ret; + + if (adspis & MTL_DWICTL_FINALSTATUSL_IPC) { + avs_mtl_ipc_interrupt(adev); + ret = IRQ_HANDLED; + } + + return ret; +} + +void avs_mtl_interrupt_control(struct avs_dev *adev, bool enable) +{ + if (enable) { + snd_hdac_adsp_updatel(adev, MTL_DWICTL_REG_INTENL, MTL_DWICTL_INTENL_IE, + MTL_DWICTL_INTENL_IE); + snd_hdac_adsp_updatew(adev, MTL_REG_HfHIPCIE, MTL_HfHIPCIE_IE, MTL_HfHIPCIE_IE); + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxCTL, AVS_ADSP_HIPCCTL_DONE, + AVS_ADSP_HIPCCTL_DONE); + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxCTL, AVS_ADSP_HIPCCTL_BUSY, + AVS_ADSP_HIPCCTL_BUSY); + } else { + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxCTL, AVS_ADSP_HIPCCTL_BUSY, 0); + snd_hdac_adsp_updatel(adev, MTL_REG_HfIPCxCTL, AVS_ADSP_HIPCCTL_DONE, 0); + snd_hdac_adsp_updatew(adev, MTL_REG_HfHIPCIE, MTL_HfHIPCIE_IE, 0); + snd_hdac_adsp_updatel(adev, MTL_DWICTL_REG_INTENL, MTL_DWICTL_INTENL_IE, 0); + } +} diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c new file mode 100644 index 000000000000..c8b586aced20 --- /dev/null +++ b/sound/soc/intel/avs/path.c @@ -0,0 +1,1608 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/acpi.h> +#include <acpi/nhlt.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "avs.h" +#include "control.h" +#include "path.h" +#include "topology.h" + +/* Must be called with adev->comp_list_mutex held. */ +static struct avs_tplg * +avs_path_find_tplg(struct avs_dev *adev, const char *name) +{ + struct avs_soc_component *acomp; + + list_for_each_entry(acomp, &adev->comp_list, node) + if (!strcmp(acomp->tplg->name, name)) + return acomp->tplg; + return NULL; +} + +static struct avs_path_module * +avs_path_find_module(struct avs_path_pipeline *ppl, u32 template_id) +{ + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) + if (mod->template->id == template_id) + return mod; + return NULL; +} + +static struct avs_path_pipeline * +avs_path_find_pipeline(struct avs_path *path, u32 template_id) +{ + struct avs_path_pipeline *ppl; + + list_for_each_entry(ppl, &path->ppl_list, node) + if (ppl->template->id == template_id) + return ppl; + return NULL; +} + +static struct avs_path * +avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) +{ + struct avs_tplg_path_template *pos, *template = NULL; + struct avs_tplg *tplg; + struct avs_path *path; + + tplg = avs_path_find_tplg(adev, name); + if (!tplg) + return NULL; + + list_for_each_entry(pos, &tplg->path_tmpl_list, node) { + if (pos->id == template_id) { + template = pos; + break; + } + } + if (!template) + return NULL; + + spin_lock(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { + if (path->template->owner == template) { + spin_unlock(&adev->path_list_lock); + return path; + } + } + + spin_unlock(&adev->path_list_lock); + return NULL; +} + +static bool avs_test_hw_params(struct snd_pcm_hw_params *params, + struct avs_audio_format *fmt) +{ + return (params_rate(params) == fmt->sampling_freq && + params_channels(params) == fmt->num_channels && + params_physical_width(params) == fmt->bit_depth && + snd_pcm_hw_params_bits(params) == fmt->valid_bit_depth); +} + +static struct avs_tplg_path * +avs_path_find_variant(struct avs_dev *adev, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params) +{ + struct avs_tplg_path *variant; + + list_for_each_entry(variant, &template->path_list, node) { + dev_dbg(adev->dev, "check FE rate %d chn %d vbd %d bd %d\n", + variant->fe_fmt->sampling_freq, variant->fe_fmt->num_channels, + variant->fe_fmt->valid_bit_depth, variant->fe_fmt->bit_depth); + dev_dbg(adev->dev, "check BE rate %d chn %d vbd %d bd %d\n", + variant->be_fmt->sampling_freq, variant->be_fmt->num_channels, + variant->be_fmt->valid_bit_depth, variant->be_fmt->bit_depth); + + if (variant->fe_fmt && avs_test_hw_params(fe_params, variant->fe_fmt) && + variant->be_fmt && avs_test_hw_params(be_params, variant->be_fmt)) + return variant; + } + + return NULL; +} + +static struct avs_tplg_path *avs_condpath_find_variant(struct avs_dev *adev, + struct avs_tplg_path_template *template, + struct avs_path *source, + struct avs_path *sink) +{ + struct avs_tplg_path *variant; + + list_for_each_entry(variant, &template->path_list, node) { + if (variant->source_path_id == source->template->id && + variant->sink_path_id == sink->template->id) + return variant; + } + + return NULL; +} + +static bool avs_tplg_path_template_id_equal(struct avs_tplg_path_template_id *id, + struct avs_tplg_path_template_id *id2) +{ + return id->id == id2->id && !strcmp(id->tplg_name, id2->tplg_name); +} + +static struct avs_path *avs_condpath_find_match(struct avs_dev *adev, + struct avs_tplg_path_template *template, + struct avs_path *path, int dir) +{ + struct avs_tplg_path_template_id *id, *id2; + + if (dir) { + id = &template->source; + id2 = &template->sink; + } else { + id = &template->sink; + id2 = &template->source; + } + + /* Check whether this path is either source or sink of condpath template. */ + if (id->id != path->template->owner->id || + strcmp(id->tplg_name, path->template->owner->owner->name)) + return NULL; + + /* Unidirectional condpaths are allowed. */ + if (avs_tplg_path_template_id_equal(id, id2)) + return path; + + /* Now find the counterpart. */ + return avs_path_find_path(adev, id2->tplg_name, id2->id); +} + +static struct acpi_nhlt_config * +avs_nhlt_config_or_default(struct avs_dev *adev, struct avs_tplg_module *t); + +int avs_path_set_constraint(struct avs_dev *adev, struct avs_tplg_path_template *template, + struct snd_pcm_hw_constraint_list *rate_list, + struct snd_pcm_hw_constraint_list *channels_list, + struct snd_pcm_hw_constraint_list *sample_bits_list) +{ + struct avs_tplg_path *path_template; + unsigned int *rlist, *clist, *slist; + size_t i; + + i = 0; + list_for_each_entry(path_template, &template->path_list, node) + i++; + + rlist = kcalloc(i, sizeof(*rlist), GFP_KERNEL); + clist = kcalloc(i, sizeof(*clist), GFP_KERNEL); + slist = kcalloc(i, sizeof(*slist), GFP_KERNEL); + if (!rlist || !clist || !slist) + return -ENOMEM; + + i = 0; + list_for_each_entry(path_template, &template->path_list, node) { + struct avs_tplg_pipeline *pipeline_template; + + list_for_each_entry(pipeline_template, &path_template->ppl_list, node) { + struct avs_tplg_module *module_template; + + list_for_each_entry(module_template, &pipeline_template->mod_list, node) { + const guid_t *type = &module_template->cfg_ext->type; + struct acpi_nhlt_config *blob; + + if (!guid_equal(type, &AVS_COPIER_MOD_UUID) && + !guid_equal(type, &AVS_WOVHOSTM_MOD_UUID)) + continue; + + switch (module_template->cfg_ext->copier.dma_type) { + case AVS_DMA_DMIC_LINK_INPUT: + case AVS_DMA_I2S_LINK_OUTPUT: + case AVS_DMA_I2S_LINK_INPUT: + break; + default: + continue; + } + + if (!module_template->nhlt_config) { + blob = avs_nhlt_config_or_default(adev, module_template); + if (IS_ERR(blob)) + continue; + } + + rlist[i] = path_template->fe_fmt->sampling_freq; + clist[i] = path_template->fe_fmt->num_channels; + slist[i] = path_template->fe_fmt->bit_depth; + i++; + } + } + } + + if (i) { + rate_list->count = i; + rate_list->list = rlist; + channels_list->count = i; + channels_list->list = clist; + sample_bits_list->count = i; + sample_bits_list->list = slist; + } else { + kfree(rlist); + kfree(clist); + kfree(slist); + } + + return i; +} + +static void avs_init_node_id(union avs_connector_node_id *node_id, + struct avs_tplg_modcfg_ext *te, u32 dma_id) +{ + node_id->val = 0; + node_id->dma_type = te->copier.dma_type; + + switch (node_id->dma_type) { + case AVS_DMA_DMIC_LINK_INPUT: + case AVS_DMA_I2S_LINK_OUTPUT: + case AVS_DMA_I2S_LINK_INPUT: + /* Gateway's virtual index is statically assigned in the topology. */ + node_id->vindex = te->copier.vindex.val; + break; + + case AVS_DMA_HDA_HOST_OUTPUT: + case AVS_DMA_HDA_HOST_INPUT: + /* Gateway's virtual index is dynamically assigned with DMA ID */ + node_id->vindex = dma_id; + break; + + case AVS_DMA_HDA_LINK_OUTPUT: + case AVS_DMA_HDA_LINK_INPUT: + node_id->vindex = te->copier.vindex.val | dma_id; + break; + + default: + *node_id = INVALID_NODE_ID; + break; + } +} + +/* Every BLOB contains at least gateway attributes. */ +static struct acpi_nhlt_config *default_blob = (struct acpi_nhlt_config *)&(u32[2]) {4}; + +static struct acpi_nhlt_config * +avs_nhlt_config_or_default(struct avs_dev *adev, struct avs_tplg_module *t) +{ + struct acpi_nhlt_format_config *fmtcfg; + struct avs_tplg_modcfg_ext *te; + struct avs_audio_format *fmt; + int link_type, dev_type; + int bus_id, dir; + + te = t->cfg_ext; + + switch (te->copier.dma_type) { + case AVS_DMA_I2S_LINK_OUTPUT: + link_type = ACPI_NHLT_LINKTYPE_SSP; + dev_type = ACPI_NHLT_DEVICETYPE_CODEC; + bus_id = te->copier.vindex.i2s.instance; + dir = SNDRV_PCM_STREAM_PLAYBACK; + fmt = te->copier.out_fmt; + break; + + case AVS_DMA_I2S_LINK_INPUT: + link_type = ACPI_NHLT_LINKTYPE_SSP; + dev_type = ACPI_NHLT_DEVICETYPE_CODEC; + bus_id = te->copier.vindex.i2s.instance; + dir = SNDRV_PCM_STREAM_CAPTURE; + fmt = t->in_fmt; + break; + + case AVS_DMA_DMIC_LINK_INPUT: + link_type = ACPI_NHLT_LINKTYPE_PDM; + dev_type = -1; /* ignored */ + bus_id = 0; + dir = SNDRV_PCM_STREAM_CAPTURE; + fmt = t->in_fmt; + break; + + default: + return default_blob; + } + + /* Override format selection if necessary. */ + if (te->copier.blob_fmt) + fmt = te->copier.blob_fmt; + + fmtcfg = acpi_nhlt_find_fmtcfg(link_type, dev_type, dir, bus_id, + fmt->num_channels, fmt->sampling_freq, fmt->valid_bit_depth, + fmt->bit_depth); + if (!fmtcfg) { + dev_warn(adev->dev, "Endpoint format configuration not found.\n"); + return ERR_PTR(-ENOENT); + } + + if (fmtcfg->config.capabilities_size < default_blob->capabilities_size) + return ERR_PTR(-ETOOSMALL); + /* The firmware expects the payload to be DWORD-aligned. */ + if (fmtcfg->config.capabilities_size % sizeof(u32)) + return ERR_PTR(-EINVAL); + + return &fmtcfg->config; +} + +static int avs_append_dma_cfg(struct avs_dev *adev, struct avs_copier_gtw_cfg *gtw, + struct avs_tplg_module *t, u32 dma_id, size_t *cfg_size) +{ + u32 dma_type = t->cfg_ext->copier.dma_type; + struct avs_dma_cfg *dma; + struct avs_tlv *tlv; + size_t tlv_size; + + if (!avs_platattr_test(adev, ALTHDA)) + return 0; + + switch (dma_type) { + case AVS_DMA_HDA_HOST_OUTPUT: + case AVS_DMA_HDA_HOST_INPUT: + case AVS_DMA_HDA_LINK_OUTPUT: + case AVS_DMA_HDA_LINK_INPUT: + return 0; + default: + break; + } + + tlv_size = sizeof(*tlv) + sizeof(*dma); + if (*cfg_size + tlv_size > AVS_MAILBOX_SIZE) + return -E2BIG; + + /* DMA config is a TLV tailing the existing payload. */ + tlv = (struct avs_tlv *)>w->config.blob[gtw->config_length]; + tlv->type = AVS_GTW_DMA_CONFIG_ID; + tlv->length = sizeof(*dma); + + dma = (struct avs_dma_cfg *)tlv->value; + memset(dma, 0, sizeof(*dma)); + dma->dma_method = AVS_DMA_METHOD_HDA; + dma->pre_allocated = true; + dma->dma_channel_id = dma_id; + dma->stream_id = dma_id + 1; + + gtw->config_length += tlv_size / sizeof(u32); + *cfg_size += tlv_size; + + return 0; +} + +static int avs_fill_gtw_config(struct avs_dev *adev, struct avs_copier_gtw_cfg *gtw, + struct avs_tplg_module *t, u32 dma_id, size_t *cfg_size) +{ + struct acpi_nhlt_config *blob; + size_t gtw_size; + + if (t->nhlt_config) + blob = t->nhlt_config->blob; + else + blob = avs_nhlt_config_or_default(adev, t); + if (IS_ERR(blob)) + return PTR_ERR(blob); + + gtw_size = blob->capabilities_size; + if (*cfg_size + gtw_size > AVS_MAILBOX_SIZE) + return -E2BIG; + + gtw->config_length = gtw_size / sizeof(u32); + memcpy(gtw->config.blob, blob->capabilities, blob->capabilities_size); + *cfg_size += gtw_size; + + return avs_append_dma_cfg(adev, gtw, t, dma_id, cfg_size); +} + +static int avs_copier_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_tplg_modcfg_ext *te; + struct avs_copier_cfg *cfg; + size_t cfg_size; + u32 dma_id; + int ret; + + te = t->cfg_ext; + cfg = adev->modcfg_buf; + dma_id = mod->owner->owner->dma_id; + cfg_size = offsetof(struct avs_copier_cfg, gtw_cfg.config); + + ret = avs_fill_gtw_config(adev, &cfg->gtw_cfg, t, dma_id, &cfg_size); + if (ret) + return ret; + + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->out_fmt = *te->copier.out_fmt; + cfg->feature_mask = te->copier.feature_mask; + avs_init_node_id(&cfg->gtw_cfg.node_id, te, dma_id); + cfg->gtw_cfg.dma_buffer_size = te->copier.dma_buffer_size; + mod->gtw_attrs = cfg->gtw_cfg.config.attrs; + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, t->core_id, + t->domain, cfg, cfg_size, &mod->instance_id); + return ret; +} + +static int avs_whm_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_tplg_modcfg_ext *te; + struct avs_whm_cfg *cfg; + size_t cfg_size; + u32 dma_id; + int ret; + + te = t->cfg_ext; + cfg = adev->modcfg_buf; + dma_id = mod->owner->owner->dma_id; + cfg_size = offsetof(struct avs_whm_cfg, gtw_cfg.config); + + ret = avs_fill_gtw_config(adev, &cfg->gtw_cfg, t, dma_id, &cfg_size); + if (ret) + return ret; + + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->ref_fmt = *te->whm.ref_fmt; + cfg->out_fmt = *te->whm.out_fmt; + cfg->wake_tick_period = te->whm.wake_tick_period; + avs_init_node_id(&cfg->gtw_cfg.node_id, te, dma_id); + cfg->gtw_cfg.dma_buffer_size = te->whm.dma_buffer_size; + mod->gtw_attrs = cfg->gtw_cfg.config.attrs; + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, t->core_id, + t->domain, cfg, cfg_size, &mod->instance_id); + return ret; +} + +static struct soc_mixer_control *avs_get_module_control(struct avs_path_module *mod, + const char *name) +{ + struct avs_tplg_module *t = mod->template; + struct avs_tplg_path_template *path_tmpl; + struct snd_soc_dapm_widget *w; + int i; + + path_tmpl = t->owner->owner->owner; + w = path_tmpl->w; + + for (i = 0; i < w->num_kcontrols; i++) { + struct avs_control_data *ctl_data; + struct soc_mixer_control *mc; + + mc = (struct soc_mixer_control *)w->kcontrols[i]->private_value; + ctl_data = (struct avs_control_data *)mc->dobj.private; + if (ctl_data->id == t->ctl_id && strstr(w->kcontrols[i]->id.name, name)) + return mc; + } + + return NULL; +} + +int avs_peakvol_set_volume(struct avs_dev *adev, struct avs_path_module *mod, + struct soc_mixer_control *mc, long *input) +{ + struct avs_volume_cfg vols[SND_SOC_TPLG_MAX_CHAN] = {{0}}; + struct avs_control_data *ctl_data; + struct avs_tplg_module *t; + int ret, i; + + ctl_data = mc->dobj.private; + t = mod->template; + if (!input) + input = ctl_data->values; + + if (mc->num_channels) { + for (i = 0; i < mc->num_channels; i++) { + vols[i].channel_id = i; + vols[i].target_volume = input[i]; + vols[i].curve_type = t->cfg_ext->peakvol.curve_type; + vols[i].curve_duration = t->cfg_ext->peakvol.curve_duration; + } + + ret = avs_ipc_peakvol_set_volumes(adev, mod->module_id, mod->instance_id, vols, + mc->num_channels); + return AVS_IPC_RET(ret); + } + + /* Target all channels if no individual selected. */ + vols[0].channel_id = AVS_ALL_CHANNELS_MASK; + vols[0].target_volume = input[0]; + vols[0].curve_type = t->cfg_ext->peakvol.curve_type; + vols[0].curve_duration = t->cfg_ext->peakvol.curve_duration; + + ret = avs_ipc_peakvol_set_volume(adev, mod->module_id, mod->instance_id, &vols[0]); + return AVS_IPC_RET(ret); +} + +int avs_peakvol_set_mute(struct avs_dev *adev, struct avs_path_module *mod, + struct soc_mixer_control *mc, long *input) +{ + struct avs_mute_cfg mutes[SND_SOC_TPLG_MAX_CHAN] = {{0}}; + struct avs_control_data *ctl_data; + struct avs_tplg_module *t; + int ret, i; + + ctl_data = mc->dobj.private; + t = mod->template; + if (!input) + input = ctl_data->values; + + if (mc->num_channels) { + for (i = 0; i < mc->num_channels; i++) { + mutes[i].channel_id = i; + mutes[i].mute = !input[i]; + mutes[i].curve_type = t->cfg_ext->peakvol.curve_type; + mutes[i].curve_duration = t->cfg_ext->peakvol.curve_duration; + } + + ret = avs_ipc_peakvol_set_mutes(adev, mod->module_id, mod->instance_id, mutes, + mc->num_channels); + return AVS_IPC_RET(ret); + } + + /* Target all channels if no individual selected. */ + mutes[0].channel_id = AVS_ALL_CHANNELS_MASK; + mutes[0].mute = !input[0]; + mutes[0].curve_type = t->cfg_ext->peakvol.curve_type; + mutes[0].curve_duration = t->cfg_ext->peakvol.curve_duration; + + ret = avs_ipc_peakvol_set_mute(adev, mod->module_id, mod->instance_id, &mutes[0]); + return AVS_IPC_RET(ret); +} + +static int avs_peakvol_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct soc_mixer_control *mc; + struct avs_peakvol_cfg *cfg; + size_t cfg_size; + int ret; + + cfg_size = struct_size(cfg, vols, 1); + if (cfg_size > AVS_MAILBOX_SIZE) + return -EINVAL; + + cfg = adev->modcfg_buf; + memset(cfg, 0, cfg_size); + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->vols[0].channel_id = AVS_ALL_CHANNELS_MASK; + cfg->vols[0].target_volume = S32_MAX; + cfg->vols[0].curve_type = t->cfg_ext->peakvol.curve_type; + cfg->vols[0].curve_duration = t->cfg_ext->peakvol.curve_duration; + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, t->core_id, + t->domain, cfg, cfg_size, &mod->instance_id); + if (ret) + return ret; + + /* Now configure both VOLUME and MUTE parameters. */ + mc = avs_get_module_control(mod, "Volume"); + if (mc) { + ret = avs_peakvol_set_volume(adev, mod, mc, NULL); + if (ret) + return ret; + } + + mc = avs_get_module_control(mod, "Switch"); + if (mc) + return avs_peakvol_set_mute(adev, mod, mc, NULL); + return 0; +} + +static int avs_updown_mix_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_updown_mixer_cfg cfg; + int i; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_channel_config = t->cfg_ext->updown_mix.out_channel_config; + cfg.coefficients_select = t->cfg_ext->updown_mix.coefficients_select; + for (i = 0; i < AVS_COEFF_CHANNELS_MAX; i++) + cfg.coefficients[i] = t->cfg_ext->updown_mix.coefficients[i]; + cfg.channel_map = t->cfg_ext->updown_mix.channel_map; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_src_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_src_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_freq = t->cfg_ext->src.out_freq; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_asrc_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_asrc_cfg cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_freq = t->cfg_ext->asrc.out_freq; + cfg.mode = t->cfg_ext->asrc.mode; + cfg.disable_jitter_buffer = t->cfg_ext->asrc.disable_jitter_buffer; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_aec_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_aec_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.ref_fmt = *t->cfg_ext->aec.ref_fmt; + cfg.out_fmt = *t->cfg_ext->aec.out_fmt; + cfg.cpc_lp_mode = t->cfg_ext->aec.cpc_lp_mode; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_mux_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_mux_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.ref_fmt = *t->cfg_ext->mux.ref_fmt; + cfg.out_fmt = *t->cfg_ext->mux.out_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_wov_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_wov_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.cpc_lp_mode = t->cfg_ext->wov.cpc_lp_mode; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_micsel_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_micsel_cfg cfg; + + cfg.base.cpc = t->cfg_base->cpc; + cfg.base.ibs = t->cfg_base->ibs; + cfg.base.obs = t->cfg_base->obs; + cfg.base.is_pages = t->cfg_base->is_pages; + cfg.base.audio_fmt = *t->in_fmt; + cfg.out_fmt = *t->cfg_ext->micsel.out_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_modbase_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_modcfg_base cfg; + + cfg.cpc = t->cfg_base->cpc; + cfg.ibs = t->cfg_base->ibs; + cfg.obs = t->cfg_base->obs; + cfg.is_pages = t->cfg_base->is_pages; + cfg.audio_fmt = *t->in_fmt; + + return avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, &cfg, sizeof(cfg), + &mod->instance_id); +} + +static int avs_modext_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_tplg_module *t = mod->template; + struct avs_tplg_modcfg_ext *tcfg = t->cfg_ext; + struct avs_modcfg_ext *cfg; + size_t cfg_size, num_pins; + int ret, i; + + num_pins = tcfg->generic.num_input_pins + tcfg->generic.num_output_pins; + cfg_size = struct_size(cfg, pin_fmts, num_pins); + + if (cfg_size > AVS_MAILBOX_SIZE) + return -EINVAL; + + cfg = adev->modcfg_buf; + memset(cfg, 0, cfg_size); + cfg->base.cpc = t->cfg_base->cpc; + cfg->base.ibs = t->cfg_base->ibs; + cfg->base.obs = t->cfg_base->obs; + cfg->base.is_pages = t->cfg_base->is_pages; + cfg->base.audio_fmt = *t->in_fmt; + cfg->num_input_pins = tcfg->generic.num_input_pins; + cfg->num_output_pins = tcfg->generic.num_output_pins; + + /* configure pin formats */ + for (i = 0; i < num_pins; i++) { + struct avs_tplg_pin_format *tpin = &tcfg->generic.pin_fmts[i]; + struct avs_pin_format *pin = &cfg->pin_fmts[i]; + + pin->pin_index = tpin->pin_index; + pin->iobs = tpin->iobs; + pin->audio_fmt = *tpin->fmt; + } + + ret = avs_dsp_init_module(adev, mod->module_id, mod->owner->instance_id, + t->core_id, t->domain, cfg, cfg_size, + &mod->instance_id); + return ret; +} + +static int avs_probe_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + dev_err(adev->dev, "Probe module can't be instantiated by topology"); + return -EINVAL; +} + +struct avs_module_create { + guid_t *guid; + int (*create)(struct avs_dev *adev, struct avs_path_module *mod); +}; + +static struct avs_module_create avs_module_create[] = { + { &AVS_MIXIN_MOD_UUID, avs_modbase_create }, + { &AVS_MIXOUT_MOD_UUID, avs_modbase_create }, + { &AVS_KPBUFF_MOD_UUID, avs_modbase_create }, + { &AVS_COPIER_MOD_UUID, avs_copier_create }, + { &AVS_PEAKVOL_MOD_UUID, avs_peakvol_create }, + { &AVS_GAIN_MOD_UUID, avs_peakvol_create }, + { &AVS_MICSEL_MOD_UUID, avs_micsel_create }, + { &AVS_MUX_MOD_UUID, avs_mux_create }, + { &AVS_UPDWMIX_MOD_UUID, avs_updown_mix_create }, + { &AVS_SRCINTC_MOD_UUID, avs_src_create }, + { &AVS_AEC_MOD_UUID, avs_aec_create }, + { &AVS_ASRC_MOD_UUID, avs_asrc_create }, + { &AVS_INTELWOV_MOD_UUID, avs_wov_create }, + { &AVS_PROBE_MOD_UUID, avs_probe_create }, + { &AVS_WOVHOSTM_MOD_UUID, avs_whm_create }, +}; + +static int avs_path_module_type_create(struct avs_dev *adev, struct avs_path_module *mod) +{ + const guid_t *type = &mod->template->cfg_ext->type; + + for (int i = 0; i < ARRAY_SIZE(avs_module_create); i++) + if (guid_equal(type, avs_module_create[i].guid)) + return avs_module_create[i].create(adev, mod); + + return avs_modext_create(adev, mod); +} + +static int avs_path_module_send_init_configs(struct avs_dev *adev, struct avs_path_module *mod) +{ + struct avs_soc_component *acomp; + + acomp = to_avs_soc_component(mod->template->owner->owner->owner->owner->comp); + + u32 num_ids = mod->template->num_config_ids; + u32 *ids = mod->template->config_ids; + + for (int i = 0; i < num_ids; i++) { + struct avs_tplg_init_config *config = &acomp->tplg->init_configs[ids[i]]; + size_t len = config->length; + void *data = config->data; + u32 param = config->param; + int ret; + + ret = avs_ipc_set_large_config(adev, mod->module_id, mod->instance_id, + param, data, len); + if (ret) { + dev_err(adev->dev, "send initial module config failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + } + + return 0; +} + +static void avs_path_module_free(struct avs_dev *adev, struct avs_path_module *mod) +{ + kfree(mod); +} + +static struct avs_path_module * +avs_path_module_create(struct avs_dev *adev, + struct avs_path_pipeline *owner, + struct avs_tplg_module *template) +{ + struct avs_path_module *mod; + int module_id, ret; + + module_id = avs_get_module_id(adev, &template->cfg_ext->type); + if (module_id < 0) + return ERR_PTR(module_id); + + mod = kzalloc(sizeof(*mod), GFP_KERNEL); + if (!mod) + return ERR_PTR(-ENOMEM); + + mod->template = template; + mod->module_id = module_id; + mod->owner = owner; + INIT_LIST_HEAD(&mod->node); + + ret = avs_path_module_type_create(adev, mod); + if (ret) { + dev_err(adev->dev, "module-type create failed: %d\n", ret); + kfree(mod); + return ERR_PTR(ret); + } + + ret = avs_path_module_send_init_configs(adev, mod); + if (ret) { + kfree(mod); + return ERR_PTR(ret); + } + + return mod; +} + +static int avs_path_binding_arm(struct avs_dev *adev, struct avs_path_binding *binding) +{ + struct avs_path_module *this_mod, *target_mod; + struct avs_path_pipeline *target_ppl; + struct avs_path *target_path; + struct avs_tplg_binding *t; + + t = binding->template; + this_mod = avs_path_find_module(binding->owner, + t->mod_id); + if (!this_mod) { + dev_err(adev->dev, "path mod %d not found\n", t->mod_id); + return -EINVAL; + } + + /* update with target_tplg_name too */ + target_path = avs_path_find_path(adev, t->target_tplg_name, + t->target_path_tmpl_id); + if (!target_path) { + dev_err(adev->dev, "target path %s:%d not found\n", + t->target_tplg_name, t->target_path_tmpl_id); + return -EINVAL; + } + + target_ppl = avs_path_find_pipeline(target_path, + t->target_ppl_id); + if (!target_ppl) { + dev_err(adev->dev, "target ppl %d not found\n", t->target_ppl_id); + return -EINVAL; + } + + target_mod = avs_path_find_module(target_ppl, t->target_mod_id); + if (!target_mod) { + dev_err(adev->dev, "target mod %d not found\n", t->target_mod_id); + return -EINVAL; + } + + if (t->is_sink) { + binding->sink = this_mod; + binding->sink_pin = t->mod_pin; + binding->source = target_mod; + binding->source_pin = t->target_mod_pin; + } else { + binding->sink = target_mod; + binding->sink_pin = t->target_mod_pin; + binding->source = this_mod; + binding->source_pin = t->mod_pin; + } + + return 0; +} + +static void avs_path_binding_free(struct avs_dev *adev, struct avs_path_binding *binding) +{ + kfree(binding); +} + +static struct avs_path_binding *avs_path_binding_create(struct avs_dev *adev, + struct avs_path_pipeline *owner, + struct avs_tplg_binding *t) +{ + struct avs_path_binding *binding; + + binding = kzalloc(sizeof(*binding), GFP_KERNEL); + if (!binding) + return ERR_PTR(-ENOMEM); + + binding->template = t; + binding->owner = owner; + INIT_LIST_HEAD(&binding->node); + + return binding; +} + +static int avs_path_pipeline_arm(struct avs_dev *adev, + struct avs_path_pipeline *ppl) +{ + struct avs_path_module *mod; + + list_for_each_entry(mod, &ppl->mod_list, node) { + struct avs_path_module *source, *sink; + int ret; + + /* + * Only one module (so it's implicitly last) or it is the last + * one, either way we don't have next module to bind it to. + */ + if (mod == list_last_entry(&ppl->mod_list, + struct avs_path_module, node)) + break; + + /* bind current module to next module on list */ + source = mod; + sink = list_next_entry(mod, node); + + ret = avs_ipc_bind(adev, source->module_id, source->instance_id, + sink->module_id, sink->instance_id, 0, 0); + if (ret) + return AVS_IPC_RET(ret); + } + + return 0; +} + +static void avs_path_pipeline_free(struct avs_dev *adev, + struct avs_path_pipeline *ppl) +{ + struct avs_path_binding *binding, *bsave; + struct avs_path_module *mod, *save; + + list_for_each_entry_safe(binding, bsave, &ppl->binding_list, node) { + list_del(&binding->node); + avs_path_binding_free(adev, binding); + } + + avs_dsp_delete_pipeline(adev, ppl->instance_id); + + /* Unload resources occupied by owned modules */ + list_for_each_entry_safe(mod, save, &ppl->mod_list, node) { + avs_dsp_delete_module(adev, mod->module_id, mod->instance_id, + mod->owner->instance_id, + mod->template->core_id); + avs_path_module_free(adev, mod); + } + + list_del(&ppl->node); + kfree(ppl); +} + +static struct avs_path_pipeline * +avs_path_pipeline_create(struct avs_dev *adev, struct avs_path *owner, + struct avs_tplg_pipeline *template) +{ + struct avs_path_pipeline *ppl; + struct avs_tplg_pplcfg *cfg = template->cfg; + struct avs_tplg_module *tmod; + int ret, i; + + ppl = kzalloc(sizeof(*ppl), GFP_KERNEL); + if (!ppl) + return ERR_PTR(-ENOMEM); + + ppl->template = template; + ppl->owner = owner; + INIT_LIST_HEAD(&ppl->binding_list); + INIT_LIST_HEAD(&ppl->mod_list); + INIT_LIST_HEAD(&ppl->node); + + ret = avs_dsp_create_pipeline(adev, cfg->req_size, cfg->priority, + cfg->lp, cfg->attributes, + &ppl->instance_id); + if (ret) { + dev_err(adev->dev, "error creating pipeline %d\n", ret); + kfree(ppl); + return ERR_PTR(ret); + } + + list_for_each_entry(tmod, &template->mod_list, node) { + struct avs_path_module *mod; + + mod = avs_path_module_create(adev, ppl, tmod); + if (IS_ERR(mod)) { + ret = PTR_ERR(mod); + dev_err(adev->dev, "error creating module %d\n", ret); + goto init_err; + } + + list_add_tail(&mod->node, &ppl->mod_list); + } + + for (i = 0; i < template->num_bindings; i++) { + struct avs_path_binding *binding; + + binding = avs_path_binding_create(adev, ppl, template->bindings[i]); + if (IS_ERR(binding)) { + ret = PTR_ERR(binding); + dev_err(adev->dev, "error creating binding %d\n", ret); + goto init_err; + } + + list_add_tail(&binding->node, &ppl->binding_list); + } + + return ppl; + +init_err: + avs_path_pipeline_free(adev, ppl); + return ERR_PTR(ret); +} + +static int avs_path_init(struct avs_dev *adev, struct avs_path *path, + struct avs_tplg_path *template, u32 dma_id) +{ + struct avs_tplg_pipeline *tppl; + + path->owner = adev; + path->template = template; + path->dma_id = dma_id; + INIT_LIST_HEAD(&path->ppl_list); + INIT_LIST_HEAD(&path->node); + INIT_LIST_HEAD(&path->source_list); + INIT_LIST_HEAD(&path->sink_list); + INIT_LIST_HEAD(&path->source_node); + INIT_LIST_HEAD(&path->sink_node); + + /* create all the pipelines */ + list_for_each_entry(tppl, &template->ppl_list, node) { + struct avs_path_pipeline *ppl; + + ppl = avs_path_pipeline_create(adev, path, tppl); + if (IS_ERR(ppl)) + return PTR_ERR(ppl); + + list_add_tail(&ppl->node, &path->ppl_list); + } + + spin_lock(&adev->path_list_lock); + list_add_tail(&path->node, &adev->path_list); + spin_unlock(&adev->path_list_lock); + + return 0; +} + +static int avs_path_arm(struct avs_dev *adev, struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_path_binding *binding; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + /* + * Arm all ppl bindings before binding internal modules + * as it costs no IPCs which isn't true for the latter. + */ + list_for_each_entry(binding, &ppl->binding_list, node) { + ret = avs_path_binding_arm(adev, binding); + if (ret < 0) + return ret; + } + + ret = avs_path_pipeline_arm(adev, ppl); + if (ret < 0) + return ret; + } + + return 0; +} + +static void avs_path_free_unlocked(struct avs_path *path) +{ + struct avs_path_pipeline *ppl, *save; + + spin_lock(&path->owner->path_list_lock); + list_del(&path->node); + spin_unlock(&path->owner->path_list_lock); + + list_for_each_entry_safe(ppl, save, &path->ppl_list, node) + avs_path_pipeline_free(path->owner, ppl); + + kfree(path); +} + +static struct avs_path *avs_path_create_unlocked(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path *template) +{ + struct avs_path *path; + int ret; + + path = kzalloc(sizeof(*path), GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + + ret = avs_path_init(adev, path, template, dma_id); + if (ret < 0) + goto err; + + ret = avs_path_arm(adev, path); + if (ret < 0) + goto err; + + path->state = AVS_PPL_STATE_INVALID; + return path; +err: + avs_path_free_unlocked(path); + return ERR_PTR(ret); +} + +static void avs_condpath_free(struct avs_dev *adev, struct avs_path *path) +{ + int ret; + + list_del(&path->source_node); + list_del(&path->sink_node); + + ret = avs_path_reset(path); + if (ret < 0) + dev_err(adev->dev, "reset condpath failed: %d\n", ret); + + ret = avs_path_unbind(path); + if (ret < 0) + dev_err(adev->dev, "unbind condpath failed: %d\n", ret); + + avs_path_free_unlocked(path); +} + +static struct avs_path *avs_condpath_create(struct avs_dev *adev, + struct avs_tplg_path *template, + struct avs_path *source, + struct avs_path *sink) +{ + struct avs_path *path; + int ret; + + path = avs_path_create_unlocked(adev, 0, template); + if (IS_ERR(path)) + return path; + + ret = avs_path_bind(path); + if (ret) + goto err_bind; + + ret = avs_path_reset(path); + if (ret) + goto err_reset; + + path->source = source; + path->sink = sink; + list_add_tail(&path->source_node, &source->source_list); + list_add_tail(&path->sink_node, &sink->sink_list); + + return path; + +err_reset: + avs_path_unbind(path); +err_bind: + avs_path_free_unlocked(path); + return ERR_PTR(ret); +} + +static int avs_condpaths_walk(struct avs_dev *adev, struct avs_path *path, int dir) +{ + struct avs_soc_component *acomp; + struct avs_path *source, *sink; + struct avs_path **other; + + if (dir) { + source = path; + other = &sink; + } else { + sink = path; + other = &source; + } + + list_for_each_entry(acomp, &adev->comp_list, node) { + for (int i = 0; i < acomp->tplg->num_condpath_tmpls; i++) { + struct avs_tplg_path_template *template; + struct avs_tplg_path *variant; + struct avs_path *cpath; + + template = &acomp->tplg->condpath_tmpls[i]; + + /* Do not create unidirectional condpaths twice. */ + if (avs_tplg_path_template_id_equal(&template->source, + &template->sink) && dir) + continue; + + *other = avs_condpath_find_match(adev, template, path, dir); + if (!*other) + continue; + + variant = avs_condpath_find_variant(adev, template, source, sink); + if (!variant) + continue; + + cpath = avs_condpath_create(adev, variant, source, sink); + if (IS_ERR(cpath)) + return PTR_ERR(cpath); + } + } + + return 0; +} + +/* Caller responsible for holding adev->path_mutex. */ +static int avs_condpaths_walk_all(struct avs_dev *adev, struct avs_path *path) +{ + int ret; + + ret = avs_condpaths_walk(adev, path, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + + return avs_condpaths_walk(adev, path, SNDRV_PCM_STREAM_PLAYBACK); +} + +void avs_path_free(struct avs_path *path) +{ + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + + mutex_lock(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) + avs_condpath_free(path->owner, cpath); + list_for_each_entry_safe(cpath, csave, &path->sink_list, sink_node) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); + + mutex_unlock(&adev->path_mutex); +} + +struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params) +{ + struct avs_tplg_path *variant; + struct avs_path *path; + int ret; + + variant = avs_path_find_variant(adev, template, fe_params, be_params); + if (!variant) { + dev_err(adev->dev, "no matching variant found\n"); + return ERR_PTR(-ENOENT); + } + + /* Serialize path and its components creation. */ + mutex_lock(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ + mutex_lock(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) + goto exit; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { + avs_path_free_unlocked(path); + path = ERR_PTR(ret); + } + +exit: + mutex_unlock(&adev->comp_list_mutex); + mutex_unlock(&adev->path_mutex); + + return path; +} + +static int avs_path_bind_prepare(struct avs_dev *adev, + struct avs_path_binding *binding) +{ + const struct avs_audio_format *src_fmt, *sink_fmt; + struct avs_tplg_module *tsource = binding->source->template; + struct avs_path_module *source = binding->source; + int ret; + + /* + * only copier modules about to be bound + * to output pin other than 0 need preparation + */ + if (!binding->source_pin) + return 0; + if (!guid_equal(&tsource->cfg_ext->type, &AVS_COPIER_MOD_UUID)) + return 0; + + src_fmt = tsource->in_fmt; + sink_fmt = binding->sink->template->in_fmt; + + ret = avs_ipc_copier_set_sink_format(adev, source->module_id, + source->instance_id, binding->source_pin, + src_fmt, sink_fmt); + if (ret) { + dev_err(adev->dev, "config copier failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + + return 0; +} + +int avs_path_bind(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_binding *binding; + + list_for_each_entry(binding, &ppl->binding_list, node) { + struct avs_path_module *source, *sink; + + source = binding->source; + sink = binding->sink; + + ret = avs_path_bind_prepare(adev, binding); + if (ret < 0) + return ret; + + ret = avs_ipc_bind(adev, source->module_id, + source->instance_id, sink->module_id, + sink->instance_id, binding->sink_pin, + binding->source_pin); + if (ret) { + dev_err(adev->dev, "bind path failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + } + } + + return 0; +} + +int avs_path_unbind(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + list_for_each_entry(ppl, &path->ppl_list, node) { + struct avs_path_binding *binding; + + list_for_each_entry(binding, &ppl->binding_list, node) { + struct avs_path_module *source, *sink; + + source = binding->source; + sink = binding->sink; + + ret = avs_ipc_unbind(adev, source->module_id, + source->instance_id, sink->module_id, + sink->instance_id, binding->sink_pin, + binding->source_pin); + if (ret) { + dev_err(adev->dev, "unbind path failed: %d\n", ret); + return AVS_IPC_RET(ret); + } + } + } + + return 0; +} + +int avs_path_reset(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_RESET) + return 0; + + list_for_each_entry(ppl, &path->ppl_list, node) { + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_RESET); + if (ret) { + dev_err(adev->dev, "reset path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_RESET; + return 0; +} + +static int avs_condpath_pause(struct avs_dev *adev, struct avs_path *cpath) +{ + struct avs_path_pipeline *ppl; + int ret; + + if (cpath->state == AVS_PPL_STATE_PAUSED) + return 0; + + list_for_each_entry_reverse(ppl, &cpath->ppl_list, node) { + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, AVS_PPL_STATE_PAUSED); + if (ret) { + dev_err(adev->dev, "pause cpath failed: %d\n", ret); + cpath->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + cpath->state = AVS_PPL_STATE_PAUSED; + return 0; +} + +static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) +{ + struct avs_path *cpath; + + mutex_lock(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); + + mutex_unlock(&adev->path_mutex); +} + +int avs_path_pause(struct avs_path *path) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_PAUSED) + return 0; + + avs_condpaths_pause(adev, path); + + list_for_each_entry_reverse(ppl, &path->ppl_list, node) { + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_PAUSED); + if (ret) { + dev_err(adev->dev, "pause path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_PAUSED; + return 0; +} + +static int avs_condpath_run(struct avs_dev *adev, struct avs_path *cpath, int trigger) +{ + struct avs_path_pipeline *ppl; + int ret; + + if (cpath->state == AVS_PPL_STATE_RUNNING) + return 0; + + list_for_each_entry(ppl, &cpath->ppl_list, node) { + if (ppl->template->cfg->trigger != trigger) + continue; + + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, AVS_PPL_STATE_RUNNING); + if (ret) { + dev_err(adev->dev, "run cpath failed: %d\n", ret); + cpath->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + cpath->state = AVS_PPL_STATE_RUNNING; + return 0; +} + +static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int trigger) +{ + struct avs_path *cpath; + + mutex_lock(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); + + list_for_each_entry(cpath, &path->sink_list, sink_node) + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); + + mutex_unlock(&adev->path_mutex); +} + +int avs_path_run(struct avs_path *path, int trigger) +{ + struct avs_path_pipeline *ppl; + struct avs_dev *adev = path->owner; + int ret; + + if (path->state == AVS_PPL_STATE_RUNNING && trigger == AVS_TPLG_TRIGGER_AUTO) + return 0; + + list_for_each_entry(ppl, &path->ppl_list, node) { + if (ppl->template->cfg->trigger != trigger) + continue; + + ret = avs_ipc_set_pipeline_state(adev, ppl->instance_id, + AVS_PPL_STATE_RUNNING); + if (ret) { + dev_err(adev->dev, "run path failed: %d\n", ret); + path->state = AVS_PPL_STATE_INVALID; + return AVS_IPC_RET(ret); + } + } + + path->state = AVS_PPL_STATE_RUNNING; + + /* Granular pipeline triggering not intended for conditional paths. */ + if (trigger == AVS_TPLG_TRIGGER_AUTO) + avs_condpaths_run(adev, path, trigger); + + return 0; +} diff --git a/sound/soc/intel/avs/path.h b/sound/soc/intel/avs/path.h new file mode 100644 index 000000000000..ceb89971a902 --- /dev/null +++ b/sound/soc/intel/avs/path.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_PATH_H +#define __SOUND_SOC_INTEL_AVS_PATH_H + +#include <linux/list.h> +#include "avs.h" +#include "topology.h" + +#define AVS_COND_TYPE_NONE 0 +#define AVS_COND_TYPE_AECREF 1 + +struct avs_path { + u32 dma_id; + struct list_head ppl_list; + u32 state; + + /* condpath navigation for standard paths */ + struct list_head source_list; + struct list_head sink_list; + + /* conditional path fields */ + struct avs_path *source; + struct avs_path *sink; + struct list_head source_node; + struct list_head sink_node; + + struct avs_tplg_path *template; + struct avs_dev *owner; + /* device path management */ + struct list_head node; +}; + +struct avs_path_pipeline { + u8 instance_id; + struct list_head mod_list; + struct list_head binding_list; + + struct avs_tplg_pipeline *template; + struct avs_path *owner; + /* path pipelines management */ + struct list_head node; +}; + +struct avs_path_module { + u16 module_id; + u8 instance_id; + union avs_gtw_attributes gtw_attrs; + + struct avs_tplg_module *template; + struct avs_path_pipeline *owner; + /* pipeline modules management */ + struct list_head node; +}; + +struct avs_path_binding { + struct avs_path_module *source; + u8 source_pin; + struct avs_path_module *sink; + u8 sink_pin; + + struct avs_tplg_binding *template; + struct avs_path_pipeline *owner; + /* pipeline bindings management */ + struct list_head node; +}; + +void avs_path_free(struct avs_path *path); +struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + struct avs_tplg_path_template *template, + struct snd_pcm_hw_params *fe_params, + struct snd_pcm_hw_params *be_params); +int avs_path_bind(struct avs_path *path); +int avs_path_unbind(struct avs_path *path); +int avs_path_reset(struct avs_path *path); +int avs_path_pause(struct avs_path *path); +int avs_path_run(struct avs_path *path, int trigger); + +int avs_path_set_constraint(struct avs_dev *adev, struct avs_tplg_path_template *template, + struct snd_pcm_hw_constraint_list *rate_list, + struct snd_pcm_hw_constraint_list *channels_list, + struct snd_pcm_hw_constraint_list *sample_bits_list); + +int avs_peakvol_set_volume(struct avs_dev *adev, struct avs_path_module *mod, + struct soc_mixer_control *mc, long *input); +int avs_peakvol_set_mute(struct avs_dev *adev, struct avs_path_module *mod, + struct soc_mixer_control *mc, long *input); + +#endif diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c new file mode 100644 index 000000000000..4a6deb599c88 --- /dev/null +++ b/sound/soc/intel/avs/pcm.c @@ -0,0 +1,1773 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/soc-component.h> +#include "avs.h" +#include "path.h" +#include "pcm.h" +#include "topology.h" +#include "utils.h" +#include "../../codecs/hda.h" + +struct avs_dma_data { + struct avs_tplg_path_template *template; + struct avs_path *path; + struct avs_dev *adev; + + /* LINK-stream utilized in BE operations while HOST in FE ones. */ + union { + struct hdac_ext_stream *link_stream; + struct hdac_ext_stream *host_stream; + }; + + struct snd_pcm_hw_constraint_list rate_list; + struct snd_pcm_hw_constraint_list channels_list; + struct snd_pcm_hw_constraint_list sample_bits_list; + + struct work_struct period_elapsed_work; + struct hdac_ext_link *link; + struct snd_pcm_substream *substream; +}; + +static struct avs_tplg_path_template * +avs_dai_find_path_template(struct snd_soc_dai *dai, bool is_fe, int direction) +{ + struct snd_soc_dapm_widget *dw = snd_soc_dai_get_widget(dai, direction); + struct snd_soc_dapm_path *dp; + enum snd_soc_dapm_direction dir; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + dir = is_fe ? SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN; + } else { + dir = is_fe ? SND_SOC_DAPM_DIR_IN : SND_SOC_DAPM_DIR_OUT; + } + + dp = list_first_entry_or_null(&dw->edges[dir], typeof(*dp), list_node[dir]); + if (!dp) + return NULL; + + /* Get the other widget, with actual path template data */ + dw = (dp->source == dw) ? dp->sink : dp->source; + + return dw->priv; +} + +static void avs_period_elapsed_work(struct work_struct *work) +{ + struct avs_dma_data *data = container_of(work, struct avs_dma_data, period_elapsed_work); + + snd_pcm_period_elapsed(data->substream); +} + +void avs_period_elapsed(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0); + struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream); + + schedule_work(&data->period_elapsed_work); +} + +static int hw_rule_param_size(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule); +static int avs_hw_constraints_init(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hw_constraint_list *r, *c, *s; + struct avs_dma_data *data; + int ret; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + r = &(data->rate_list); + c = &(data->channels_list); + s = &(data->sample_bits_list); + + ret = avs_path_set_constraint(data->adev, data->template, r, c, s); + if (ret <= 0) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, r); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, c); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, s); + if (ret < 0) + return ret; + + return 0; +} + +static int avs_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dev *adev = to_avs_dev(dai->component->dev); + struct avs_tplg_path_template *template; + struct avs_dma_data *data; + + template = avs_dai_find_path_template(dai, !rtd->dai_link->no_pcm, substream->stream); + if (!template) { + dev_err(dai->dev, "no %s path for dai %s, invalid tplg?\n", + snd_pcm_stream_str(substream), dai->name); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->substream = substream; + data->template = template; + data->adev = adev; + INIT_WORK(&data->period_elapsed_work, avs_period_elapsed_work); + snd_soc_dai_set_dma_data(dai, substream, data); + + if (rtd->dai_link->ignore_suspend) + adev->num_lp_paths++; + + return avs_hw_constraints_init(substream, dai); +} + +static void avs_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + + if (rtd->dai_link->ignore_suspend) + data->adev->num_lp_paths--; + + kfree(data->rate_list.list); + kfree(data->channels_list.list); + kfree(data->sample_bits_list.list); + + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(data); +} + +static int avs_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *fe_hw_params, + struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, + int dma_id) +{ + struct avs_dma_data *data; + struct avs_path *path; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + + dev_dbg(dai->dev, "%s FE hw_params str %p rtd %p", + __func__, substream, substream->runtime); + dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n", + params_rate(fe_hw_params), params_channels(fe_hw_params), + params_width(fe_hw_params), params_physical_width(fe_hw_params)); + + dev_dbg(dai->dev, "%s BE hw_params str %p rtd %p", + __func__, substream, substream->runtime); + dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n", + params_rate(be_hw_params), params_channels(be_hw_params), + params_width(be_hw_params), params_physical_width(be_hw_params)); + + path = avs_path_create(data->adev, dma_id, data->template, fe_hw_params, be_hw_params); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + dev_err(dai->dev, "create path failed: %d\n", ret); + return ret; + } + + data->path = path; + return 0; +} + +static int avs_dai_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, + int dma_id) +{ + struct snd_pcm_hw_params *fe_hw_params = NULL; + struct snd_soc_pcm_runtime *fe, *be; + struct snd_soc_dpcm *dpcm; + + be = snd_soc_substream_to_rtd(substream); + /* dpcm_fe_dai_open() guarantees the list is not empty at this point. */ + for_each_dpcm_fe(be, substream->stream, dpcm) { + fe = dpcm->fe; + fe_hw_params = &fe->dpcm[substream->stream].hw_params; + } + + return avs_dai_hw_params(substream, fe_hw_params, be_hw_params, dai, dma_id); +} + +static int avs_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + ret = avs_path_reset(data->path); + if (ret < 0) { + dev_err(dai->dev, "reset path failed: %d\n", ret); + return ret; + } + + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause path failed: %d\n", ret); + return ret; +} + +static int avs_dai_nonhda_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + /* Actual port-id comes from topology. */ + return avs_dai_be_hw_params(substream, hw_params, dai, 0); +} + +static int avs_dai_nonhda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) { + avs_path_free(data->path); + data->path = NULL; + } + + return 0; +} + +static int avs_dai_nonhda_be_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dma_data *data; + int ret = 0; + + data = snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = avs_path_pause(data->path); + if (ret < 0) { + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + break; + } + + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run BE path failed: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset BE path failed: %d\n", ret); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops avs_dai_nonhda_be_ops = { + .startup = avs_dai_startup, + .shutdown = avs_dai_shutdown, + .hw_params = avs_dai_nonhda_be_hw_params, + .hw_free = avs_dai_nonhda_be_hw_free, + .prepare = avs_dai_prepare, + .trigger = avs_dai_nonhda_be_trigger, +}; + +static int __avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, + struct hdac_ext_link *link) +{ + struct hdac_ext_stream *link_stream; + struct avs_dma_data *data; + int ret; + + ret = avs_dai_startup(substream, dai); + if (ret) + return ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + link_stream = snd_hdac_ext_stream_assign(&data->adev->base.core, substream, + HDAC_EXT_STREAM_TYPE_LINK); + if (!link_stream) { + avs_dai_shutdown(substream, dai); + return -EBUSY; + } + + data->link_stream = link_stream; + data->link = link; + return 0; +} + +static int avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct hdac_ext_link *link; + struct avs_dma_data *data; + struct hda_codec *codec; + int ret; + + codec = dev_to_hda_codec(snd_soc_rtd_to_codec(rtd, 0)->dev); + + link = snd_hdac_ext_bus_get_hlink_by_addr(&codec->bus->core, codec->core.addr); + if (!link) + return -EINVAL; + + ret = __avs_dai_hda_be_startup(substream, dai, link); + if (!ret) { + data = snd_soc_dai_get_dma_data(dai, substream); + substream->runtime->private_data = data->link_stream; + } + + return ret; +} + +static int avs_dai_i2shda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dev *adev = to_avs_dev(dai->component->dev); + struct hdac_ext_link *link; + + link = snd_hdac_ext_bus_get_hlink_by_id(&adev->base.core, AZX_REG_ML_LEPTR_ID_INTEL_SSP); + if (!link) + return -EINVAL; + return __avs_dai_hda_be_startup(substream, dai, link); +} + +static int avs_dai_dmichda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dev *adev = to_avs_dev(dai->component->dev); + struct hdac_ext_link *link; + + link = snd_hdac_ext_bus_get_hlink_by_id(&adev->base.core, AZX_REG_ML_LEPTR_ID_INTEL_DMIC); + if (!link) + return -EINVAL; + return __avs_dai_hda_be_startup(substream, dai, link); +} + +static void avs_dai_hda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream); + + snd_hdac_ext_stream_release(data->link_stream, HDAC_EXT_STREAM_TYPE_LINK); + substream->runtime->private_data = NULL; + avs_dai_shutdown(substream, dai); +} + +static void avs_dai_althda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream); + + snd_hdac_ext_stream_release(data->link_stream, HDAC_EXT_STREAM_TYPE_LINK); + avs_dai_shutdown(substream, dai); +} + +static int avs_dai_hda_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + return avs_dai_be_hw_params(substream, hw_params, dai, + hdac_stream(data->link_stream)->stream_tag - 1); +} + +static int avs_dai_hda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_stream; + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + link_stream = data->link_stream; + link_stream->link_prepared = false; + avs_path_free(data->path); + data->path = NULL; + + /* clear link <-> stream mapping */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_hdac_ext_bus_link_clear_stream_id(data->link, + hdac_stream(link_stream)->stream_tag); + + return 0; +} + +static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); + const struct snd_soc_pcm_stream *stream_info; + struct hdac_ext_stream *link_stream; + const struct snd_pcm_hw_params *p; + struct avs_dma_data *data; + unsigned int format_val; + unsigned int bits; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + link_stream = data->link_stream; + p = &be->dpcm[substream->stream].hw_params; + + if (link_stream->link_prepared) + return 0; + + stream_info = snd_soc_dai_get_pcm_stream(dai, substream->stream); + bits = snd_hdac_stream_format_bits(params_format(p), params_subformat(p), + stream_info->sig_bits); + format_val = snd_hdac_stream_format(params_channels(p), bits, params_rate(p)); + + snd_hdac_ext_stream_decouple(&data->adev->base.core, link_stream, true); + snd_hdac_ext_stream_reset(link_stream); + snd_hdac_ext_stream_setup(link_stream, format_val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_hdac_ext_bus_link_set_stream_id(data->link, + hdac_stream(link_stream)->stream_tag); + + ret = avs_dai_prepare(substream, dai); + if (ret) + return ret; + + link_stream->link_prepared = true; + return 0; +} + +static int avs_dai_hda_be_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dma_data *data; + int ret = 0; + + dev_dbg(dai->dev, "entry %s cmd=%d\n", __func__, cmd); + + data = snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_stream_start(data->link_stream); + + ret = avs_path_pause(data->path); + if (ret < 0) { + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + break; + } + + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run BE path failed: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause BE path failed: %d\n", ret); + + snd_hdac_ext_stream_clear(data->link_stream); + + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset BE path failed: %d\n", ret); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops avs_dai_hda_be_ops = { + .startup = avs_dai_hda_be_startup, + .shutdown = avs_dai_hda_be_shutdown, + .hw_params = avs_dai_hda_be_hw_params, + .hw_free = avs_dai_hda_be_hw_free, + .prepare = avs_dai_hda_be_prepare, + .trigger = avs_dai_hda_be_trigger, +}; + +static const struct snd_soc_dai_ops avs_dai_i2shda_be_ops = { + .startup = avs_dai_i2shda_be_startup, + .shutdown = avs_dai_althda_be_shutdown, + .hw_params = avs_dai_hda_be_hw_params, + .hw_free = avs_dai_hda_be_hw_free, + .prepare = avs_dai_hda_be_prepare, + .trigger = avs_dai_hda_be_trigger, +}; + +static const struct snd_soc_dai_ops avs_dai_dmichda_be_ops = { + .startup = avs_dai_dmichda_be_startup, + .shutdown = avs_dai_althda_be_shutdown, + .hw_params = avs_dai_hda_be_hw_params, + .hw_free = avs_dai_hda_be_hw_free, + .prepare = avs_dai_hda_be_prepare, + .trigger = avs_dai_hda_be_trigger, +}; + +static int hw_rule_param_size(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *interval = hw_param_interval(params, rule->var); + struct snd_interval to; + + snd_interval_any(&to); + to.integer = interval->integer; + to.max = interval->max; + /* + * Commonly 2ms buffer size is used in HDA scenarios whereas 4ms is used + * when streaming through GPDMA. Align to the latter to account for both. + */ + to.min = params_rate(params) / 1000 * 4; + + if (rule->var == SNDRV_PCM_HW_PARAM_PERIOD_SIZE) + to.min /= params_periods(params); + + return snd_interval_refine(interval, &to); +} + +static int avs_pcm_hw_constraints_init(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + /* Avoid wrap-around with wall-clock. */ + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, 20, 178000000); + if (ret < 0) + return ret; + + /* Adjust buffer and period size based on the audio format. */ + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, hw_rule_param_size, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, hw_rule_param_size, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, -1); + + return 0; +} + +static int avs_dai_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *host_stream; + struct avs_dma_data *data; + struct hdac_bus *bus; + int ret; + + ret = avs_pcm_hw_constraints_init(substream); + if (ret) + return ret; + + ret = avs_dai_startup(substream, dai); + if (ret) + return ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + bus = &data->adev->base.core; + + host_stream = snd_hdac_ext_stream_assign(bus, substream, HDAC_EXT_STREAM_TYPE_HOST); + if (!host_stream) { + avs_dai_shutdown(substream, dai); + return -EBUSY; + } + + data->host_stream = host_stream; + snd_pcm_set_sync(substream); + + dev_dbg(dai->dev, "%s fe STARTUP tag %d str %p", + __func__, hdac_stream(host_stream)->stream_tag, substream); + + return 0; +} + +static void avs_dai_fe_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + + data = snd_soc_dai_get_dma_data(dai, substream); + + disable_work_sync(&data->period_elapsed_work); + snd_hdac_ext_stream_release(data->host_stream, HDAC_EXT_STREAM_TYPE_HOST); + avs_dai_shutdown(substream, dai); +} + +static int avs_dai_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct snd_pcm_hw_params *be_hw_params = NULL; + struct snd_soc_pcm_runtime *fe, *be; + struct snd_soc_dpcm *dpcm; + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + if (data->path) + return 0; + + host_stream = data->host_stream; + + hdac_stream(host_stream)->bufsize = 0; + hdac_stream(host_stream)->period_bytes = 0; + hdac_stream(host_stream)->format_val = 0; + + fe = snd_soc_substream_to_rtd(substream); + /* dpcm_fe_dai_open() guarantees the list is not empty at this point. */ + for_each_dpcm_be(fe, substream->stream, dpcm) { + be = dpcm->be; + be_hw_params = &be->dpcm[substream->stream].hw_params; + } + + ret = avs_dai_hw_params(substream, hw_params, be_hw_params, dai, + hdac_stream(host_stream)->stream_tag - 1); + if (ret) + goto create_err; + + ret = avs_path_bind(data->path); + if (ret < 0) { + dev_err(dai->dev, "bind FE <-> BE failed: %d\n", ret); + goto bind_err; + } + + return 0; + +bind_err: + avs_path_free(data->path); + data->path = NULL; +create_err: + snd_pcm_lib_free_pages(substream); + return ret; +} + +static int __avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + int ret; + + dev_dbg(dai->dev, "%s fe HW_FREE str %p rtd %p", + __func__, substream, substream->runtime); + + data = snd_soc_dai_get_dma_data(dai, substream); + if (!data->path) + return 0; + + host_stream = data->host_stream; + + ret = avs_path_unbind(data->path); + if (ret < 0) + dev_err(dai->dev, "unbind FE <-> BE failed: %d\n", ret); + + avs_path_free(data->path); + data->path = NULL; + snd_hdac_stream_cleanup(hdac_stream(host_stream)); + hdac_stream(host_stream)->prepared = false; + + return ret; +} + +static int avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + int ret; + + ret = __avs_dai_fe_hw_free(substream, dai); + snd_pcm_lib_free_pages(substream); + + return ret; +} + +static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + const struct snd_soc_pcm_stream *stream_info; + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + unsigned int format_val; + struct hdac_bus *bus; + unsigned int bits; + int ret; + + data = snd_soc_dai_get_dma_data(dai, substream); + host_stream = data->host_stream; + + if (runtime->state == SNDRV_PCM_STATE_XRUN) + hdac_stream(host_stream)->prepared = false; + if (hdac_stream(host_stream)->prepared) + return 0; + + bus = hdac_stream(host_stream)->bus; + snd_hdac_ext_stream_decouple(bus, data->host_stream, true); + snd_hdac_stream_reset(hdac_stream(host_stream)); + + stream_info = snd_soc_dai_get_pcm_stream(dai, substream->stream); + bits = snd_hdac_stream_format_bits(runtime->format, runtime->subformat, + stream_info->sig_bits); + format_val = snd_hdac_stream_format(runtime->channels, bits, runtime->rate); + + ret = snd_hdac_stream_set_params(hdac_stream(host_stream), format_val); + if (ret < 0) + return ret; + + ret = snd_hdac_ext_host_stream_setup(host_stream, false); + if (ret < 0) + return ret; + + ret = avs_dai_prepare(substream, dai); + if (ret) + return ret; + + hdac_stream(host_stream)->prepared = true; + return 0; +} + +static void avs_hda_stream_start(struct hdac_bus *bus, struct hdac_ext_stream *host_stream) +{ + struct hdac_stream *first_running = NULL; + struct hdac_stream *pos; + struct avs_dev *adev = hdac_to_avs(bus); + + list_for_each_entry(pos, &bus->stream_list, list) { + if (pos->running) { + if (first_running) + break; /* more than one running */ + first_running = pos; + } + } + + /* + * If host_stream is a CAPTURE stream and will be the only one running, + * disable L1SEN to avoid sound clipping. + */ + if (!first_running) { + if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE) + avs_hda_l1sen_enable(adev, false); + snd_hdac_stream_start(hdac_stream(host_stream)); + return; + } + + snd_hdac_stream_start(hdac_stream(host_stream)); + /* + * If host_stream is the first stream to break the rule above, + * re-enable L1SEN. + */ + if (list_entry_is_head(pos, &bus->stream_list, list) && + first_running->direction == SNDRV_PCM_STREAM_CAPTURE) + avs_hda_l1sen_enable(adev, true); +} + +static void avs_hda_stream_stop(struct hdac_bus *bus, struct hdac_ext_stream *host_stream) +{ + struct hdac_stream *first_running = NULL; + struct hdac_stream *pos; + struct avs_dev *adev = hdac_to_avs(bus); + + list_for_each_entry(pos, &bus->stream_list, list) { + if (pos == hdac_stream(host_stream)) + continue; /* ignore stream that is about to be stopped */ + if (pos->running) { + if (first_running) + break; /* more than one running */ + first_running = pos; + } + } + + /* + * If host_stream is a CAPTURE stream and is the only one running, + * re-enable L1SEN. + */ + if (!first_running) { + snd_hdac_stream_stop(hdac_stream(host_stream)); + if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE) + avs_hda_l1sen_enable(adev, true); + return; + } + + /* + * If by stopping host_stream there is only a single, CAPTURE stream running + * left, disable L1SEN to avoid sound clipping. + */ + if (list_entry_is_head(pos, &bus->stream_list, list) && + first_running->direction == SNDRV_PCM_STREAM_CAPTURE) + avs_hda_l1sen_enable(adev, false); + + snd_hdac_stream_stop(hdac_stream(host_stream)); +} + +static int avs_dai_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + struct hdac_bus *bus; + unsigned long flags; + int ret = 0; + + data = snd_soc_dai_get_dma_data(dai, substream); + host_stream = data->host_stream; + bus = hdac_stream(host_stream)->bus; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&bus->reg_lock, flags); + avs_hda_stream_start(bus, host_stream); + spin_unlock_irqrestore(&bus->reg_lock, flags); + + /* Timeout on DRSM poll shall not stop the resume so ignore the result. */ + if (cmd == SNDRV_PCM_TRIGGER_RESUME) + snd_hdac_stream_wait_drsm(hdac_stream(host_stream)); + + ret = avs_path_pause(data->path); + if (ret < 0) { + dev_err(dai->dev, "pause FE path failed: %d\n", ret); + break; + } + + ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO); + if (ret < 0) + dev_err(dai->dev, "run FE path failed: %d\n", ret); + + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + if (rtd->dai_link->ignore_suspend) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = avs_path_pause(data->path); + if (ret < 0) + dev_err(dai->dev, "pause FE path failed: %d\n", ret); + + spin_lock_irqsave(&bus->reg_lock, flags); + avs_hda_stream_stop(bus, host_stream); + spin_unlock_irqrestore(&bus->reg_lock, flags); + + ret = avs_path_reset(data->path); + if (ret < 0) + dev_err(dai->dev, "reset FE path failed: %d\n", ret); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +const struct snd_soc_dai_ops avs_dai_fe_ops = { + .startup = avs_dai_fe_startup, + .shutdown = avs_dai_fe_shutdown, + .hw_params = avs_dai_fe_hw_params, + .hw_free = avs_dai_fe_hw_free, + .prepare = avs_dai_fe_prepare, + .trigger = avs_dai_fe_trigger, +}; + +static ssize_t topology_name_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct snd_soc_component *component = file->private_data; + struct snd_soc_card *card = component->card; + struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); + char buf[64]; + size_t len; + + len = scnprintf(buf, sizeof(buf), "%s/%s\n", component->driver->topology_name_prefix, + mach->tplg_filename); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations topology_name_fops = { + .open = simple_open, + .read = topology_name_read, + .llseek = default_llseek, +}; + +static int avs_component_load_libraries(struct avs_soc_component *acomp) +{ + struct avs_tplg *tplg = acomp->tplg; + struct avs_dev *adev = to_avs_dev(acomp->base.dev); + int ret; + + if (!tplg->num_libs) + return 0; + + /* Parent device may be asleep and library loading involves IPCs. */ + ret = pm_runtime_resume_and_get(adev->dev); + if (ret < 0) + return ret; + + avs_hda_power_gating_enable(adev, false); + avs_hda_clock_gating_enable(adev, false); + avs_hda_l1sen_enable(adev, false); + + ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); + + avs_hda_l1sen_enable(adev, true); + avs_hda_clock_gating_enable(adev, true); + avs_hda_power_gating_enable(adev, true); + + if (!ret) + ret = avs_module_info_init(adev, false); + + pm_runtime_put_autosuspend(adev->dev); + + return ret; +} + +static int avs_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct snd_soc_acpi_mach *mach; + struct avs_soc_component *acomp; + struct avs_dev *adev; + char *filename; + int ret; + + dev_dbg(card->dev, "probing %s card %s\n", component->name, card->name); + mach = dev_get_platdata(card->dev); + acomp = to_avs_soc_component(component); + adev = to_avs_dev(component->dev); + + acomp->tplg = avs_tplg_new(component); + if (!acomp->tplg) + return -ENOMEM; + + if (!mach->tplg_filename) + goto finalize; + + /* Load specified topology and create debugfs for it. */ + filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, + mach->tplg_filename); + if (!filename) + return -ENOMEM; + + ret = avs_load_topology(component, filename); + kfree(filename); + if (ret == -ENOENT && !strncmp(mach->tplg_filename, "hda-", 4)) { + unsigned int vendor_id; + + if (sscanf(mach->tplg_filename, "hda-%08x-tplg.bin", &vendor_id) != 1) + return ret; + + if (((vendor_id >> 16) & 0xFFFF) == 0x8086) + mach->tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL, + "hda-8086-generic-tplg.bin"); + else + mach->tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL, + "hda-generic-tplg.bin"); + if (!mach->tplg_filename) + return -ENOMEM; + filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, + mach->tplg_filename); + if (!filename) + return -ENOMEM; + + dev_info(card->dev, "trying to load fallback topology %s\n", mach->tplg_filename); + ret = avs_load_topology(component, filename); + kfree(filename); + } + if (ret < 0) + return ret; + + ret = avs_component_load_libraries(acomp); + if (ret < 0) { + dev_err(card->dev, "libraries loading failed: %d\n", ret); + goto err_load_libs; + } + +finalize: + debugfs_create_file("topology_name", 0444, component->debugfs_root, component, + &topology_name_fops); + + mutex_lock(&adev->comp_list_mutex); + list_add_tail(&acomp->node, &adev->comp_list); + mutex_unlock(&adev->comp_list_mutex); + + return 0; + +err_load_libs: + avs_remove_topology(component); + return ret; +} + +static void avs_component_remove(struct snd_soc_component *component) +{ + struct avs_soc_component *acomp = to_avs_soc_component(component); + struct snd_soc_acpi_mach *mach; + struct avs_dev *adev = to_avs_dev(component->dev); + int ret; + + mach = dev_get_platdata(component->card->dev); + + mutex_lock(&adev->comp_list_mutex); + list_del(&acomp->node); + mutex_unlock(&adev->comp_list_mutex); + + if (mach->tplg_filename) { + ret = avs_remove_topology(component); + if (ret < 0) + dev_err(component->dev, "unload topology failed: %d\n", ret); + } +} + +static int avs_dai_resume_hw_params(struct snd_soc_dai *dai, struct avs_dma_data *data) +{ + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + int ret; + + substream = data->substream; + rtd = snd_soc_substream_to_rtd(substream); + + ret = dai->driver->ops->hw_params(substream, &rtd->dpcm[substream->stream].hw_params, dai); + if (ret) + dev_err(dai->dev, "hw_params on resume failed: %d\n", ret); + + return ret; +} + +static int avs_dai_resume_fe_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data) +{ + struct hdac_ext_stream *host_stream; + struct hdac_stream *hstream; + struct hdac_bus *bus; + int ret; + + host_stream = data->host_stream; + hstream = hdac_stream(host_stream); + bus = hdac_stream(host_stream)->bus; + + /* Set DRSM before programming stream and position registers. */ + snd_hdac_stream_drsm_enable(bus, true, hstream->index); + + ret = dai->driver->ops->prepare(data->substream, dai); + if (ret) { + dev_err(dai->dev, "prepare FE on resume failed: %d\n", ret); + return ret; + } + + writel(host_stream->pphcllpl, host_stream->pphc_addr + AZX_REG_PPHCLLPL); + writel(host_stream->pphcllpu, host_stream->pphc_addr + AZX_REG_PPHCLLPU); + writel(host_stream->pphcldpl, host_stream->pphc_addr + AZX_REG_PPHCLDPL); + writel(host_stream->pphcldpu, host_stream->pphc_addr + AZX_REG_PPHCLDPU); + + /* As per HW spec recommendation, program LPIB and DPIB to the same value. */ + snd_hdac_stream_set_lpib(hstream, hstream->lpib); + snd_hdac_stream_set_dpibr(bus, hstream, hstream->lpib); + + return 0; +} + +static int avs_dai_resume_be_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data) +{ + int ret; + + ret = dai->driver->ops->prepare(data->substream, dai); + if (ret) + dev_err(dai->dev, "prepare BE on resume failed: %d\n", ret); + + return ret; +} + +static int avs_dai_suspend_fe_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data) +{ + struct hdac_ext_stream *host_stream; + int ret; + + host_stream = data->host_stream; + + /* Store position addresses so we can resume from them later on. */ + hdac_stream(host_stream)->lpib = snd_hdac_stream_get_pos_lpib(hdac_stream(host_stream)); + host_stream->pphcllpl = readl(host_stream->pphc_addr + AZX_REG_PPHCLLPL); + host_stream->pphcllpu = readl(host_stream->pphc_addr + AZX_REG_PPHCLLPU); + host_stream->pphcldpl = readl(host_stream->pphc_addr + AZX_REG_PPHCLDPL); + host_stream->pphcldpu = readl(host_stream->pphc_addr + AZX_REG_PPHCLDPU); + + ret = __avs_dai_fe_hw_free(data->substream, dai); + if (ret < 0) + dev_err(dai->dev, "hw_free FE on suspend failed: %d\n", ret); + + return ret; +} + +static int avs_dai_suspend_be_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data) +{ + int ret; + + ret = dai->driver->ops->hw_free(data->substream, dai); + if (ret < 0) + dev_err(dai->dev, "hw_free BE on suspend failed: %d\n", ret); + + return ret; +} + +static int avs_component_pm_op(struct snd_soc_component *component, bool be, + int (*op)(struct snd_soc_dai *, struct avs_dma_data *)) +{ + struct snd_soc_pcm_runtime *rtd; + struct avs_dma_data *data; + struct snd_soc_dai *dai; + int ret; + + for_each_component_dais(component, dai) { + data = snd_soc_dai_dma_data_get_playback(dai); + if (data) { + rtd = snd_soc_substream_to_rtd(data->substream); + if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) { + ret = op(dai, data); + if (ret < 0) { + __snd_pcm_set_state(data->substream->runtime, + SNDRV_PCM_STATE_DISCONNECTED); + return ret; + } + } + } + + data = snd_soc_dai_dma_data_get_capture(dai); + if (data) { + rtd = snd_soc_substream_to_rtd(data->substream); + if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) { + ret = op(dai, data); + if (ret < 0) { + __snd_pcm_set_state(data->substream->runtime, + SNDRV_PCM_STATE_DISCONNECTED); + return ret; + } + } + } + } + + return 0; +} + +static int avs_component_resume_hw_params(struct snd_soc_component *component, bool be) +{ + return avs_component_pm_op(component, be, &avs_dai_resume_hw_params); +} + +static int avs_component_resume_prepare(struct snd_soc_component *component, bool be) +{ + int (*prepare_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data); + + if (be) + prepare_cb = &avs_dai_resume_be_prepare; + else + prepare_cb = &avs_dai_resume_fe_prepare; + + return avs_component_pm_op(component, be, prepare_cb); +} + +static int avs_component_suspend_hw_free(struct snd_soc_component *component, bool be) +{ + int (*hw_free_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data); + + if (be) + hw_free_cb = &avs_dai_suspend_be_hw_free; + else + hw_free_cb = &avs_dai_suspend_fe_hw_free; + + return avs_component_pm_op(component, be, hw_free_cb); +} + +static int avs_component_suspend(struct snd_soc_component *component) +{ + int ret; + + /* + * When freeing paths, FEs need to be first as they perform + * path unbinding. + */ + ret = avs_component_suspend_hw_free(component, false); + if (ret) + return ret; + + return avs_component_suspend_hw_free(component, true); +} + +static int avs_component_resume(struct snd_soc_component *component) +{ + int ret; + + /* + * When creating paths, FEs need to be last as they perform + * path binding. + */ + ret = avs_component_resume_hw_params(component, true); + if (ret) + return ret; + + ret = avs_component_resume_hw_params(component, false); + if (ret) + return ret; + + /* It is expected that the LINK stream is prepared first. */ + ret = avs_component_resume_prepare(component, true); + if (ret) + return ret; + + return avs_component_resume_prepare(component, false); +} + +static const struct snd_pcm_hardware avs_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, + .buffer_bytes_max = AZX_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = AZX_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = AZX_MAX_FRAG, + .fifo_size = 0, +}; + +static int avs_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + + /* only FE DAI links are handled here */ + if (rtd->dai_link->no_pcm) + return 0; + + return snd_soc_set_runtime_hwparams(substream, &avs_pcm_hardware); +} + +static unsigned int avs_hda_stream_dpib_read(struct hdac_ext_stream *stream) +{ + return readl(hdac_stream(stream)->bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(stream)->index)); +} + +static snd_pcm_uframes_t +avs_component_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct avs_dma_data *data; + struct hdac_ext_stream *host_stream; + unsigned int pos; + + data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); + if (!data->host_stream) + return 0; + + host_stream = data->host_stream; + pos = avs_hda_stream_dpib_read(host_stream); + + if (pos >= hdac_stream(host_stream)->bufsize) + pos = 0; + + return bytes_to_frames(substream->runtime, pos); +} + +static int avs_component_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return snd_pcm_lib_default_mmap(substream, vma); +} + +#define MAX_PREALLOC_SIZE (32 * 1024 * 1024) + +static int avs_component_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_pcm *pcm = rtd->pcm; + + if (dai->driver->playback.channels_min) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_DEV_SG, component->dev, 0, + MAX_PREALLOC_SIZE); + + if (dai->driver->capture.channels_min) + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV_SG, component->dev, 0, + MAX_PREALLOC_SIZE); + + return 0; +} + +static struct snd_soc_component_driver avs_component_driver = { + .name = "avs-pcm", + .probe = avs_component_probe, + .remove = avs_component_remove, + .suspend = avs_component_suspend, + .resume = avs_component_resume, + .open = avs_component_open, + .pointer = avs_component_pointer, + .mmap = avs_component_mmap, + .pcm_construct = avs_component_construct, + .module_get_upon_open = 1, /* increment refcount when a pcm is opened */ + .topology_name_prefix = "intel/avs", +}; + +int avs_register_component(struct device *dev, const char *name, + struct snd_soc_component_driver *drv, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) +{ + struct avs_soc_component *acomp; + int ret; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + + acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); + if (!acomp->base.name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + + ret = snd_soc_component_initialize(&acomp->base, drv, dev); + if (ret < 0) + return ret; + + return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); +} + +static struct snd_soc_dai_driver dmic_cpu_dais[] = { +{ + .name = "DMIC Pin", + .capture = { + .stream_name = "DMIC Rx", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "DMIC WoV Pin", + .capture = { + .stream_name = "DMIC WoV Rx", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +int avs_register_dmic_component(struct avs_dev *adev, const char *name) +{ + const struct snd_soc_dai_ops *ops; + + if (avs_platattr_test(adev, ALTHDA)) + ops = &avs_dai_dmichda_be_ops; + else + ops = &avs_dai_nonhda_be_ops; + + dmic_cpu_dais[0].ops = ops; + dmic_cpu_dais[1].ops = ops; + return avs_register_component(adev->dev, name, &avs_component_driver, dmic_cpu_dais, + ARRAY_SIZE(dmic_cpu_dais)); +} + +static const struct snd_soc_dai_driver i2s_dai_template = { + .playback = { + .channels_min = 1, + .channels_max = AVS_CHANNELS_MAX, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_12000 | + SNDRV_PCM_RATE_24000 | + SNDRV_PCM_RATE_128000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, + }, + .capture = { + .channels_min = 1, + .channels_max = AVS_CHANNELS_MAX, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_12000 | + SNDRV_PCM_RATE_24000 | + SNDRV_PCM_RATE_128000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, + }, +}; + +int avs_register_i2s_component(struct avs_dev *adev, const char *name, unsigned long port_mask, + unsigned long *tdms) +{ + struct snd_soc_dai_driver *cpus, *dai; + const struct snd_soc_dai_ops *ops; + size_t ssp_count, cpu_count; + int i, j; + + ssp_count = adev->hw_cfg.i2s_caps.ctrl_count; + if (avs_platattr_test(adev, ALTHDA)) + ops = &avs_dai_i2shda_be_ops; + else + ops = &avs_dai_nonhda_be_ops; + + cpu_count = 0; + for_each_set_bit(i, &port_mask, ssp_count) + if (!tdms || test_bit(0, &tdms[i])) + cpu_count++; + if (tdms) + for_each_set_bit(i, &port_mask, ssp_count) + cpu_count += hweight_long(tdms[i]); + + cpus = devm_kcalloc(adev->dev, cpu_count, sizeof(*cpus), GFP_KERNEL); + if (!cpus) + return -ENOMEM; + + dai = cpus; + for_each_set_bit(i, &port_mask, ssp_count) { + if (!tdms || test_bit(0, &tdms[i])) { + memcpy(dai, &i2s_dai_template, sizeof(*dai)); + + dai->name = + devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d Pin", i); + dai->playback.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Tx", i); + dai->capture.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Rx", i); + + if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) + return -ENOMEM; + dai->ops = ops; + dai++; + } + } + + if (!tdms) + goto plat_register; + + for_each_set_bit(i, &port_mask, ssp_count) { + for_each_set_bit(j, &tdms[i], AVS_CHANNELS_MAX) { + memcpy(dai, &i2s_dai_template, sizeof(*dai)); + + dai->name = + devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d:%d Pin", i, j); + dai->playback.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Tx", i, j); + dai->capture.stream_name = + devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Rx", i, j); + + if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) + return -ENOMEM; + dai->ops = ops; + dai++; + } + } + +plat_register: + return avs_register_component(adev->dev, name, &avs_component_driver, cpus, cpu_count); +} + +/* HD-Audio CPU DAI template */ +static const struct snd_soc_dai_driver hda_cpu_dai = { + .ops = &avs_dai_hda_be_ops, + .playback = { + .channels_min = 1, + .channels_max = AVS_CHANNELS_MAX, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, + }, + .capture = { + .channels_min = 1, + .channels_max = AVS_CHANNELS_MAX, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, + }, +}; + +static void avs_component_hda_unregister_dais(struct snd_soc_component *component) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_dai *dai, *save; + struct avs_mach_pdata *pdata; + struct hda_codec *codec; + char name[32]; + + mach = dev_get_platdata(component->card->dev); + pdata = mach->pdata; + codec = pdata->codec; + snprintf(name, sizeof(name), "%s-cpu", dev_name(&codec->core.dev)); + + for_each_component_dais_safe(component, dai, save) { + int stream; + + if (!strstr(dai->driver->name, name)) + continue; + + for_each_pcm_streams(stream) + snd_soc_dapm_free_widget(snd_soc_dai_get_widget(dai, stream)); + + snd_soc_unregister_dai(dai); + } +} + +static int avs_component_hda_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_dai_driver *dais; + struct snd_soc_acpi_mach *mach; + struct avs_mach_pdata *pdata; + struct hda_codec *codec; + struct hda_pcm *pcm; + const char *cname; + int pcm_count = 0, ret, i; + + mach = dev_get_platdata(component->card->dev); + if (!mach) + return -EINVAL; + + pdata = mach->pdata; + codec = pdata->codec; + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + dais = devm_kcalloc(component->dev, pcm_count, sizeof(*dais), + GFP_KERNEL); + if (!dais) + return -ENOMEM; + + cname = dev_name(&codec->core.dev); + dapm = snd_soc_component_to_dapm(component); + pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); + + for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { + struct snd_soc_dai *dai; + + memcpy(&dais[i], &hda_cpu_dai, sizeof(*dais)); + dais[i].id = i; + dais[i].name = devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d", cname, i); + if (!dais[i].name) { + ret = -ENOMEM; + goto exit; + } + + if (pcm->stream[0].substreams) { + dais[i].playback.stream_name = + devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d Tx", cname, i); + if (!dais[i].playback.stream_name) { + ret = -ENOMEM; + goto exit; + } + + if (!hda_codec_is_display(codec)) { + dais[i].playback.formats = pcm->stream[0].formats; + dais[i].playback.subformats = pcm->stream[0].subformats; + dais[i].playback.rates = pcm->stream[0].rates; + dais[i].playback.channels_min = pcm->stream[0].channels_min; + dais[i].playback.channels_max = pcm->stream[0].channels_max; + dais[i].playback.sig_bits = pcm->stream[0].maxbps; + } + } + + if (pcm->stream[1].substreams) { + dais[i].capture.stream_name = + devm_kasprintf(component->dev, GFP_KERNEL, + "%s-cpu%d Rx", cname, i); + if (!dais[i].capture.stream_name) { + ret = -ENOMEM; + goto exit; + } + + if (!hda_codec_is_display(codec)) { + dais[i].capture.formats = pcm->stream[1].formats; + dais[i].capture.subformats = pcm->stream[1].subformats; + dais[i].capture.rates = pcm->stream[1].rates; + dais[i].capture.channels_min = pcm->stream[1].channels_min; + dais[i].capture.channels_max = pcm->stream[1].channels_max; + dais[i].capture.sig_bits = pcm->stream[1].maxbps; + } + } + + dai = snd_soc_register_dai(component, &dais[i], false); + if (!dai) { + dev_err(component->dev, "register dai for %s failed\n", + pcm->name); + ret = -EINVAL; + goto exit; + } + + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret < 0) { + dev_err(component->dev, "create widgets failed: %d\n", + ret); + snd_soc_unregister_dai(dai); + goto exit; + } + } + + ret = avs_component_probe(component); +exit: + if (ret) + avs_component_hda_unregister_dais(component); + + return ret; +} + +static void avs_component_hda_remove(struct snd_soc_component *component) +{ + avs_component_remove(component); + avs_component_hda_unregister_dais(component); +} + +static int avs_component_hda_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + + if (!rtd->dai_link->no_pcm) { + struct snd_pcm_hardware hwparams = avs_pcm_hardware; + struct snd_soc_pcm_runtime *be; + struct snd_soc_dpcm *dpcm; + int dir = substream->stream; + + /* + * Support the DPCM reparenting while still fulfilling expectations of HDAudio + * common code - a valid stream pointer at substream->runtime->private_data - + * by having all FEs point to the same private data. + */ + for_each_dpcm_be(rtd, dir, dpcm) { + struct snd_pcm_substream *be_substream; + + be = dpcm->be; + if (be->dpcm[dir].users == 1) + break; + + be_substream = snd_soc_dpcm_get_substream(be, dir); + substream->runtime->private_data = be_substream->runtime->private_data; + break; + } + + /* RESUME unsupported for de-coupled HD-Audio capture. */ + if (dir == SNDRV_PCM_STREAM_CAPTURE) + hwparams.info &= ~SNDRV_PCM_INFO_RESUME; + + return snd_soc_set_runtime_hwparams(substream, &hwparams); + } + + return 0; +} + +static struct snd_soc_component_driver avs_hda_component_driver = { + .name = "avs-hda-pcm", + .probe = avs_component_hda_probe, + .remove = avs_component_hda_remove, + .suspend = avs_component_suspend, + .resume = avs_component_resume, + .open = avs_component_hda_open, + .pointer = avs_component_pointer, + .mmap = avs_component_mmap, + .pcm_construct = avs_component_construct, + /* + * hda platform component's probe() is dependent on + * codec->pcm_list_head, it needs to be initialized after codec + * component. remove_order is here for completeness sake + */ + .probe_order = SND_SOC_COMP_ORDER_LATE, + .remove_order = SND_SOC_COMP_ORDER_EARLY, + .module_get_upon_open = 1, + .topology_name_prefix = "intel/avs", +}; + +int avs_register_hda_component(struct avs_dev *adev, const char *name) +{ + return avs_register_component(adev->dev, name, &avs_hda_component_driver, NULL, 0); +} diff --git a/sound/soc/intel/avs/pcm.h b/sound/soc/intel/avs/pcm.h new file mode 100644 index 000000000000..0f3615c90398 --- /dev/null +++ b/sound/soc/intel/avs/pcm.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2024 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_PCM_H +#define __SOUND_SOC_INTEL_AVS_PCM_H + +#include <sound/pcm.h> + +void avs_period_elapsed(struct snd_pcm_substream *substream); + +#endif diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c new file mode 100644 index 000000000000..74096236984a --- /dev/null +++ b/sound/soc/intel/avs/probes.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <sound/compress_driver.h> +#include <sound/hdaudio_ext.h> +#include <sound/hdaudio.h> +#include <sound/soc.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" + +static int avs_dsp_init_probe(struct avs_dev *adev, struct snd_compr_params *params, int bps, + union avs_connector_node_id node_id, size_t buffer_size) +{ + struct avs_probe_cfg cfg = {{0}}; + struct avs_module_entry mentry; + u8 dummy; + int ret; + + ret = avs_get_module_entry(adev, &AVS_PROBE_MOD_UUID, &mentry); + if (ret) + return ret; + + /* + * Probe module uses no cycles, input and output frame sizes are unused. + * It is also not owned by any pipeline. + */ + cfg.base.ibs = 1; + /* BSS module descriptor is always segment of index=2. */ + cfg.base.is_pages = mentry.segments[2].flags.length; + cfg.base.audio_fmt.sampling_freq = params->codec.sample_rate; + cfg.base.audio_fmt.bit_depth = bps; + cfg.base.audio_fmt.num_channels = params->codec.ch_out; + cfg.base.audio_fmt.valid_bit_depth = bps; + cfg.gtw_cfg.node_id = node_id; + cfg.gtw_cfg.dma_buffer_size = buffer_size; + + return avs_dsp_init_module(adev, mentry.module_id, INVALID_PIPELINE_ID, 0, 0, &cfg, + sizeof(cfg), &dummy); +} + +static void avs_dsp_delete_probe(struct avs_dev *adev) +{ + struct avs_module_entry mentry; + int ret; + + ret = avs_get_module_entry(adev, &AVS_PROBE_MOD_UUID, &mentry); + if (!ret) + /* There is only ever one probe module instance. */ + avs_dsp_delete_module(adev, mentry.module_id, 0, INVALID_PIPELINE_ID, 0); +} + +static inline struct hdac_ext_stream *avs_compr_get_host_stream(struct snd_compr_stream *cstream) +{ + return cstream->runtime->private_data; +} + +static int avs_probe_compr_open(struct snd_compr_stream *cstream, struct snd_soc_dai *dai) +{ + struct avs_dev *adev = to_avs_dev(dai->dev); + struct hdac_bus *bus = &adev->base.core; + struct hdac_ext_stream *host_stream; + + if (adev->extractor) { + dev_err(dai->dev, "Cannot open more than one extractor stream\n"); + return -EEXIST; + } + + host_stream = snd_hdac_ext_cstream_assign(bus, cstream); + if (!host_stream) { + dev_err(dai->dev, "Failed to assign HDAudio stream for extraction\n"); + return -EBUSY; + } + + adev->extractor = host_stream; + hdac_stream(host_stream)->curr_pos = 0; + cstream->runtime->private_data = host_stream; + + return 0; +} + +static int avs_probe_compr_free(struct snd_compr_stream *cstream, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *host_stream = avs_compr_get_host_stream(cstream); + struct avs_dev *adev = to_avs_dev(dai->dev); + struct avs_probe_point_desc *desc; + /* Extractor node identifier. */ + unsigned int vindex = INVALID_NODE_ID.vindex; + size_t num_desc; + int i, ret; + + /* Disconnect all probe points. */ + ret = avs_ipc_probe_get_points(adev, &desc, &num_desc); + if (ret) { + dev_err(dai->dev, "get probe points failed: %d\n", ret); + ret = AVS_IPC_RET(ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + if (desc[i].node_id.vindex == vindex) + avs_ipc_probe_disconnect_points(adev, &desc[i].id, 1); + kfree(desc); + +exit: + if (adev->num_probe_streams) { + adev->num_probe_streams--; + if (!adev->num_probe_streams) { + avs_dsp_delete_probe(adev); + avs_dsp_enable_d0ix(adev); + } + } + + snd_hdac_stream_cleanup(hdac_stream(host_stream)); + hdac_stream(host_stream)->prepared = 0; + snd_hdac_ext_stream_release(host_stream, HDAC_EXT_STREAM_TYPE_HOST); + + snd_compr_free_pages(cstream); + adev->extractor = NULL; + + return ret; +} + +static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *host_stream = avs_compr_get_host_stream(cstream); + struct snd_compr_runtime *rtd = cstream->runtime; + struct avs_dev *adev = to_avs_dev(dai->dev); + unsigned int format_val; + int bps, ret; + + hdac_stream(host_stream)->bufsize = 0; + hdac_stream(host_stream)->period_bytes = 0; + hdac_stream(host_stream)->format_val = 0; + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = adev->dev; + + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); + ret = snd_hdac_stream_set_params(hdac_stream(host_stream), format_val); + if (ret < 0) + return ret; + ret = snd_hdac_stream_setup(hdac_stream(host_stream), false); + if (ret < 0) + return ret; + + hdac_stream(host_stream)->prepared = 1; + + if (!adev->num_probe_streams) { + union avs_connector_node_id node_id; + + /* D0ix not allowed during probing. */ + ret = avs_dsp_disable_d0ix(adev); + if (ret) + return ret; + + node_id.vindex = hdac_stream(host_stream)->stream_tag - 1; + node_id.dma_type = AVS_DMA_HDA_HOST_INPUT; + + ret = avs_dsp_init_probe(adev, params, bps, node_id, rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "probe init failed: %d\n", ret); + avs_dsp_enable_d0ix(adev); + return ret; + } + } + + adev->num_probe_streams++; + return 0; +} + +static int avs_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *host_stream = avs_compr_get_host_stream(cstream); + struct avs_dev *adev = to_avs_dev(dai->dev); + struct hdac_bus *bus = &adev->base.core; + unsigned long cookie; + + if (!hdac_stream(host_stream)->prepared) + return -EPIPE; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + spin_lock_irqsave(&bus->reg_lock, cookie); + snd_hdac_stream_start(hdac_stream(host_stream)); + spin_unlock_irqrestore(&bus->reg_lock, cookie); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + spin_lock_irqsave(&bus->reg_lock, cookie); + snd_hdac_stream_stop(hdac_stream(host_stream)); + spin_unlock_irqrestore(&bus->reg_lock, cookie); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int avs_probe_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *host_stream = avs_compr_get_host_stream(cstream); + struct snd_soc_pcm_stream *pstream; + + pstream = &dai->driver->capture; + tstamp->copied_total = hdac_stream(host_stream)->curr_pos; + tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates); + + return 0; +} + +static int avs_probe_compr_copy(struct snd_soc_component *comp, struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} + +static const struct snd_soc_cdai_ops avs_probe_cdai_ops = { + .startup = avs_probe_compr_open, + .shutdown = avs_probe_compr_free, + .set_params = avs_probe_compr_set_params, + .trigger = avs_probe_compr_trigger, + .pointer = avs_probe_compr_pointer, +}; + +static const struct snd_soc_dai_ops avs_probe_dai_ops = { + .compress_new = snd_soc_new_compress, +}; + +static const struct snd_compress_ops avs_probe_compress_ops = { + .copy = avs_probe_compr_copy, +}; + +static struct snd_soc_dai_driver probe_cpu_dais[] = { +{ + .name = "Probe Extraction CPU DAI", + .cops = &avs_probe_cdai_ops, + .ops = &avs_probe_dai_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +}; + +static const struct snd_soc_component_driver avs_probe_component_driver = { + .name = "avs-probe-compr", + .compress_ops = &avs_probe_compress_ops, + .module_get_upon_open = 1, /* increment refcount when a stream is opened */ +}; + +int avs_register_probe_component(struct avs_dev *adev, const char *name) +{ + struct snd_soc_component *component; + int ret; + + component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); + if (!component) + return -ENOMEM; + + component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); + if (!component->name) + return -ENOMEM; + + ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); + if (ret) + return ret; + + return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); +} diff --git a/sound/soc/intel/avs/ptl.c b/sound/soc/intel/avs/ptl.c new file mode 100644 index 000000000000..07da9b0aa2b8 --- /dev/null +++ b/sound/soc/intel/avs/ptl.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2024-2025 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "debug.h" +#include "registers.h" +#include "trace.h" + +#define MTL_HfDSSGBL_BASE 0x1000 +#define MTL_REG_HfDSSCS (MTL_HfDSSGBL_BASE + 0x0) +#define MTL_HfDSSCS_SPA BIT(16) +#define MTL_HfDSSCS_CPA BIT(24) + +#define MTL_DSPCS_BASE 0x178D00 +#define MTL_REG_DSPCCTL (MTL_DSPCS_BASE + 0x4) +#define MTL_DSPCCTL_OSEL GENMASK(25, 24) +#define MTL_DSPCCTL_OSEL_HOST BIT(25) + +static int avs_ptl_core_power_on(struct avs_dev *adev) +{ + u32 reg; + int ret; + + /* Power up DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfDSSCS, MTL_HfDSSCS_SPA, MTL_HfDSSCS_SPA); + trace_avs_dsp_core_op(1, AVS_MAIN_CORE_MASK, "power dsp", true); + + ret = snd_hdac_adsp_readl_poll(adev, MTL_REG_HfDSSCS, reg, + (reg & MTL_HfDSSCS_CPA) == MTL_HfDSSCS_CPA, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); + if (ret) { + dev_err(adev->dev, "power on domain dsp failed: %d\n", ret); + return ret; + } + + /* Prevent power gating of DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfPWRCTL2, MTL_HfPWRCTL2_WPDSPHPxPG, + MTL_HfPWRCTL2_WPDSPHPxPG); + trace_avs_dsp_core_op(1, AVS_MAIN_CORE_MASK, "prevent dsp PG", true); + + ret = snd_hdac_adsp_readl_poll(adev, MTL_REG_HfPWRSTS2, reg, + (reg & MTL_HfPWRSTS2_DSPHPxPGS) == MTL_HfPWRSTS2_DSPHPxPGS, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); + + /* Set ownership to HOST. */ + snd_hdac_adsp_updatel(adev, MTL_REG_DSPCCTL, MTL_DSPCCTL_OSEL, MTL_DSPCCTL_OSEL_HOST); + return ret; +} + +static int avs_ptl_core_power_off(struct avs_dev *adev) +{ + u32 reg; + + /* Allow power gating of DSP domain. No STS polling as HOST is only one of its users. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfPWRCTL2, MTL_HfPWRCTL2_WPDSPHPxPG, 0); + trace_avs_dsp_core_op(0, AVS_MAIN_CORE_MASK, "allow dsp pg", false); + + /* Power down DSP domain. */ + snd_hdac_adsp_updatel(adev, MTL_REG_HfDSSCS, MTL_HfDSSCS_SPA, 0); + trace_avs_dsp_core_op(0, AVS_MAIN_CORE_MASK, "power dsp", false); + + return snd_hdac_adsp_readl_poll(adev, MTL_REG_HfDSSCS, reg, + (reg & MTL_HfDSSCS_CPA) == 0, + AVS_ADSPCS_INTERVAL_US, AVS_ADSPCS_TIMEOUT_US); +} + +static int avs_ptl_core_power(struct avs_dev *adev, u32 core_mask, bool power) +{ + core_mask &= AVS_MAIN_CORE_MASK; + if (!core_mask) + return 0; + + if (power) + return avs_ptl_core_power_on(adev); + return avs_ptl_core_power_off(adev); +} + +const struct avs_dsp_ops avs_ptl_dsp_ops = { + .power = avs_ptl_core_power, + .reset = avs_mtl_core_reset, + .stall = avs_lnl_core_stall, + .dsp_interrupt = avs_mtl_dsp_interrupt, + .int_control = avs_mtl_interrupt_control, + .load_basefw = avs_hda_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .log_buffer_offset = avs_icl_log_buffer_offset, + .log_buffer_status = avs_apl_log_buffer_status, + .coredump = avs_apl_coredump, + .d0ix_toggle = avs_icl_d0ix_toggle, + .set_d0ix = avs_icl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(icl) +}; diff --git a/sound/soc/intel/avs/registers.h b/sound/soc/intel/avs/registers.h new file mode 100644 index 000000000000..97767882ffa1 --- /dev/null +++ b/sound/soc/intel/avs/registers.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021-2022 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_REGS_H +#define __SOUND_SOC_INTEL_AVS_REGS_H + +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/iopoll.h> +#include <linux/sizes.h> + +#define AZX_PCIREG_PGCTL 0x44 +#define AZX_PCIREG_CGCTL 0x48 +#define AZX_PGCTL_LSRMD_MASK BIT(4) +#define AZX_CGCTL_MISCBDCGE_MASK BIT(6) +#define AZX_VS_EM2_L1SEN BIT(13) +#define AZX_VS_EM2_DUM BIT(23) + +/* Intel HD Audio General DSP Registers */ +#define AVS_ADSP_GEN_BASE 0x0 +#define AVS_ADSP_REG_ADSPCS (AVS_ADSP_GEN_BASE + 0x04) +#define AVS_ADSP_REG_ADSPIC (AVS_ADSP_GEN_BASE + 0x08) +#define AVS_ADSP_REG_ADSPIS (AVS_ADSP_GEN_BASE + 0x0C) + +#define AVS_ADSP_ADSPIC_IPC BIT(0) +#define AVS_ADSP_ADSPIC_CLDMA BIT(1) +#define AVS_ADSP_ADSPIS_IPC BIT(0) +#define AVS_ADSP_ADSPIS_CLDMA BIT(1) + +#define AVS_ADSPCS_CRST_MASK(cm) (cm) +#define AVS_ADSPCS_CSTALL_MASK(cm) ((cm) << 8) +#define AVS_ADSPCS_SPA_MASK(cm) ((cm) << 16) +#define AVS_ADSPCS_CPA_MASK(cm) ((cm) << 24) +#define AVS_ADSPCS_INTERVAL_US 500 +#define AVS_ADSPCS_TIMEOUT_US 10000 +#define AVS_MAIN_CORE_MASK BIT(0) + +#define AVS_ADSP_HIPCCTL_BUSY BIT(0) +#define AVS_ADSP_HIPCCTL_DONE BIT(1) + +/* SKL Intel HD Audio Inter-Processor Communication Registers */ +#define SKL_ADSP_IPC_BASE 0x40 +#define SKL_ADSP_REG_HIPCT (SKL_ADSP_IPC_BASE + 0x00) +#define SKL_ADSP_REG_HIPCTE (SKL_ADSP_IPC_BASE + 0x04) +#define SKL_ADSP_REG_HIPCI (SKL_ADSP_IPC_BASE + 0x08) +#define SKL_ADSP_REG_HIPCIE (SKL_ADSP_IPC_BASE + 0x0C) +#define SKL_ADSP_REG_HIPCCTL (SKL_ADSP_IPC_BASE + 0x10) + +#define SKL_ADSP_HIPCI_BUSY BIT(31) +#define SKL_ADSP_HIPCIE_DONE BIT(30) +#define SKL_ADSP_HIPCT_BUSY BIT(31) + +/* CNL Intel HD Audio Inter-Processor Communication Registers */ +#define CNL_ADSP_IPC_BASE 0xC0 +#define CNL_ADSP_REG_HIPCTDR (CNL_ADSP_IPC_BASE + 0x00) +#define CNL_ADSP_REG_HIPCTDA (CNL_ADSP_IPC_BASE + 0x04) +#define CNL_ADSP_REG_HIPCTDD (CNL_ADSP_IPC_BASE + 0x08) +#define CNL_ADSP_REG_HIPCIDR (CNL_ADSP_IPC_BASE + 0x10) +#define CNL_ADSP_REG_HIPCIDA (CNL_ADSP_IPC_BASE + 0x14) +#define CNL_ADSP_REG_HIPCIDD (CNL_ADSP_IPC_BASE + 0x18) +#define CNL_ADSP_REG_HIPCCTL (CNL_ADSP_IPC_BASE + 0x28) + +#define CNL_ADSP_HIPCTDR_BUSY BIT(31) +#define CNL_ADSP_HIPCTDA_DONE BIT(31) +#define CNL_ADSP_HIPCIDR_BUSY BIT(31) +#define CNL_ADSP_HIPCIDA_DONE BIT(31) + +/* MTL Intel HOST Inter-Processor Communication Registers */ +#define MTL_HfIPC_BASE 0x73000 +#define MTL_REG_HfIPCxTDR (MTL_HfIPC_BASE + 0x200) +#define MTL_REG_HfIPCxTDA (MTL_HfIPC_BASE + 0x204) +#define MTL_REG_HfIPCxIDR (MTL_HfIPC_BASE + 0x210) +#define MTL_REG_HfIPCxIDA (MTL_HfIPC_BASE + 0x214) +#define MTL_REG_HfIPCxCTL (MTL_HfIPC_BASE + 0x228) +#define MTL_REG_HfIPCxTDD (MTL_HfIPC_BASE + 0x300) +#define MTL_REG_HfIPCxIDD (MTL_HfIPC_BASE + 0x380) + +#define MTL_HfIPCxTDR_BUSY BIT(31) +#define MTL_HfIPCxTDA_BUSY BIT(31) +#define MTL_HfIPCxIDR_BUSY BIT(31) +#define MTL_HfIPCxIDA_DONE BIT(31) + +#define MTL_HfFLV_BASE 0x162000 +#define MTL_REG_HfFLGP(x, y) (MTL_HfFLV_BASE + 0x1200 + (x) * 0x20 + (y) * 0x08) +#define LNL_REG_HfDFR(x) (0x160200 + (x) * 0x8) + +#define MTL_DWICTL_BASE 0x1800 +#define MTL_DWICTL_REG_INTENL (MTL_DWICTL_BASE + 0x0) +#define MTL_DWICTL_REG_FINALSTATUSL (MTL_DWICTL_BASE + 0x30) + +#define MTL_HfPMCCU_BASE 0x1D00 +#define MTL_REG_HfCLKCTL (MTL_HfPMCCU_BASE + 0x10) +#define MTL_REG_HfPWRCTL (MTL_HfPMCCU_BASE + 0x18) +#define MTL_REG_HfPWRSTS (MTL_HfPMCCU_BASE + 0x1C) +#define MTL_REG_HfPWRCTL2 (MTL_HfPMCCU_BASE + 0x20) +#define MTL_REG_HfPWRSTS2 (MTL_HfPMCCU_BASE + 0x24) +#define MTL_HfPWRCTL_WPDSPHPxPG BIT(0) +#define MTL_HfPWRSTS_DSPHPxPGS BIT(0) +#define MTL_HfPWRCTL2_WPDSPHPxPG BIT(0) +#define MTL_HfPWRSTS2_DSPHPxPGS BIT(0) + +/* Intel HD Audio SRAM windows base addresses */ +#define SKL_ADSP_SRAM_BASE_OFFSET 0x8000 +#define SKL_ADSP_SRAM_WINDOW_SIZE 0x2000 +#define APL_ADSP_SRAM_BASE_OFFSET 0x80000 +#define APL_ADSP_SRAM_WINDOW_SIZE 0x20000 +#define MTL_ADSP_SRAM_BASE_OFFSET 0x180000 +#define MTL_ADSP_SRAM_WINDOW_SIZE 0x8000 + +/* Constants used when accessing SRAM, space shared with firmware */ +#define AVS_FW_REG_BASE(adev) ((adev)->spec->hipc->sts_offset) +#define AVS_FW_REG_STATUS(adev) (AVS_FW_REG_BASE(adev) + 0x0) +#define AVS_FW_REG_ERROR(adev) (AVS_FW_REG_BASE(adev) + 0x4) + +#define AVS_WINDOW_CHUNK_SIZE SZ_4K +#define AVS_FW_REGS_SIZE AVS_WINDOW_CHUNK_SIZE +#define AVS_FW_REGS_WINDOW 0 +/* DSP -> HOST communication window */ +#define AVS_UPLINK_WINDOW AVS_FW_REGS_WINDOW +/* HOST -> DSP communication window */ +#define AVS_DOWNLINK_WINDOW 1 +#define AVS_DEBUG_WINDOW 2 + +/* registry I/O helpers */ +#define avs_sram_offset(adev, window_idx) \ + ((adev)->spec->sram->base_offset + \ + (adev)->spec->sram->window_size * (window_idx)) + +#define avs_sram_addr(adev, window_idx) \ + ((adev)->dsp_ba + avs_sram_offset(adev, window_idx)) + +#define avs_uplink_addr(adev) \ + (avs_sram_addr(adev, AVS_UPLINK_WINDOW) + AVS_FW_REGS_SIZE) +#define avs_downlink_addr(adev) \ + avs_sram_addr(adev, AVS_DOWNLINK_WINDOW) + +#define snd_hdac_adsp_writeb(adev, reg, value) \ + snd_hdac_reg_writeb(&(adev)->base.core, (adev)->dsp_ba + (reg), value) +#define snd_hdac_adsp_readb(adev, reg) \ + snd_hdac_reg_readb(&(adev)->base.core, (adev)->dsp_ba + (reg)) +#define snd_hdac_adsp_writew(adev, reg, value) \ + snd_hdac_reg_writew(&(adev)->base.core, (adev)->dsp_ba + (reg), value) +#define snd_hdac_adsp_readw(adev, reg) \ + snd_hdac_reg_readw(&(adev)->base.core, (adev)->dsp_ba + (reg)) +#define snd_hdac_adsp_writel(adev, reg, value) \ + snd_hdac_reg_writel(&(adev)->base.core, (adev)->dsp_ba + (reg), value) +#define snd_hdac_adsp_readl(adev, reg) \ + snd_hdac_reg_readl(&(adev)->base.core, (adev)->dsp_ba + (reg)) +#define snd_hdac_adsp_writeq(adev, reg, value) \ + snd_hdac_reg_writeq(&(adev)->base.core, (adev)->dsp_ba + (reg), value) +#define snd_hdac_adsp_readq(adev, reg) \ + snd_hdac_reg_readq(&(adev)->base.core, (adev)->dsp_ba + (reg)) + +#define snd_hdac_adsp_updateb(adev, reg, mask, val) \ + snd_hdac_adsp_writeb(adev, reg, \ + (snd_hdac_adsp_readb(adev, reg) & ~(mask)) | (val)) +#define snd_hdac_adsp_updatew(adev, reg, mask, val) \ + snd_hdac_adsp_writew(adev, reg, \ + (snd_hdac_adsp_readw(adev, reg) & ~(mask)) | (val)) +#define snd_hdac_adsp_updatel(adev, reg, mask, val) \ + snd_hdac_adsp_writel(adev, reg, \ + (snd_hdac_adsp_readl(adev, reg) & ~(mask)) | (val)) +#define snd_hdac_adsp_updateq(adev, reg, mask, val) \ + snd_hdac_adsp_writeq(adev, reg, \ + (snd_hdac_adsp_readq(adev, reg) & ~(mask)) | (val)) + +#define snd_hdac_adsp_readb_poll(adev, reg, val, cond, delay_us, timeout_us) \ + readb_poll_timeout((adev)->dsp_ba + (reg), val, cond, \ + delay_us, timeout_us) +#define snd_hdac_adsp_readw_poll(adev, reg, val, cond, delay_us, timeout_us) \ + readw_poll_timeout((adev)->dsp_ba + (reg), val, cond, \ + delay_us, timeout_us) +#define snd_hdac_adsp_readl_poll(adev, reg, val, cond, delay_us, timeout_us) \ + readl_poll_timeout((adev)->dsp_ba + (reg), val, cond, \ + delay_us, timeout_us) +#define snd_hdac_adsp_readq_poll(adev, reg, val, cond, delay_us, timeout_us) \ + readq_poll_timeout((adev)->dsp_ba + (reg), val, cond, \ + delay_us, timeout_us) + +#endif /* __SOUND_SOC_INTEL_AVS_REGS_H */ diff --git a/sound/soc/intel/avs/skl.c b/sound/soc/intel/avs/skl.c new file mode 100644 index 000000000000..8fb86f364ff3 --- /dev/null +++ b/sound/soc/intel/avs/skl.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/devcoredump.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include "avs.h" +#include "cldma.h" +#include "debug.h" +#include "messages.h" +#include "registers.h" + +void avs_skl_ipc_interrupt(struct avs_dev *adev) +{ + const struct avs_spec *spec = adev->spec; + u32 hipc_ack, hipc_rsp; + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, 0); + + hipc_ack = snd_hdac_adsp_readl(adev, spec->hipc->ack_offset); + hipc_rsp = snd_hdac_adsp_readl(adev, spec->hipc->rsp_offset); + + /* DSP acked host's request. */ + if (hipc_ack & spec->hipc->ack_done_mask) { + complete(&adev->ipc->done_completion); + + /* Tell DSP it has our attention. */ + snd_hdac_adsp_updatel(adev, spec->hipc->ack_offset, spec->hipc->ack_done_mask, + spec->hipc->ack_done_mask); + } + + /* DSP sent new response to process */ + if (hipc_rsp & spec->hipc->rsp_busy_mask) { + union avs_reply_msg msg; + + msg.primary = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCT); + msg.ext.val = snd_hdac_adsp_readl(adev, SKL_ADSP_REG_HIPCTE); + + avs_dsp_process_response(adev, msg.val); + + /* Tell DSP we accepted its message. */ + snd_hdac_adsp_updatel(adev, SKL_ADSP_REG_HIPCT, SKL_ADSP_HIPCT_BUSY, + SKL_ADSP_HIPCT_BUSY); + } + + snd_hdac_adsp_updatel(adev, spec->hipc->ctl_offset, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY, + AVS_ADSP_HIPCCTL_DONE | AVS_ADSP_HIPCCTL_BUSY); +} + +static irqreturn_t avs_skl_dsp_interrupt(struct avs_dev *adev) +{ + u32 adspis = snd_hdac_adsp_readl(adev, AVS_ADSP_REG_ADSPIS); + irqreturn_t ret = IRQ_NONE; + + if (adspis == UINT_MAX) + return ret; + + if (adspis & AVS_ADSP_ADSPIS_CLDMA) { + hda_cldma_interrupt(&code_loader); + ret = IRQ_HANDLED; + } + + if (adspis & AVS_ADSP_ADSPIS_IPC) { + avs_skl_ipc_interrupt(adev); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int __maybe_unused +avs_skl_enable_logs(struct avs_dev *adev, enum avs_log_enable enable, u32 aging_period, + u32 fifo_full_period, unsigned long resource_mask, u32 *priorities) +{ + struct avs_skl_log_state_info *info; + u32 size, num_cores = adev->hw_cfg.dsp_cores; + int ret, i; + + if (fls_long(resource_mask) > num_cores) + return -EINVAL; + size = struct_size(info, logs_core, num_cores); + info = kzalloc(size, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->core_mask = resource_mask; + if (enable) + for_each_set_bit(i, &resource_mask, num_cores) { + info->logs_core[i].enable = enable; + info->logs_core[i].min_priority = *priorities++; + } + else + for_each_set_bit(i, &resource_mask, num_cores) + info->logs_core[i].enable = enable; + + ret = avs_ipc_set_enable_logs(adev, (u8 *)info, size); + kfree(info); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +int avs_skl_log_buffer_offset(struct avs_dev *adev, u32 core) +{ + return core * avs_log_buffer_size(adev); +} + +/* fw DbgLogWp registers */ +#define FW_REGS_DBG_LOG_WP(core) (0x30 + 0x4 * core) + +static int avs_skl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg) +{ + void __iomem *buf; + u16 size, write, offset; + + if (!avs_logging_fw(adev)) + return 0; + + size = avs_log_buffer_size(adev) / 2; + write = readl(avs_sram_addr(adev, AVS_FW_REGS_WINDOW) + FW_REGS_DBG_LOG_WP(msg->log.core)); + /* determine buffer half */ + offset = (write < size) ? size : 0; + + /* Address is guaranteed to exist in SRAM2. */ + buf = avs_log_buffer_addr(adev, msg->log.core) + offset; + avs_dump_fw_log_wakeup(adev, buf, size); + + return 0; +} + +static int avs_skl_coredump(struct avs_dev *adev, union avs_notify_msg *msg) +{ + u8 *dump; + + dump = vzalloc(AVS_FW_REGS_SIZE); + if (!dump) + return -ENOMEM; + + memcpy_fromio(dump, avs_sram_addr(adev, AVS_FW_REGS_WINDOW), AVS_FW_REGS_SIZE); + dev_coredumpv(adev->dev, dump, AVS_FW_REGS_SIZE, GFP_KERNEL); + + return 0; +} + +static bool avs_skl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake) +{ + /* unsupported on cAVS 1.5 hw */ + return false; +} + +static int avs_skl_set_d0ix(struct avs_dev *adev, bool enable) +{ + /* unsupported on cAVS 1.5 hw */ + return 0; +} + +const struct avs_dsp_ops avs_skl_dsp_ops = { + .power = avs_dsp_core_power, + .reset = avs_dsp_core_reset, + .stall = avs_dsp_core_stall, + .dsp_interrupt = avs_skl_dsp_interrupt, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_cldma_load_basefw, + .load_lib = avs_cldma_load_library, + .transfer_mods = avs_cldma_transfer_modules, + .log_buffer_offset = avs_skl_log_buffer_offset, + .log_buffer_status = avs_skl_log_buffer_status, + .coredump = avs_skl_coredump, + .d0ix_toggle = avs_skl_d0ix_toggle, + .set_d0ix = avs_skl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(skl) +}; diff --git a/sound/soc/intel/avs/sysfs.c b/sound/soc/intel/avs/sysfs.c new file mode 100644 index 000000000000..74b2e6f38d76 --- /dev/null +++ b/sound/soc/intel/avs/sysfs.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2024 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/sysfs.h> +#include "avs.h" + +static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct avs_dev *adev = to_avs_dev(dev); + struct avs_fw_version *fw_version = &adev->fw_cfg.fw_version; + + return sysfs_emit(buf, "%d.%d.%d.%d\n", fw_version->major, fw_version->minor, + fw_version->hotfix, fw_version->build); +} +static DEVICE_ATTR_RO(fw_version); + +static struct attribute *avs_fw_attrs[] = { + &dev_attr_fw_version.attr, + NULL +}; + +static const struct attribute_group avs_attr_group = { + .name = "avs", + .attrs = avs_fw_attrs, +}; + +const struct attribute_group *avs_attr_groups[] = { + &avs_attr_group, + NULL +}; diff --git a/sound/soc/intel/avs/tgl.c b/sound/soc/intel/avs/tgl.c new file mode 100644 index 000000000000..afb066516101 --- /dev/null +++ b/sound/soc/intel/avs/tgl.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2024 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/pci.h> +#include "avs.h" +#include "debug.h" +#include "messages.h" + +#define CPUID_TSC_LEAF 0x15 + +static int avs_tgl_dsp_core_power(struct avs_dev *adev, u32 core_mask, bool power) +{ + core_mask &= AVS_MAIN_CORE_MASK; + + if (!core_mask) + return 0; + return avs_dsp_core_power(adev, core_mask, power); +} + +static int avs_tgl_dsp_core_reset(struct avs_dev *adev, u32 core_mask, bool reset) +{ + core_mask &= AVS_MAIN_CORE_MASK; + + if (!core_mask) + return 0; + return avs_dsp_core_reset(adev, core_mask, reset); +} + +static int avs_tgl_dsp_core_stall(struct avs_dev *adev, u32 core_mask, bool stall) +{ + core_mask &= AVS_MAIN_CORE_MASK; + + if (!core_mask) + return 0; + return avs_dsp_core_stall(adev, core_mask, stall); +} + +static int avs_tgl_config_basefw(struct avs_dev *adev) +{ + struct pci_dev *pci = adev->base.pci; + struct avs_bus_hwid hwid; + int ret; +#ifdef CONFIG_X86 + unsigned int ecx; + +#include <asm/cpuid/api.h> + ecx = cpuid_ecx(CPUID_TSC_LEAF); + if (ecx) { + ret = avs_ipc_set_fw_config(adev, 1, AVS_FW_CFG_XTAL_FREQ_HZ, sizeof(ecx), &ecx); + if (ret) + return AVS_IPC_RET(ret); + } +#endif + + hwid.device = pci->device; + hwid.subsystem = pci->subsystem_vendor | (pci->subsystem_device << 16); + hwid.revision = pci->revision; + + ret = avs_ipc_set_fw_config(adev, 1, AVS_FW_CFG_BUS_HARDWARE_ID, sizeof(hwid), &hwid); + if (ret) + return AVS_IPC_RET(ret); + + return 0; +} + +const struct avs_dsp_ops avs_tgl_dsp_ops = { + .power = avs_tgl_dsp_core_power, + .reset = avs_tgl_dsp_core_reset, + .stall = avs_tgl_dsp_core_stall, + .dsp_interrupt = avs_cnl_dsp_interrupt, + .int_control = avs_dsp_interrupt_control, + .load_basefw = avs_icl_load_basefw, + .load_lib = avs_hda_load_library, + .transfer_mods = avs_hda_transfer_modules, + .config_basefw = avs_tgl_config_basefw, + .log_buffer_offset = avs_icl_log_buffer_offset, + .log_buffer_status = avs_apl_log_buffer_status, + .coredump = avs_apl_coredump, + .d0ix_toggle = avs_icl_d0ix_toggle, + .set_d0ix = avs_icl_set_d0ix, + AVS_SET_ENABLE_LOGS_OP(icl) +}; diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c new file mode 100644 index 000000000000..9033f683393c --- /dev/null +++ b/sound/soc/intel/avs/topology.c @@ -0,0 +1,2247 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/uuid.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-topology.h> +#include <uapi/sound/intel/avs/tokens.h> +#include "avs.h" +#include "control.h" +#include "topology.h" +#include "utils.h" + +/* Get pointer to vendor array at the specified offset. */ +#define avs_tplg_vendor_array_at(array, offset) \ + ((struct snd_soc_tplg_vendor_array *)((u8 *)array + offset)) + +/* Get pointer to vendor array that is next in line. */ +#define avs_tplg_vendor_array_next(array) \ + (avs_tplg_vendor_array_at(array, le32_to_cpu((array)->size))) + +/* + * Scan provided block of tuples for the specified token. If found, + * @offset is updated with position at which first matching token is + * located. + * + * Returns 0 on success, -ENOENT if not found and error code otherwise. + */ +static int +avs_tplg_vendor_array_lookup(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 token, u32 *offset) +{ + u32 pos = 0; + + while (block_size > 0) { + struct snd_soc_tplg_vendor_value_elem *tuple; + u32 tuples_size = le32_to_cpu(tuples->size); + + if (tuples_size > block_size) + return -EINVAL; + + tuple = tuples->value; + if (le32_to_cpu(tuple->token) == token) { + *offset = pos; + return 0; + } + + block_size -= tuples_size; + pos += tuples_size; + tuples = avs_tplg_vendor_array_next(tuples); + } + + return -ENOENT; +} + +/* + * See avs_tplg_vendor_array_lookup() for description. + * + * Behaves exactly like avs_tplg_vendor_lookup() but starts from the + * next vendor array in line. Useful when searching for the finish line + * of an arbitrary entry in a list of entries where each is composed of + * several vendor tuples and a specific token marks the beginning of + * a new entry block. + */ +static int +avs_tplg_vendor_array_lookup_next(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 token, u32 *offset) +{ + u32 tuples_size = le32_to_cpu(tuples->size); + int ret; + + if (tuples_size > block_size) + return -EINVAL; + + tuples = avs_tplg_vendor_array_next(tuples); + block_size -= tuples_size; + + ret = avs_tplg_vendor_array_lookup(tuples, block_size, token, offset); + if (!ret) + *offset += tuples_size; + return ret; +} + +/* + * Scan provided block of tuples for the specified token which marks + * the border of an entry block. Behavior is similar to + * avs_tplg_vendor_array_lookup() except 0 is also returned if no + * matching token has been found. In such case, returned @size is + * assigned to @block_size as the entire block belongs to the current + * entry. + * + * Returns 0 on success, error code otherwise. + */ +static int +avs_tplg_vendor_entry_size(struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 entry_id_token, u32 *size) +{ + int ret; + + ret = avs_tplg_vendor_array_lookup_next(tuples, block_size, entry_id_token, size); + if (ret == -ENOENT) { + *size = block_size; + ret = 0; + } + + return ret; +} + +/* + * Vendor tuple parsing descriptor. + * + * @token: vendor specific token that identifies tuple + * @type: tuple type, one of SND_SOC_TPLG_TUPLE_TYPE_XXX + * @offset: offset of a struct's field to initialize + * @parse: parsing function, extracts and assigns value to object's field + */ +struct avs_tplg_token_parser { + enum avs_tplg_token token; + u32 type; + u32 offset; + int (*parse)(struct snd_soc_component *comp, void *elem, void *object, u32 offset); +}; + +static int +avs_parse_uuid_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_uuid_elem *tuple = elem; + guid_t *val = (guid_t *)((u8 *)object + offset); + + guid_copy((guid_t *)val, (const guid_t *)&tuple->uuid); + + return 0; +} + +static int +avs_parse_bool_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + bool *val = (bool *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_byte_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u8 *val = ((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_short_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u16 *val = (u16 *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_word_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = le32_to_cpu(tuple->value); + + return 0; +} + +static int +avs_parse_string_token(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_string_elem *tuple = elem; + char *val = (char *)((u8 *)object + offset); + + snprintf(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", tuple->string); + + return 0; +} + +static int avs_parse_uuid_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_uuid_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->uuid[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-UUID tokens. */ + if (parsers[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID || + parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_string_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_string_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->string[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-string tokens. */ + if (parsers[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING || + parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_word_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, int count, + struct snd_soc_tplg_vendor_array *tuples) +{ + struct snd_soc_tplg_vendor_value_elem *tuple; + int ret, i, j; + + /* Parse element by element. */ + for (i = 0; i < le32_to_cpu(tuples->num_elems); i++) { + tuple = &tuples->value[i]; + + for (j = 0; j < count; j++) { + /* Ignore non-integer tokens. */ + if (!(parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE || + parsers[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL)) + continue; + + if (parsers[j].token != le32_to_cpu(tuple->token)) + continue; + + ret = parsers[j].parse(comp, tuple, object, parsers[j].offset); + if (ret) + return ret; + } + } + + return 0; +} + +static int avs_parse_tokens(struct snd_soc_component *comp, void *object, + const struct avs_tplg_token_parser *parsers, size_t count, + struct snd_soc_tplg_vendor_array *tuples, int priv_size) +{ + int array_size, ret; + + while (priv_size > 0) { + array_size = le32_to_cpu(tuples->size); + + if (array_size <= 0) { + dev_err(comp->dev, "invalid array size 0x%x\n", array_size); + return -EINVAL; + } + + /* Make sure there is enough data before parsing. */ + priv_size -= array_size; + if (priv_size < 0) { + dev_err(comp->dev, "invalid array size 0x%x\n", array_size); + return -EINVAL; + } + + switch (le32_to_cpu(tuples->type)) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + ret = avs_parse_uuid_tokens(comp, object, parsers, count, tuples); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + ret = avs_parse_string_tokens(comp, object, parsers, count, tuples); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + ret = avs_parse_word_tokens(comp, object, parsers, count, tuples); + break; + default: + dev_err(comp->dev, "unknown token type %d\n", tuples->type); + ret = -EINVAL; + } + + if (ret) { + dev_err(comp->dev, "parsing %zu tokens of %d type failed: %d\n", + count, tuples->type, ret); + return ret; + } + + tuples = avs_tplg_vendor_array_next(tuples); + } + + return 0; +} + +#define AVS_DEFINE_PTR_PARSER(name, type, member) \ +static int \ +avs_parse_##name##_ptr(struct snd_soc_component *comp, void *elem, void *object, u32 offset) \ +{ \ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; \ + struct avs_soc_component *acomp = to_avs_soc_component(comp); \ + type **val = (type **)(object + offset); \ + u32 idx; \ + \ + idx = le32_to_cpu(tuple->value); \ + if (idx >= acomp->tplg->num_##member) \ + return -EINVAL; \ + \ + *val = &acomp->tplg->member[idx]; \ + \ + return 0; \ +} + +AVS_DEFINE_PTR_PARSER(audio_format, struct avs_audio_format, fmts); +AVS_DEFINE_PTR_PARSER(modcfg_base, struct avs_tplg_modcfg_base, modcfgs_base); +AVS_DEFINE_PTR_PARSER(modcfg_ext, struct avs_tplg_modcfg_ext, modcfgs_ext); +AVS_DEFINE_PTR_PARSER(pplcfg, struct avs_tplg_pplcfg, pplcfgs); +AVS_DEFINE_PTR_PARSER(binding, struct avs_tplg_binding, bindings); +AVS_DEFINE_PTR_PARSER(nhlt_config, struct avs_tplg_nhlt_config, nhlt_configs); + +static int +parse_audio_format_bitfield(struct snd_soc_component *comp, void *elem, void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + struct avs_audio_format *audio_format = object; + + switch (offset) { + case AVS_TKN_AFMT_NUM_CHANNELS_U32: + audio_format->num_channels = le32_to_cpu(velem->value); + break; + case AVS_TKN_AFMT_VALID_BIT_DEPTH_U32: + audio_format->valid_bit_depth = le32_to_cpu(velem->value); + break; + case AVS_TKN_AFMT_SAMPLE_TYPE_U32: + audio_format->sample_type = le32_to_cpu(velem->value); + break; + } + + return 0; +} + +static int avs_ssp_sprint(char *buf, size_t size, const char *fmt, int port, int tdm) +{ + char *needle = strstr(fmt, "%d"); + int retsize; + + /* + * If there is %d present in fmt string it should be replaced by either + * SSP or SSP:TDM, where SSP and TDM are numbers, all other formatting + * will be ignored. + */ + if (needle) { + retsize = scnprintf(buf, min_t(size_t, size, needle - fmt + 1), "%s", fmt); + retsize += scnprintf(buf + retsize, size - retsize, "%d", port); + if (tdm) + retsize += scnprintf(buf + retsize, size - retsize, ":%d", tdm); + retsize += scnprintf(buf + retsize, size - retsize, "%s", needle + 2); + return retsize; + } + + return snprintf(buf, size, "%s", fmt); +} + +static int parse_link_formatted_string(struct snd_soc_component *comp, void *elem, + void *object, u32 offset) +{ + struct snd_soc_tplg_vendor_string_elem *tuple = elem; + struct snd_soc_acpi_mach *mach = dev_get_platdata(comp->card->dev); + char *val = (char *)((u8 *)object + offset); + int ssp_port, tdm_slot; + + /* + * Dynamic naming - string formats, e.g.: ssp%d - supported only for + * topologies describing single device e.g.: an I2S codec on SSP0. + */ + if (!avs_mach_singular_ssp(mach)) + return avs_parse_string_token(comp, elem, object, offset); + + ssp_port = avs_mach_ssp_port(mach); + if (!avs_mach_singular_tdm(mach, ssp_port)) + return avs_parse_string_token(comp, elem, object, offset); + + tdm_slot = avs_mach_ssp_tdm(mach, ssp_port); + + avs_ssp_sprint(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, tuple->string, ssp_port, tdm_slot); + + return 0; +} + +static int avs_parse_nhlt_config_size(struct snd_soc_component *comp, void *elem, void *object, + u32 offset) +{ + struct snd_soc_tplg_vendor_value_elem *tuple = elem; + struct acpi_nhlt_config **blob = (struct acpi_nhlt_config **)((u8 *)object + offset); + u32 size; + + size = le32_to_cpu(tuple->value); + *blob = devm_kzalloc(comp->card->dev, struct_size(*blob, capabilities, size), GFP_KERNEL); + if (!*blob) + return -ENOMEM; + + (*blob)->capabilities_size = size; + return 0; +} + +static int +parse_dictionary_header(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + void **dict, u32 *num_entries, size_t entry_size, + u32 num_entries_token) +{ + struct snd_soc_tplg_vendor_value_elem *tuple; + + /* Dictionary header consists of single tuple - entry count. */ + tuple = tuples->value; + if (le32_to_cpu(tuple->token) != num_entries_token) { + dev_err(comp->dev, "invalid dictionary header, expected: %d\n", + num_entries_token); + return -EINVAL; + } + + *num_entries = le32_to_cpu(tuple->value); + *dict = devm_kcalloc(comp->card->dev, *num_entries, entry_size, GFP_KERNEL); + if (!*dict) + return -ENOMEM; + + return 0; +} + +static int +parse_dictionary_entries(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + void *dict, u32 num_entries, size_t entry_size, + u32 entry_id_token, + const struct avs_tplg_token_parser *parsers, size_t num_parsers) +{ + void *pos = dict; + int i; + + for (i = 0; i < num_entries; i++) { + u32 esize; + int ret; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + entry_id_token, &esize); + if (ret) + return ret; + + ret = avs_parse_tokens(comp, pos, parsers, num_parsers, tuples, esize); + if (ret < 0) { + dev_err(comp->dev, "parse entry: %d of type: %d failed: %d\n", + i, entry_id_token, ret); + return ret; + } + + pos += entry_size; + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static int parse_dictionary(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + void **dict, u32 *num_entries, size_t entry_size, + u32 num_entries_token, u32 entry_id_token, + const struct avs_tplg_token_parser *parsers, size_t num_parsers) +{ + int ret; + + ret = parse_dictionary_header(comp, tuples, dict, num_entries, + entry_size, num_entries_token); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + return parse_dictionary_entries(comp, tuples, block_size, *dict, + *num_entries, entry_size, + entry_id_token, parsers, num_parsers); +} + +static const struct avs_tplg_token_parser library_parsers[] = { + { + .token = AVS_TKN_LIBRARY_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_library, name), + .parse = avs_parse_string_token, + }, +}; + +static int avs_tplg_parse_libraries(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->libs, + &tplg->num_libs, sizeof(*tplg->libs), + AVS_TKN_MANIFEST_NUM_LIBRARIES_U32, + AVS_TKN_LIBRARY_ID_U32, + library_parsers, ARRAY_SIZE(library_parsers)); +} + +static const struct avs_tplg_token_parser audio_format_parsers[] = { + { + .token = AVS_TKN_AFMT_SAMPLE_RATE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, sampling_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_BIT_DEPTH_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, bit_depth), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_CHANNEL_MAP_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, channel_map), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_CHANNEL_CFG_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, channel_config), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_INTERLEAVING_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_audio_format, interleaving), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_AFMT_NUM_CHANNELS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_NUM_CHANNELS_U32, + .parse = parse_audio_format_bitfield, + }, + { + .token = AVS_TKN_AFMT_VALID_BIT_DEPTH_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_VALID_BIT_DEPTH_U32, + .parse = parse_audio_format_bitfield, + }, + { + .token = AVS_TKN_AFMT_SAMPLE_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = AVS_TKN_AFMT_SAMPLE_TYPE_U32, + .parse = parse_audio_format_bitfield, + }, +}; + +static int avs_tplg_parse_audio_formats(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->fmts, + &tplg->num_fmts, sizeof(*tplg->fmts), + AVS_TKN_MANIFEST_NUM_AFMTS_U32, + AVS_TKN_AFMT_ID_U32, + audio_format_parsers, ARRAY_SIZE(audio_format_parsers)); +} + +static const struct avs_tplg_token_parser modcfg_base_parsers[] = { + { + .token = AVS_TKN_MODCFG_BASE_CPC_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, cpc), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_IBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, ibs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_OBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, obs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_BASE_PAGES_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_base, is_pages), + .parse = avs_parse_word_token, + }, +}; + +static int avs_tplg_parse_modcfgs_base(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->modcfgs_base, + &tplg->num_modcfgs_base, sizeof(*tplg->modcfgs_base), + AVS_TKN_MANIFEST_NUM_MODCFGS_BASE_U32, + AVS_TKN_MODCFG_BASE_ID_U32, + modcfg_base_parsers, ARRAY_SIZE(modcfg_base_parsers)); +} + +static const struct avs_tplg_token_parser modcfg_ext_parsers[] = { + { + .token = AVS_TKN_MODCFG_EXT_TYPE_UUID, + .type = SND_SOC_TPLG_TUPLE_TYPE_UUID, + .offset = offsetof(struct avs_tplg_modcfg_ext, type), + .parse = avs_parse_uuid_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_CPR_FEATURE_MASK_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.feature_mask), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_VINDEX_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.vindex), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_DMA_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.dma_type), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_DMABUFF_SIZE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.dma_buffer_size), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_CPR_BLOB_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, copier.blob_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_MICSEL_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, micsel.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_INTELWOV_CPC_LP_MODE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, wov.cpc_lp_mode), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_SRC_OUT_FREQ_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, src.out_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_MUX_REF_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, mux.ref_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_MUX_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, mux.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_REF_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.ref_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_AEC_CPC_LP_MODE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, aec.cpc_lp_mode), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_OUT_FREQ_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.out_freq), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_MODE_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.mode), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_ASRC_DISABLE_JITTER_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, asrc.disable_jitter_buffer), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_OUT_CHAN_CFG_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.out_channel_config), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_SELECT_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients_select), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_0_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[0]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_1_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[1]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_2_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[2]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_3_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[3]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_4_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[4]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_5_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[5]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_6_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[6]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_COEFF_7_S32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.coefficients[7]), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_UPDOWN_MIX_CHAN_MAP_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, updown_mix.channel_map), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_EXT_NUM_INPUT_PINS_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_modcfg_ext, generic.num_input_pins), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_MODCFG_EXT_NUM_OUTPUT_PINS_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_modcfg_ext, generic.num_output_pins), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_MODCFG_WHM_REF_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.ref_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_WHM_OUT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.out_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_WHM_WAKE_TICK_PERIOD_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.wake_tick_period), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_WHM_VINDEX_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.vindex), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MODCFG_WHM_DMA_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.dma_type), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_WHM_DMABUFF_SIZE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.dma_buffer_size), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_WHM_BLOB_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, whm.blob_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MODCFG_PEAKVOL_VOLUME_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, peakvol.target_volume), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_PEAKVOL_CURVE_TYPE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, peakvol.curve_type), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MODCFG_PEAKVOL_CURVE_DURATION_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_modcfg_ext, peakvol.curve_duration), + .parse = avs_parse_word_token, + }, +}; + +static const struct avs_tplg_token_parser pin_format_parsers[] = { + { + .token = AVS_TKN_PIN_FMT_INDEX_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, pin_index), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PIN_FMT_IOBS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, iobs), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PIN_FMT_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pin_format, fmt), + .parse = avs_parse_audio_format_ptr, + }, +}; + +static void +assign_copier_gtw_instance(struct snd_soc_component *comp, struct avs_tplg_modcfg_ext *cfg) +{ + struct snd_soc_acpi_mach *mach; + int ssp_port, tdm_slot; + + if (!guid_equal(&cfg->type, &AVS_COPIER_MOD_UUID)) + return; + + /* Only I2S boards assign port instance in ->i2s_link_mask. */ + switch (cfg->copier.dma_type) { + case AVS_DMA_I2S_LINK_OUTPUT: + case AVS_DMA_I2S_LINK_INPUT: + break; + default: + return; + } + + /* If topology sets value don't overwrite it */ + if (cfg->copier.vindex.val) + return; + + mach = dev_get_platdata(comp->card->dev); + + if (!avs_mach_singular_ssp(mach)) + return; + ssp_port = avs_mach_ssp_port(mach); + + if (!avs_mach_singular_tdm(mach, ssp_port)) + return; + tdm_slot = avs_mach_ssp_tdm(mach, ssp_port); + + cfg->copier.vindex.i2s.instance = ssp_port; + cfg->copier.vindex.i2s.time_slot = tdm_slot; +} + +static int avs_tplg_parse_modcfg_ext(struct snd_soc_component *comp, + struct avs_tplg_modcfg_ext *cfg, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + u32 esize; + int ret; + + /* See where pin block starts. */ + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PIN_FMT_INDEX_U32, &esize); + if (ret) + return ret; + + ret = avs_parse_tokens(comp, cfg, modcfg_ext_parsers, + ARRAY_SIZE(modcfg_ext_parsers), tuples, esize); + if (ret) + return ret; + + /* Update copier gateway based on board's i2s_link_mask. */ + assign_copier_gtw_instance(comp, cfg); + + block_size -= esize; + /* Parse trailing in/out pin formats if any. */ + if (block_size) { + struct avs_tplg_pin_format *pins; + u32 num_pins; + + num_pins = cfg->generic.num_input_pins + cfg->generic.num_output_pins; + if (!num_pins) + return -EINVAL; + + pins = devm_kcalloc(comp->card->dev, num_pins, sizeof(*pins), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + tuples = avs_tplg_vendor_array_at(tuples, esize); + ret = parse_dictionary_entries(comp, tuples, block_size, + pins, num_pins, sizeof(*pins), + AVS_TKN_PIN_FMT_INDEX_U32, + pin_format_parsers, + ARRAY_SIZE(pin_format_parsers)); + if (ret) + return ret; + cfg->generic.pin_fmts = pins; + } + + return 0; +} + +static int avs_tplg_parse_modcfgs_ext(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + int ret, i; + + ret = parse_dictionary_header(comp, tuples, (void **)&tplg->modcfgs_ext, + &tplg->num_modcfgs_ext, + sizeof(*tplg->modcfgs_ext), + AVS_TKN_MANIFEST_NUM_MODCFGS_EXT_U32); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + for (i = 0; i < tplg->num_modcfgs_ext; i++) { + struct avs_tplg_modcfg_ext *cfg = &tplg->modcfgs_ext[i]; + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_MODCFG_EXT_ID_U32, &esize); + if (ret) + return ret; + + ret = avs_tplg_parse_modcfg_ext(comp, cfg, tuples, esize); + if (ret) + return ret; + + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static const struct avs_tplg_token_parser pplcfg_parsers[] = { + { + .token = AVS_TKN_PPLCFG_REQ_SIZE_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_pplcfg, req_size), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_PPLCFG_PRIORITY_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_pplcfg, priority), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_PPLCFG_LOW_POWER_BOOL, + .type = SND_SOC_TPLG_TUPLE_TYPE_BOOL, + .offset = offsetof(struct avs_tplg_pplcfg, lp), + .parse = avs_parse_bool_token, + }, + { + .token = AVS_TKN_PPLCFG_ATTRIBUTES_U16, + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .offset = offsetof(struct avs_tplg_pplcfg, attributes), + .parse = avs_parse_short_token, + }, + { + .token = AVS_TKN_PPLCFG_TRIGGER_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pplcfg, trigger), + .parse = avs_parse_word_token, + }, +}; + +static int avs_tplg_parse_pplcfgs(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->pplcfgs, + &tplg->num_pplcfgs, sizeof(*tplg->pplcfgs), + AVS_TKN_MANIFEST_NUM_PPLCFGS_U32, + AVS_TKN_PPLCFG_ID_U32, + pplcfg_parsers, ARRAY_SIZE(pplcfg_parsers)); +} + +static const struct avs_tplg_token_parser binding_parsers[] = { + { + .token = AVS_TKN_BINDING_TARGET_TPLG_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_binding, target_tplg_name), + .parse = parse_link_formatted_string, + }, + { + .token = AVS_TKN_BINDING_TARGET_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_path_tmpl_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_PPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_ppl_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, target_mod_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_TARGET_MOD_PIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, target_mod_pin), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_BINDING_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_binding, mod_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_BINDING_MOD_PIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, mod_pin), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_BINDING_IS_SINK_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_binding, is_sink), + .parse = avs_parse_byte_token, + }, +}; + +static int avs_tplg_parse_bindings(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + + return parse_dictionary(comp, tuples, block_size, (void **)&tplg->bindings, + &tplg->num_bindings, sizeof(*tplg->bindings), + AVS_TKN_MANIFEST_NUM_BINDINGS_U32, + AVS_TKN_BINDING_ID_U32, + binding_parsers, ARRAY_SIZE(binding_parsers)); +} + +static const struct avs_tplg_token_parser module_parsers[] = { + { + .token = AVS_TKN_MOD_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_MOD_MODCFG_BASE_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, cfg_base), + .parse = avs_parse_modcfg_base_ptr, + }, + { + .token = AVS_TKN_MOD_IN_AFMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, in_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_MOD_CORE_ID_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_module, core_id), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_PROC_DOMAIN_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_module, domain), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_MODCFG_EXT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, cfg_ext), + .parse = avs_parse_modcfg_ext_ptr, + }, + { + .token = AVS_TKN_MOD_KCONTROL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, ctl_id), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_INIT_CONFIG_NUM_IDS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, num_config_ids), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_MOD_NHLT_CONFIG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_module, nhlt_config), + .parse = avs_parse_nhlt_config_ptr, + }, +}; + +static const struct avs_tplg_token_parser init_config_parsers[] = { + { + .token = AVS_TKN_MOD_INIT_CONFIG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = 0, + .parse = avs_parse_word_token, + }, +}; + +static struct avs_tplg_module * +avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_module *module; + u32 esize; + int ret; + + /* See where config id block starts. */ + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_MOD_INIT_CONFIG_ID_U32, &esize); + if (ret) + return ERR_PTR(ret); + + module = devm_kzalloc(comp->card->dev, sizeof(*module), GFP_KERNEL); + if (!module) + return ERR_PTR(-ENOMEM); + + ret = avs_parse_tokens(comp, module, module_parsers, + ARRAY_SIZE(module_parsers), tuples, esize); + if (ret < 0) + return ERR_PTR(ret); + + block_size -= esize; + /* Parse trailing config ids if any. */ + if (block_size) { + u32 num_config_ids = module->num_config_ids; + u32 *config_ids; + + if (!num_config_ids) + return ERR_PTR(-EINVAL); + + config_ids = devm_kcalloc(comp->card->dev, num_config_ids, sizeof(*config_ids), + GFP_KERNEL); + if (!config_ids) + return ERR_PTR(-ENOMEM); + + tuples = avs_tplg_vendor_array_at(tuples, esize); + ret = parse_dictionary_entries(comp, tuples, block_size, + config_ids, num_config_ids, sizeof(*config_ids), + AVS_TKN_MOD_INIT_CONFIG_ID_U32, + init_config_parsers, + ARRAY_SIZE(init_config_parsers)); + if (ret) + return ERR_PTR(ret); + + module->config_ids = config_ids; + } + + module->owner = owner; + INIT_LIST_HEAD(&module->node); + + return module; +} + +static const struct avs_tplg_token_parser pipeline_parsers[] = { + { + .token = AVS_TKN_PPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PPL_PPLCFG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, cfg), + .parse = avs_parse_pplcfg_ptr, + }, + { + .token = AVS_TKN_PPL_NUM_BINDING_IDS_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_pipeline, num_bindings), + .parse = avs_parse_word_token, + }, +}; + +static const struct avs_tplg_token_parser bindings_parsers[] = { + { + .token = AVS_TKN_PPL_BINDING_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = 0, /* to treat pipeline->bindings as dictionary */ + .parse = avs_parse_binding_ptr, + }, +}; + +static struct avs_tplg_pipeline * +avs_tplg_pipeline_create(struct snd_soc_component *comp, struct avs_tplg_path *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_pipeline *pipeline; + u32 modblk_size, offset; + int ret; + + pipeline = devm_kzalloc(comp->card->dev, sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return ERR_PTR(-ENOMEM); + + pipeline->owner = owner; + INIT_LIST_HEAD(&pipeline->mod_list); + + /* Pipeline header MUST be followed by at least one module. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_MOD_ID_U32, &offset); + if (!ret && !offset) + ret = -EINVAL; + if (ret) + return ERR_PTR(ret); + + /* Process header which precedes module sections. */ + ret = avs_parse_tokens(comp, pipeline, pipeline_parsers, + ARRAY_SIZE(pipeline_parsers), tuples, offset); + if (ret < 0) + return ERR_PTR(ret); + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + /* Optionally, binding sections follow module ones. */ + ret = avs_tplg_vendor_array_lookup_next(tuples, block_size, + AVS_TKN_PPL_BINDING_ID_U32, &offset); + if (ret) { + if (ret != -ENOENT) + return ERR_PTR(ret); + + /* Does header information match actual block layout? */ + if (pipeline->num_bindings) + return ERR_PTR(-EINVAL); + + modblk_size = block_size; + } else { + pipeline->bindings = devm_kcalloc(comp->card->dev, pipeline->num_bindings, + sizeof(*pipeline->bindings), GFP_KERNEL); + if (!pipeline->bindings) + return ERR_PTR(-ENOMEM); + + modblk_size = offset; + } + + block_size -= modblk_size; + do { + struct avs_tplg_module *module; + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, modblk_size, + AVS_TKN_MOD_ID_U32, &esize); + if (ret) + return ERR_PTR(ret); + + module = avs_tplg_module_create(comp, pipeline, tuples, esize); + if (IS_ERR(module)) { + dev_err(comp->dev, "parse module failed: %ld\n", + PTR_ERR(module)); + return ERR_CAST(module); + } + + list_add_tail(&module->node, &pipeline->mod_list); + modblk_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } while (modblk_size > 0); + + /* What's left is optional range of bindings. */ + ret = parse_dictionary_entries(comp, tuples, block_size, pipeline->bindings, + pipeline->num_bindings, sizeof(*pipeline->bindings), + AVS_TKN_PPL_BINDING_ID_U32, + bindings_parsers, ARRAY_SIZE(bindings_parsers)); + if (ret) + return ERR_PTR(ret); + + return pipeline; +} + +static const struct avs_tplg_token_parser path_parsers[] = { + { + .token = AVS_TKN_PATH_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_PATH_FE_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, fe_fmt), + .parse = avs_parse_audio_format_ptr, + }, + { + .token = AVS_TKN_PATH_BE_FMT_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, be_fmt), + .parse = avs_parse_audio_format_ptr, + }, +}; + +static const struct avs_tplg_token_parser condpath_parsers[] = { + { + .token = AVS_TKN_CONDPATH_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_CONDPATH_SOURCE_PATH_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, source_path_id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_CONDPATH_SINK_PATH_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path, sink_path_id), + .parse = avs_parse_word_token, + }, +}; + +static struct avs_tplg_path * +avs_tplg_path_create(struct snd_soc_component *comp, struct avs_tplg_path_template *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + const struct avs_tplg_token_parser *parsers, u32 num_parsers) +{ + struct avs_tplg_pipeline *pipeline; + struct avs_tplg_path *path; + u32 offset; + int ret; + + path = devm_kzalloc(comp->card->dev, sizeof(*path), GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + + path->owner = owner; + INIT_LIST_HEAD(&path->ppl_list); + INIT_LIST_HEAD(&path->node); + + /* Path header MAY be followed by one or more pipelines. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_PPL_ID_U32, &offset); + if (ret == -ENOENT) + offset = block_size; + else if (ret) + return ERR_PTR(ret); + else if (!offset) + return ERR_PTR(-EINVAL); + + /* Process header which precedes pipeline sections. */ + ret = avs_parse_tokens(comp, path, parsers, num_parsers, tuples, offset); + if (ret < 0) + return ERR_PTR(ret); + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + while (block_size > 0) { + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PPL_ID_U32, &esize); + if (ret) + return ERR_PTR(ret); + + pipeline = avs_tplg_pipeline_create(comp, path, tuples, esize); + if (IS_ERR(pipeline)) { + dev_err(comp->dev, "parse pipeline failed: %ld\n", + PTR_ERR(pipeline)); + return ERR_CAST(pipeline); + } + + list_add_tail(&pipeline->node, &path->ppl_list); + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return path; +} + +static const struct avs_tplg_token_parser path_tmpl_parsers[] = { + { + .token = AVS_TKN_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path_template, id), + .parse = avs_parse_word_token, + }, +}; + +static const struct avs_tplg_token_parser condpath_tmpl_parsers[] = { + { + .token = AVS_TKN_CONDPATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path_template, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_CONDPATH_TMPL_SOURCE_TPLG_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_path_template, source.tplg_name), + .parse = avs_parse_string_token, + }, + { + .token = AVS_TKN_CONDPATH_TMPL_SOURCE_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path_template, source.id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_CONDPATH_TMPL_SINK_TPLG_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg_path_template, sink.tplg_name), + .parse = avs_parse_string_token, + }, + { + .token = AVS_TKN_CONDPATH_TMPL_SINK_PATH_TMPL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_path_template, sink.id), + .parse = avs_parse_word_token, + }, +}; + +static int parse_path_template(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size, + struct avs_tplg_path_template *template, + const struct avs_tplg_token_parser *tmpl_tokens, u32 num_tmpl_tokens, + const struct avs_tplg_token_parser *path_tokens, u32 num_path_tokens) +{ + struct avs_tplg_path *path; + u32 offset; + int ret; + + /* Path template header MUST be followed by at least one path variant. */ + ret = avs_tplg_vendor_array_lookup(tuples, block_size, + AVS_TKN_PATH_ID_U32, &offset); + if (ret) + return ret; + + /* Process header which precedes path variants sections. */ + ret = avs_parse_tokens(comp, template, tmpl_tokens, num_tmpl_tokens, tuples, offset); + if (ret < 0) + return ret; + + block_size -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + do { + u32 esize; + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_PATH_ID_U32, &esize); + if (ret) + return ret; + + path = avs_tplg_path_create(comp, template, tuples, esize, path_tokens, + num_path_tokens); + if (IS_ERR(path)) { + dev_err(comp->dev, "parse path failed: %ld\n", PTR_ERR(path)); + return PTR_ERR(path); + } + + list_add_tail(&path->node, &template->path_list); + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } while (block_size > 0); + + return 0; +} + +static struct avs_tplg_path_template * +avs_tplg_path_template_create(struct snd_soc_component *comp, struct avs_tplg *owner, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_path_template *template; + int ret; + + template = devm_kzalloc(comp->card->dev, sizeof(*template), GFP_KERNEL); + if (!template) + return ERR_PTR(-ENOMEM); + + template->owner = owner; /* Used to access component tplg is assigned to. */ + INIT_LIST_HEAD(&template->path_list); + INIT_LIST_HEAD(&template->node); + + ret = parse_path_template(comp, tuples, block_size, template, path_tmpl_parsers, + ARRAY_SIZE(path_tmpl_parsers), path_parsers, + ARRAY_SIZE(path_parsers)); + if (ret) + return ERR_PTR(ret); + + return template; +} + +static int avs_tplg_parse_condpath_templates(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + int ret, i; + + ret = parse_dictionary_header(comp, tuples, (void **)&tplg->condpath_tmpls, + &tplg->num_condpath_tmpls, + sizeof(*tplg->condpath_tmpls), + AVS_TKN_MANIFEST_NUM_CONDPATH_TMPLS_U32); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + for (i = 0; i < tplg->num_condpath_tmpls; i++) { + struct avs_tplg_path_template *template; + u32 esize; + + template = &tplg->condpath_tmpls[i]; + template->owner = tplg; /* Used when building sysfs hierarchy. */ + INIT_LIST_HEAD(&template->path_list); + INIT_LIST_HEAD(&template->node); + + ret = avs_tplg_vendor_entry_size(tuples, block_size, + AVS_TKN_CONDPATH_TMPL_ID_U32, &esize); + if (ret) + return ret; + + ret = parse_path_template(comp, tuples, esize, template, + condpath_tmpl_parsers, + ARRAY_SIZE(condpath_tmpl_parsers), + condpath_parsers, + ARRAY_SIZE(condpath_parsers)); + if (ret < 0) { + dev_err(comp->dev, "parse condpath_tmpl: %d failed: %d\n", i, ret); + return ret; + } + + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static const struct avs_tplg_token_parser mod_init_config_parsers[] = { + { + .token = AVS_TKN_INIT_CONFIG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_init_config, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_INIT_CONFIG_PARAM_U8, + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .offset = offsetof(struct avs_tplg_init_config, param), + .parse = avs_parse_byte_token, + }, + { + .token = AVS_TKN_INIT_CONFIG_LENGTH_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_init_config, length), + .parse = avs_parse_word_token, + }, +}; + +static int avs_tplg_parse_initial_configs(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size, u32 *offset) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + int ret, i; + + *offset = 0; + + /* Parse tuple section telling how many init configs there are. */ + ret = parse_dictionary_header(comp, tuples, (void **)&tplg->init_configs, + &tplg->num_init_configs, + sizeof(*tplg->init_configs), + AVS_TKN_MANIFEST_NUM_INIT_CONFIGS_U32); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + *offset += le32_to_cpu(tuples->size); + /* With header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + for (i = 0; i < tplg->num_init_configs && block_size > 0; i++) { + struct avs_tplg_init_config *config = &tplg->init_configs[i]; + struct snd_soc_tplg_vendor_array *tmp; + void *init_config_data; + u32 esize; + + /* + * Usually to get section length we search for first token of next group of data, + * but in this case we can't as tuples are followed by raw data. + */ + tmp = avs_tplg_vendor_array_next(tuples); + esize = le32_to_cpu(tuples->size) + le32_to_cpu(tmp->size); + *offset += esize; + + ret = parse_dictionary_entries(comp, tuples, esize, config, 1, sizeof(*config), + AVS_TKN_INIT_CONFIG_ID_U32, + mod_init_config_parsers, + ARRAY_SIZE(mod_init_config_parsers)); + + block_size -= esize; + + /* handle raw data section */ + init_config_data = (void *)tuples + esize; + esize = config->length; + *offset += esize; + + config->data = devm_kmemdup(comp->card->dev, init_config_data, esize, GFP_KERNEL); + if (!config->data) + return -ENOMEM; + + tuples = init_config_data + esize; + block_size -= esize; + } + + return 0; +} + +static const struct avs_tplg_token_parser mod_nhlt_config_parsers[] = { + { + .token = AVS_TKN_NHLT_CONFIG_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_nhlt_config, id), + .parse = avs_parse_word_token, + }, + { + .token = AVS_TKN_NHLT_CONFIG_SIZE_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg_nhlt_config, blob), + .parse = avs_parse_nhlt_config_size, + }, +}; + +static int avs_tplg_parse_nhlt_configs(struct snd_soc_component *comp, + struct snd_soc_tplg_vendor_array *tuples, + u32 block_size) +{ + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg = acomp->tplg; + int ret, i; + + /* Parse the header section to know how many entries there are. */ + ret = parse_dictionary_header(comp, tuples, (void **)&tplg->nhlt_configs, + &tplg->num_nhlt_configs, + sizeof(*tplg->nhlt_configs), + AVS_TKN_MANIFEST_NUM_NHLT_CONFIGS_U32); + if (ret) + return ret; + + block_size -= le32_to_cpu(tuples->size); + /* With the header parsed, move on to parsing entries. */ + tuples = avs_tplg_vendor_array_next(tuples); + + for (i = 0; i < tplg->num_nhlt_configs && block_size > 0; i++) { + struct avs_tplg_nhlt_config *config; + u32 esize; + + config = &tplg->nhlt_configs[i]; + esize = le32_to_cpu(tuples->size); + + ret = parse_dictionary_entries(comp, tuples, esize, config, 1, sizeof(*config), + AVS_TKN_NHLT_CONFIG_ID_U32, + mod_nhlt_config_parsers, + ARRAY_SIZE(mod_nhlt_config_parsers)); + if (ret) + return ret; + /* With tuples parsed, the blob shall be allocated. */ + if (!config->blob) + return -EINVAL; + + /* Consume the raw data and move to the next entry. */ + memcpy(config->blob->capabilities, (u8 *)tuples + esize, + config->blob->capabilities_size); + esize += config->blob->capabilities_size; + + block_size -= esize; + tuples = avs_tplg_vendor_array_at(tuples, esize); + } + + return 0; +} + +static int avs_route_load(struct snd_soc_component *comp, int index, + struct snd_soc_dapm_route *route) +{ + struct snd_soc_acpi_mach *mach = dev_get_platdata(comp->card->dev); + size_t len = SNDRV_CTL_ELEM_ID_NAME_MAXLEN; + int ssp_port, tdm_slot; + char *buf; + + /* See parse_link_formatted_string() for dynamic naming when(s). */ + if (!avs_mach_singular_ssp(mach)) + return 0; + ssp_port = avs_mach_ssp_port(mach); + + if (!avs_mach_singular_tdm(mach, ssp_port)) + return 0; + tdm_slot = avs_mach_ssp_tdm(mach, ssp_port); + + buf = devm_kzalloc(comp->card->dev, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + avs_ssp_sprint(buf, len, route->source, ssp_port, tdm_slot); + route->source = buf; + + buf = devm_kzalloc(comp->card->dev, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + avs_ssp_sprint(buf, len, route->sink, ssp_port, tdm_slot); + route->sink = buf; + + if (route->control) { + buf = devm_kzalloc(comp->card->dev, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + avs_ssp_sprint(buf, len, route->control, ssp_port, tdm_slot); + route->control = buf; + } + + return 0; +} + +static int avs_widget_load(struct snd_soc_component *comp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *dw) +{ + struct snd_soc_acpi_mach *mach; + struct avs_tplg_path_template *template; + struct avs_soc_component *acomp = to_avs_soc_component(comp); + struct avs_tplg *tplg; + int ssp_port, tdm_slot; + + if (!le32_to_cpu(dw->priv.size)) + return 0; + + w->no_wname_in_kcontrol_name = true; + + if (w->ignore_suspend && !AVS_S0IX_SUPPORTED) { + dev_info_once(comp->dev, "Device does not support S0IX, check BIOS settings\n"); + w->ignore_suspend = false; + } + + tplg = acomp->tplg; + mach = dev_get_platdata(comp->card->dev); + if (!avs_mach_singular_ssp(mach)) + goto static_name; + ssp_port = avs_mach_ssp_port(mach); + + /* See parse_link_formatted_string() for dynamic naming when(s). */ + if (avs_mach_singular_tdm(mach, ssp_port)) { + /* size is based on possible %d -> SSP:TDM, where SSP and TDM < 16 + '\0' */ + size_t size = strlen(dw->name) + 3; + char *buf; + + tdm_slot = avs_mach_ssp_tdm(mach, ssp_port); + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + avs_ssp_sprint(buf, size, dw->name, ssp_port, tdm_slot); + kfree(w->name); + /* w->name is freed later by soc_tplg_dapm_widget_create() */ + w->name = buf; + } + +static_name: + template = avs_tplg_path_template_create(comp, tplg, dw->priv.array, + le32_to_cpu(dw->priv.size)); + if (IS_ERR(template)) { + dev_err(comp->dev, "widget %s load failed: %ld\n", dw->name, + PTR_ERR(template)); + return PTR_ERR(template); + } + + w->priv = template; /* link path information to widget */ + list_add_tail(&template->node, &tplg->path_tmpl_list); + return 0; +} + +static int avs_widget_ready(struct snd_soc_component *comp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *dw) +{ + struct avs_tplg_path_template *template = w->priv; + + template->w = w; + return 0; +} + +static int avs_dai_load(struct snd_soc_component *comp, int index, + struct snd_soc_dai_driver *dai_drv, struct snd_soc_tplg_pcm *pcm, + struct snd_soc_dai *dai) +{ + u32 fe_subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | + SNDRV_PCM_SUBFMTBIT_MSBITS_24 | + SNDRV_PCM_SUBFMTBIT_MSBITS_MAX; + + if (pcm) { + dai_drv->ops = &avs_dai_fe_ops; + dai_drv->capture.subformats = fe_subformats; + dai_drv->playback.subformats = fe_subformats; + } + + return 0; +} + +static int avs_link_load(struct snd_soc_component *comp, int index, struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + if (link->ignore_suspend && !AVS_S0IX_SUPPORTED) { + dev_info_once(comp->dev, "Device does not support S0IX, check BIOS settings\n"); + link->ignore_suspend = false; + } + + if (!link->no_pcm) { + /* Stream control handled by IPCs. */ + link->nonatomic = true; + + /* Open LINK (BE) pipes last and close them first to prevent xruns. */ + link->trigger[0] = SND_SOC_DPCM_TRIGGER_PRE; + link->trigger[1] = SND_SOC_DPCM_TRIGGER_PRE; + } else { + /* Do not ignore codec capabilities. */ + link->dpcm_merged_format = 1; + } + + return 0; +} + +static const struct avs_tplg_token_parser manifest_parsers[] = { + { + .token = AVS_TKN_MANIFEST_NAME_STRING, + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .offset = offsetof(struct avs_tplg, name), + .parse = parse_link_formatted_string, + }, + { + .token = AVS_TKN_MANIFEST_VERSION_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_tplg, version), + .parse = avs_parse_word_token, + }, +}; + +static int avs_manifest(struct snd_soc_component *comp, int index, + struct snd_soc_tplg_manifest *manifest) +{ + struct snd_soc_tplg_vendor_array *tuples = manifest->priv.array; + struct avs_soc_component *acomp = to_avs_soc_component(comp); + size_t remaining = le32_to_cpu(manifest->priv.size); + bool has_init_config = true; + u32 offset; + int ret; + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_LIBRARIES_U32, &offset); + /* Manifest MUST begin with a header. */ + if (!ret && !offset) + ret = -EINVAL; + if (ret) { + dev_err(comp->dev, "incorrect manifest format: %d\n", ret); + return ret; + } + + /* Process header which precedes any of the dictionaries. */ + ret = avs_parse_tokens(comp, acomp->tplg, manifest_parsers, + ARRAY_SIZE(manifest_parsers), tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_AFMTS_U32, &offset); + if (ret) { + dev_err(comp->dev, "audio formats lookup failed: %d\n", ret); + return ret; + } + + /* Libraries dictionary. */ + ret = avs_tplg_parse_libraries(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_MODCFGS_BASE_U32, &offset); + if (ret) { + dev_err(comp->dev, "modcfgs_base lookup failed: %d\n", ret); + return ret; + } + + /* Audio formats dictionary. */ + ret = avs_tplg_parse_audio_formats(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_MODCFGS_EXT_U32, &offset); + if (ret) { + dev_err(comp->dev, "modcfgs_ext lookup failed: %d\n", ret); + return ret; + } + + /* Module configs-base dictionary. */ + ret = avs_tplg_parse_modcfgs_base(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_PPLCFGS_U32, &offset); + if (ret) { + dev_err(comp->dev, "pplcfgs lookup failed: %d\n", ret); + return ret; + } + + /* Module configs-ext dictionary. */ + ret = avs_tplg_parse_modcfgs_ext(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_BINDINGS_U32, &offset); + if (ret) { + dev_err(comp->dev, "bindings lookup failed: %d\n", ret); + return ret; + } + + /* Pipeline configs dictionary. */ + ret = avs_tplg_parse_pplcfgs(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_CONDPATH_TMPLS_U32, &offset); + if (ret) { + dev_err(comp->dev, "condpath lookup failed: %d\n", ret); + return ret; + } + + /* Bindings dictionary. */ + ret = avs_tplg_parse_bindings(comp, tuples, offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_INIT_CONFIGS_U32, &offset); + if (ret == -ENOENT) { + dev_dbg(comp->dev, "init config lookup failed: %d\n", ret); + has_init_config = false; + } else if (ret) { + dev_err(comp->dev, "init config lookup failed: %d\n", ret); + return ret; + } + + /* Condpaths dictionary. */ + ret = avs_tplg_parse_condpath_templates(comp, tuples, + has_init_config ? offset : remaining); + if (ret < 0) + return ret; + + if (!has_init_config) + return 0; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + /* Initial configs dictionary. */ + ret = avs_tplg_parse_initial_configs(comp, tuples, remaining, &offset); + if (ret < 0) + return ret; + + remaining -= offset; + tuples = avs_tplg_vendor_array_at(tuples, offset); + + ret = avs_tplg_vendor_array_lookup(tuples, remaining, + AVS_TKN_MANIFEST_NUM_NHLT_CONFIGS_U32, &offset); + if (ret == -ENOENT) + return 0; + if (ret) { + dev_err(comp->dev, "NHLT config lookup failed: %d\n", ret); + return ret; + } + + tuples = avs_tplg_vendor_array_at(tuples, offset); + + /* NHLT configs dictionary. */ + return avs_tplg_parse_nhlt_configs(comp, tuples, remaining); +} + +enum { + AVS_CONTROL_OPS_VOLUME = 257, + AVS_CONTROL_OPS_MUTE, +}; + +static const struct snd_soc_tplg_kcontrol_ops avs_control_ops[] = { + { + .id = AVS_CONTROL_OPS_VOLUME, + .get = avs_control_volume_get, + .put = avs_control_volume_put, + .info = avs_control_volume_info, + }, + { + .id = AVS_CONTROL_OPS_MUTE, + .get = avs_control_mute_get, + .put = avs_control_mute_put, + .info = avs_control_mute_info, + }, +}; + +static const struct avs_tplg_token_parser control_parsers[] = { + { + .token = AVS_TKN_KCONTROL_ID_U32, + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .offset = offsetof(struct avs_control_data, id), + .parse = avs_parse_word_token, + }, +}; + +static int +avs_control_load(struct snd_soc_component *comp, int index, struct snd_kcontrol_new *ctmpl, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_soc_tplg_vendor_array *tuples; + struct snd_soc_tplg_mixer_control *tmc; + struct avs_control_data *ctl_data; + struct soc_mixer_control *mc; + size_t block_size; + int ret, i; + + switch (le32_to_cpu(hdr->type)) { + case SND_SOC_TPLG_TYPE_MIXER: + break; + default: + return -EINVAL; + } + + mc = (struct soc_mixer_control *)ctmpl->private_value; + tmc = container_of(hdr, typeof(*tmc), hdr); + tuples = tmc->priv.array; + block_size = le32_to_cpu(tmc->priv.size); + + ctl_data = devm_kzalloc(comp->card->dev, sizeof(*ctl_data), GFP_KERNEL); + if (!ctl_data) + return -ENOMEM; + + ret = parse_dictionary_entries(comp, tuples, block_size, ctl_data, 1, sizeof(*ctl_data), + AVS_TKN_KCONTROL_ID_U32, control_parsers, + ARRAY_SIZE(control_parsers)); + if (ret) + return ret; + + mc->dobj.private = ctl_data; + if (tmc->invert) { + ctl_data->values[0] = mc->max; + for (i = 1; i < mc->num_channels; i++) + ctl_data->values[i] = mc->max; + } + + return 0; +} + +static const struct snd_soc_tplg_ops avs_tplg_ops = { + .io_ops = avs_control_ops, + .io_ops_count = ARRAY_SIZE(avs_control_ops), + .control_load = avs_control_load, + .dapm_route_load = avs_route_load, + .widget_load = avs_widget_load, + .widget_ready = avs_widget_ready, + .dai_load = avs_dai_load, + .link_load = avs_link_load, + .manifest = avs_manifest, +}; + +struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) +{ + struct avs_tplg *tplg; + + tplg = devm_kzalloc(comp->card->dev, sizeof(*tplg), GFP_KERNEL); + if (!tplg) + return NULL; + + tplg->comp = comp; + INIT_LIST_HEAD(&tplg->path_tmpl_list); + + return tplg; +} + +int avs_load_topology(struct snd_soc_component *comp, const char *filename) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); + if (ret < 0) { + dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret); + return ret; + } + + ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw); + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + + release_firmware(fw); + return ret; +} + +int avs_remove_topology(struct snd_soc_component *comp) +{ + snd_soc_tplg_component_remove(comp); + + return 0; +} diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h new file mode 100644 index 000000000000..1cf7455b6c01 --- /dev/null +++ b/sound/soc/intel/avs/topology.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2021 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_TPLG_H +#define __SOUND_SOC_INTEL_AVS_TPLG_H + +#include <linux/list.h> +#include "messages.h" + +#define INVALID_OBJECT_ID UINT_MAX + +struct snd_soc_component; + +struct avs_tplg { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + u32 version; + struct snd_soc_component *comp; + + struct avs_tplg_library *libs; + u32 num_libs; + struct avs_audio_format *fmts; + u32 num_fmts; + struct avs_tplg_modcfg_base *modcfgs_base; + u32 num_modcfgs_base; + struct avs_tplg_modcfg_ext *modcfgs_ext; + u32 num_modcfgs_ext; + struct avs_tplg_pplcfg *pplcfgs; + u32 num_pplcfgs; + struct avs_tplg_binding *bindings; + u32 num_bindings; + struct avs_tplg_path_template *condpath_tmpls; + u32 num_condpath_tmpls; + struct avs_tplg_init_config *init_configs; + u32 num_init_configs; + struct avs_tplg_nhlt_config *nhlt_configs; + u32 num_nhlt_configs; + + struct list_head path_tmpl_list; +}; + +struct avs_tplg_library { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +}; + +/* Matches header of struct avs_mod_cfg_base. */ +struct avs_tplg_modcfg_base { + u32 cpc; + u32 ibs; + u32 obs; + u32 is_pages; +}; + +struct avs_tplg_pin_format { + u32 pin_index; + u32 iobs; + struct avs_audio_format *fmt; +}; + +struct avs_tplg_modcfg_ext { + guid_t type; + + union { + struct { + u16 num_input_pins; + u16 num_output_pins; + struct avs_tplg_pin_format *pin_fmts; + } generic; + struct { + struct avs_audio_format *out_fmt; + struct avs_audio_format *blob_fmt; /* optional override */ + u32 feature_mask; + union avs_virtual_index vindex; + u32 dma_type; + u32 dma_buffer_size; + } copier; + struct { + struct avs_audio_format *ref_fmt; + struct avs_audio_format *out_fmt; + u32 wake_tick_period; + union avs_virtual_index vindex; + u32 dma_type; + u32 dma_buffer_size; + struct avs_audio_format *blob_fmt; /* optional override */ + } whm; + struct { + u32 out_channel_config; + u32 coefficients_select; + s32 coefficients[AVS_COEFF_CHANNELS_MAX]; + u32 channel_map; + } updown_mix; + struct { + u32 out_freq; + } src; + struct { + u32 out_freq; + u8 mode; + u8 disable_jitter_buffer; + } asrc; + struct { + u32 cpc_lp_mode; + } wov; + struct { + struct avs_audio_format *ref_fmt; + struct avs_audio_format *out_fmt; + u32 cpc_lp_mode; + } aec; + struct { + struct avs_audio_format *ref_fmt; + struct avs_audio_format *out_fmt; + } mux; + struct { + struct avs_audio_format *out_fmt; + } micsel; + struct { + u32 target_volume; + u32 curve_type; + u32 curve_duration; + } peakvol; + }; +}; + +/* Specifies path behaviour during PCM ->trigger(START) command. */ +enum avs_tplg_trigger { + AVS_TPLG_TRIGGER_AUTO = 0, +}; + +struct avs_tplg_pplcfg { + u16 req_size; + u8 priority; + bool lp; + u16 attributes; + enum avs_tplg_trigger trigger; +}; + +struct avs_tplg_binding { + char target_tplg_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + u32 target_path_tmpl_id; + u32 target_ppl_id; + u32 target_mod_id; + u8 target_mod_pin; + u32 mod_id; + u8 mod_pin; + u8 is_sink; +}; + +struct avs_tplg_path_template_id { + u32 id; + char tplg_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +}; + +struct avs_tplg_path_template { + u32 id; + + struct snd_soc_dapm_widget *w; + + /* Conditional path. */ + struct avs_tplg_path_template_id source; + struct avs_tplg_path_template_id sink; + + struct list_head path_list; + + struct avs_tplg *owner; + /* Driver path templates management. */ + struct list_head node; +}; + +struct avs_tplg_init_config { + u32 id; + + u8 param; + size_t length; + void *data; +}; + +struct avs_tplg_nhlt_config { + u32 id; + struct acpi_nhlt_config *blob; +}; + +struct avs_tplg_path { + u32 id; + + /* Path format requirements. */ + struct avs_audio_format *fe_fmt; + struct avs_audio_format *be_fmt; + /* Condpath path-variant requirements. */ + u32 source_path_id; + u32 sink_path_id; + + struct list_head ppl_list; + + struct avs_tplg_path_template *owner; + /* Path template path-variants management. */ + struct list_head node; +}; + +struct avs_tplg_pipeline { + u32 id; + + struct avs_tplg_pplcfg *cfg; + struct avs_tplg_binding **bindings; + u32 num_bindings; + struct list_head mod_list; + + struct avs_tplg_path *owner; + /* Path pipelines management. */ + struct list_head node; +}; + +struct avs_tplg_module { + u32 id; + + struct avs_tplg_modcfg_base *cfg_base; + struct avs_audio_format *in_fmt; + u8 core_id; + u8 domain; + struct avs_tplg_modcfg_ext *cfg_ext; + u32 ctl_id; + u32 num_config_ids; + u32 *config_ids; + struct avs_tplg_nhlt_config *nhlt_config; + + struct avs_tplg_pipeline *owner; + /* Pipeline modules management. */ + struct list_head node; +}; + +struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); + +int avs_load_topology(struct snd_soc_component *comp, const char *filename); +int avs_remove_topology(struct snd_soc_component *comp); + +#endif diff --git a/sound/soc/intel/avs/trace.c b/sound/soc/intel/avs/trace.c new file mode 100644 index 000000000000..a98da521db0f --- /dev/null +++ b/sound/soc/intel/avs/trace.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/types.h> + +#define CREATE_TRACE_POINTS +#include "trace.h" + +#define BYTES_PER_LINE 16 +#define MAX_CHUNK_SIZE ((PAGE_SIZE - 150) /* Place for trace header */ \ + / (2 * BYTES_PER_LINE + 4) /* chars per line */ \ + * BYTES_PER_LINE) + +void trace_avs_msg_payload(const void *data, size_t size) +{ + size_t remaining = size; + size_t offset = 0; + + while (remaining > 0) { + u32 chunk; + + chunk = min_t(size_t, remaining, MAX_CHUNK_SIZE); + trace_avs_ipc_msg_payload(data, chunk, offset, size); + + remaining -= chunk; + offset += chunk; + } +} diff --git a/sound/soc/intel/avs/trace.h b/sound/soc/intel/avs/trace.h new file mode 100644 index 000000000000..f4288d0ad5ef --- /dev/null +++ b/sound/soc/intel/avs/trace.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM intel_avs + +#if !defined(_TRACE_INTEL_AVS_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_INTEL_AVS_H + +#include <linux/types.h> +#include <linux/tracepoint.h> + +TRACE_EVENT(avs_dsp_core_op, + + TP_PROTO(unsigned int reg, unsigned int mask, const char *op, bool flag), + + TP_ARGS(reg, mask, op, flag), + + TP_STRUCT__entry( + __field(unsigned int, reg ) + __field(unsigned int, mask ) + __string(op, op ) + __field(bool, flag ) + ), + + TP_fast_assign( + __entry->reg = reg; + __entry->mask = mask; + __assign_str(op); + __entry->flag = flag; + ), + + TP_printk("%s: %d, core mask: 0x%X, prev state: 0x%08X", + __get_str(op), __entry->flag, __entry->mask, __entry->reg) +); + +#ifndef __TRACE_INTEL_AVS_TRACE_HELPER +#define __TRACE_INTEL_AVS_TRACE_HELPER + +void trace_avs_msg_payload(const void *data, size_t size); + +#define trace_avs_request(msg, sts, lec) \ +({ \ + trace_avs_ipc_request_msg((msg)->header, sts, lec); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) + +#define trace_avs_reply(msg, sts, lec) \ +({ \ + trace_avs_ipc_reply_msg((msg)->header, sts, lec); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) + +#define trace_avs_notify(msg, sts, lec) \ +({ \ + trace_avs_ipc_notify_msg((msg)->header, sts, lec); \ + trace_avs_msg_payload((msg)->data, (msg)->size); \ +}) +#endif + +DECLARE_EVENT_CLASS(avs_ipc_msg_hdr, + + TP_PROTO(u64 header, u32 sts, u32 lec), + + TP_ARGS(header, sts, lec), + + TP_STRUCT__entry( + __field(u64, header) + __field(u32, sts) + __field(u32, lec) + ), + + TP_fast_assign( + __entry->header = header; + __entry->sts = sts; + __entry->lec = lec; + ), + + TP_printk("primary: 0x%08X, extension: 0x%08X,\n" + "status: 0x%08X, error: 0x%08X", + lower_32_bits(__entry->header), upper_32_bits(__entry->header), + __entry->sts, __entry->lec) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_request_msg, + TP_PROTO(u64 header, u32 sts, u32 lec), + TP_ARGS(header, sts, lec) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_reply_msg, + TP_PROTO(u64 header, u32 sts, u32 lec), + TP_ARGS(header, sts, lec) +); + +DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_notify_msg, + TP_PROTO(u64 header, u32 sts, u32 lec), + TP_ARGS(header, sts, lec) +); + +TRACE_EVENT_CONDITION(avs_ipc_msg_payload, + + TP_PROTO(const u8 *data, size_t size, size_t offset, size_t total), + + TP_ARGS(data, size, offset, total), + + TP_CONDITION(data && size), + + TP_STRUCT__entry( + __dynamic_array(u8, buf, size ) + __field(size_t, offset ) + __field(size_t, pos ) + __field(size_t, total ) + ), + + TP_fast_assign( + memcpy(__get_dynamic_array(buf), data + offset, size); + __entry->offset = offset; + __entry->pos = offset + size; + __entry->total = total; + ), + + TP_printk("range %zu-%zu out of %zu bytes%s", + __entry->offset, __entry->pos, __entry->total, + __print_hex_dump("", DUMP_PREFIX_NONE, 16, 4, + __get_dynamic_array(buf), + __get_dynamic_array_len(buf), false)) +); + +TRACE_EVENT(avs_d0ix, + + TP_PROTO(const char *op, bool proceed, u64 header), + + TP_ARGS(op, proceed, header), + + TP_STRUCT__entry( + __string(op, op ) + __field(bool, proceed ) + __field(u64, header ) + ), + + TP_fast_assign( + __assign_str(op); + __entry->proceed = proceed; + __entry->header = header; + ), + + TP_printk("%s%s for request: 0x%08X 0x%08X", + __entry->proceed ? "" : "ignore ", __get_str(op), + lower_32_bits(__entry->header), upper_32_bits(__entry->header)) +); + +#endif /* _TRACE_INTEL_AVS_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE trace +#include <trace/define_trace.h> diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c new file mode 100644 index 000000000000..81f9b67d8e29 --- /dev/null +++ b/sound/soc/intel/avs/utils.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation +// +// Authors: Cezary Rojewski <cezary.rojewski@intel.com> +// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/kfifo.h> +#include <linux/slab.h> +#include "avs.h" +#include "messages.h" + +/* Caller responsible for holding adev->modres_mutex. */ +static int avs_module_entry_index(struct avs_dev *adev, const guid_t *uuid) +{ + int i; + + for (i = 0; i < adev->mods_info->count; i++) { + struct avs_module_entry *module; + + module = &adev->mods_info->entries[i]; + if (guid_equal(&module->uuid, uuid)) + return i; + } + + return -ENOENT; +} + +/* Caller responsible for holding adev->modres_mutex. */ +static int avs_module_id_entry_index(struct avs_dev *adev, u32 module_id) +{ + int i; + + for (i = 0; i < adev->mods_info->count; i++) { + struct avs_module_entry *module; + + module = &adev->mods_info->entries[i]; + if (module->module_id == module_id) + return i; + } + + return -ENOENT; +} + +int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + + mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; +} + +int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_module_entry *entry) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + + mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; +} + +int avs_get_module_id(struct avs_dev *adev, const guid_t *uuid) +{ + struct avs_module_entry module; + int ret; + + ret = avs_get_module_entry(adev, uuid, &module); + return !ret ? module.module_id : -ENOENT; +} + +bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) +{ + bool ret = false; + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + + mutex_unlock(&adev->modres_mutex); + return ret; +} + +/* Caller responsible for holding adev->modres_mutex. */ +static void avs_module_ida_destroy(struct avs_dev *adev) +{ + int i = adev->mods_info ? adev->mods_info->count : 0; + + while (i--) { + ida_destroy(adev->mod_idas[i]); + kfree(adev->mod_idas[i]); + } + kfree(adev->mod_idas); +} + +/* Caller responsible for holding adev->modres_mutex. */ +static int +avs_module_ida_alloc(struct avs_dev *adev, struct avs_mods_info *newinfo, bool purge) +{ + struct avs_mods_info *oldinfo = adev->mods_info; + struct ida **ida_ptrs; + u32 tocopy_count = 0; + int i; + + if (!purge && oldinfo) { + if (oldinfo->count >= newinfo->count) + dev_warn(adev->dev, "refreshing %d modules info with %d\n", + oldinfo->count, newinfo->count); + tocopy_count = oldinfo->count; + } + + ida_ptrs = kcalloc(newinfo->count, sizeof(*ida_ptrs), GFP_KERNEL); + if (!ida_ptrs) + return -ENOMEM; + + if (tocopy_count) + memcpy(ida_ptrs, adev->mod_idas, tocopy_count * sizeof(*ida_ptrs)); + + for (i = tocopy_count; i < newinfo->count; i++) { + ida_ptrs[i] = kzalloc(sizeof(**ida_ptrs), GFP_KERNEL); + if (!ida_ptrs[i]) { + while (i--) + kfree(ida_ptrs[i]); + + kfree(ida_ptrs); + return -ENOMEM; + } + + ida_init(ida_ptrs[i]); + } + + /* If old elements have been reused, don't wipe them. */ + if (tocopy_count) + kfree(adev->mod_idas); + else + avs_module_ida_destroy(adev); + + adev->mod_idas = ida_ptrs; + return 0; +} + +int avs_module_info_init(struct avs_dev *adev, bool purge) +{ + struct avs_mods_info *info; + int ret; + + ret = avs_ipc_get_modules_info(adev, &info); + if (ret) + return AVS_IPC_RET(ret); + + mutex_lock(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); + goto exit; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +exit: + mutex_unlock(&adev->modres_mutex); + return ret; +} + +void avs_module_info_free(struct avs_dev *adev) +{ + mutex_lock(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; + + mutex_unlock(&adev->modres_mutex); +} + +int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) +{ + int ret, idx, max_id; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); + ret = -EINVAL; + goto exit; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; + ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +exit: + mutex_unlock(&adev->modres_mutex); + return ret; +} + +void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) +{ + int idx; + + mutex_lock(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); + goto exit; + } + + ida_free(adev->mod_idas[idx], instance_id); +exit: + mutex_unlock(&adev->modres_mutex); +} + +/* + * Once driver loads FW it should keep it in memory, so we are not affected + * by FW removal from filesystem or even worse by loading different FW at + * runtime suspend/resume. + */ +int avs_request_firmware(struct avs_dev *adev, const struct firmware **fw_p, const char *name) +{ + struct avs_fw_entry *entry; + int ret; + + /* first check in list if it is not already loaded */ + list_for_each_entry(entry, &adev->fw_list, node) { + if (!strcmp(name, entry->name)) { + *fw_p = entry->fw; + return 0; + } + } + + /* FW is not loaded, let's load it now and add to the list */ + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->name = kstrdup_const(name, GFP_KERNEL); + if (!entry->name) { + kfree(entry); + return -ENOMEM; + } + + ret = request_firmware(&entry->fw, name, adev->dev); + if (ret < 0) { + kfree_const(entry->name); + kfree(entry); + return ret; + } + + *fw_p = entry->fw; + + list_add_tail(&entry->node, &adev->fw_list); + + return 0; +} + +/* + * Release single FW entry, used to handle errors in functions calling + * avs_request_firmware() + */ +void avs_release_last_firmware(struct avs_dev *adev) +{ + struct avs_fw_entry *entry; + + entry = list_last_entry(&adev->fw_list, typeof(*entry), node); + + list_del(&entry->node); + release_firmware(entry->fw); + kfree_const(entry->name); + kfree(entry); +} + +/* + * Release all FW entries, used on driver removal + */ +void avs_release_firmwares(struct avs_dev *adev) +{ + struct avs_fw_entry *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &adev->fw_list, node) { + list_del(&entry->node); + release_firmware(entry->fw); + kfree_const(entry->name); + kfree(entry); + } +} diff --git a/sound/soc/intel/avs/utils.h b/sound/soc/intel/avs/utils.h new file mode 100644 index 000000000000..955a40d2c30c --- /dev/null +++ b/sound/soc/intel/avs/utils.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2023 Intel Corporation + * + * Authors: Cezary Rojewski <cezary.rojewski@intel.com> + * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> + */ + +#ifndef __SOUND_SOC_INTEL_AVS_UTILS_H +#define __SOUND_SOC_INTEL_AVS_UTILS_H + +#include <sound/soc-acpi.h> + +extern bool obsolete_card_names; + +struct avs_mach_pdata { + struct hda_codec *codec; + unsigned long *tdms; + char *codec_name; /* DMIC only */ + + bool obsolete_card_names; +}; + +static inline bool avs_mach_singular_ssp(struct snd_soc_acpi_mach *mach) +{ + return hweight_long(mach->mach_params.i2s_link_mask) == 1; +} + +static inline u32 avs_mach_ssp_port(struct snd_soc_acpi_mach *mach) +{ + return __ffs(mach->mach_params.i2s_link_mask); +} + +static inline bool avs_mach_singular_tdm(struct snd_soc_acpi_mach *mach, u32 port) +{ + struct avs_mach_pdata *pdata = mach->pdata; + unsigned long *tdms = pdata->tdms; + + return !tdms || (hweight_long(tdms[port]) == 1); +} + +static inline u32 avs_mach_ssp_tdm(struct snd_soc_acpi_mach *mach, u32 port) +{ + struct avs_mach_pdata *pdata = mach->pdata; + unsigned long *tdms = pdata->tdms; + + return tdms ? __ffs(tdms[port]) : 0; +} + +static inline int avs_mach_get_ssp_tdm(struct device *dev, struct snd_soc_acpi_mach *mach, + int *ssp_port, int *tdm_slot) +{ + int port; + + if (!avs_mach_singular_ssp(mach)) { + dev_err(dev, "Invalid SSP configuration\n"); + return -EINVAL; + } + port = avs_mach_ssp_port(mach); + + if (!avs_mach_singular_tdm(mach, port)) { + dev_err(dev, "Invalid TDM configuration\n"); + return -EINVAL; + } + *ssp_port = port; + *tdm_slot = avs_mach_ssp_tdm(mach, *ssp_port); + + return 0; +} + +/* + * Macro to easily generate format strings + */ +#define AVS_STRING_FMT(prefix, suffix, ssp, tdm) \ + (tdm) ? prefix "%d:%d" suffix : prefix "%d" suffix, (ssp), (tdm) + +#endif |
