Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+20
View File
@@ -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/
+270
View File
@@ -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)
};
+367
View File
@@ -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 */
@@ -0,0 +1,675 @@
// 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_GLK, 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);
ret = -EINVAL;
goto exit;
}
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)
goto exit;
}
}
exit:
kfree(array);
return ret;
}
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);
}
@@ -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
@@ -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
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
+290
View File
@@ -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);
}
+32
View File
@@ -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
+92
View File
@@ -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)
};
+214
View File
@@ -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;
}
+27
View File
@@ -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
+959
View File
@@ -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_GLK, &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");
+91
View File
@@ -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
+438
View File
@@ -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);
}
+329
View File
@@ -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;
}
+203
View File
@@ -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)
};
+583
View File
@@ -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;
}
+28
View File
@@ -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;
}
+750
View File
@@ -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);
}
+904
View File
@@ -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
File diff suppressed because it is too large Load Diff
+201
View File
@@ -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);
}
}
File diff suppressed because it is too large Load Diff
+95
View File
@@ -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
File diff suppressed because it is too large Load Diff
+16
View File
@@ -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
+314
View File
@@ -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((__force snd_pcm_format_t)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));
}
+99
View File
@@ -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)
};
+184
View File
@@ -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 */
+180
View File
@@ -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)
};
+35
View File
@@ -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
};
+101
View File
@@ -0,0 +1,101 @@
// 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 <asm/cpuid/api.h>
#include "avs.h"
#include "debug.h"
#include "messages.h"
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);
}
/*
* Succeed if CPUID(0x15) is not available, or if the nominal core crystal clock
* frequency cannot be enumerated from it. There is nothing to do in both cases.
*/
static int avs_tgl_set_xtal_freq(struct avs_dev *adev)
{
unsigned int freq;
int ret;
if (boot_cpu_data.cpuid_level < CPUID_LEAF_TSC)
return 0;
freq = cpuid_ecx(CPUID_LEAF_TSC);
if (freq) {
ret = avs_ipc_set_fw_config(adev, 1, AVS_FW_CFG_XTAL_FREQ_HZ, sizeof(freq), &freq);
if (ret)
return AVS_IPC_RET(ret);
}
return 0;
}
static int avs_tgl_config_basefw(struct avs_dev *adev)
{
struct pci_dev *pci = adev->base.pci;
struct avs_bus_hwid hwid;
int ret;
ret = avs_tgl_set_xtal_freq(adev);
if (ret)
return ret;
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)
};
File diff suppressed because it is too large Load Diff
+238
View File
@@ -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
+33
View File
@@ -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;
}
}
+156
View File
@@ -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>
+302
View File
@@ -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 = kzalloc_objs(*ida_ptrs, newinfo->count);
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_obj(**ida_ptrs);
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_obj(*entry);
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);
}
}
+77
View File
@@ -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