729 lines
20 KiB
C
Executable file
729 lines
20 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox UDMA driver
|
|
*
|
|
* Copyright (c) 2020 Samsung Electronics Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include "abox.h"
|
|
#include "abox_cmpnt.h"
|
|
#include "abox_dma.h"
|
|
#include "abox_udma.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#define VERBOSE 0
|
|
|
|
static int abox_udma_request_ipc(struct abox_dma_data *data,
|
|
ABOX_IPC_MSG *msg, int atomic, int sync)
|
|
{
|
|
return abox_request_ipc(data->dev_abox, msg->ipcid, msg, sizeof(*msg),
|
|
atomic, sync);
|
|
}
|
|
|
|
static bool abox_udma_has_playback(struct abox_dma_data *data)
|
|
{
|
|
return !!data->dai_drv[0].playback.formats;
|
|
}
|
|
|
|
static int abox_udma_get_ipcid(struct abox_dma_data *data)
|
|
{
|
|
return abox_udma_has_playback(data) ? IPC_USBPLAYBACK : IPC_USBCAPTURE;
|
|
}
|
|
|
|
static irqreturn_t abox_udma_ipc_handler(int ipc, void *dev_id,
|
|
ABOX_IPC_MSG *msg)
|
|
{
|
|
struct abox_data *abox_data = dev_id;
|
|
struct abox_dma_data *data;
|
|
struct device *dev;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask;
|
|
int id = pcmtask_msg->channel_id;
|
|
|
|
switch (ipc) {
|
|
case IPC_USBPLAYBACK:
|
|
if (id >= ARRAY_SIZE(abox_data->dev_udma_rd))
|
|
return IRQ_NONE;
|
|
dev = abox_data->dev_udma_rd[id];
|
|
break;
|
|
case IPC_USBCAPTURE:
|
|
if (id >= ARRAY_SIZE(abox_data->dev_udma_wr))
|
|
return IRQ_NONE;
|
|
dev = abox_data->dev_udma_wr[id];
|
|
break;
|
|
default:
|
|
return IRQ_NONE;
|
|
}
|
|
data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, pcmtask_msg->msgtype);
|
|
|
|
switch (pcmtask_msg->msgtype) {
|
|
default:
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int abox_udma_open(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct device *dev = data->dev;
|
|
struct abox_data *abox_data = data->abox_data;
|
|
int id = data->id;
|
|
int ret;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_wait_restored(abox_data);
|
|
|
|
abox_dma.open(component, substream);
|
|
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_OPEN;
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_udma_close(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct abox_data *abox_data = data->abox_data;
|
|
struct device *dev = data->dev;
|
|
int id = data->id;
|
|
int ret;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_dma_barrier(dev, data, 0);
|
|
abox_dma.close(component, substream);
|
|
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE;
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 1);
|
|
|
|
abox_request_cpu_gear_dai(dev, abox_data, cpu_dai, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_udma_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct device *dev = data->dev;
|
|
struct abox_data *abox_data = data->abox_data;
|
|
struct device *dev_abox = abox_data->dev;
|
|
int id = data->id;
|
|
size_t buffer_bytes = params_buffer_bytes(params);
|
|
unsigned int iova = abox_dma_iova(data);
|
|
phys_addr_t phys;
|
|
int ret;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
switch (data->buf_type) {
|
|
case BUFFER_TYPE_RAM:
|
|
if (data->ramb.bytes >= buffer_bytes) {
|
|
iova = data->ramb.addr;
|
|
phys = data->ramb.addr;
|
|
break;
|
|
}
|
|
/* fallback to DMA mode */
|
|
abox_info(dev, "fallback to dma buffer\n");
|
|
case BUFFER_TYPE_DMA:
|
|
if (data->dmab.bytes < buffer_bytes) {
|
|
abox_iommu_unmap(dev_abox, iova);
|
|
snd_dma_free_pages(&data->dmab);
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
|
|
dev, PAGE_ALIGN(buffer_bytes),
|
|
&data->dmab);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = abox_iommu_map(dev_abox, iova, data->dmab.addr,
|
|
data->dmab.bytes, data->dmab.area);
|
|
if (ret < 0)
|
|
return ret;
|
|
abox_info(dev, "dma buffer changed\n");
|
|
}
|
|
phys = data->dmab.addr;
|
|
break;
|
|
case BUFFER_TYPE_ION:
|
|
if (data->dmab.bytes < buffer_bytes)
|
|
return -ENOMEM;
|
|
|
|
abox_info(dev, "ion_buffer %s bytes(%zu) size(%zu)\n",
|
|
__func__, buffer_bytes, data->ion_buf->size);
|
|
/* physical address of scatter-gather ion buffer is useless */
|
|
phys = data->dmab.addr;
|
|
break;
|
|
default:
|
|
abox_err(dev, "buf_type is not defined\n");
|
|
break;
|
|
}
|
|
|
|
/* udma is always backend */
|
|
data->backend = true;
|
|
|
|
ret = abox_dma.hw_params(component, substream, params);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pcmtask_msg->channel_id = id;
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
|
|
pcmtask_msg->msgtype = PCM_SET_BUFFER;
|
|
pcmtask_msg->param.setbuff.addr = iova;
|
|
pcmtask_msg->param.setbuff.size = params_period_bytes(params);
|
|
pcmtask_msg->param.setbuff.count = params_periods(params);
|
|
pcmtask_msg->param.setbuff.phys = phys;
|
|
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS;
|
|
pcmtask_msg->param.hw_params.sample_rate = params_rate(params);
|
|
pcmtask_msg->param.hw_params.bit_depth = params_width(params);
|
|
pcmtask_msg->param.hw_params.channels = params_channels(params);
|
|
pcmtask_msg->param.hw_params.packed =
|
|
(params_format(params) == SNDRV_PCM_FORMAT_S24_3LE);
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
abox_dbg(dev, "%s:Total=%u PrdSz=%u(%u) #Prds=%u rate=%u, width=%d, channels=%u\n",
|
|
snd_pcm_stream_str(substream),
|
|
params_buffer_bytes(params), params_period_size(params),
|
|
params_period_bytes(params), params_periods(params),
|
|
params_rate(params), params_width(params),
|
|
params_channels(params));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_udma_prepare(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct device *dev = data->dev;
|
|
int id = data->id;
|
|
int ret;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
data->pointer = abox_dma_iova(data);
|
|
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE;
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_udma_hw_free(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai);
|
|
struct device *dev = data->dev;
|
|
int id = data->id;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE;
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
abox_udma_request_ipc(data, &msg, 0, 0);
|
|
|
|
abox_dma.hw_free(component, substream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t abox_udma_pointer(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
return abox_dma.pointer(component, substream);
|
|
}
|
|
|
|
static int abox_udma_mmap(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct vm_area_struct *vma)
|
|
{
|
|
return abox_dma.mmap(component, substream, vma);
|
|
}
|
|
|
|
static int abox_udma_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai);
|
|
struct device *dev = dai->dev;
|
|
int id = data->id;
|
|
int ret;
|
|
ABOX_IPC_MSG msg;
|
|
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
|
|
|
|
abox_info(dev, "%s(%d)\n", __func__, mute);
|
|
|
|
if (data->enabled == !mute)
|
|
return 0;
|
|
data->enabled = !mute;
|
|
|
|
msg.ipcid = abox_udma_get_ipcid(data);
|
|
msg.task_id = pcmtask_msg->channel_id = id;
|
|
pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER;
|
|
pcmtask_msg->param.trigger = !mute;
|
|
ret = abox_udma_request_ipc(data, &msg, 0, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops abox_udma_dai_ops = {
|
|
.mute_stream = abox_udma_mute_stream,
|
|
};
|
|
|
|
static unsigned int abox_udma_read(struct snd_soc_component *cmpnt,
|
|
unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
struct abox_data *abox_data = data->abox_data;
|
|
unsigned int base, val = 0;
|
|
|
|
if (reg > DMA_REG_MAX) {
|
|
abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg);
|
|
dump_stack();
|
|
}
|
|
|
|
base = (unsigned int)(data->sfr_phys - abox_data->sfr_phys);
|
|
val = snd_soc_component_read(abox_data->cmpnt, base + reg);
|
|
|
|
if (VERBOSE)
|
|
abox_dbg(cmpnt->dev, "%s(%#x): %#x\n", __func__, reg, val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static int abox_udma_write(struct snd_soc_component *cmpnt,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
struct abox_data *abox_data = data->abox_data;
|
|
unsigned int base;
|
|
int ret;
|
|
|
|
if (VERBOSE)
|
|
abox_dbg(cmpnt->dev, "%s(%#x, %#x)\n", __func__, reg, val);
|
|
|
|
if (reg > DMA_REG_MAX) {
|
|
abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg);
|
|
dump_stack();
|
|
}
|
|
|
|
base = (unsigned int)(data->sfr_phys - abox_data->sfr_phys);
|
|
ret = snd_soc_component_write(abox_data->cmpnt, base + reg, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_udma_probe(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->abox_data->dev;
|
|
size_t buffer_bytes = data->dmab.bytes;
|
|
int ret = 0;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_dma.probe(cmpnt);
|
|
/* Set UDMA to discrete operation mode */
|
|
snd_soc_component_update_bits(cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_BUF_TYPE_MASK, 1 << ABOX_DMA_BUF_TYPE_L);
|
|
abox_dma_hw_params_set(data->dev, 48000, 16, 2, 48, 2, false);
|
|
abox_register_ipc_handler(data->dev_abox, abox_udma_get_ipcid(data),
|
|
abox_udma_ipc_handler, data->abox_data);
|
|
|
|
switch (data->buf_type) {
|
|
case BUFFER_TYPE_ION:
|
|
buffer_bytes = BUFFER_ION_BYTES_MAX;
|
|
data->ion_buf = abox_ion_alloc(dev, data->abox_data,
|
|
abox_dma_iova(data), buffer_bytes, false);
|
|
if (IS_ERR(data->ion_buf))
|
|
return PTR_ERR(data->ion_buf);
|
|
|
|
/* update buffer infomation using ion allocated buffer */
|
|
data->dmab.area = data->ion_buf->kva;
|
|
data->dmab.addr = data->ion_buf->iova;
|
|
data->dmab.bytes = data->ion_buf->size;
|
|
break;
|
|
case BUFFER_TYPE_DMA:
|
|
if (buffer_bytes < BUFFER_BYTES_MIN)
|
|
buffer_bytes = BUFFER_BYTES_MIN;
|
|
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev,
|
|
buffer_bytes, &data->dmab);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = abox_iommu_map(dev_abox, abox_dma_iova(data),
|
|
data->dmab.addr, data->dmab.bytes,
|
|
data->dmab.area);
|
|
break;
|
|
case BUFFER_TYPE_RAM:
|
|
ret = abox_of_get_addr(data->abox_data, dev->of_node,
|
|
"samsung,buffer-address",
|
|
(void **)&data->ramb.area,
|
|
&data->ramb.addr, &data->ramb.bytes);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void abox_udma_remove(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->abox_data->dev;
|
|
int ret = 0;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
switch (data->buf_type) {
|
|
case BUFFER_TYPE_ION:
|
|
ret = abox_ion_free(dev, data->abox_data, data->ion_buf);
|
|
if (ret < 0)
|
|
abox_err(dev, "abox_ion_free() failed %d\n", ret);
|
|
break;
|
|
case BUFFER_TYPE_DMA:
|
|
abox_iommu_unmap(dev_abox, data->dmab.addr);
|
|
snd_dma_free_pages(&data->dmab);
|
|
break;
|
|
case BUFFER_TYPE_RAM:
|
|
/* nothing to do */
|
|
break;
|
|
}
|
|
|
|
abox_dma.remove(cmpnt);
|
|
}
|
|
|
|
static const struct snd_kcontrol_new abox_udma_controls[] = {
|
|
SOC_SINGLE_EXT("Rate", DMA_RATE, 0, 384000, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE_EXT("Width", DMA_WIDTH, 0, 32, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE_EXT("Channel", DMA_CHANNEL, 0, 8, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE_EXT("Period", DMA_PERIOD, 0, INT_MAX, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE_EXT("Periods", DMA_PERIODS, 0, INT_MAX, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE_EXT("Packed", DMA_PACKED, 0, 1, 0,
|
|
abox_dma_hw_params_get, abox_dma_hw_params_put),
|
|
SOC_SINGLE("Auto Fade In", DMA_REG_CTRL, ABOX_DMA_AUTO_FADE_IN_L,
|
|
1, 0),
|
|
SOC_SINGLE("Vol Factor", DMA_REG_VOL_FACTOR, ABOX_DMA_VOL_FACTOR_L,
|
|
0xffffff, 0),
|
|
SOC_SINGLE("Vol Change", DMA_REG_VOL_CHANGE, ABOX_DMA_VOL_FACTOR_L,
|
|
0xffffff, 0),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new abox_udma_rd_controls[] = {
|
|
SOC_SINGLE("Expand", DMA_REG_CTRL, ABOX_DMA_EXPAND_L, 1, 0),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget abox_udma_rd_widgets[] = {
|
|
SND_SOC_DAPM_INPUT("INPUT"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route abox_udma_rd_routes[] = {
|
|
/* sink, control, source */
|
|
{"Capture", NULL, "INPUT"},
|
|
};
|
|
|
|
static int abox_udma_rd_probe(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
snd_soc_add_component_controls(cmpnt, abox_udma_rd_controls,
|
|
ARRAY_SIZE(abox_udma_rd_controls));
|
|
abox_udma_probe(cmpnt);
|
|
abox_cmpnt_register_udma_rd(data->abox_data->dev, dev, data->id,
|
|
data->dai_drv[DMA_DAI_PCM].name);
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver abox_udma_rd = {
|
|
.controls = abox_udma_controls,
|
|
.num_controls = ARRAY_SIZE(abox_udma_controls),
|
|
.dapm_widgets = abox_udma_rd_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(abox_udma_rd_widgets),
|
|
.dapm_routes = abox_udma_rd_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(abox_udma_rd_routes),
|
|
.probe = abox_udma_rd_probe,
|
|
.remove = abox_udma_remove,
|
|
.read = abox_udma_read,
|
|
.write = abox_udma_write,
|
|
.open = abox_udma_open,
|
|
.close = abox_udma_close,
|
|
.hw_params = abox_udma_hw_params,
|
|
.hw_free = abox_udma_hw_free,
|
|
.prepare = abox_udma_prepare,
|
|
.pointer = abox_udma_pointer,
|
|
.mmap = abox_udma_mmap,
|
|
};
|
|
|
|
static enum abox_irq abox_udma_rd_get_irq(struct abox_dma_data *data,
|
|
enum abox_dma_irq irq)
|
|
{
|
|
unsigned int id = data->id;
|
|
enum abox_irq ret;
|
|
|
|
switch (irq) {
|
|
case DMA_IRQ_BUF_EMPTY:
|
|
ret = IRQ_UDMA_RD0_BUF_EMPTY + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_UDMA_RD0_ERR + id;
|
|
break;
|
|
case DMA_IRQ_FADE_DONE:
|
|
ret = IRQ_UDMA_RD0_FADE_DONE + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_udma_rd_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_UDMA_RD0 + id;
|
|
}
|
|
|
|
static char *abox_udma_rd_get_dai_name(struct device *dev,
|
|
enum abox_dma_dai dai, int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "UDMA RD%d", id);
|
|
}
|
|
|
|
static const struct snd_soc_dapm_widget abox_udma_wr_widgets[] = {
|
|
SND_SOC_DAPM_OUTPUT("OUTPUT"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route abox_udma_wr_routes[] = {
|
|
/* sink, control, source */
|
|
{"OUTPUT", NULL, "Playback"},
|
|
};
|
|
|
|
static int abox_udma_wr_probe(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_udma_probe(cmpnt);
|
|
abox_cmpnt_register_udma_wr(data->abox_data->dev, dev, data->id,
|
|
data->dai_drv[DMA_DAI_PCM].name);
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver abox_udma_wr = {
|
|
.controls = abox_udma_controls,
|
|
.num_controls = ARRAY_SIZE(abox_udma_controls),
|
|
.dapm_widgets = abox_udma_wr_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(abox_udma_wr_widgets),
|
|
.dapm_routes = abox_udma_wr_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(abox_udma_wr_routes),
|
|
.probe = abox_udma_wr_probe,
|
|
.remove = abox_udma_remove,
|
|
.read = abox_udma_read,
|
|
.write = abox_udma_write,
|
|
.open = abox_udma_open,
|
|
.close = abox_udma_close,
|
|
.hw_params = abox_udma_hw_params,
|
|
.hw_free = abox_udma_hw_free,
|
|
.prepare = abox_udma_prepare,
|
|
.pointer = abox_udma_pointer,
|
|
.mmap = abox_udma_mmap,
|
|
};
|
|
|
|
static enum abox_irq abox_udma_wr_get_irq(struct abox_dma_data *data,
|
|
enum abox_dma_irq irq)
|
|
{
|
|
unsigned int id = data->id;
|
|
enum abox_irq ret;
|
|
|
|
switch (irq) {
|
|
case DMA_IRQ_BUF_EMPTY:
|
|
ret = IRQ_UDMA_WR0_BUF_FULL + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_UDMA_WR0_ERR + id;
|
|
break;
|
|
case DMA_IRQ_FADE_DONE:
|
|
ret = IRQ_UDMA_WR0_FADE_DONE + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_udma_wr_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_UDMA_WR0 + id;
|
|
}
|
|
|
|
static char *abox_udma_wr_get_dai_name(struct device *dev,
|
|
enum abox_dma_dai dai, int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "UDMA WR%d", id);
|
|
}
|
|
|
|
static const struct snd_soc_dai_driver abox_udma_cap_dai_drv[] = {
|
|
{
|
|
.ops = &abox_udma_dai_ops,
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.rates = ABOX_SAMPLING_RATES,
|
|
.rate_min = 8000,
|
|
.rate_max = 384000,
|
|
.formats = ABOX_SAMPLE_FORMATS,
|
|
},
|
|
},
|
|
};
|
|
|
|
static const struct snd_soc_dai_driver abox_udma_pla_dai_drv[] = {
|
|
{
|
|
.ops = &abox_udma_dai_ops,
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.rates = ABOX_SAMPLING_RATES,
|
|
.rate_min = 8000,
|
|
.rate_max = 384000,
|
|
.formats = ABOX_SAMPLE_FORMATS,
|
|
},
|
|
},
|
|
};
|
|
|
|
struct abox_dma_of_data abox_udma_rd_of_data = {
|
|
.get_irq = abox_udma_rd_get_irq,
|
|
.get_dai_id = abox_udma_rd_get_dai_id,
|
|
.get_dai_name = abox_udma_rd_get_dai_name,
|
|
.dai_drv = abox_udma_cap_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_udma_rd,
|
|
};
|
|
|
|
struct abox_dma_of_data abox_udma_wr_of_data = {
|
|
.get_irq = abox_udma_wr_get_irq,
|
|
.get_dai_id = abox_udma_wr_get_dai_id,
|
|
.get_dai_name = abox_udma_wr_get_dai_name,
|
|
.dai_drv = abox_udma_pla_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_udma_wr,
|
|
};
|
|
|
|
static const char * const abox_udma_trigger_method_texts[] = {
|
|
"USB", "SFR",
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(abox_udma_rd0_trigger_method_enum,
|
|
ABOX_UDMA_TRIGGER_CTRL_RD(0), ABOX_UDMA_TRIGGER_METHOD_L,
|
|
abox_udma_trigger_method_texts);
|
|
|
|
static SOC_ENUM_SINGLE_DECL(abox_udma_rd1_trigger_method_enum,
|
|
ABOX_UDMA_TRIGGER_CTRL_RD(1), ABOX_UDMA_TRIGGER_METHOD_L,
|
|
abox_udma_trigger_method_texts);
|
|
|
|
static SOC_ENUM_SINGLE_DECL(abox_udma_wr0_trigger_method_enum,
|
|
ABOX_UDMA_TRIGGER_CTRL_WR(0), ABOX_UDMA_TRIGGER_METHOD_L,
|
|
abox_udma_trigger_method_texts);
|
|
|
|
static SOC_ENUM_SINGLE_DECL(abox_udma_wr1_trigger_method_enum,
|
|
ABOX_UDMA_TRIGGER_CTRL_WR(1), ABOX_UDMA_TRIGGER_METHOD_L,
|
|
abox_udma_trigger_method_texts);
|
|
|
|
static const struct snd_kcontrol_new abox_udma_global_controls[] = {
|
|
SOC_ENUM("UDMA RD0 Trigger Method", abox_udma_rd0_trigger_method_enum),
|
|
SOC_SINGLE("UDMA RD0 Trigger Counter", ABOX_UDMA_TRIGGER_CNT_RD(0),
|
|
ABOX_UDMA_TRIGGER_CNT_VAL_L, 0xffff, 0),
|
|
SOC_SINGLE("UDMA RD0 Trigger Offset", ABOX_UDMA_TRIGGER_OFFSET_RD(0),
|
|
ABOX_UDMA_TRIGGER_OFFSET_L, 0xffff, 0),
|
|
|
|
SOC_ENUM("UDMA RD1 Trigger Method", abox_udma_rd1_trigger_method_enum),
|
|
SOC_SINGLE("UDMA RD1 Trigger Counter", ABOX_UDMA_TRIGGER_CNT_RD(1),
|
|
ABOX_UDMA_TRIGGER_CNT_VAL_L, 0xffff, 0),
|
|
SOC_SINGLE("UDMA RD1 Trigger Offset", ABOX_UDMA_TRIGGER_OFFSET_RD(1),
|
|
ABOX_UDMA_TRIGGER_OFFSET_L, 0xffff, 0),
|
|
|
|
SOC_ENUM("UDMA WR0 Trigger Method", abox_udma_wr0_trigger_method_enum),
|
|
SOC_SINGLE("UDMA WR0 Trigger Counter", ABOX_UDMA_TRIGGER_CNT_WR(0),
|
|
ABOX_UDMA_TRIGGER_CNT_VAL_L, 0xffff, 0),
|
|
SOC_SINGLE("UDMA WR0 Trigger Offset", ABOX_UDMA_TRIGGER_OFFSET_WR(0),
|
|
ABOX_UDMA_TRIGGER_OFFSET_L, 0xffff, 0),
|
|
|
|
SOC_ENUM("UDMA WR1 Trigger Method", abox_udma_wr1_trigger_method_enum),
|
|
SOC_SINGLE("UDMA WR1 Trigger Counter", ABOX_UDMA_TRIGGER_CNT_WR(1),
|
|
ABOX_UDMA_TRIGGER_CNT_VAL_L, 0xffff, 0),
|
|
SOC_SINGLE("UDMA WR1 Trigger Offset", ABOX_UDMA_TRIGGER_OFFSET_WR(1),
|
|
ABOX_UDMA_TRIGGER_OFFSET_L, 0xffff, 0),
|
|
};
|
|
|
|
int abox_udma_init(struct abox_data *data)
|
|
{
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
|
|
if (!cmpnt)
|
|
return -EAGAIN;
|
|
|
|
return snd_soc_add_component_controls(cmpnt, abox_udma_global_controls,
|
|
ARRAY_SIZE(abox_udma_global_controls));
|
|
}
|