zephyr/drivers/dma/dma_intel_adsp_hda.c
Peter Ujfalusi 8dfa116750 drivers: dma: intel-adsp-hda: Correct DGCS:SCS bit for 32bit sample size
If the channel was used for 16bit in the once, subsequent 32bit sample size
audio will be broken since the SCS bit remains set.

Example sequence with SOF:
normal audio playback with 16bit
ChainDMA audio playback with 16bit
normal audio playback with 16bit

The last playback results garbled audio.

Introduce intel_adsp_hda_set_sample_container_size() helper function
to handle the SCS bit and use it in the driver.


Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
2023-10-12 17:31:23 +03:00

467 lines
13 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
#if CONFIG_SOC_SERIES_INTEL_ACE
ACE_DfPMCCH.svcfg |= ADSP_FORCE_DECOUPLED_HDMA_L1_EXIT_BIT;
#endif
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) %
intel_adsp_hda_get_buffer_size(cfg->base, cfg->regblock_size, channel);
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) %
intel_adsp_hda_get_buffer_size(cfg->base, cfg->regblock_size, channel);
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;
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;
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);
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_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;
#ifdef CONFIG_PM_DEVICE_RUNTIME
if (pm_device_on_power_domain(dev)) {
pm_device_init_off(dev);
} else {
intel_adsp_hda_channels_init(dev);
pm_device_init_suspended(dev);
}
return pm_device_runtime_enable(dev);
#else
intel_adsp_hda_channels_init(dev);
return 0;
#endif
}
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;
}
#ifdef CONFIG_PM_DEVICE
int intel_adsp_hda_dma_pm_action(const struct device *dev, enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
intel_adsp_hda_channels_init(dev);
break;
case PM_DEVICE_ACTION_SUSPEND:
case PM_DEVICE_ACTION_TURN_ON:
case PM_DEVICE_ACTION_TURN_OFF:
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif
#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 clear_l1_exit = false;
int i, j;
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
};
for (i = 0; i < ARRAY_SIZE(host_dev); i++) {
dma_ctx = (struct dma_context *)host_dev[i]->data;
cfg = host_dev[i]->config;
for (j = 0; j < dma_ctx->dma_channels; j++) {
if (atomic_test_bit(dma_ctx->atomic, j)) {
clear_l1_exit |=
intel_adsp_hda_check_buffer_interrupt(cfg->base,
cfg->regblock_size,
j);
intel_adsp_hda_disable_buffer_interrupt(cfg->base,
cfg->regblock_size, j);
intel_adsp_hda_clear_buffer_interrupt(cfg->base,
cfg->regblock_size, j);
}
}
}
if (clear_l1_exit) {
#if CONFIG_SOC_SERIES_INTEL_ACE
ACE_DfPMCCH.svcfg &= ~(ADSP_FORCE_DECOUPLED_HDMA_L1_EXIT_BIT);
#endif
}
#endif
}