Commit ec432e2a authored by Mark Brown's avatar Mark Brown

ASoC: Intel: avs: Driver core and PCM operations

Merge series from Cezary Rojewski <cezary.rojewski@intel.com>:

Part three of main AVS driver series. This series was originally part of
the initial series which was later divided [1] into smaller,
easier-to-review chunks. Thus, many patches found here were already
present on the list.

This series consists of code typical to many drivers - PCI driver
operations, trace ability, PM operations - as well as PCM handlers for
all standard audio interfaces, that is, HDA, I2S and DMIC are found
here.

Series starts with updating firmware boot flow - libraries are no longer
ignored. This change is dependent on already merged topology code [2]
and because of that could not be part of the initial series [1].

PCM operations are split into four changes. First component operations
alone i.e. operations which are usually agnostic towards path position
(FE/BE). Then it continues with "generic" FE operations - there is no
interface split here as from Intel ADSP point of view, FE, or HOST side
as it's called in the specs, involves HD-Audio operations only.
BE (also known as LINK) side on the other hand is divided into
"non-HD-Audio" and HD-Audio part. The former represents transfer over
DMIC and I2S interfaces both.

While patches implementing standard PCI driver operations along (again
standard) HD-Audio initialization routines followed up by power
management handlers are two major ones, series covers also other
important subjects such as:

While patches implementing standard PCI driver operations along (again
standard) HD-Audio initialization routines followed up by power
management handlers are two major ones, series covers also other
important subjects such as:

- event tracing
- preparation for firmware tracing (debugability)
- coredump (debugability)
- recovery flow (attempt recovery after IPC timeout or exception)
- D0ix (D0 device substate, complements standard power management)

Series is finalized by actual addition of supported platforms: SKL and
APL-based. Platform-specific files are limited to firmware-specific
bits, that is, bits that are specific to given firmware generation.
Everything else is shared and is part of already upstream messaging
code found in ipc.c, messages.c and messages.h files.

Changes in v3:
- addressed (hopefully) trace-code compilation under .configs with
  CONFIG_FTRACE dropped

Changes in v2:
- usage of avs_releast_last_firmware() dropped in error path for library
  loading procedure as suggested by Pierre
- 'link_mask' usage replaced with 'i2s_link_mask' as requested by
  Pierre. Existing code addressed with new patch:
  "ASoC: Intel: avs: Replace link_mask usage with i2s_link_mask"
- fixed possible race during recovery flow (->recovering flag is now
  atomic and tested in single location only).
- dropped platform prefixes for basically all i2s board descriptors
- 'ssp_test' renamed to 'i2s_test' to match naming convention of other
  boards
- simplified PM implementation for current series, 'low_power' bits
  moved to future series
- replaced SND_INTEL_DSP_DRIVER_SST with _AVS as suggested by Mark.
  Required changes to intel-dspcfg will be added in future series
- number of typos across commit messages addressed

[1]: https://lore.kernel.org/all/20220311153544.136854-1-cezary.rojewski@intel.com/
[2]: https://lore.kernel.org/all/20220331135246.993089-1-cezary.rojewski@intel.com/

Cezary Rojewski (14):
  ASoC: Intel: avs: Account for libraries when booting basefw
  ASoC: Intel: avs: Generic soc component driver
  ASoC: Intel: avs: Generic PCM FE operations
  ASoC: Intel: avs: non-HDA PCM BE operations
  ASoC: Intel: avs: HDA PCM BE operations
  ASoC: Intel: avs: Coredump and recovery flow
  ASoC: Intel: avs: Prepare for firmware tracing
  ASoC: Intel: avs: D0ix power state support
  ASoC: Intel: avs: Event tracing
  ASoC: Intel: avs: Machine board registration
  ASoC: Intel: avs: PCI driver implementation
  ASoC: Intel: avs: Power management
  ASoC: Intel: avs: SKL-based platforms support
  ASoC: Intel: avs: APL-based platforms support

