blob: b13083911ab0a5f3dc061e2d871eb6f0ca0f5aa8 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*
* qaif-platform.c -- ALSA SoC PCM platform driver for the Qualcomm Audio Interface (QAIF)
*/
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "qaif-reg.h"
#include "qaif.h"
#define DRV_NAME "qaif-platform"
/* period: 5ms @ 48kHz/1ch/S16 min, 20ms @ 48kHz/8ch/S32 max */
#define QAIF_PLATFORM_PERIOD_BYTES_MIN (48 * 5 * 1 * 2) /* 480 B */
#define QAIF_PLATFORM_PERIOD_BYTES_MAX (48 * 20 * 8 * 4) /* 30720 B */
#define QAIF_PLATFORM_PERIODS_MIN 2
#define QAIF_PLATFORM_PERIODS_MAX 4
/* QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX = 122880 B */
#define QAIF_PLATFORM_BUFFER_BYTES_MAX \
(QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX)
/* DMA CFG register bit positions */
#define QAIF_DMACFG_SHRAM_WM_MASK GENMASK(11, 0)
#define QAIF_DMACFG_BURST4_BIT BIT(18)
#define QAIF_DMACFG_DYNCLK_BIT BIT(24)
/* DMA CTL register bit positions */
#define QAIF_DMACTL_ENABLE_BIT BIT(0)
/* DMA SHUTDOWN_STATUS register bit positions */
#define QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT BIT(0)
/* DMA shutdown takes ~10 bit clocks (~100us worst case at 8 kHz). */
#define QAIF_DMA_SHUTDOWN_POLL_US 10
#define QAIF_DMA_SHUTDOWN_TIMEOUT_US 1000
#define QAIF_ALL_CLIENTS_MASK (QAIF_BITMASK_AIF_RDDMA_WRDMA | \
QAIF_BITMASK_CIF_RDDMA_WRDMA)
static const struct snd_pcm_hardware qaif_platform_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.rates = SNDRV_PCM_RATE_8000_192000,
.rate_min = 8000,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = QAIF_PLATFORM_BUFFER_BYTES_MAX,
.period_bytes_min = QAIF_PLATFORM_PERIOD_BYTES_MIN,
.period_bytes_max = QAIF_PLATFORM_PERIOD_BYTES_MAX,
.periods_min = QAIF_PLATFORM_PERIODS_MIN,
.periods_max = QAIF_PLATFORM_PERIODS_MAX,
.fifo_size = 0,
};
static int qaif_map_ee_resource(struct qaif_drv_data *drvdata)
{
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
int ret;
ret = regmap_write(map, QAIF_EE_RDDMA_MAP_REG(v), GENMASK(v->num_rddma - 1, 0));
if (ret)
return ret;
ret = regmap_write(map, QAIF_EE_WRDMA_MAP_REG(v), GENMASK(v->num_wrdma - 1, 0));
if (ret)
return ret;
if (v->num_intf > 0) {
ret = regmap_write(map, QAIF_EE_INTF_MAP_REG(v), GENMASK(v->num_intf - 1, 0));
if (ret)
return ret;
}
ret = regmap_write(map, QAIF_EE_CODEC_RDDMA_MAP_REG(v),
GENMASK(v->num_codec_rddma - 1, 0));
if (ret)
return ret;
return regmap_write(map, QAIF_EE_CODEC_WRDMA_MAP_REG(v),
GENMASK(v->num_codec_wrdma - 1, 0));
}
static int qaif_map_dma_path(struct qaif_drv_data *drvdata)
{
struct regmap *map = drvdata->audio_qaif_map;
int ret;
if (drvdata->variant->qxm_type != QAIF_QXM0) {
dev_err(regmap_get_device(map),
"only QXM0 is supported, qxm_type=%d\n",
drvdata->variant->qxm_type);
return -EINVAL;
}
ret = regmap_write(map, QAIF_RDDMA_MAP_QXM, QAIF_QXM0);
if (ret)
return ret;
ret = regmap_write(map, QAIF_WRDMA_MAP_QXM, QAIF_QXM0);
if (ret)
return ret;
ret = regmap_write(map, QAIF_CDC_RDDMA_MAP_QXM, QAIF_QXM0);
if (ret)
return ret;
return regmap_write(map, QAIF_CDC_WRDMA_MAP_QXM, QAIF_QXM0);
}
static int qaif_config_shram(struct qaif_drv_data *drvdata)
{
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
u32 start_addr, shram_len;
int ret, i;
if (v->qxm_type != QAIF_QXM0) {
dev_err(regmap_get_device(map),
"only QXM0 is supported, qxm_type=%d\n",
v->qxm_type);
return -EINVAL;
}
start_addr = v->rddma_shram_start_addr[QAIF_AIF_DMA];
shram_len = v->rddma_shram_len;
for (i = 0; i < v->num_rddma; i++) {
ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i),
start_addr + (shram_len * i));
if (ret)
return ret;
ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_LEN(i), shram_len);
if (ret)
return ret;
}
start_addr = v->wrdma_shram_start_addr[QAIF_AIF_DMA];
shram_len = v->wrdma_shram_len;
for (i = 0; i < v->num_wrdma; i++) {
ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i),
start_addr + (shram_len * i));
if (ret)
return ret;
ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_LEN(i), shram_len);
if (ret)
return ret;
}
start_addr = v->rddma_shram_start_addr[QAIF_CIF_DMA];
shram_len = v->rddma_shram_len;
for (i = 0; i < v->num_codec_rddma; i++) {
ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i),
start_addr + (shram_len * i));
if (ret)
return ret;
ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i), shram_len);
if (ret)
return ret;
}
start_addr = v->wrdma_shram_start_addr[QAIF_CIF_DMA];
shram_len = v->wrdma_shram_len;
for (i = 0; i < v->num_codec_wrdma; i++) {
ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i),
start_addr + (shram_len * i));
if (ret)
return ret;
ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i), shram_len);
if (ret)
return ret;
}
return 0;
}
static int qaif_init(struct snd_soc_component *component)
{
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
int ret;
if (drvdata->qaif_hw_configured)
return 0;
ret = qaif_config_shram(drvdata);
if (ret) {
dev_err(component->dev, "failed to config shram: %d\n", ret);
return ret;
}
ret = qaif_map_ee_resource(drvdata);
if (ret) {
dev_err(component->dev, "failed to map EE resources: %d\n", ret);
return ret;
}
ret = qaif_map_dma_path(drvdata);
if (ret)
return ret;
drvdata->qaif_hw_configured = true;
return 0;
}
static int qaif_platform_pcmops_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
unsigned int dai_id = cpu_dai->driver->id;
struct qaif_pcm_data *data;
int ret, stream_dma_idx, dma_reg_idx, dir = substream->stream;
dma_reg_idx = v->get_dma_idx(dai_id);
if (dma_reg_idx < 0) {
dev_err(component->dev, "invalid DMA register index for DAI %u: %d\n",
dai_id, dma_reg_idx);
return dma_reg_idx;
}
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
runtime->private_data = data;
mutex_lock(&drvdata->stream_lock);
stream_dma_idx = v->alloc_stream_dma_idx(drvdata, dir, dai_id);
mutex_unlock(&drvdata->stream_lock);
if (stream_dma_idx < 0) {
ret = stream_dma_idx;
goto err_free_data;
}
data->stream_dma_idx = stream_dma_idx;
data->dma_reg_idx = dma_reg_idx;
if (qaif_is_cif_port(dai_id))
WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], substream);
else
WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], substream);
snd_soc_set_runtime_hwparams(substream, &qaif_platform_hardware);
/*
* The DMA buffer/period length registers are programmed in 64-bit
* (8-byte) words, so constrain buffer and period sizes to that step
* to keep the ALSA and hardware sizes identical.
*/
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT));
if (ret >= 0)
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT));
if (ret >= 0)
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(soc_runtime->dev, "setting constraints failed: %d\n", ret);
if (qaif_is_cif_port(dai_id))
WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], NULL);
else
WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], NULL);
mutex_lock(&drvdata->stream_lock);
v->free_stream_dma_idx(drvdata, stream_dma_idx, dai_id);
mutex_unlock(&drvdata->stream_lock);
goto err_free_data;
}
return 0;
err_free_data:
kfree(data);
return ret;
}
static int qaif_platform_pcmops_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct qaif_pcm_data *data = substream->runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
if (qaif_is_cif_port(dai_id))
WRITE_ONCE(drvdata->cif_substream[data->stream_dma_idx], NULL);
else
WRITE_ONCE(drvdata->aif_substream[data->stream_dma_idx], NULL);
mutex_lock(&drvdata->stream_lock);
v->free_stream_dma_idx(drvdata, data->stream_dma_idx, dai_id);
mutex_unlock(&drvdata->stream_lock);
substream->runtime->private_data = NULL;
kfree(data);
return 0;
}
static int qaif_platform_pcmops_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct qaif_pcm_data *data = substream->runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
int idx = data->dma_reg_idx;
int ret;
mutex_lock(&drvdata->stream_lock);
ret = qaif_init(component);
mutex_unlock(&drvdata->stream_lock);
if (ret) {
dev_err(soc_runtime->dev, "qaif_init failed: %d\n", ret);
return ret;
}
ret = regmap_update_bits(drvdata->audio_qaif_map,
qaif_dmacfg_reg(v, idx, substream->stream, dai_id),
QAIF_DMACFG_BURST4_BIT | QAIF_DMACFG_SHRAM_WM_MASK,
QAIF_DMACFG_BURST4_BIT | QAIF_DMACTL_WM_5);
if (ret)
dev_err(soc_runtime->dev, "error updating DMA CFG: %d\n", ret);
return ret;
}
static int qaif_platform_pcmops_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct qaif_pcm_data *data = substream->runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
int idx = data->dma_reg_idx;
int ret;
ret = regmap_update_bits(drvdata->audio_qaif_map,
qaif_dmactl_reg(v, idx, substream->stream, dai_id),
QAIF_DMACTL_ENABLE_BIT, 0);
if (ret) {
dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret);
return ret;
}
ret = regmap_write(drvdata->audio_qaif_map,
qaif_dmacfg_reg(v, idx, substream->stream, dai_id), 0);
if (ret)
dev_err(soc_runtime->dev, "error clearing DMA CFG: %d\n", ret);
return ret;
}
static int qaif_platform_pcmops_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
struct qaif_pcm_data *data = runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
int idx = data->dma_reg_idx;
int ret, dir = substream->stream;
ret = clk_set_rate(drvdata->aud_dma_clk, QAIF_DMA_CLOCK_FREQ);
if (ret) {
dev_err(soc_runtime->dev, "error setting aud_dma_clk rate: %d\n", ret);
return ret;
}
ret = clk_set_rate(drvdata->aud_dma_mem_clk, QAIF_DMA_CLOCK_FREQ);
if (ret) {
dev_err(soc_runtime->dev, "error setting aud_dma_mem_clk rate: %d\n", ret);
return ret;
}
ret = regmap_write(map, qaif_sid_map_reg(dir, dai_id), drvdata->smmu_csid_bits);
if (ret) {
dev_err(soc_runtime->dev, "error writing to SID MAP reg: %d\n", ret);
return ret;
}
ret = regmap_write(map, qaif_dmabase_reg(v, idx, dir, dai_id),
lower_32_bits(runtime->dma_addr));
if (ret) {
dev_err(soc_runtime->dev, "error writing to dma base reg: %d\n", ret);
return ret;
}
ret = regmap_write(map, qaif_dmabuff_reg(v, idx, dir, dai_id),
(snd_pcm_lib_buffer_bytes(substream) >>
QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1);
if (ret) {
dev_err(soc_runtime->dev, "error writing to dma buf reg: %d\n", ret);
return ret;
}
ret = regmap_write(map, qaif_dmaper_len_reg(v, idx, dir, dai_id),
(snd_pcm_lib_period_bytes(substream) >>
QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1);
if (ret)
dev_err(soc_runtime->dev, "error writing to dma period reg: %d\n", ret);
return ret;
}
static snd_pcm_uframes_t qaif_platform_pcmops_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
struct qaif_pcm_data *data = substream->runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
unsigned int base_addr, curr_addr;
int idx = data->dma_reg_idx;
int ret, dir = substream->stream;
ret = regmap_read(map, qaif_dmabase_reg(v, idx, dir, dai_id), &base_addr);
if (ret) {
dev_err(soc_runtime->dev, "error reading from rdmabase reg: %d\n", ret);
return SNDRV_PCM_POS_XRUN;
}
ret = regmap_read(map, qaif_dmacurr_reg(v, idx, dir, dai_id), &curr_addr);
if (ret) {
dev_err(soc_runtime->dev, "error reading from rdmacurr reg: %d\n", ret);
return SNDRV_PCM_POS_XRUN;
}
return bytes_to_frames(substream->runtime,
(curr_addr - base_addr) %
snd_pcm_lib_buffer_bytes(substream));
}
static int qaif_platform_pcmops_mmap(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
return dma_mmap_coherent(component->dev, vma,
runtime->dma_area, runtime->dma_addr,
runtime->dma_bytes);
}
static int qaif_platform_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm *pcm = rtd->pcm;
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
component->dev,
QAIF_PLATFORM_BUFFER_BYTES_MAX);
}
static int qaif_platform_irq_op(struct qaif_drv_data *drvdata, int dir,
enum qaif_irq_type irq_type, int idx,
enum qaif_irq_op op)
{
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
unsigned int bit = BIT(idx);
unsigned int val = op == QAIF_IRQ_ENABLE ? bit : 0;
u32 per_reg, err_reg, xrun_reg;
int ret;
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, irq_type);
xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, irq_type);
err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, irq_type);
} else {
per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, irq_type);
xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, irq_type);
err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type)
: QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, irq_type);
}
if (op == QAIF_IRQ_CLEAR) {
ret = regmap_write(map, per_reg, bit);
if (ret)
return ret;
ret = regmap_write(map, xrun_reg, bit);
if (ret)
return ret;
return regmap_write(map, err_reg, bit);
}
ret = regmap_write_bits(map, per_reg, bit, val);
if (ret)
return ret;
ret = regmap_write_bits(map, xrun_reg, bit, val);
if (ret)
return ret;
return regmap_write_bits(map, err_reg, bit, val);
}
static int qaif_platform_pcmops_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
int cmd)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
const struct qaif_variant *v = drvdata->variant;
struct qaif_pcm_data *data = substream->runtime->private_data;
unsigned int dai_id = cpu_dai->driver->id;
enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ;
struct regmap *map = drvdata->audio_qaif_map;
int idx = data->dma_reg_idx;
int ret = 0, irq_op_err = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type,
idx, QAIF_IRQ_CLEAR);
if (irq_op_err) {
dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", irq_op_err);
break;
}
ret = regmap_update_bits(map,
qaif_dmacfg_reg(v, idx, substream->stream, dai_id),
QAIF_DMACFG_DYNCLK_BIT, QAIF_DMACFG_DYNCLK_BIT);
if (ret) {
dev_err(soc_runtime->dev, "error enabling dma_dynclk: %d\n", ret);
break;
}
ret = regmap_update_bits(map,
qaif_dmactl_reg(v, idx, substream->stream, dai_id),
QAIF_DMACTL_ENABLE_BIT, QAIF_DMACTL_ENABLE_BIT);
if (ret) {
dev_err(soc_runtime->dev, "error enabling DMA: %d\n", ret);
break;
}
irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type,
idx, QAIF_IRQ_ENABLE);
if (irq_op_err)
dev_err(soc_runtime->dev, "error enabling irq regs: %d\n", irq_op_err);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = regmap_update_bits(map,
qaif_dmactl_reg(v, idx, substream->stream, dai_id),
QAIF_DMACTL_ENABLE_BIT, 0);
if (ret) {
dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret);
break;
}
ret = regmap_update_bits(map,
qaif_dmacfg_reg(v, idx, substream->stream, dai_id),
QAIF_DMACFG_DYNCLK_BIT, 0);
if (ret)
dev_err(soc_runtime->dev, "error disabling dma_dynclk: %d\n", ret);
irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type,
idx, QAIF_IRQ_DISABLE);
if (irq_op_err)
dev_err(soc_runtime->dev, "error disabling irq regs: %d\n", irq_op_err);
break;
default:
ret = -EINVAL;
break;
}
return ret ? : irq_op_err;
}
static int qaif_platform_pcmops_sync_stop(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component);
struct qaif_pcm_data *data = substream->runtime->private_data;
const struct qaif_variant *v = drvdata->variant;
struct regmap *map = drvdata->audio_qaif_map;
unsigned int dai_id = cpu_dai->driver->id;
enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ;
int idx = data->dma_reg_idx;
unsigned int val;
int ret;
/*
* Wait for the DMA channel to report shutdown complete before
* clearing its interrupts, so a fast STOP->START or suspend/resume
* does not race a channel still shutting down.
*/
ret = regmap_read_poll_timeout(map,
qaif_dma_shutdown_stat_reg(v, idx,
substream->stream,
dai_id),
val,
!(val & QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT),
QAIF_DMA_SHUTDOWN_POLL_US,
QAIF_DMA_SHUTDOWN_TIMEOUT_US);
if (ret)
dev_err(soc_runtime->dev, "timeout waiting for DMA shutdown: %d\n", ret);
synchronize_irq(drvdata->audio_qaif_irq);
ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type,
idx, QAIF_IRQ_CLEAR);
if (ret)
dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", ret);
return ret;
}
static irqreturn_t qaif_process_dma_irq(struct qaif_drv_data *drvdata,
u32 stat_reg_addr,
u32 clr_reg_addr,
enum qaif_irq_type irq_type,
enum qaif_dma_dir dma_type,
enum qaif_irq irq,
struct snd_pcm_substream **substream)
{
const struct qaif_variant *v = drvdata->variant;
unsigned int reg = 0;
int dma_idx, stream_dma_idx, rv, num_dma, stream_offset, array_size;
u32 mask;
struct snd_pcm_substream *ss;
stream_offset = (dma_type == QAIF_DMA_WRDMA) ?
((irq_type == QAIF_AIF_IRQ) ? v->wrdma_start : v->codec_wrdma_start) : 0;
num_dma = (dma_type == QAIF_DMA_WRDMA) ?
((irq_type == QAIF_AIF_IRQ) ? v->num_wrdma : v->num_codec_wrdma) :
((irq_type == QAIF_AIF_IRQ) ? v->num_rddma : v->num_codec_rddma);
if (!num_dma)
return IRQ_NONE;
array_size = (irq_type == QAIF_AIF_IRQ) ?
QAIF_MAX_AIF_DMA_IDX : QAIF_MAX_CIF_DMA_IDX;
if (stream_offset + num_dma > array_size) {
dev_err(regmap_get_device(drvdata->audio_qaif_map),
"DMA index range %d+%d exceeds substream table %d\n",
stream_offset, num_dma, array_size);
return IRQ_NONE;
}
mask = GENMASK(num_dma - 1, 0);
rv = regmap_read(drvdata->audio_qaif_map, stat_reg_addr, &reg);
if (rv) {
dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map),
"error reading stat reg 0x%x: %d\n", stat_reg_addr, rv);
return IRQ_NONE;
}
if (!(reg & mask)) {
dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map),
"spurious IRQ: stat reg 0x%x status 0x%x mask 0x%x\n",
stat_reg_addr, reg, mask);
return IRQ_NONE;
}
if (regmap_write(drvdata->audio_qaif_map, clr_reg_addr, reg & mask))
return IRQ_NONE;
for (dma_idx = 0; dma_idx < num_dma; dma_idx++) {
stream_dma_idx = dma_idx + stream_offset;
ss = READ_ONCE(substream[stream_dma_idx]);
if (!(reg & BIT(dma_idx)) || !ss)
continue;
switch (irq) {
case QAIF_IRQ_PERIOD:
snd_pcm_period_elapsed(ss);
break;
case QAIF_IRQ_OVERFLOW:
case QAIF_IRQ_UNDERFLOW:
dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map),
"QAIF DMA xRun\n");
snd_pcm_stop_xrun(ss);
break;
case QAIF_IRQ_ERROR:
snd_pcm_stop_xrun(ss);
dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map),
"QAIF bus error\n");
break;
}
}
return IRQ_HANDLED;
}
static irqreturn_t qaif_aif_irq_handler(struct qaif_drv_data *drvdata, u32 status)
{
const struct qaif_variant *v = drvdata->variant;
struct snd_pcm_substream **ss = drvdata->aif_substream;
irqreturn_t ret = IRQ_NONE;
if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss);
if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss);
if (status & QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss);
if (status & QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss);
if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss);
if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ),
QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ),
QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss);
return ret;
}
static irqreturn_t qaif_cif_irq_handler(struct qaif_drv_data *drvdata, u32 status)
{
const struct qaif_variant *v = drvdata->variant;
struct snd_pcm_substream **ss = drvdata->cif_substream;
irqreturn_t ret = IRQ_NONE;
if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss);
if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss);
if (status & QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss);
if (status & QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss);
if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss);
if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA)
ret |= qaif_process_dma_irq(drvdata,
QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ),
QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ),
QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss);
return ret;
}
static const struct qaif_irq_map qaif_irq_clients[] = {
{ QAIF_CLIENT_ID_AIF_DMA, QAIF_BITMASK_AIF_RDDMA_WRDMA, qaif_aif_irq_handler },
{ QAIF_CLIENT_ID_CIF_DMA, QAIF_BITMASK_CIF_RDDMA_WRDMA, qaif_cif_irq_handler },
};
static irqreturn_t asoc_platform_qaif_irq(int irq, void *data)
{
struct qaif_drv_data *drvdata = data;
const struct qaif_variant *v = drvdata->variant;
u32 summary_irq_status;
int rv, client;
irqreturn_t ret = IRQ_NONE;
rv = regmap_read(drvdata->audio_qaif_map, QAIF_SUMMARY_IRQSTAT_REG(v),
&summary_irq_status);
if (rv) {
dev_err(regmap_get_device(drvdata->audio_qaif_map),
"error reading from irqstat reg: %d\n", rv);
return IRQ_NONE;
}
if (!(summary_irq_status & QAIF_ALL_CLIENTS_MASK))
return IRQ_NONE;
for (client = 0; client < ARRAY_SIZE(qaif_irq_clients); client++) {
if (summary_irq_status & qaif_irq_clients[client].mask)
ret |= qaif_irq_clients[client].client_irq_handler(drvdata,
summary_irq_status);
}
return ret;
}
static const struct snd_soc_component_driver qaif_component_driver = {
.name = DRV_NAME,
.pcm_new = qaif_platform_pcm_new,
.open = qaif_platform_pcmops_open,
.close = qaif_platform_pcmops_close,
.hw_params = qaif_platform_pcmops_hw_params,
.hw_free = qaif_platform_pcmops_hw_free,
.prepare = qaif_platform_pcmops_prepare,
.trigger = qaif_platform_pcmops_trigger,
.sync_stop = qaif_platform_pcmops_sync_stop,
.pointer = qaif_platform_pcmops_pointer,
.mmap = qaif_platform_pcmops_mmap,
};
int asoc_qcom_qaif_platform_register(struct platform_device *pdev)
{
struct qaif_drv_data *drvdata = platform_get_drvdata(pdev);
struct of_phandle_args iommu_spec;
u32 sid;
int ret;
if (!drvdata->variant->alloc_stream_dma_idx ||
!drvdata->variant->free_stream_dma_idx)
return dev_err_probe(&pdev->dev, -EINVAL,
"Variant missing mandatory callbacks\n");
ret = of_parse_phandle_with_args(pdev->dev.of_node,
"iommus", "#iommu-cells", 0,
&iommu_spec);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to parse iommus property\n");
sid = iommu_spec.args[0];
of_node_put(iommu_spec.np);
drvdata->smmu_csid_bits = sid & QAIF_CSID_MASK;
/*
* The DMA base register only holds the low 32 bits of the buffer
* address, so the coherent buffer must come from the low 32-bit
* address space.
*/
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to set 32-bit DMA mask\n");
drvdata->audio_qaif_irq = platform_get_irq(pdev, 0);
if (drvdata->audio_qaif_irq < 0)
return dev_err_probe(&pdev->dev, drvdata->audio_qaif_irq,
"Failed to get IRQ\n");
drvdata->qaif_hw_configured = false;
mutex_init(&drvdata->stream_lock);
ret = devm_request_irq(&pdev->dev, drvdata->audio_qaif_irq,
asoc_platform_qaif_irq, 0,
"qaif-irq-audio-core", drvdata);
if (ret)
return dev_err_probe(&pdev->dev, ret, "irq request failed\n");
ret = devm_snd_soc_register_component(&pdev->dev, &qaif_component_driver, NULL, 0);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to register platform component\n");
return 0;
}
EXPORT_SYMBOL_GPL(asoc_qcom_qaif_platform_register);
MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) PCM platform driver");
MODULE_AUTHOR("Harendra Gautam <harendra.gautam@oss.qualcomm.com>");
MODULE_LICENSE("GPL");