This patch addresses the issue of invalid initialization sequence and the use of registers in `dma_config` before the device is fully initialized in the Intel ADSP HDA DMA driver. Changes include: 1. Moving the `intel_adsp_hda_channels_init` call to the `intel_adsp_hda_dma_init` function to ensure that channels are initialized during device initialization. 2. Removing the redundant call to `intel_adsp_hda_channels_init` from the `PM_DEVICE_ACTION_RESUME` case in the `intel_adsp_hda_dma_pm_action` function. These changes ensure that the device and its channels are properly initialized before any DMA configuration is performed, preventing access to hardware registers before the device is ready. **Note:** This is a proposed solution, and a different approach should be considered. Currently, we are accessing registers before the device and power domain are fully powered up. This solution likely works because the DMA is used to load firmware during the boot process, and the necessary power domains are already powered up. Further investigation and a more robust solution are recommended to ensure proper initialization and power management. Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
502 lines
14 KiB
C
502 lines
14 KiB
C
/*
|
|
* Copyright (c) 2022 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @brief Intel ADSP HDA DMA (Stream) driver
|
|
*
|
|
* HDA is effectively, from the DSP, a ringbuffer (fifo) where the read
|
|
* and write positions are maintained by the hardware and the software may
|
|
* commit read/writes by writing to another register (DGFPBI) the length of
|
|
* the read or write.
|
|
*
|
|
* It's important that the software knows the position in the ringbuffer to read
|
|
* or write from. It's also important that the buffer be placed in the correct
|
|
* memory region and aligned to 128 bytes. Lastly it's important the host and
|
|
* dsp coordinate the order in which operations takes place. Doing all that
|
|
* HDA streams are a fantastic bit of hardware and do their job well.
|
|
*
|
|
* There are 4 types of streams, with a set of each available to be used to
|
|
* communicate to or from the Host or Link. Each stream set is uni directional.
|
|
*/
|
|
|
|
#include <zephyr/drivers/dma.h>
|
|
|
|
#include "dma_intel_adsp_hda.h"
|
|
#include <intel_adsp_hda.h>
|
|
|
|
int intel_adsp_hda_dma_host_in_config(const struct device *dev,
|
|
uint32_t channel,
|
|
struct dma_config *dma_cfg)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
struct dma_block_config *blk_cfg;
|
|
uint8_t *buf;
|
|
int res;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
__ASSERT(dma_cfg->block_count == 1,
|
|
"HDA does not support scatter gather or chained "
|
|
"block transfers.");
|
|
__ASSERT(dma_cfg->channel_direction == cfg->direction,
|
|
"Unexpected channel direction, HDA host in supports "
|
|
"MEMORY_TO_HOST");
|
|
|
|
blk_cfg = dma_cfg->head_block;
|
|
buf = (uint8_t *)(uintptr_t)(blk_cfg->source_address);
|
|
res = intel_adsp_hda_set_buffer(cfg->base, cfg->regblock_size, channel, buf,
|
|
blk_cfg->block_size);
|
|
|
|
if (res == 0) {
|
|
*DGMBS(cfg->base, cfg->regblock_size, channel) =
|
|
blk_cfg->block_size & HDA_ALIGN_MASK;
|
|
|
|
intel_adsp_hda_set_sample_container_size(cfg->base, cfg->regblock_size, channel,
|
|
dma_cfg->source_data_size);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
int intel_adsp_hda_dma_host_out_config(const struct device *dev,
|
|
uint32_t channel,
|
|
struct dma_config *dma_cfg)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
struct dma_block_config *blk_cfg;
|
|
uint8_t *buf;
|
|
int res;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
__ASSERT(dma_cfg->block_count == 1,
|
|
"HDA does not support scatter gather or chained "
|
|
"block transfers.");
|
|
__ASSERT(dma_cfg->channel_direction == cfg->direction,
|
|
"Unexpected channel direction, HDA host out supports "
|
|
"HOST_TO_MEMORY");
|
|
|
|
blk_cfg = dma_cfg->head_block;
|
|
buf = (uint8_t *)(uintptr_t)(blk_cfg->dest_address);
|
|
|
|
res = intel_adsp_hda_set_buffer(cfg->base, cfg->regblock_size, channel, buf,
|
|
blk_cfg->block_size);
|
|
|
|
if (res == 0) {
|
|
*DGMBS(cfg->base, cfg->regblock_size, channel) =
|
|
blk_cfg->block_size & HDA_ALIGN_MASK;
|
|
|
|
intel_adsp_hda_set_sample_container_size(cfg->base, cfg->regblock_size, channel,
|
|
dma_cfg->dest_data_size);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_link_in_config(const struct device *dev,
|
|
uint32_t channel,
|
|
struct dma_config *dma_cfg)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
struct dma_block_config *blk_cfg;
|
|
uint8_t *buf;
|
|
int res;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
__ASSERT(dma_cfg->block_count == 1,
|
|
"HDA does not support scatter gather or chained "
|
|
"block transfers.");
|
|
__ASSERT(dma_cfg->channel_direction == cfg->direction,
|
|
"Unexpected channel direction, HDA link in supports "
|
|
"PERIPHERAL_TO_MEMORY");
|
|
|
|
blk_cfg = dma_cfg->head_block;
|
|
buf = (uint8_t *)(uintptr_t)(blk_cfg->dest_address);
|
|
res = intel_adsp_hda_set_buffer(cfg->base, cfg->regblock_size, channel, buf,
|
|
blk_cfg->block_size);
|
|
if (res == 0) {
|
|
intel_adsp_hda_set_sample_container_size(cfg->base, cfg->regblock_size, channel,
|
|
dma_cfg->dest_data_size);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
int intel_adsp_hda_dma_link_out_config(const struct device *dev,
|
|
uint32_t channel,
|
|
struct dma_config *dma_cfg)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
struct dma_block_config *blk_cfg;
|
|
uint8_t *buf;
|
|
int res;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
__ASSERT(dma_cfg->block_count == 1,
|
|
"HDA does not support scatter gather or chained "
|
|
"block transfers.");
|
|
__ASSERT(dma_cfg->channel_direction == cfg->direction,
|
|
"Unexpected channel direction, HDA link out supports "
|
|
"MEMORY_TO_PERIPHERAL");
|
|
|
|
blk_cfg = dma_cfg->head_block;
|
|
buf = (uint8_t *)(uintptr_t)(blk_cfg->source_address);
|
|
|
|
res = intel_adsp_hda_set_buffer(cfg->base, cfg->regblock_size, channel, buf,
|
|
blk_cfg->block_size);
|
|
if (res == 0) {
|
|
intel_adsp_hda_set_sample_container_size(cfg->base, cfg->regblock_size, channel,
|
|
dma_cfg->source_data_size);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
int intel_adsp_hda_dma_link_reload(const struct device *dev, uint32_t channel,
|
|
uint32_t src, uint32_t dst, size_t size)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
|
|
intel_adsp_hda_link_commit(cfg->base, cfg->regblock_size, channel, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_host_reload(const struct device *dev, uint32_t channel,
|
|
uint32_t src, uint32_t dst, size_t size)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
|
|
#if CONFIG_DMA_INTEL_ADSP_HDA_TIMING_L1_EXIT
|
|
const size_t buf_size = intel_adsp_hda_get_buffer_size(cfg->base, cfg->regblock_size,
|
|
channel);
|
|
|
|
if (!buf_size) {
|
|
return -EIO;
|
|
}
|
|
|
|
intel_adsp_force_dmi_l0_state();
|
|
switch (cfg->direction) {
|
|
case HOST_TO_MEMORY:
|
|
; /* Only statements can be labeled in C, a declaration is not valid */
|
|
const uint32_t rp = *DGBRP(cfg->base, cfg->regblock_size, channel);
|
|
const uint32_t next_rp = (rp + INTEL_HDA_MIN_FPI_INCREMENT_FOR_INTERRUPT) %
|
|
buf_size;
|
|
|
|
intel_adsp_hda_set_buffer_segment_ptr(cfg->base, cfg->regblock_size,
|
|
channel, next_rp);
|
|
intel_adsp_hda_enable_buffer_interrupt(cfg->base, cfg->regblock_size, channel);
|
|
break;
|
|
case MEMORY_TO_HOST:
|
|
;
|
|
const uint32_t wp = *DGBWP(cfg->base, cfg->regblock_size, channel);
|
|
const uint32_t next_wp = (wp + INTEL_HDA_MIN_FPI_INCREMENT_FOR_INTERRUPT) %
|
|
buf_size;
|
|
|
|
intel_adsp_hda_set_buffer_segment_ptr(cfg->base, cfg->regblock_size,
|
|
channel, next_wp);
|
|
intel_adsp_hda_enable_buffer_interrupt(cfg->base, cfg->regblock_size, channel);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
intel_adsp_hda_host_commit(cfg->base, cfg->regblock_size, channel, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_status(const struct device *dev, uint32_t channel,
|
|
struct dma_status *stat)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
uint32_t llp_l = 0;
|
|
uint32_t llp_u = 0;
|
|
bool xrun_det;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
|
|
uint32_t unused = intel_adsp_hda_unused(cfg->base, cfg->regblock_size, channel);
|
|
uint32_t used = *DGBS(cfg->base, cfg->regblock_size, channel) - unused;
|
|
|
|
stat->dir = cfg->direction;
|
|
stat->busy = *DGCS(cfg->base, cfg->regblock_size, channel) & DGCS_GBUSY;
|
|
stat->write_position = *DGBWP(cfg->base, cfg->regblock_size, channel);
|
|
stat->read_position = *DGBRP(cfg->base, cfg->regblock_size, channel);
|
|
stat->pending_length = used;
|
|
stat->free = unused;
|
|
|
|
#if CONFIG_SOC_INTEL_ACE20_LNL || CONFIG_SOC_INTEL_ACE30
|
|
/* Linear Link Position via HDA-DMA is only supported on ACE2 or newer */
|
|
if (cfg->direction == MEMORY_TO_PERIPHERAL || cfg->direction == PERIPHERAL_TO_MEMORY) {
|
|
uint32_t tmp;
|
|
|
|
tmp = *DGLLLPL(cfg->base, cfg->regblock_size, channel);
|
|
llp_u = *DGLLLPU(cfg->base, cfg->regblock_size, channel);
|
|
llp_l = *DGLLLPL(cfg->base, cfg->regblock_size, channel);
|
|
if (tmp > llp_l) {
|
|
/* re-read the LLPU value, as LLPL just wrapped */
|
|
llp_u = *DGLLLPU(cfg->base, cfg->regblock_size, channel);
|
|
}
|
|
}
|
|
#endif
|
|
stat->total_copied = ((uint64_t)llp_u << 32) | llp_l;
|
|
|
|
switch (cfg->direction) {
|
|
case MEMORY_TO_PERIPHERAL:
|
|
xrun_det = intel_adsp_hda_is_buffer_underrun(cfg->base, cfg->regblock_size,
|
|
channel);
|
|
if (xrun_det) {
|
|
intel_adsp_hda_underrun_clear(cfg->base, cfg->regblock_size, channel);
|
|
return -EPIPE;
|
|
}
|
|
break;
|
|
case PERIPHERAL_TO_MEMORY:
|
|
xrun_det = intel_adsp_hda_is_buffer_overrun(cfg->base, cfg->regblock_size,
|
|
channel);
|
|
if (xrun_det) {
|
|
intel_adsp_hda_overrun_clear(cfg->base, cfg->regblock_size, channel);
|
|
return -EPIPE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool intel_adsp_hda_dma_chan_filter(const struct device *dev, int channel, void *filter_param)
|
|
{
|
|
uint32_t requested_channel;
|
|
|
|
if (!filter_param) {
|
|
return true;
|
|
}
|
|
|
|
requested_channel = *(uint32_t *)filter_param;
|
|
|
|
if (channel == requested_channel) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_start(const struct device *dev, uint32_t channel)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
uint32_t size;
|
|
bool set_fifordy;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
|
|
#if CONFIG_PM_DEVICE_RUNTIME
|
|
bool first_use = false;
|
|
enum pm_device_state state;
|
|
|
|
/* If the device is used for the first time, we need to let the power domain know that
|
|
* we want to use it.
|
|
*/
|
|
if (pm_device_state_get(dev, &state) == 0) {
|
|
first_use = state != PM_DEVICE_STATE_ACTIVE;
|
|
if (first_use) {
|
|
int ret = pm_device_runtime_get(dev);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (intel_adsp_hda_is_enabled(cfg->base, cfg->regblock_size, channel)) {
|
|
return 0;
|
|
}
|
|
|
|
set_fifordy = (cfg->direction == HOST_TO_MEMORY || cfg->direction == MEMORY_TO_HOST);
|
|
intel_adsp_hda_enable(cfg->base, cfg->regblock_size, channel, set_fifordy);
|
|
|
|
if (cfg->direction == MEMORY_TO_PERIPHERAL) {
|
|
size = intel_adsp_hda_get_buffer_size(cfg->base, cfg->regblock_size, channel);
|
|
intel_adsp_hda_link_commit(cfg->base, cfg->regblock_size, channel, size);
|
|
}
|
|
|
|
#if CONFIG_PM_DEVICE_RUNTIME
|
|
if (!first_use) {
|
|
return pm_device_runtime_get(dev);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_stop(const struct device *dev, uint32_t channel)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
|
|
__ASSERT(channel < cfg->dma_channels, "Channel does not exist");
|
|
|
|
if (!intel_adsp_hda_is_enabled(cfg->base, cfg->regblock_size, channel)) {
|
|
return 0;
|
|
}
|
|
|
|
intel_adsp_hda_disable(cfg->base, cfg->regblock_size, channel);
|
|
|
|
if (!WAIT_FOR(!intel_adsp_hda_is_enabled(cfg->base, cfg->regblock_size, channel), 1000,
|
|
k_busy_wait(1))) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
return pm_device_runtime_put(dev);
|
|
}
|
|
|
|
static void intel_adsp_hda_channels_init(const struct device *dev)
|
|
{
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
|
|
for (uint32_t i = 0; i < cfg->dma_channels; i++) {
|
|
intel_adsp_hda_init(cfg->base, cfg->regblock_size, i);
|
|
|
|
if (intel_adsp_hda_is_enabled(cfg->base, cfg->regblock_size, i)) {
|
|
uint32_t size;
|
|
|
|
size = intel_adsp_hda_get_buffer_size(cfg->base, cfg->regblock_size, i);
|
|
intel_adsp_hda_disable(cfg->base, cfg->regblock_size, i);
|
|
intel_adsp_hda_link_commit(cfg->base, cfg->regblock_size, i, size);
|
|
}
|
|
}
|
|
|
|
#if CONFIG_DMA_INTEL_ADSP_HDA_TIMING_L1_EXIT
|
|
/* Configure interrupts */
|
|
if (cfg->irq_config) {
|
|
cfg->irq_config();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int intel_adsp_hda_dma_pm_action(const struct device *dev, enum pm_device_action action)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
case PM_DEVICE_ACTION_TURN_ON:
|
|
case PM_DEVICE_ACTION_TURN_OFF:
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_adsp_hda_dma_init(const struct device *dev)
|
|
{
|
|
struct intel_adsp_hda_dma_data *data = dev->data;
|
|
const struct intel_adsp_hda_dma_cfg *const cfg = dev->config;
|
|
|
|
data->ctx.dma_channels = cfg->dma_channels;
|
|
data->ctx.atomic = data->channels_atomic;
|
|
data->ctx.magic = DMA_MAGIC;
|
|
intel_adsp_hda_channels_init(dev);
|
|
return pm_device_driver_init(dev, intel_adsp_hda_dma_pm_action);
|
|
}
|
|
|
|
int intel_adsp_hda_dma_get_attribute(const struct device *dev, uint32_t type, uint32_t *value)
|
|
{
|
|
switch (type) {
|
|
case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT:
|
|
*value = DMA_BUF_ADDR_ALIGNMENT(
|
|
DT_COMPAT_GET_ANY_STATUS_OKAY(intel_adsp_hda_link_out));
|
|
break;
|
|
case DMA_ATTR_BUFFER_SIZE_ALIGNMENT:
|
|
*value = DMA_BUF_SIZE_ALIGNMENT(
|
|
DT_COMPAT_GET_ANY_STATUS_OKAY(intel_adsp_hda_link_out));
|
|
break;
|
|
case DMA_ATTR_COPY_ALIGNMENT:
|
|
*value = DMA_COPY_ALIGNMENT(DT_COMPAT_GET_ANY_STATUS_OKAY(intel_adsp_hda_link_out));
|
|
break;
|
|
case DMA_ATTR_MAX_BLOCK_COUNT:
|
|
*value = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DEVICE_DT_GET_AND_COMMA(node_id) DEVICE_DT_GET(node_id),
|
|
|
|
void intel_adsp_hda_dma_isr(void)
|
|
{
|
|
#if CONFIG_DMA_INTEL_ADSP_HDA_TIMING_L1_EXIT
|
|
struct dma_context *dma_ctx;
|
|
const struct intel_adsp_hda_dma_cfg *cfg;
|
|
bool triggered_interrupts = false;
|
|
int i, j;
|
|
int expected_interrupts = 0;
|
|
atomic_val_t enabled_chs;
|
|
const struct device *host_dev[] = {
|
|
#if CONFIG_DMA_INTEL_ADSP_HDA_HOST_OUT
|
|
DT_FOREACH_STATUS_OKAY(intel_adsp_hda_host_out, DEVICE_DT_GET_AND_COMMA)
|
|
#endif
|
|
#if CONFIG_DMA_INTEL_ADSP_HDA_HOST_IN
|
|
DT_FOREACH_STATUS_OKAY(intel_adsp_hda_host_in, DEVICE_DT_GET_AND_COMMA)
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* To initiate transfer, DSP must be in L0 state. Once the transfer is started, DSP can go
|
|
* to the low power L1 state, and the transfer will be able to continue and finish in L1
|
|
* state. Interrupts are configured to trigger after the first 32 bytes of data arrive.
|
|
* Once such an interrupt arrives, the transfer has already started. If all expected
|
|
* transfers have started, it is safe to allow the low power L1 state.
|
|
*/
|
|
|
|
for (i = 0; i < ARRAY_SIZE(host_dev); i++) {
|
|
dma_ctx = (struct dma_context *)host_dev[i]->data;
|
|
cfg = host_dev[i]->config;
|
|
enabled_chs = atomic_get(dma_ctx->atomic);
|
|
for (j = 0; enabled_chs && j < dma_ctx->dma_channels; j++) {
|
|
if (!(enabled_chs & BIT(j))) {
|
|
continue;
|
|
}
|
|
enabled_chs &= ~(BIT(j));
|
|
|
|
if (!intel_adsp_hda_is_buffer_interrupt_enabled(cfg->base,
|
|
cfg->regblock_size, j)) {
|
|
continue;
|
|
}
|
|
|
|
if (intel_adsp_hda_check_buffer_interrupt(cfg->base,
|
|
cfg->regblock_size, j)) {
|
|
triggered_interrupts = true;
|
|
intel_adsp_hda_disable_buffer_interrupt(cfg->base,
|
|
cfg->regblock_size, j);
|
|
intel_adsp_hda_clear_buffer_interrupt(cfg->base,
|
|
cfg->regblock_size, j);
|
|
} else {
|
|
expected_interrupts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allow entering low power L1 state only after all enabled interrupts arrived, i.e.,
|
|
* transfers started on all channels.
|
|
*/
|
|
if (triggered_interrupts && expected_interrupts == 0) {
|
|
intel_adsp_allow_dmi_l1_state();
|
|
}
|
|
#endif
|
|
}
|