Piotr Maziarz (1):
  ASoC: Intel: avs: Replace link_mask usage with i2s_link_mask

 include/sound/intel-dsp-config.h      |    3 +-
 include/sound/soc-acpi.h              |    2 +
 sound/soc/intel/Kconfig               |    2 +
 sound/soc/intel/avs/Makefile          |    7 +-
 sound/soc/intel/avs/apl.c             |  250 ++++++
 sound/soc/intel/avs/avs.h             |   79 ++
 sound/soc/intel/avs/board_selection.c |  501 +++++++++++
 sound/soc/intel/avs/core.c            |  631 +++++++++++++
 sound/soc/intel/avs/dsp.c             |   27 +-
 sound/soc/intel/avs/ipc.c             |  253 +++++-
 sound/soc/intel/avs/loader.c          |   84 ++
 sound/soc/intel/avs/messages.c        |   35 +-
 sound/soc/intel/avs/messages.h        |   51 ++
 sound/soc/intel/avs/pcm.c             | 1182 +++++++++++++++++++++++++
 sound/soc/intel/avs/registers.h       |    8 +
 sound/soc/intel/avs/skl.c             |  125 +++
 sound/soc/intel/avs/topology.c        |   14 +-
 sound/soc/intel/avs/trace.c           |   33 +
 sound/soc/intel/avs/trace.h           |  154 ++++
 sound/soc/intel/avs/utils.c           |   23 +
 20 files changed, 3443 insertions(+), 21 deletions(-)
 create mode 100644 sound/soc/intel/avs/apl.c
 create mode 100644 sound/soc/intel/avs/board_selection.c
 create mode 100644 sound/soc/intel/avs/pcm.c
 create mode 100644 sound/soc/intel/avs/skl.c
 create mode 100644 sound/soc/intel/avs/trace.c
 create mode 100644 sound/soc/intel/avs/trace.h

--
2.25.1
parents 37a86b32 c8c960c1
......@@ -15,7 +15,8 @@ enum {
SND_INTEL_DSP_DRIVER_LEGACY,
SND_INTEL_DSP_DRIVER_SST,
SND_INTEL_DSP_DRIVER_SOF,
SND_INTEL_DSP_DRIVER_LAST = SND_INTEL_DSP_DRIVER_SOF
SND_INTEL_DSP_DRIVER_AVS,
SND_INTEL_DSP_DRIVER_LAST = SND_INTEL_DSP_DRIVER_AVS
};
#if IS_ENABLED(CONFIG_SND_INTEL_DSP_CONFIG)
......
......@@ -156,6 +156,7 @@ struct snd_soc_acpi_link_adr {
* @links: array of link _ADR descriptors, null terminated.
* @drv_name: machine driver name
* @fw_filename: firmware file name. Used when SOF is not enabled.
* @tplg_filename: topology file name. Used when SOF is not enabled.
* @board: board name
* @machine_quirk: pointer to quirk, usually based on DMI information when
* ACPI ID alone is not sufficient, wrong or misleading
......@@ -174,6 +175,7 @@ struct snd_soc_acpi_mach {
const struct snd_soc_acpi_link_adr *links;
const char *drv_name;
const char *fw_filename;
const char *tplg_filename;
const char *board;
struct snd_soc_acpi_mach * (*machine_quirk)(void *arg);
const void *quirk_data;
......
......@@ -216,9 +216,11 @@ config SND_SOC_INTEL_AVS
depends on COMMON_CLK
select SND_SOC_ACPI if ACPI
select SND_SOC_TOPOLOGY
select SND_HDA
select SND_HDA_EXT_CORE
select SND_HDA_DSP_LOADER
select SND_INTEL_DSP_CONFIG
select WANT_DEV_COREDUMP
help
Enable support for Intel(R) cAVS 1.5 platforms with DSP
capabilities. This includes Skylake, Kabylake, Amberlake and
......
# SPDX-License-Identifier: GPL-2.0-only
snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o \
topology.o path.o
topology.o path.o pcm.o board_selection.o
snd-soc-avs-objs += cldma.o
snd-soc-avs-objs += skl.o apl.o
snd-soc-avs-objs += trace.o
# tell define_trace.h where to find the trace header
CFLAGS_trace.o := -I$(src)
obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
//
// Authors: Cezary Rojewski <cezary.rojewski@intel.com>
// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
//
#include <linux/devcoredump.h>
#include <linux/slab.h>
#include "avs.h"
#include "messages.h"
#include "path.h"
#include "topology.h"
static int 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 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;
}
static int apl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg)
{
struct apl_log_buffer_layout layout;
unsigned long flags;
void __iomem *addr, *buf;
addr = avs_log_buffer_addr(adev, msg->log.core);
if (!addr)
return -ENXIO;
memcpy_fromio(&layout, addr, sizeof(layout));
spin_lock_irqsave(&adev->dbg.trace_lock, flags);
if (!kfifo_initialized(&adev->dbg.trace_fifo))
/* consume the logs regardless of consumer presence */
goto update_read_ptr;
buf = apl_log_payload_addr(addr);
if (layout.read_ptr > layout.write_ptr) {
__kfifo_fromio_locked(&adev->dbg.trace_fifo, buf + layout.read_ptr,
apl_log_payload_size(adev) - layout.read_ptr,
&adev->dbg.fifo_lock);
layout.read_ptr = 0;
}
__kfifo_fromio_locked(&adev->dbg.trace_fifo, buf + layout.read_ptr,
layout.write_ptr - layout.read_ptr, &adev->dbg.fifo_lock);
wake_up(&adev->dbg.trace_waitq);
update_read_ptr:
spin_unlock_irqrestore(&adev->dbg.trace_lock, flags);
writel(layout.write_ptr, addr);
return 0;
}
static int apl_wait_log_entry(struct avs_dev *adev, u32 core, struct 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 apl_is_entry_stackdump(addr) ((readl(addr) >> 30) & 0x1)
static int apl_coredump(struct avs_dev *adev, union avs_notify_msg *msg)
{
struct apl_log_buffer_layout layout;
void __iomem *addr, *buf;
size_t dump_size;
u16 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 = apl_log_payload_addr(addr);
memcpy_fromio(&layout, addr, sizeof(layout));
if (!apl_is_entry_stackdump(buf + layout.read_ptr)) {
/*
* DSP awaits the remaining logs to be
* gathered before dumping stack
*/
msg->log.core = msg->ext.coredump.core_id;
avs_dsp_op(adev, log_buffer_status, msg);
}
pos = dump + AVS_FW_REGS_SIZE;
/* gather the stack */
do {
u32 count;
if (apl_wait_log_entry(adev, msg->ext.coredump.core_id, &layout))
break;
if (layout.read_ptr > layout.write_ptr) {
count = 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 apl_lp_streaming(struct avs_dev *adev)
{
struct avs_path *path;
/* 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)
return false;
}
}
}
return true;
}
static bool 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 apl_lp_streaming(adev);
}
static int 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 apl_dsp_ops = {
.power = avs_dsp_core_power,
.reset = avs_dsp_core_reset,
.stall = avs_dsp_core_stall,
.irq_handler = avs_dsp_irq_handler,
.irq_thread = avs_dsp_irq_thread,
.int_control = avs_dsp_interrupt_control,
.load_basefw = avs_hda_load_basefw,
.load_lib = avs_hda_load_library,
.transfer_mods = avs_hda_transfer_modules,
.enable_logs = apl_enable_logs,
.log_buffer_offset = skl_log_buffer_offset,
.log_buffer_status = apl_log_buffer_status,
.coredump = apl_coredump,
.d0ix_toggle = apl_d0ix_toggle,
.set_d0ix = apl_set_d0ix,
};
......@@ -11,6 +11,7 @@
#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>
......@@ -19,6 +20,9 @@
struct avs_dev;
struct avs_tplg;
struct avs_tplg_library;
struct avs_soc_component;
struct avs_ipc_msg;
/*
* struct avs_dsp_ops - Platform-specific DSP operations
......@@ -40,11 +44,21 @@ struct avs_dsp_ops {
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 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 skl_dsp_ops;
extern const struct avs_dsp_ops apl_dsp_ops;
#define AVS_PLATATTR_CLDMA BIT_ULL(0)
#define AVS_PLATATTR_IMR BIT_ULL(1)
......@@ -72,6 +86,16 @@ struct avs_fw_entry {
struct list_head node;
};
struct avs_debug {
struct kfifo trace_fifo;
spinlock_t fifo_lock; /* serialize I/O for trace_fifo */
spinlock_t trace_lock; /* serialize debug window I/O between each LOG_BUFFER_STATUS */
wait_queue_head_t trace_waitq;
u32 aging_timer_period;
u32 fifo_full_timer_period;
u32 logged_resources; /* context dependent: core or library */
};
/*
* struct avs_dev - Intel HD-Audio driver data
*
......@@ -105,6 +129,7 @@ struct avs_dev {
char **lib_names;
struct completion fw_ready;
struct work_struct probe_work;
struct nhlt_acpi_table *nhlt;
struct list_head comp_list;
......@@ -112,6 +137,8 @@ struct avs_dev {
struct list_head path_list;
spinlock_t path_list_lock;
struct mutex path_mutex;
struct avs_debug dbg;
};
/* from hda_bus to avs_dev */
......@@ -162,12 +189,18 @@ struct avs_ipc {
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
......@@ -204,6 +237,11 @@ int avs_dsp_send_msg_timeout(struct avs_dev *adev,
struct avs_ipc_msg *reply, int timeout);
int avs_dsp_send_msg(struct avs_dev *adev,
struct avs_ipc_msg *request, struct avs_ipc_msg *reply);
/* 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);
int avs_dsp_send_pm_msg(struct avs_dev *adev, struct avs_ipc_msg *request,
struct avs_ipc_msg *reply, bool wake_d0i0);
int avs_dsp_send_rom_msg_timeout(struct avs_dev *adev,
struct avs_ipc_msg *request, int timeout);
int avs_dsp_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *request);
......@@ -211,6 +249,11 @@ 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 skl_log_buffer_offset(struct avs_dev *adev, u32 core);
/* Firmware resources management */
int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_module_entry *entry);
......@@ -241,6 +284,7 @@ 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);
......@@ -267,4 +311,39 @@ struct avs_soc_component {
extern const struct snd_soc_dai_ops avs_dai_fe_ops;
int avs_dmic_platform_register(struct avs_dev *adev, const char *name);
int avs_i2s_platform_register(struct avs_dev *adev, const char *name, unsigned long port_mask,
unsigned long *tdms);
int avs_hda_platform_register(struct avs_dev *adev, const char *name);
int avs_register_all_boards(struct avs_dev *adev);
void avs_unregister_all_boards(struct avs_dev *adev);
/* Firmware tracing helpers */
unsigned int __kfifo_fromio_locked(struct kfifo *fifo, const void __iomem *src, unsigned int len,
spinlock_t *lock);
#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); \
})
struct apl_log_buffer_layout {
u32 read_ptr;
u32 write_ptr;
u8 buffer[];
} __packed;
#define apl_log_payload_size(adev) \
(avs_log_buffer_size(adev) - sizeof(struct apl_log_buffer_layout))
#define apl_log_payload_addr(addr) \
(addr + sizeof(struct apl_log_buffer_layout))
#endif /* __SOUND_SOC_INTEL_AVS_H */
This diff is collapsed.
This diff is collapsed.
......@@ -6,10 +6,10 @@
// Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
//
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include "avs.h"
#include "registers.h"
#include "trace.h"
#define AVS_ADSPCS_INTERVAL_US 500
#define AVS_ADSPCS_TIMEOUT_US 50000
......@@ -19,6 +19,9 @@ 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;
......@@ -43,6 +46,9 @@ 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;
......@@ -64,6 +70,9 @@ 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;
......@@ -152,6 +161,15 @@ static int avs_dsp_get_core(struct avs_dev *adev, u32 core_id)
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;
......@@ -160,6 +178,8 @@ static int avs_dsp_get_core(struct avs_dev *adev, u32 core_id)
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);
......@@ -185,6 +205,9 @@ static int avs_dsp_put_core(struct avs_dev *adev, u32 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;
......@@ -298,5 +321,3 @@ int avs_dsp_delete_pipeline(struct avs_dev *adev, u8 instance_id)
ida_free(&adev->ppl_ida, instance_id);
return ret;
}
MODULE_LICENSE("GPL");
This diff is collapsed.
......@@ -15,6 +15,7 @@
#include "cldma.h"
#include "messages.h"
#include "registers.h"
#include "topology.h"
#define AVS_ROM_STS_MASK 0xFF
#define AVS_ROM_INIT_DONE 0x1
......@@ -36,6 +37,8 @@
#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;
......@@ -86,6 +89,8 @@ static int avs_fw_manifest_offset(struct firmware *fw)
switch (magic) {
case SKL_MANIFEST_MAGIC:
return SKL_ADSPFW_OFFSET;
case APL_MANIFEST_MAGIC:
return APL_ADSPFW_OFFSET;
default:
return -EINVAL;
}
......@@ -466,6 +471,71 @@ int avs_hda_transfer_modules(struct avs_dev *adev, bool load,
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;
strncpy(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;
......@@ -519,6 +589,7 @@ static int avs_dsp_load_basefw(struct avs_dev *adev)
int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge)
{
struct avs_soc_component *acomp;
int ret, i;
/* Forgo full boot if flash from IMR succeeds. */
......@@ -538,7 +609,20 @@ int avs_dsp_boot_firmware(struct avs_dev *adev, bool purge)
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);
......
......@@ -432,7 +432,7 @@ int avs_ipc_set_dx(struct avs_dev *adev, u32 core_mask, bool powerup)
request.data = &dx;
request.size = sizeof(dx);
ret = avs_dsp_send_msg(adev, &request, NULL);
ret = avs_dsp_send_pm_msg(adev, &request, NULL, true);
if (ret)
avs_ipc_err(adev, &request, "set dx", ret);
......@@ -456,7 +456,7 @@ int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming)
request.header = msg.val;
ret = avs_dsp_send_msg(adev, &request, NULL);
ret = avs_dsp_send_pm_msg(adev, &request, NULL, false);
if (ret)
avs_ipc_err(adev, &request, "set d0ix", ret);
......@@ -677,6 +677,37 @@ int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info)
return 0;
}
int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size)
{
int ret;
ret = avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID,
AVS_BASEFW_ENABLE_LOGS, log_info, size);
if (ret)
dev_err(adev->dev, "enable logs failed: %d\n", ret);
return ret;
}
int avs_ipc_set_system_time(struct avs_dev *adev)
{
struct avs_sys_time sys_time;
int ret;
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;
ret = avs_ipc_set_large_config(adev, AVS_BASEFW_MOD_ID, AVS_BASEFW_INST_ID,
AVS_BASEFW_SYSTEM_TIME, (u8 *)&sys_time, sizeof(sys_time));
if (ret)
dev_err(adev->dev, "set system time failed: %d\n", ret);
return ret;
}
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,
......
......@@ -186,7 +186,9 @@ union avs_reply_msg {
enum avs_notify_msg_type {
AVS_NOTIFY_PHRASE_DETECTED = 4,
AVS_NOTIFY_RESOURCE_EVENT = 5,
AVS_NOTIFY_LOG_BUFFER_STATUS = 6,
AVS_NOTIFY_FW_READY = 8,
AVS_NOTIFY_EXCEPTION_CAUGHT = 10,
AVS_NOTIFY_MODULE_EVENT = 12,
};
......@@ -202,9 +204,17 @@ union avs_notify_msg {
u32 msg_direction:1;
u32 msg_target:1;
};
struct {
u16 rsvd:12;
u16 core:4;
} log;
};
union {
u32 val;
struct {
u32 core_id:2;
u32 stack_dump_size:16;
} coredump;
} ext;
};
} __packed;
......@@ -324,12 +334,46 @@ int avs_ipc_set_d0ix(struct avs_dev *adev, bool enable_pg, bool streaming);
#define AVS_BASEFW_INST_ID 0
enum avs_basefw_runtime_param {
AVS_BASEFW_ENABLE_LOGS = 6,
AVS_BASEFW_FIRMWARE_CONFIG = 7,
AVS_BASEFW_HARDWARE_CONFIG = 8,
AVS_BASEFW_MODULES_INFO = 9,
AVS_BASEFW_LIBRARIES_INFO = 16,
AVS_BASEFW_SYSTEM_TIME = 20,
};
enum avs_log_enable {
AVS_LOG_DISABLE = 0,
AVS_LOG_ENABLE = 1
};
enum avs_skl_log_priority {
AVS_SKL_LOG_CRITICAL = 1,
AVS_SKL_LOG_HIGH,
AVS_SKL_LOG_MEDIUM,
AVS_SKL_LOG_LOW,
AVS_SKL_LOG_VERBOSE,
};
struct skl_log_state {
u32 enable;
u32 min_priority;
} __packed;
struct skl_log_state_info {
u32 core_mask;
struct skl_log_state logs_core[];
} __packed;
struct apl_log_state_info {
u32 aging_timer_period;
u32 fifo_full_timer_period;
u32 core_mask;
struct skl_log_state logs_core[];
} __packed;
int avs_ipc_set_enable_logs(struct avs_dev *adev, u8 *log_info, size_t size);
struct avs_fw_version {
u16 major;
u16 minor;
......@@ -497,6 +541,13 @@ static inline bool avs_module_entry_is_loaded(struct avs_module_entry *mentry)
int avs_ipc_get_modules_info(struct avs_dev *adev, struct avs_mods_info **info);
struct avs_sys_time {
u32 val_l;
u32 val_u;
} __packed;
int avs_ipc_set_system_time(struct avs_dev *adev);
/* Module configuration */
#define AVS_MIXIN_MOD_UUID \
......
This diff is collapsed.
......@@ -14,6 +14,7 @@
#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
......@@ -47,6 +48,12 @@
#define SKL_ADSP_HIPCIE_DONE BIT(30)
#define SKL_ADSP_HIPCT_BUSY BIT(31)
/* 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
/* Constants used when accessing SRAM, space shared with firmware */
#define AVS_FW_REG_BASE(adev) ((adev)->spec->sram_base_offset)
#define AVS_FW_REG_STATUS(adev) (AVS_FW_REG_BASE(adev) + 0x0)
......@@ -58,6 +65,7 @@
#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) \
......
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
//
// 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 "messages.h"
static int 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 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, GENMASK(num_cores, 0)) {
info->logs_core[i].enable = enable;
info->logs_core[i].min_priority = *priorities++;
}
else
for_each_set_bit(i, &resource_mask, GENMASK(num_cores, 0))
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 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
skl_log_buffer_status(struct avs_dev *adev, union avs_notify_msg *msg)
{
unsigned long flags;
void __iomem *buf;
u16 size, write, offset;
spin_lock_irqsave(&adev->dbg.trace_lock, flags);
if (!kfifo_initialized(&adev->dbg.trace_fifo)) {
spin_unlock_irqrestore(&adev->dbg.trace_lock, flags);
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;
__kfifo_fromio_locked(&adev->dbg.trace_fifo, buf, size, &adev->dbg.fifo_lock);
wake_up(&adev->dbg.trace_waitq);
spin_unlock_irqrestore(&adev->dbg.trace_lock, flags);
return 0;
}
static int 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
skl_d0ix_toggle(struct avs_dev *adev, struct avs_ipc_msg *tx, bool wake)
{
/* unsupported on cAVS 1.5 hw */
return false;
}
static int skl_set_d0ix(struct avs_dev *adev, bool enable)
{
/* unsupported on cAVS 1.5 hw */
return 0;
}
const struct avs_dsp_ops skl_dsp_ops = {
.power = avs_dsp_core_power,
.reset = avs_dsp_core_reset,
.stall = avs_dsp_core_stall,
.irq_handler = avs_dsp_irq_handler,
.irq_thread = avs_dsp_irq_thread,
.int_control = avs_dsp_interrupt_control,
.load_basefw = avs_cldma_load_basefw,
.load_lib = avs_cldma_load_library,
.transfer_mods = avs_cldma_transfer_modules,
.enable_logs = skl_enable_logs,
.log_buffer_offset = skl_log_buffer_offset,
.log_buffer_status = skl_log_buffer_status,
.coredump = skl_coredump,
.d0ix_toggle = skl_d0ix_toggle,
.set_d0ix = skl_set_d0ix,
};
......@@ -15,8 +15,6 @@
#include "avs.h"
#include "topology.h"
const struct snd_soc_dai_ops avs_dai_fe_ops;
/* Get pointer to vendor array at the specified offset. */
#define avs_tplg_vendor_array_at(array, offset) \
((struct snd_soc_tplg_vendor_array *)((u8 *)array + offset))
......@@ -383,11 +381,11 @@ static int parse_link_formatted_string(struct snd_soc_component *comp, void *ele
* Dynamic naming - string formats, e.g.: ssp%d - supported only for
* topologies describing single device e.g.: an I2S codec on SSP0.
*/
if (hweight_long(mach->link_mask) != 1)
if (hweight_long(mach->mach_params.i2s_link_mask) != 1)
return avs_parse_string_token(comp, elem, object, offset);
snprintf(val, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, tuple->string,
__ffs(mach->link_mask));
__ffs(mach->mach_params.i2s_link_mask));
return 0;
}
......@@ -1352,8 +1350,8 @@ static int avs_route_load(struct snd_soc_component *comp, int index,
u32 port;
/* See parse_link_formatted_string() for dynamic naming when(s). */
if (hweight_long(mach->link_mask) == 1) {
port = __ffs(mach->link_mask);
if (hweight_long(mach->mach_params.i2s_link_mask) == 1) {
port = __ffs(mach->mach_params.i2s_link_mask);
snprintf(buf, len, route->source, port);
strncpy((char *)route->source, buf, len);
......@@ -1384,10 +1382,10 @@ static int avs_widget_load(struct snd_soc_component *comp, int index,
mach = dev_get_platdata(comp->card->dev);
/* See parse_link_formatted_string() for dynamic naming when(s). */
if (hweight_long(mach->link_mask) == 1) {
if (hweight_long(mach->mach_params.i2s_link_mask) == 1) {
kfree(w->name);
/* w->name is freed later by soc_tplg_dapm_widget_create() */
w->name = kasprintf(GFP_KERNEL, dw->name, __ffs(mach->link_mask));
w->name = kasprintf(GFP_KERNEL, dw->name, __ffs(mach->mach_params.i2s_link_mask));
if (!w->name)
return -ENOMEM;
}
......
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
//
// 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(remaining, (size_t)MAX_CHUNK_SIZE);
trace_avs_ipc_msg_payload(data, chunk, offset, size);
remaining -= chunk;
offset += chunk;
}
}
/* 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, 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, fwregs) \
({ \
trace_avs_ipc_request_msg((msg)->header, fwregs); \
trace_avs_msg_payload((msg)->data, (msg)->size); \
})
#define trace_avs_reply(msg, fwregs) \
({ \
trace_avs_ipc_reply_msg((msg)->header, fwregs); \
trace_avs_msg_payload((msg)->data, (msg)->size); \
})
#define trace_avs_notify(msg, fwregs) \
({ \
trace_avs_ipc_notify_msg((msg)->header, fwregs); \
trace_avs_msg_payload((msg)->data, (msg)->size); \
})
#endif
DECLARE_EVENT_CLASS(avs_ipc_msg_hdr,
TP_PROTO(u64 header, u64 fwregs),
TP_ARGS(header, fwregs),
TP_STRUCT__entry(
__field(u64, header)
__field(u64, fwregs)
),
TP_fast_assign(
__entry->header = header;
__entry->fwregs = fwregs;
),
TP_printk("primary: 0x%08X, extension: 0x%08X,\n"
"fwstatus: 0x%08X, fwerror: 0x%08X",
lower_32_bits(__entry->header), upper_32_bits(__entry->header),
lower_32_bits(__entry->fwregs), upper_32_bits(__entry->fwregs))
);
DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_request_msg,
TP_PROTO(u64 header, u64 fwregs),
TP_ARGS(header, fwregs)
);
DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_reply_msg,
TP_PROTO(u64 header, u64 fwregs),
TP_ARGS(header, fwregs)
);
DEFINE_EVENT(avs_ipc_msg_hdr, avs_ipc_notify_msg,
TP_PROTO(u64 header, u64 fwregs),
TP_ARGS(header, fwregs)
);
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, 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>
......@@ -7,6 +7,7 @@
//
#include <linux/firmware.h>
#include <linux/kfifo.h>
#include <linux/slab.h>
#include "avs.h"
#include "messages.h"
......@@ -299,3 +300,25 @@ void avs_release_firmwares(struct avs_dev *adev)
kfree(entry);
}
}
unsigned int __kfifo_fromio_locked(struct kfifo *fifo, const void __iomem *src, unsigned int len,
spinlock_t *lock)
{
struct __kfifo *__fifo = &fifo->kfifo;
unsigned long flags;
unsigned int l, off;
spin_lock_irqsave(lock, flags);
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;
spin_unlock_irqrestore(lock, flags);
return len;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment