2227 lines
58 KiB
C
Executable file
2227 lines
58 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox DMA driver
|
|
*
|
|
* Copyright (c) 2019 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 <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include <sound/soc.h>
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/samsung/abox.h>
|
|
#include "abox_util.h"
|
|
#include "abox_gic.h"
|
|
#include "abox_cmpnt.h"
|
|
#include "abox.h"
|
|
#include "abox_dump.h"
|
|
#include "abox_udma.h"
|
|
#include "abox_dma.h"
|
|
#include "abox_memlog.h"
|
|
|
|
static const struct snd_pcm_hardware abox_dma_hardware = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED
|
|
| SNDRV_PCM_INFO_BLOCK_TRANSFER
|
|
| SNDRV_PCM_INFO_MMAP
|
|
| SNDRV_PCM_INFO_MMAP_VALID,
|
|
.formats = ABOX_SAMPLE_FORMATS,
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.buffer_bytes_max = BUFFER_BYTES_MAX,
|
|
.period_bytes_min = PERIOD_BYTES_MIN,
|
|
.period_bytes_max = PERIOD_BYTES_MAX,
|
|
.periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX,
|
|
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
|
|
};
|
|
|
|
unsigned int abox_dma_iova(struct abox_dma_data *data)
|
|
{
|
|
unsigned int id = data->dai_drv->id;
|
|
unsigned int ret;
|
|
|
|
if (id >= ABOX_RDMA0 && id < ABOX_WDMA0)
|
|
ret = IOVA_RDMA_BUFFER(id - ABOX_RDMA0);
|
|
else if (id >= ABOX_WDMA0 && id < ABOX_WDMA0_DUAL)
|
|
ret = IOVA_WDMA_BUFFER(id - ABOX_WDMA0);
|
|
else if (id >= ABOX_WDMA0_DUAL && id < ABOX_DDMA0)
|
|
ret = IOVA_DUAL_BUFFER(id - ABOX_WDMA0_DUAL);
|
|
else if (id >= ABOX_DDMA0 && id < ABOX_UAIF0)
|
|
ret = IOVA_DDMA_BUFFER(id - ABOX_DDMA0);
|
|
else if (id >= ABOX_UDMA_RD0 && id < ABOX_UDMA_WR0)
|
|
ret = IOVA_UDMA_RD_BUFFER(id - ABOX_UDMA_RD0);
|
|
else if (id >= ABOX_UDMA_WR0 && id < ABOX_UDMA_WR0_DUAL)
|
|
ret = IOVA_UDMA_WR_BUFFER(id - ABOX_UDMA_WR0);
|
|
else if (id >= ABOX_UDMA_WR0_DUAL && id < ABOX_UDMA_WR_DBG0)
|
|
ret = IOVA_UDMA_WR_DUAL_BUFFER(id - ABOX_UDMA_WR0_DUAL);
|
|
else if (id >= ABOX_UDMA_WR_DBG0)
|
|
ret = IOVA_UDMA_WR_DBG_BUFFER(id - ABOX_UDMA_WR_DBG0);
|
|
else
|
|
ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool abox_dma_is_sync_mode(struct abox_dma_data *data)
|
|
{
|
|
struct abox_data *abox_data = data->abox_data;
|
|
int id = data->id;
|
|
unsigned int val;
|
|
|
|
if (id >= ARRAY_SIZE(abox_data->dev_rdma) || !abox_data->dev_rdma[id])
|
|
return false;
|
|
|
|
data = dev_get_drvdata(abox_data->dev_rdma[id]);
|
|
val = snd_soc_component_read(data->cmpnt, DMA_REG_CTRL0);
|
|
|
|
return ((val & ABOX_DMA_SYNC_MODE_MASK) >> ABOX_DMA_SYNC_MODE_L);
|
|
}
|
|
|
|
bool abox_dma_is_opened(struct device *dev)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
return !!data->substream;
|
|
}
|
|
|
|
enum abox_irq abox_dma_get_irq(struct abox_dma_data *data,
|
|
enum abox_dma_irq irq)
|
|
{
|
|
if (!data || !data->of_data || !data->of_data->get_irq)
|
|
return -EINVAL;
|
|
|
|
return data->of_data->get_irq(data, irq);
|
|
}
|
|
|
|
void abox_dma_acquire_irq(struct abox_dma_data *data, enum abox_dma_irq dma_irq)
|
|
{
|
|
struct device *dev_gic = data->abox_data->dev_gic;
|
|
enum abox_irq irq;
|
|
|
|
/* Acquire IRQ from the firmware */
|
|
irq = abox_dma_get_irq(data, dma_irq);
|
|
if (irq < 0 || irq > IRQ_COUNT)
|
|
return;
|
|
|
|
abox_gic_target(dev_gic, irq, ABOX_GIC_AP);
|
|
abox_gic_enable(dev_gic, irq, true);
|
|
}
|
|
|
|
void abox_dma_release_irq(struct abox_dma_data *data, enum abox_dma_irq dma_irq)
|
|
{
|
|
struct device *dev_gic = data->abox_data->dev_gic;
|
|
enum abox_irq irq;
|
|
|
|
/* Return back IRQ to the firmware */
|
|
irq = abox_dma_get_irq(data, dma_irq);
|
|
if (irq < 0 || irq > IRQ_COUNT)
|
|
return;
|
|
|
|
abox_gic_enable(dev_gic, irq, false);
|
|
abox_gic_target(dev_gic, irq, ABOX_GIC_CORE0);
|
|
}
|
|
|
|
static void abox_dma_acquire_irq_all(struct abox_dma_data *data)
|
|
{
|
|
int i;
|
|
|
|
/* Acquire IRQ from the firmware */
|
|
for (i = DMA_IRQ_BUF_DONE; i < DMA_IRQ_COUNT; i++)
|
|
abox_dma_acquire_irq(data, i);
|
|
}
|
|
|
|
static void abox_dma_release_irq_all(struct abox_dma_data *data)
|
|
{
|
|
int i;
|
|
|
|
/* Return back IRQ to the firmware */
|
|
for (i = DMA_IRQ_BUF_DONE; i < DMA_IRQ_COUNT; i++)
|
|
abox_dma_release_irq(data, i);
|
|
}
|
|
|
|
int abox_dma_register_irq(struct abox_dma_data *data,
|
|
enum abox_dma_irq irq, irq_handler_t handler, void *dev_id)
|
|
{
|
|
struct device *dev_gic = data->abox_data->dev_gic;
|
|
enum abox_irq _irq;
|
|
|
|
_irq = abox_dma_get_irq(data, irq);
|
|
if (_irq < 0 || _irq > IRQ_COUNT)
|
|
return -EINVAL;
|
|
|
|
return abox_gic_register_irq_handler(dev_gic, _irq, handler, dev_id);
|
|
}
|
|
|
|
void abox_dma_unregister_irq(struct abox_dma_data *data,
|
|
enum abox_dma_irq irq)
|
|
{
|
|
struct device *dev_gic = data->abox_data->dev_gic;
|
|
enum abox_irq _irq;
|
|
|
|
_irq = abox_dma_get_irq(data, irq);
|
|
if (_irq < 0 || _irq > IRQ_COUNT)
|
|
return;
|
|
|
|
abox_gic_unregister_irq_handler(dev_gic, _irq);
|
|
}
|
|
|
|
int abox_dma_mixer_control_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
unsigned int reg = mc->reg;
|
|
unsigned int shift = mc->shift;
|
|
int max = mc->max;
|
|
int sign_bit = mc->sign_bit;
|
|
unsigned int mask = (1 << fls(max)) - 1;
|
|
int val;
|
|
|
|
if (sign_bit)
|
|
mask = (unsigned int)(BIT(sign_bit + 1) - 1);
|
|
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
val >>= shift;
|
|
val &= mask;
|
|
|
|
if (sign_bit) {
|
|
/* use shift for sign extension */
|
|
val <<= 31 - sign_bit;
|
|
val >>= 31 - sign_bit;
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_dma_mixer_control_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
unsigned int reg = mc->reg;
|
|
unsigned int shift = mc->shift;
|
|
int max = mc->max;
|
|
unsigned int sign_bit = mc->sign_bit;
|
|
unsigned int mask = (1 << fls(max)) - 1;
|
|
int err;
|
|
unsigned int val, val_mask;
|
|
|
|
if (sign_bit)
|
|
mask = (unsigned int)(BIT(sign_bit + 1) - 1);
|
|
|
|
val = (unsigned int)(ucontrol->value.integer.value[0] << shift);
|
|
val_mask = mask << shift;
|
|
err = snd_soc_component_update_bits(cmpnt, reg, val_mask, val);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_progress(struct snd_soc_component *cmpnt)
|
|
{
|
|
unsigned int val;
|
|
|
|
val = snd_soc_component_read(cmpnt, DMA_REG_STATUS);
|
|
|
|
return !!(val & ABOX_DMA_PROGRESS_MASK);
|
|
}
|
|
|
|
void abox_dma_barrier(struct device *dev, struct abox_dma_data *data,
|
|
int enable)
|
|
{
|
|
const int wait_ns = 10000000; /* 10ms */
|
|
u64 timeout = local_clock() + wait_ns;
|
|
|
|
while (abox_dma_progress(data->cmpnt) != enable) {
|
|
if (local_clock() <= timeout) {
|
|
udelay(10);
|
|
continue;
|
|
}
|
|
dev_warn_ratelimited(dev, "%s timeout\n",
|
|
enable ? "enable" : "disable");
|
|
|
|
/* Disable DMA by force */
|
|
snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_ENABLE_MASK, 1 << ABOX_DMA_ENABLE_L);
|
|
snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_ENABLE_MASK, 0 << ABOX_DMA_ENABLE_L);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static irqreturn_t abox_dma_buf_done(int irq, void *dev_id)
|
|
{
|
|
struct device *dev = dev_id;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, irq);
|
|
|
|
snd_pcm_period_elapsed(data->substream);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t abox_dma_fade_done(int irq, void *dev_id)
|
|
{
|
|
struct device *dev = dev_id;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, irq);
|
|
|
|
complete(&data->func_changed);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t abox_dma_err(int irq, void *dev_id)
|
|
{
|
|
struct device *dev = dev_id;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
unsigned int reg, val;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, irq);
|
|
|
|
abox_err(dev, "Error\n");
|
|
for (reg = DMA_REG_CTRL0; reg <= DMA_REG_MAX; reg += 4) {
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
abox_err(dev, "%08x: %08x\n", reg, val);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const irq_handler_t abox_dma_irq_handlers[DMA_IRQ_COUNT] = {
|
|
abox_dma_buf_done,
|
|
abox_dma_fade_done,
|
|
abox_dma_err,
|
|
};
|
|
|
|
static int abox_dma_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
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 snd_soc_component *cmpnt = data->cmpnt;
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->dev_abox;
|
|
unsigned int iova = abox_dma_iova(data);
|
|
unsigned int reg, mask, val;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
data->hw_params = *params;
|
|
|
|
if (!rtd->dai_link->no_pcm) {
|
|
ret = snd_pcm_lib_malloc_pages(substream,
|
|
params_buffer_bytes(params));
|
|
if (ret < 0) {
|
|
abox_err(dev, "memory allocation fail: %d\n", ret);
|
|
return ret;
|
|
} else if (ret > 0) {
|
|
abox_iommu_unmap(dev_abox, iova);
|
|
ret = abox_iommu_map(dev_abox, iova, runtime->dma_addr,
|
|
PAGE_ALIGN(runtime->dma_bytes),
|
|
runtime->dma_area);
|
|
if (ret < 0) {
|
|
abox_err(dev, "memory mapping fail: %d\n", ret);
|
|
snd_pcm_lib_free_pages(substream);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data->buf_type == BUFFER_TYPE_RAM) {
|
|
if (data->ramb.bytes >= params_buffer_bytes(params))
|
|
iova = data->ramb.addr;
|
|
}
|
|
|
|
reg = DMA_REG_BUF_STR;
|
|
mask = ABOX_DMA_BUF_STR_MASK;
|
|
val = iova;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_BUF_END;
|
|
mask = ABOX_DMA_BUF_END_MASK;
|
|
val = iova + params_buffer_bytes(params);
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_BUF_OFFSET;
|
|
mask = ABOX_DMA_BUF_OFFSET_MASK;
|
|
val = params_period_bytes(params);
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_STR_POINT;
|
|
mask = ABOX_DMA_STR_POINT_MASK;
|
|
val = iova;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_WIDTH_MASK;
|
|
val = ((params_width(params) / 8) - 1) << ABOX_DMA_WIDTH_L;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_PACKED_MASK;
|
|
val = (params_physical_width(params) == 24) << ABOX_DMA_PACKED_L;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_CHANNELS_MASK;
|
|
val = (params_channels(params) - 1) << ABOX_DMA_CHANNELS_L;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
snd_soc_component_async_complete(cmpnt);
|
|
|
|
if (!rtd->dai_link->no_pcm)
|
|
abox_dma_acquire_irq_all(data);
|
|
|
|
abox_info(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_dma_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;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (rtd->dai_link->no_pcm)
|
|
return 0;
|
|
|
|
abox_dma_release_irq_all(data);
|
|
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
static int abox_dma_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;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
/* set auto fade in before dma enable */
|
|
snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_AUTO_FADE_IN_MASK,
|
|
data->auto_fade_in ? ABOX_DMA_AUTO_FADE_IN_MASK : 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
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 snd_soc_component *cmpnt = data->cmpnt;
|
|
struct device *dev = data->dev;
|
|
unsigned int reg, mask, val;
|
|
int ret;
|
|
|
|
abox_info(dev, "%s(%d)\n", __func__, cmd);
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
abox_dma_barrier(dev, data, 0);
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_ENABLE_MASK;
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
val = (val & ~mask) | ((1 << ABOX_DMA_ENABLE_L) & mask);
|
|
ret = snd_soc_component_write(cmpnt, reg, val);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
/* ToDo: wait for stable state.
|
|
* Func shouldn't be changed during transition.
|
|
*/
|
|
abox_dma_barrier(dev, data, 1);
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_FUNC_MASK;
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
/* normal mode */
|
|
val = (val & ~mask) | ((0 << ABOX_DMA_FUNC_L) & mask);
|
|
ret = snd_soc_component_write(cmpnt, reg, val);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
abox_dma_barrier(dev, data, 1);
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_ENABLE_MASK;
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
val = (val & ~mask) | ((0 << ABOX_DMA_ENABLE_L) & mask);
|
|
ret = snd_soc_component_write(cmpnt, reg, val);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
/* ToDo: wait for stable state.
|
|
* Func shouldn't be changed during transition.
|
|
*/
|
|
abox_dma_barrier(dev, data, 1);
|
|
reg = DMA_REG_CTRL;
|
|
mask = ABOX_DMA_FUNC_MASK;
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
/* pending mode */
|
|
val = (val & ~mask) | ((1 << ABOX_DMA_FUNC_L) & mask);
|
|
ret = snd_soc_component_write(cmpnt, reg, val);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static size_t abox_dma_read_pointer(struct abox_dma_data *data)
|
|
{
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
unsigned int status = 0;
|
|
unsigned int buf_str, buf_end, buf_offset;
|
|
size_t offset, count, period_bytes;
|
|
ssize_t buffer_bytes;
|
|
|
|
status = snd_soc_component_read(cmpnt, DMA_REG_STATUS);
|
|
buf_str = snd_soc_component_read(cmpnt, DMA_REG_BUF_STR);
|
|
buf_end = snd_soc_component_read(cmpnt, DMA_REG_BUF_END);
|
|
buf_offset = snd_soc_component_read(cmpnt, DMA_REG_BUF_OFFSET);
|
|
|
|
buffer_bytes = buf_end - buf_str;
|
|
period_bytes = buf_offset;
|
|
|
|
if (hweight_long(ABOX_DMA_BUF_OFFSET_CNT_MASK) > 8)
|
|
offset = ((status & ABOX_DMA_BUF_OFFSET_CNT_MASK) >>
|
|
ABOX_DMA_BUF_OFFSET_CNT_L) << 4;
|
|
else
|
|
offset = ((status & ABOX_DMA_BUF_OFFSET_CNT_MASK) >>
|
|
ABOX_DMA_BUF_OFFSET_CNT_L) * period_bytes;
|
|
|
|
if (period_bytes > ABOX_DMA_BUF_CNT_MASK + 1)
|
|
count = 0;
|
|
else
|
|
count = (status & ABOX_DMA_BUF_CNT_MASK);
|
|
|
|
while ((offset % period_bytes) && (buffer_bytes >= 0)) {
|
|
buffer_bytes -= period_bytes;
|
|
if ((buffer_bytes & offset) == offset)
|
|
offset = buffer_bytes;
|
|
}
|
|
|
|
return offset + count;
|
|
}
|
|
|
|
static snd_pcm_uframes_t abox_dma_pointer(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
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;
|
|
size_t pointer;
|
|
|
|
if (abox_dma_progress(data->cmpnt))
|
|
pointer = abox_dma_read_pointer(data);
|
|
else
|
|
pointer = 0;
|
|
|
|
abox_dbg(dev, "%s: pointer=%08zx\n", __func__, pointer);
|
|
|
|
return bytes_to_frames(runtime, (ssize_t)pointer);
|
|
}
|
|
|
|
static int abox_dma_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;
|
|
|
|
abox_info(dev, "%s\n", __func__);
|
|
|
|
abox_request_cpu_gear_dai(dev, abox_data, cpu_dai,
|
|
abox_data->cpu_gear_min);
|
|
if (substream->runtime)
|
|
snd_soc_set_runtime_hwparams(substream, &abox_dma_hardware);
|
|
data->substream = substream;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_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 device *dev = data->dev;
|
|
|
|
abox_info(dev, "%s\n", __func__);
|
|
|
|
data->substream = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_mmap(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct vm_area_struct *vma)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
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;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
return dma_mmap_wc(dev, vma, runtime->dma_area, runtime->dma_addr,
|
|
runtime->dma_bytes);
|
|
}
|
|
|
|
static int abox_dma_pcm_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
struct snd_pcm_substream *substream =
|
|
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream ?
|
|
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream :
|
|
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
|
|
struct snd_dma_buffer *dmab = &substream->dma_buffer;
|
|
struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai);
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->dev_abox;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (dmab->bytes < BUFFER_BYTES_MIN)
|
|
dmab->bytes = BUFFER_BYTES_MIN;
|
|
|
|
snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV,
|
|
dev, dmab->bytes, dmab->bytes);
|
|
|
|
ret = abox_iommu_map(dev_abox, abox_dma_iova(data), dmab->addr,
|
|
dmab->bytes, dmab->area);
|
|
if (ret < 0)
|
|
snd_pcm_lib_preallocate_free_for_all(pcm);
|
|
|
|
data->dmab = *dmab;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void abox_dma_pcm_free(struct snd_soc_component *component,
|
|
struct snd_pcm *pcm)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_chip(pcm);
|
|
struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai);
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->dev_abox;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_iommu_unmap(dev_abox, abox_dma_iova(data));
|
|
snd_pcm_lib_preallocate_free_for_all(pcm);
|
|
}
|
|
|
|
static int abox_dma_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__);
|
|
|
|
data->cmpnt = cmpnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void abox_dma_remove(struct snd_soc_component *cmpnt)
|
|
{
|
|
struct device *dev = cmpnt->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
}
|
|
|
|
static unsigned int abox_dma_read(struct snd_soc_component *cmpnt,
|
|
unsigned int base, unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
struct abox_data *abox_data = data->abox_data;
|
|
|
|
if (reg > DMA_REG_MAX) {
|
|
abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg);
|
|
dump_stack();
|
|
}
|
|
|
|
return snd_soc_component_read(abox_data->cmpnt, base + reg);
|
|
}
|
|
|
|
static int abox_dma_write(struct snd_soc_component *cmpnt,
|
|
unsigned int base, 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;
|
|
int ret;
|
|
|
|
if (reg > DMA_REG_MAX) {
|
|
abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg);
|
|
dump_stack();
|
|
}
|
|
|
|
ret = snd_soc_component_write(abox_data->cmpnt, base + reg, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct snd_soc_component_driver abox_dma = {
|
|
.probe = abox_dma_probe,
|
|
.remove = abox_dma_remove,
|
|
.pcm_construct = abox_dma_pcm_new,
|
|
.pcm_destruct = abox_dma_pcm_free,
|
|
.open = abox_dma_open,
|
|
.close = abox_dma_close,
|
|
.hw_params = abox_dma_hw_params,
|
|
.hw_free = abox_dma_hw_free,
|
|
.prepare = abox_dma_prepare,
|
|
.trigger = abox_dma_trigger,
|
|
.pointer = abox_dma_pointer,
|
|
.mmap = abox_dma_mmap,
|
|
};
|
|
|
|
int abox_dma_set_dst_bit_width(struct device *dev, int width)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
unsigned int reg, mask, val;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, width);
|
|
|
|
reg = DMA_REG_BIT_CTRL;
|
|
mask = ABOX_DMA_DST_BIT_WIDTH_MASK;
|
|
val = ((width / 8) - 1) << ABOX_DMA_DST_BIT_WIDTH_L;
|
|
ret = snd_soc_component_update_bits(cmpnt, reg, mask, val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int abox_dma_get_dst_bit_width(struct device *dev)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
unsigned int reg, val;
|
|
int width;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
reg = DMA_REG_BIT_CTRL;
|
|
val = snd_soc_component_read(cmpnt, reg);
|
|
val &= ABOX_DMA_DST_BIT_WIDTH_MASK;
|
|
val >>= ABOX_DMA_DST_BIT_WIDTH_L;
|
|
width = (val + 1) * 8;
|
|
|
|
return width;
|
|
}
|
|
|
|
int abox_dma_get_channels(struct device *dev)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
return params_channels(&data->hw_params);
|
|
}
|
|
|
|
static void hw_param_mask_set(struct snd_pcm_hw_params *params,
|
|
snd_pcm_hw_param_t param, unsigned int val)
|
|
{
|
|
struct snd_mask *mask = hw_param_mask(params, param);
|
|
|
|
snd_mask_none(mask);
|
|
if ((int)val >= 0)
|
|
snd_mask_set(mask, val);
|
|
}
|
|
|
|
static void hw_param_interval_set(struct snd_pcm_hw_params *params,
|
|
snd_pcm_hw_param_t param, unsigned int val)
|
|
{
|
|
struct snd_interval *interval = hw_param_interval(params, param);
|
|
|
|
snd_interval_none(interval);
|
|
if ((int)val >= 0) {
|
|
interval->empty = 0;
|
|
interval->min = interval->max = val;
|
|
interval->openmin = interval->openmax = 0;
|
|
interval->integer = 1;
|
|
}
|
|
}
|
|
|
|
int abox_dma_hw_params_fixup(struct device *dev,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct abox_dma_data *data;
|
|
const struct snd_pcm_hw_params *fix;
|
|
const struct snd_mask *mask;
|
|
const struct snd_interval *interval;
|
|
snd_pcm_hw_param_t param;
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
data = dev_get_drvdata(dev);
|
|
fix = &data->hw_params;
|
|
|
|
abox_dbg(dev, "%s:Total=%u PrdSz=%u(%u) #Prds=%u rate=%u, width=%d, channels=%u\n",
|
|
__func__, params_buffer_bytes(fix),
|
|
params_period_size(fix), params_period_bytes(fix),
|
|
params_periods(fix), params_rate(fix),
|
|
params_width(fix), params_channels(fix));
|
|
|
|
for (param = SNDRV_PCM_HW_PARAM_FIRST_MASK; param <
|
|
SNDRV_PCM_HW_PARAM_LAST_MASK; param++) {
|
|
mask = hw_param_mask_c(fix, param);
|
|
if (!snd_mask_empty(mask))
|
|
snd_mask_copy(hw_param_mask(params, param), mask);
|
|
}
|
|
|
|
for (param = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; param <
|
|
SNDRV_PCM_HW_PARAM_LAST_INTERVAL; param++) {
|
|
interval = hw_param_interval_c(fix, param);
|
|
if (!snd_interval_empty(interval))
|
|
snd_interval_copy(hw_param_interval(params, param),
|
|
interval);
|
|
}
|
|
|
|
abox_dbg(dev, "%s:Total=%u PrdSz=%u(%u) #Prds=%u rate=%u, width=%d, channels=%u\n",
|
|
__func__, 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;
|
|
}
|
|
|
|
void abox_dma_hw_params_set(struct device *dev, unsigned int rate,
|
|
unsigned int width, unsigned int channels,
|
|
unsigned int period_size, unsigned int periods, bool packed)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
struct snd_pcm_hw_params *params = &data->hw_params;
|
|
unsigned int buffer_size, format, pwidth;
|
|
|
|
abox_dbg(dev, "%s(%u, %u, %u, %u, %u, %d)\n", __func__, rate, width,
|
|
channels, period_size, periods, packed);
|
|
|
|
if (!rate)
|
|
rate = params_rate(params);
|
|
|
|
if (!width)
|
|
width = params_width(params);
|
|
|
|
switch (width) {
|
|
case 8:
|
|
format = SNDRV_PCM_FORMAT_S8;
|
|
break;
|
|
case 16:
|
|
format = SNDRV_PCM_FORMAT_S16;
|
|
break;
|
|
case 24:
|
|
if (packed)
|
|
format = SNDRV_PCM_FORMAT_S24_3LE;
|
|
else
|
|
format = SNDRV_PCM_FORMAT_S24;
|
|
break;
|
|
case 32:
|
|
format = SNDRV_PCM_FORMAT_S32;
|
|
break;
|
|
default:
|
|
format = params_format(params);
|
|
break;
|
|
}
|
|
pwidth = snd_pcm_format_physical_width(format);
|
|
|
|
if (!channels)
|
|
channels = params_channels(params);
|
|
|
|
if (!period_size)
|
|
period_size = params_period_size(params);
|
|
|
|
if (!periods)
|
|
periods = params_periods(params);
|
|
|
|
buffer_size = period_size * periods;
|
|
|
|
hw_param_mask_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
|
|
format);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
|
pwidth);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
pwidth * channels);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
channels);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_RATE,
|
|
rate);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
|
USEC_PER_SEC * period_size / rate);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
|
period_size);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
period_size * (pwidth / 8) * channels);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_PERIODS,
|
|
periods);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
|
|
USEC_PER_SEC * buffer_size / rate);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
|
buffer_size);
|
|
hw_param_interval_set(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
buffer_size * (pwidth / 8) * channels);
|
|
}
|
|
|
|
int abox_dma_hw_params_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
const struct snd_pcm_hw_params *hw_params = &data->hw_params;
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
enum abox_dma_param param = mc->reg;
|
|
long value;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, param);
|
|
|
|
switch (param) {
|
|
case DMA_RATE:
|
|
value = params_rate(hw_params);
|
|
break;
|
|
case DMA_WIDTH:
|
|
value = params_width(hw_params);
|
|
break;
|
|
case DMA_CHANNEL:
|
|
value = params_channels(hw_params);
|
|
break;
|
|
case DMA_PERIOD:
|
|
value = params_period_size(hw_params);
|
|
break;
|
|
case DMA_PERIODS:
|
|
value = params_periods(hw_params);
|
|
break;
|
|
case DMA_PACKED:
|
|
value = (params_format(hw_params) == SNDRV_PCM_FORMAT_S24_3LE);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_dma_hw_params_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
|
|
struct device *dev = cmpnt->dev;
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
enum abox_dma_param param = mc->reg;
|
|
long value = ucontrol->value.integer.value[0];
|
|
|
|
abox_dbg(dev, "%s(%d, %ld)\n", __func__, param, value);
|
|
|
|
if (value < mc->min || value > mc->max)
|
|
return -EINVAL;
|
|
|
|
switch (param) {
|
|
case DMA_RATE:
|
|
abox_dma_hw_params_set(dev, value, 0, 0, 0, 0, 0);
|
|
break;
|
|
case DMA_WIDTH:
|
|
abox_dma_hw_params_set(dev, 0, value, 0, 0, 0, 0);
|
|
break;
|
|
case DMA_CHANNEL:
|
|
abox_dma_hw_params_set(dev, 0, 0, value, 0, 0, 0);
|
|
break;
|
|
case DMA_PERIOD:
|
|
abox_dma_hw_params_set(dev, 0, 0, 0, value, 0, 0);
|
|
break;
|
|
case DMA_PERIODS:
|
|
abox_dma_hw_params_set(dev, 0, 0, 0, 0, value, 0);
|
|
break;
|
|
case DMA_PACKED:
|
|
abox_dma_hw_params_set(dev, 0, 0, 0, 0, 0, !!value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_dma_auto_fade_in_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
ucontrol->value.integer.value[0] = data->auto_fade_in;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_dma_auto_fade_in_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
bool value = !!ucontrol->value.integer.value[0];
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, value);
|
|
|
|
data->auto_fade_in = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char * const dma_func_texts[] = {
|
|
"Normal", "Pending", "Mute",
|
|
};
|
|
SOC_ENUM_SINGLE_DECL(abox_dma_func_enum, DMA_REG_CTRL,
|
|
ABOX_DMA_FUNC_L, dma_func_texts);
|
|
|
|
int abox_dma_func_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
|
|
struct device *dev = cmpnt->dev;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
unsigned int *item = ucontrol->value.enumerated.item;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, item[0]);
|
|
|
|
if (abox_dma_progress(cmpnt)) {
|
|
static const unsigned int VOL_MAX = 0x800000;
|
|
unsigned int change = 1, rate, wait_ms;
|
|
|
|
change = snd_soc_component_read(cmpnt, DMA_REG_VOL_CHANGE);
|
|
rate = params_rate(&data->hw_params);
|
|
wait_ms = DIV_ROUND_UP(MSEC_PER_SEC * VOL_MAX, change);
|
|
wait_ms = DIV_ROUND_UP(wait_ms, rate);
|
|
|
|
reinit_completion(&data->func_changed);
|
|
ret = snd_soc_put_enum_double(kcontrol, ucontrol);
|
|
if (ret > 0) {
|
|
abox_info(dev, "func %d, wait %u ms\n", item[0], wait_ms);
|
|
wait_for_completion_timeout(&data->func_changed,
|
|
msecs_to_jiffies(wait_ms));
|
|
}
|
|
} else {
|
|
ret = snd_soc_put_enum_double(kcontrol, ucontrol);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct snd_soc_dai *abox_dma_get_dai(struct device *dev, enum abox_dma_dai type)
|
|
{
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
struct snd_soc_dai *dai;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, type);
|
|
|
|
list_for_each_entry(dai, &cmpnt->dai_list, list) {
|
|
if (type-- == 0)
|
|
return dai;
|
|
}
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
int abox_dma_can_close(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
int rdir = stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
switch (rtd->dpcm[rdir].state) {
|
|
case SND_SOC_DPCM_STATE_OPEN:
|
|
case SND_SOC_DPCM_STATE_HW_PARAMS:
|
|
case SND_SOC_DPCM_STATE_PREPARE:
|
|
case SND_SOC_DPCM_STATE_START:
|
|
case SND_SOC_DPCM_STATE_STOP:
|
|
case SND_SOC_DPCM_STATE_PAUSED:
|
|
case SND_SOC_DPCM_STATE_SUSPEND:
|
|
case SND_SOC_DPCM_STATE_HW_FREE:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int abox_dma_can_free(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
int rdir = stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
switch (rtd->dpcm[rdir].state) {
|
|
case SND_SOC_DPCM_STATE_HW_PARAMS:
|
|
case SND_SOC_DPCM_STATE_PREPARE:
|
|
case SND_SOC_DPCM_STATE_START:
|
|
case SND_SOC_DPCM_STATE_STOP:
|
|
case SND_SOC_DPCM_STATE_PAUSED:
|
|
case SND_SOC_DPCM_STATE_SUSPEND:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int abox_dma_can_stop(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
int rdir = stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
switch (rtd->dpcm[rdir].state) {
|
|
case SND_SOC_DPCM_STATE_PREPARE:
|
|
case SND_SOC_DPCM_STATE_START:
|
|
case SND_SOC_DPCM_STATE_PAUSED:
|
|
case SND_SOC_DPCM_STATE_SUSPEND:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int abox_dma_can_start(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
int rdir = stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
switch (rtd->dpcm[rdir].state) {
|
|
case SND_SOC_DPCM_STATE_PREPARE:
|
|
case SND_SOC_DPCM_STATE_START:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int abox_dma_can_prepare(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
return abox_dma_can_start(rtd, stream);
|
|
}
|
|
|
|
int abox_dma_can_params(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
return abox_dma_can_free(rtd, stream);
|
|
}
|
|
|
|
int abox_dma_can_open(struct snd_soc_pcm_runtime *rtd, int stream)
|
|
{
|
|
return abox_dma_can_close(rtd, stream);
|
|
}
|
|
|
|
static unsigned int abox_dma_get_dst_format(struct abox_dma_data *data)
|
|
{
|
|
unsigned int width, channels;
|
|
|
|
width = abox_dma_get_dst_bit_width(data->dev);
|
|
channels = params_channels(&data->hw_params);
|
|
|
|
return abox_get_format(width, channels);
|
|
}
|
|
|
|
static unsigned int abox_dma_get_format(struct abox_dma_data *data)
|
|
{
|
|
unsigned int width, channels;
|
|
|
|
width = params_width(&data->hw_params);
|
|
channels = params_channels(&data->hw_params);
|
|
|
|
return abox_get_format(width, channels);
|
|
}
|
|
|
|
static int abox_dma_dump_set_format(struct abox_dma_data *data)
|
|
{
|
|
struct abox_data *abox_data = data->abox_data;
|
|
enum abox_widget w;
|
|
unsigned int format;
|
|
int idx, ret;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
if (!data->of_data->get_src_widget)
|
|
return 0;
|
|
|
|
w = data->of_data->get_src_widget(data);
|
|
if (w < 0)
|
|
return w;
|
|
|
|
switch (w) {
|
|
case ABOX_WIDGET_SPUS_IN0 ... ABOX_WIDGET_SPUS_IN11:
|
|
idx = w - ABOX_WIDGET_SPUS_IN0;
|
|
format = abox_dma_get_dst_format(dev_get_drvdata(
|
|
abox_data->dev_rdma[idx]));
|
|
break;
|
|
case ABOX_WIDGET_SPUS_ASRC0 ... ABOX_WIDGET_SPUS_ASRC7:
|
|
idx = w - ABOX_WIDGET_SPUS_ASRC0;
|
|
format = abox_cmpnt_asrc_get_dst_format(abox_data,
|
|
SNDRV_PCM_STREAM_PLAYBACK, idx);
|
|
break;
|
|
case ABOX_WIDGET_SIFS0:
|
|
format = abox_cmpnt_sif_get_dst_format(abox_data,
|
|
SNDRV_PCM_STREAM_PLAYBACK, 0);
|
|
break;
|
|
case ABOX_WIDGET_NSRC0 ... ABOX_WIDGET_NSRC7:
|
|
idx = w - ABOX_WIDGET_NSRC0;
|
|
format = abox_cmpnt_sif_get_dst_format(abox_data,
|
|
SNDRV_PCM_STREAM_CAPTURE, idx);
|
|
break;
|
|
case ABOX_WIDGET_SPUM_ASRC0 ... ABOX_WIDGET_SPUM_ASRC3:
|
|
idx = w - ABOX_WIDGET_SPUM_ASRC0;
|
|
format = abox_cmpnt_asrc_get_dst_format(abox_data,
|
|
SNDRV_PCM_STREAM_CAPTURE, idx);
|
|
break;
|
|
case ABOX_WIDGET_UDMA_RD0 ... ABOX_WIDGET_UDMA_RD1:
|
|
idx = w - ABOX_WIDGET_UDMA_RD0;
|
|
format = abox_dma_get_format(dev_get_drvdata(
|
|
abox_data->dev_udma_rd[idx]));
|
|
break;
|
|
case ABOX_WIDGET_UDMA_WR0 ... ABOX_WIDGET_UDMA_WR1:
|
|
idx = w - ABOX_WIDGET_UDMA_WR0;
|
|
format = abox_dma_get_format(dev_get_drvdata(
|
|
abox_data->dev_udma_wr[idx]));
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_FORMAT_MASK, format << ABOX_DMA_FORMAT_L);
|
|
return ret;
|
|
}
|
|
|
|
static const unsigned int ABOX_DMA_DUMP_BUFFER_SIZE = SZ_512K;
|
|
static const unsigned int ABOX_DMA_DUMP_OFFSET = SZ_4K;
|
|
|
|
static int abox_dma_dump_set_buffer(struct abox_dma_data *data)
|
|
{
|
|
struct abox_dma_dump *dump = data->dump;
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->abox_data->dev;
|
|
struct snd_soc_component *cmpnt = data->cmpnt;
|
|
unsigned int iova = abox_dma_iova(data);
|
|
unsigned int reg, mask, val;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (dump->area)
|
|
abox_err(dev, "memory leak suspected\n");
|
|
|
|
dump->bytes = ABOX_DMA_DUMP_BUFFER_SIZE;
|
|
dump->area = dma_alloc_coherent(dev, dump->bytes, &dump->addr,
|
|
GFP_KERNEL);
|
|
if (!dump->area)
|
|
return -ENOMEM;
|
|
abox_iommu_unmap(dev_abox, abox_dma_iova(data));
|
|
ret = abox_iommu_map(dev_abox, abox_dma_iova(data), dump->addr,
|
|
dump->bytes, dump->area);
|
|
|
|
reg = DMA_REG_BUF_STR;
|
|
mask = ABOX_DMA_BUF_STR_MASK;
|
|
val = iova;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_BUF_END;
|
|
mask = ABOX_DMA_BUF_END_MASK;
|
|
val = iova + dump->bytes;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_BUF_OFFSET;
|
|
mask = ABOX_DMA_BUF_OFFSET_MASK;
|
|
val = ABOX_DMA_DUMP_OFFSET;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
reg = DMA_REG_STR_POINT;
|
|
mask = ABOX_DMA_STR_POINT_MASK;
|
|
val = iova;
|
|
ret = snd_soc_component_update_bits_async(cmpnt, reg, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
snd_soc_component_async_complete(cmpnt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void abox_dma_dump_free_buffer(struct abox_dma_data *data)
|
|
{
|
|
struct abox_dma_dump *dump = data->dump;
|
|
struct device *dev = data->dev;
|
|
struct device *dev_abox = data->abox_data->dev;
|
|
struct snd_dma_buffer *dmab = &data->dmab;
|
|
unsigned int size = ABOX_DMA_DUMP_BUFFER_SIZE;
|
|
unsigned int iova = abox_dma_iova(data);
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (dump->area) {
|
|
abox_iommu_unmap(dev_abox, iova);
|
|
dma_free_coherent(dev, size, dump->area, dump->addr);
|
|
abox_iommu_map(dev_abox, iova, dmab->addr, dmab->bytes,
|
|
dmab->area);
|
|
dump->area = NULL;
|
|
}
|
|
}
|
|
|
|
static bool abox_dma_dump_started(struct abox_dma_data *data)
|
|
{
|
|
unsigned int val;
|
|
|
|
val = snd_soc_component_read(data->cmpnt, DMA_REG_CTRL);
|
|
|
|
return (val & ABOX_DMA_ENABLE_MASK);
|
|
}
|
|
|
|
static int abox_dma_dump_stop(struct abox_dma_data *data)
|
|
{
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_ENABLE_MASK, 0 << ABOX_DMA_ENABLE_L);
|
|
abox_dma_barrier(data->dev, data, 0);
|
|
|
|
data->dump->updated = true;
|
|
wake_up_interruptible(&data->dump->waitqueue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_dump_start(struct abox_dma_data *data)
|
|
{
|
|
int ret;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
/* restart if it has started already */
|
|
if (abox_dma_dump_started(data))
|
|
abox_dma_dump_stop(data);
|
|
|
|
abox_dma_acquire_irq_all(data);
|
|
ret = abox_dma_dump_set_format(data);
|
|
if (ret < 0)
|
|
return ret;
|
|
data->dump->pointer = 0;
|
|
ret = snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL,
|
|
ABOX_DMA_ENABLE_MASK, 1 << ABOX_DMA_ENABLE_L);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_dump_file_notify(void *priv, bool enabled)
|
|
{
|
|
struct abox_dma_data *data = priv;
|
|
int ret;
|
|
|
|
abox_dbg(data->dev, "%s(%d)\n", __func__, enabled);
|
|
|
|
if (enabled)
|
|
ret = abox_dma_dump_start(data);
|
|
else
|
|
ret = abox_dma_dump_stop(data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int abox_dma_dump_register_event_notifier(struct abox_dma_data *data)
|
|
{
|
|
struct abox_data *abox_data = data->abox_data;
|
|
enum abox_widget w;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
if (!data->of_data->get_src_widget)
|
|
return 0;
|
|
|
|
w = data->of_data->get_src_widget(data);
|
|
if (w < 0)
|
|
return w;
|
|
|
|
abox_cmpnt_register_event_notifier(abox_data, w,
|
|
abox_dma_dump_file_notify, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_dma_dump_unregister_event_notifier(struct abox_dma_data *data)
|
|
{
|
|
struct abox_data *abox_data = data->abox_data;
|
|
enum abox_widget w;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
if (!data->of_data->get_src_widget)
|
|
return 0;
|
|
|
|
w = data->of_data->get_src_widget(data);
|
|
if (w < 0)
|
|
return w;
|
|
|
|
abox_cmpnt_unregister_event_notifier(abox_data, w);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t abox_dma_dump_file_read(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct abox_dma_data *data = file->private_data;
|
|
struct abox_dma_dump *dump = data->dump;
|
|
struct device *dev = data->dev;
|
|
size_t pointer, size, total, last;
|
|
int ret;
|
|
|
|
do {
|
|
pointer = abox_dma_read_pointer(data);
|
|
if (pointer < dump->pointer)
|
|
size = pointer + dump->bytes - dump->pointer;
|
|
else
|
|
size = pointer - dump->pointer;
|
|
|
|
if (size < count) {
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return -EAGAIN;
|
|
|
|
if (abox_dma_progress(data->cmpnt)) {
|
|
fsleep(1);
|
|
continue;
|
|
}
|
|
|
|
if (size && !abox_dma_dump_started(data))
|
|
break;
|
|
|
|
dump->updated = false;
|
|
ret = wait_event_interruptible(dump->waitqueue,
|
|
dump->updated);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
} while (size < count);
|
|
|
|
total = size = min(size, count);
|
|
abox_dbg(dev, "%s: %#zx, %#zx, %#zx, %#zx\n", __func__, count,
|
|
dump->pointer, pointer, size);
|
|
|
|
if (size > dump->bytes - dump->pointer) {
|
|
last = dump->bytes - dump->pointer;
|
|
if (copy_to_user(buffer, dump->area + dump->pointer, last))
|
|
return -EFAULT;
|
|
dump->pointer = 0;
|
|
buffer += last;
|
|
size -= last;
|
|
}
|
|
|
|
if (copy_to_user(buffer, dump->area + dump->pointer, size))
|
|
return -EFAULT;
|
|
dump->pointer += size;
|
|
dump->pointer %= dump->bytes;
|
|
|
|
return total;
|
|
}
|
|
|
|
static irqreturn_t abox_dma_dump_file_buf_done(int irq, void *dev_id)
|
|
{
|
|
struct device *dev = dev_id;
|
|
struct abox_dma_data *data = dev_get_drvdata(dev);
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, irq);
|
|
|
|
if (data->dump) {
|
|
data->dump->updated = true;
|
|
wake_up_interruptible(&data->dump->waitqueue);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int abox_dma_dump_file_open(struct inode *i, struct file *f)
|
|
{
|
|
struct abox_dma_data *data = abox_dump_get_data(f);
|
|
struct abox_data *abox_data = data->abox_data;
|
|
struct device *dev = data->dev;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (atomic_cmpxchg(&data->dump->open_state, 0, 1)) {
|
|
abox_err(dev, "already opened\n");
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
pm_runtime_get_sync(dev);
|
|
abox_wait_for_boot(abox_data, abox_get_waiting_jiffies(true));
|
|
abox_gic_register_irq_handler(abox_data->dev_gic,
|
|
abox_dma_get_irq(data, DMA_IRQ_BUF_DONE),
|
|
abox_dma_dump_file_buf_done, dev);
|
|
f->private_data = data;
|
|
ret = abox_dma_dump_register_event_notifier(data);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = abox_dma_dump_set_buffer(data);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = abox_dma_dump_start(data);
|
|
out:
|
|
if (ret < 0)
|
|
atomic_set(&data->dump->open_state, 0);
|
|
return ret;
|
|
}
|
|
|
|
static int abox_dma_dump_file_release(struct inode *i, struct file *f)
|
|
{
|
|
struct abox_dma_data *data = f->private_data;
|
|
struct device *dev = data->dev;
|
|
int ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
ret = abox_dma_dump_stop(data);
|
|
abox_dma_dump_free_buffer(data);
|
|
abox_dma_dump_unregister_event_notifier(data);
|
|
abox_dma_release_irq_all(data);
|
|
abox_gic_register_irq_handler(data->abox_data->dev_gic,
|
|
abox_dma_get_irq(data, DMA_IRQ_BUF_DONE),
|
|
abox_dma_irq_handlers[DMA_IRQ_BUF_DONE], dev);
|
|
pm_runtime_put(dev);
|
|
atomic_cmpxchg(&data->dump->open_state, 1, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int abox_dma_dump_file_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct abox_dma_data *data = file->private_data;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
poll_wait(file, &data->dump->waitqueue, wait);
|
|
return POLLIN | POLLRDNORM;
|
|
}
|
|
|
|
static const struct proc_ops abox_dma_dump_fops = {
|
|
.proc_lseek = generic_file_llseek,
|
|
.proc_read = abox_dma_dump_file_read,
|
|
.proc_poll = abox_dma_dump_file_poll,
|
|
.proc_open = abox_dma_dump_file_open,
|
|
.proc_release = abox_dma_dump_file_release,
|
|
};
|
|
|
|
static int abox_dma_dump_pcm_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai);
|
|
struct device *dev = data->dev;
|
|
|
|
data->dump = devm_kzalloc(dev, sizeof(*data->dump), GFP_KERNEL);
|
|
if (!data->dump)
|
|
return -ENOMEM;
|
|
atomic_set(&data->dump->open_state, 0);
|
|
data->dump->file = abox_dump_register_file(data->dai_drv->name, data,
|
|
&abox_dma_dump_fops);
|
|
init_waitqueue_head(&data->dump->waitqueue);
|
|
return abox_dma_pcm_new(component, rtd);
|
|
}
|
|
|
|
static void abox_dma_dump_pcm_free(struct snd_soc_component *component,
|
|
struct snd_pcm *pcm)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_chip(pcm);
|
|
struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai);
|
|
struct device *dev = data->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (!IS_ERR_OR_NULL(data->dump->file))
|
|
abox_dump_unregister_file(data->dump->file);
|
|
devm_kfree(dev, data->dump);
|
|
abox_dma_pcm_free(component, pcm);
|
|
}
|
|
|
|
static enum abox_irq abox_ddma_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_FULL:
|
|
ret = IRQ_WDMA_DBG0_BUF_FULL + id;
|
|
break;
|
|
case DMA_IRQ_FADE_DONE:
|
|
ret = IRQ_WDMA_DBG0_FADE_DONE + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_WDMA_DBG0_ERR + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_ddma_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_DDMA0 + id;
|
|
}
|
|
|
|
static char *abox_ddma_get_dai_name(struct device *dev, enum abox_dma_dai dai,
|
|
int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "DBG%d", id);
|
|
}
|
|
|
|
static enum abox_widget abox_ddma_get_src_widget(struct abox_dma_data *data)
|
|
{
|
|
enum abox_widget w;
|
|
unsigned int val;
|
|
|
|
val = snd_soc_component_read(data->cmpnt, DMA_REG_CTRL);
|
|
val = (val & ABOX_DMA_DEBUG_SRC_MASK) >> ABOX_DMA_DEBUG_SRC_L;
|
|
switch (val) {
|
|
case 0x0 ... 0xb:
|
|
w = ABOX_WIDGET_SPUS_IN0 + val - 0x0;
|
|
break;
|
|
case 0x10 ... 0x17:
|
|
w = ABOX_WIDGET_SPUS_ASRC0 + val - 0x10;
|
|
break;
|
|
case 0x18:
|
|
w = ABOX_WIDGET_SIFS0 + val - 0x18;
|
|
break;
|
|
case 0x20 ... 0x27:
|
|
w = ABOX_WIDGET_NSRC0 + val - 0x20;
|
|
break;
|
|
case 0x30 ... 0x33:
|
|
w = ABOX_WIDGET_SPUM_ASRC0 + val - 0x30;
|
|
break;
|
|
default:
|
|
w = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
const static struct snd_soc_dai_driver abox_ddma_dai_drv = {
|
|
.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 char * const abox_ddma_src_enum_texts[] = {
|
|
"RDMA0", "RDMA1", "RDMA2", "RDMA3", "RDMA4", "RDMA5",
|
|
"RDMA6", "RDMA7", "RDMA8", "RDMA9", "RDMA10", "RDMA11",
|
|
"SPUS_ASRC0", "SPUS_ASRC1", "SPUS_ASRC2", "SPUS_ASRC3", "SPUS_ASRC4",
|
|
"SPUS_ASRC5", "SPUS_ASRC6", "SPUS_ASRC7", "SPUS_MIXER",
|
|
"SPUM_SIFM0", "SPUM_SIFM1", "SPUM_SIFM2", "SPUM_SIFM3",
|
|
"SPUM_SIFM4", "SPUM_SIFM5", "SPUM_SIFM6", "SPUM_SIFM7",
|
|
"SPUM_ASRC0", "SPUM_ASRC1", "SPUM_ASRC2", "SPUM_ASRC3",
|
|
};
|
|
|
|
static const unsigned int abox_ddma_src_enum_values[] = {
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
|
|
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
|
0x10, 0x11, 0x12, 0x13, 0x14,
|
|
0x15, 0x16, 0x17, 0x18,
|
|
0x20, 0x21, 0x22, 0x23,
|
|
0x24, 0x25, 0x26, 0x27,
|
|
0x30, 0x31, 0x32, 0x33,
|
|
};
|
|
|
|
static const struct soc_enum abox_ddma_src_enum[] = {
|
|
SOC_VALUE_ENUM_SINGLE(DMA_REG_CTRL, ABOX_DMA_DEBUG_SRC_L,
|
|
ABOX_DMA_DEBUG_SRC_MASK >> ABOX_DMA_DEBUG_SRC_L,
|
|
ARRAY_SIZE(abox_ddma_src_enum_texts),
|
|
abox_ddma_src_enum_texts, abox_ddma_src_enum_values),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new abox_ddma_controls[] = {
|
|
SOC_ENUM("SRC", abox_ddma_src_enum),
|
|
SOC_ENUM_EXT("Func", abox_dma_func_enum,
|
|
snd_soc_get_enum_double, abox_dma_func_put),
|
|
SOC_SINGLE_EXT("Auto Fade In", DMA_REG_CTRL,
|
|
ABOX_DMA_AUTO_FADE_IN_L, 1, 0,
|
|
abox_dma_auto_fade_in_get, abox_dma_auto_fade_in_put),
|
|
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 unsigned int abox_ddma_read(struct snd_soc_component *cmpnt,
|
|
unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_WDMA_DEBUG_CTRL(data->id);
|
|
|
|
return abox_dma_read(cmpnt, base, reg);
|
|
}
|
|
|
|
static int abox_ddma_write(struct snd_soc_component *cmpnt,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_WDMA_DEBUG_CTRL(data->id);
|
|
|
|
return abox_dma_write(cmpnt, base, reg, val);
|
|
}
|
|
|
|
const static struct snd_soc_component_driver abox_ddma = {
|
|
.controls = abox_ddma_controls,
|
|
.num_controls = ARRAY_SIZE(abox_ddma_controls),
|
|
.probe = abox_dma_probe,
|
|
.remove = abox_dma_remove,
|
|
.read = abox_ddma_read,
|
|
.write = abox_ddma_write,
|
|
.pcm_construct = abox_dma_dump_pcm_new,
|
|
.pcm_destruct = abox_dma_dump_pcm_free,
|
|
.open = abox_dma_open,
|
|
.close = abox_dma_close,
|
|
.hw_params = abox_dma_hw_params,
|
|
.hw_free = abox_dma_hw_free,
|
|
.prepare = abox_dma_prepare,
|
|
.trigger = abox_dma_trigger,
|
|
.pointer = abox_dma_pointer,
|
|
.mmap = abox_dma_mmap,
|
|
};
|
|
|
|
static enum abox_irq abox_udma_wr_dbg_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_WR_DBG0_BUF_FULL + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_UDMA_WR_DBG0_ERR + id;
|
|
break;
|
|
case DMA_IRQ_FADE_DONE:
|
|
ret = IRQ_UDMA_WR_DBG0_FADE_DONE + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_udma_wr_dbg_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_UDMA_WR_DBG0 + id;
|
|
}
|
|
|
|
static char *abox_udma_wr_dbg_get_dai_name(struct device *dev,
|
|
enum abox_dma_dai dai, int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "UDMA DBG%d", id);
|
|
}
|
|
|
|
static enum abox_widget abox_udma_wr_dbg_get_src_widget(
|
|
struct abox_dma_data *data)
|
|
{
|
|
enum abox_widget w;
|
|
unsigned int val;
|
|
|
|
val = snd_soc_component_read(data->cmpnt, DMA_REG_CTRL);
|
|
val = (val & ABOX_DMA_DEBUG_SRC_MASK) >> ABOX_DMA_DEBUG_SRC_L;
|
|
switch (val) {
|
|
case 0x20 ... 0x21:
|
|
w = ABOX_WIDGET_UDMA_RD0 + val - 0x20;
|
|
break;
|
|
default:
|
|
w = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
static const char * const abox_udma_wr_dbg_src_enum_texts[] = {
|
|
"UDMA_RD0", "UDMA_RD1",
|
|
};
|
|
|
|
static const unsigned int abox_udma_wr_dbg_src_enum_values[] = {
|
|
0x20, 0x21,
|
|
};
|
|
|
|
static const struct soc_enum abox_udma_wr_dbg_src_enum[] = {
|
|
SOC_VALUE_ENUM_SINGLE(DMA_REG_CTRL, ABOX_DMA_DEBUG_SRC_L,
|
|
ABOX_DMA_DEBUG_SRC_MASK >> ABOX_DMA_DEBUG_SRC_L,
|
|
ARRAY_SIZE(abox_udma_wr_dbg_src_enum_texts),
|
|
abox_udma_wr_dbg_src_enum_texts,
|
|
abox_udma_wr_dbg_src_enum_values),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new abox_udma_wr_dbg_controls[] = {
|
|
SOC_ENUM("SRC", abox_udma_wr_dbg_src_enum),
|
|
SOC_ENUM_EXT("Func", abox_dma_func_enum,
|
|
snd_soc_get_enum_double, abox_dma_func_put),
|
|
SOC_SINGLE_EXT("Auto Fade In", DMA_REG_CTRL,
|
|
ABOX_DMA_AUTO_FADE_IN_L, 1, 0,
|
|
abox_dma_auto_fade_in_get, abox_dma_auto_fade_in_put),
|
|
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 unsigned int abox_udma_wr_dbg_read(struct snd_soc_component *cmpnt,
|
|
unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_UDMA_WR_DEBUG_CTRL(data->id);
|
|
|
|
return abox_dma_read(cmpnt, base, reg);
|
|
}
|
|
|
|
static int abox_udma_wr_dbg_write(struct snd_soc_component *cmpnt,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_UDMA_WR_DEBUG_CTRL(data->id);
|
|
|
|
return abox_dma_write(cmpnt, base, reg, val);
|
|
}
|
|
|
|
const static struct snd_soc_component_driver abox_udma_wr_dbg = {
|
|
.controls = abox_udma_wr_dbg_controls,
|
|
.num_controls = ARRAY_SIZE(abox_udma_wr_dbg_controls),
|
|
.probe = abox_dma_probe,
|
|
.remove = abox_dma_remove,
|
|
.read = abox_udma_wr_dbg_read,
|
|
.write = abox_udma_wr_dbg_write,
|
|
.pcm_construct = abox_dma_dump_pcm_new,
|
|
.pcm_destruct = abox_dma_dump_pcm_free,
|
|
.open = abox_dma_open,
|
|
.close = abox_dma_close,
|
|
.hw_params = abox_dma_hw_params,
|
|
.hw_free = abox_dma_hw_free,
|
|
.prepare = abox_dma_prepare,
|
|
.trigger = abox_dma_trigger,
|
|
.pointer = abox_dma_pointer,
|
|
.mmap = abox_dma_mmap,
|
|
};
|
|
|
|
static enum abox_irq abox_dual_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_FULL:
|
|
ret = IRQ_WDMA0_DUAL_BUF_FULL + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_WDMA0_DUAL_ERR + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_dual_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_WDMA0_DUAL + id;
|
|
}
|
|
|
|
static char *abox_dual_get_dai_name(struct device *dev, enum abox_dma_dai dai,
|
|
int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "WDMA%d DUAL", id);
|
|
}
|
|
|
|
const static struct snd_soc_dai_driver abox_dual_dai_drv = {
|
|
.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 unsigned int abox_dual_read(struct snd_soc_component *cmpnt,
|
|
unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_WDMA_DUAL_CTRL(data->id);
|
|
|
|
return abox_dma_read(cmpnt, base, reg);
|
|
}
|
|
|
|
static int abox_dual_write(struct snd_soc_component *cmpnt,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_WDMA_DUAL_CTRL(data->id);
|
|
|
|
return abox_dma_write(cmpnt, base, reg, val);
|
|
}
|
|
|
|
const static struct snd_soc_component_driver abox_dual = {
|
|
.probe = abox_dma_probe,
|
|
.remove = abox_dma_remove,
|
|
.read = abox_dual_read,
|
|
.write = abox_dual_write,
|
|
.pcm_construct = abox_dma_dump_pcm_new,
|
|
.pcm_destruct = abox_dma_dump_pcm_free,
|
|
.open = abox_dma_open,
|
|
.close = abox_dma_close,
|
|
.hw_params = abox_dma_hw_params,
|
|
.hw_free = abox_dma_hw_free,
|
|
.prepare = abox_dma_prepare,
|
|
.trigger = abox_dma_trigger,
|
|
.pointer = abox_dma_pointer,
|
|
.mmap = abox_dma_mmap,
|
|
};
|
|
|
|
static enum abox_irq abox_udma_wr_dual_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_DUAL_BUF_FULL + id;
|
|
break;
|
|
case DMA_IRQ_ERR:
|
|
ret = IRQ_UDMA_WR0_DUAL_ERR + id;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum abox_dai abox_udma_wr_dual_get_dai_id(enum abox_dma_dai dai, int id)
|
|
{
|
|
return ABOX_UDMA_WR0_DUAL + id;
|
|
}
|
|
|
|
static char *abox_udma_wr_dual_get_dai_name(struct device *dev,
|
|
enum abox_dma_dai dai, int id)
|
|
{
|
|
return devm_kasprintf(dev, GFP_KERNEL, "UDMA WR%d DUAL", id);
|
|
}
|
|
|
|
static enum abox_widget abox_udma_wr_dual_get_src_widget(
|
|
struct abox_dma_data *data)
|
|
{
|
|
return ABOX_WIDGET_UDMA_WR0 + data->id;
|
|
}
|
|
|
|
static unsigned int abox_udma_wr_dual_read(struct snd_soc_component *cmpnt,
|
|
unsigned int reg)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_UDMA_WR_DUAL_CTRL(data->id);
|
|
|
|
return abox_dma_read(cmpnt, base, reg);
|
|
}
|
|
|
|
static int abox_udma_wr_dual_write(struct snd_soc_component *cmpnt,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt);
|
|
unsigned int base = ABOX_UDMA_WR_DUAL_CTRL(data->id);
|
|
|
|
return abox_dma_write(cmpnt, base, reg, val);
|
|
}
|
|
|
|
const static struct snd_soc_component_driver abox_udma_wr_dual = {
|
|
.probe = abox_dma_probe,
|
|
.remove = abox_dma_remove,
|
|
.read = abox_udma_wr_dual_read,
|
|
.write = abox_udma_wr_dual_write,
|
|
.pcm_construct = abox_dma_dump_pcm_new,
|
|
.pcm_destruct = abox_dma_dump_pcm_free,
|
|
.open = abox_dma_open,
|
|
.close = abox_dma_close,
|
|
.hw_params = abox_dma_hw_params,
|
|
.hw_free = abox_dma_hw_free,
|
|
.prepare = abox_dma_prepare,
|
|
.trigger = abox_dma_trigger,
|
|
.pointer = abox_dma_pointer,
|
|
.mmap = abox_dma_mmap,
|
|
};
|
|
|
|
static const struct of_device_id samsung_abox_dma_match[] = {
|
|
{
|
|
.compatible = "samsung,abox-ddma",
|
|
.data = (void *)&(struct abox_dma_of_data){
|
|
.get_irq = abox_ddma_get_irq,
|
|
.get_dai_id = abox_ddma_get_dai_id,
|
|
.get_dai_name = abox_ddma_get_dai_name,
|
|
.get_src_widget = abox_ddma_get_src_widget,
|
|
.dai_drv = &abox_ddma_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_ddma,
|
|
},
|
|
},
|
|
{
|
|
.compatible = "samsung,abox-dual",
|
|
.data = (void *)&(struct abox_dma_of_data){
|
|
.get_irq = abox_dual_get_irq,
|
|
.get_dai_id = abox_dual_get_dai_id,
|
|
.get_dai_name = abox_dual_get_dai_name,
|
|
.dai_drv = &abox_dual_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_dual,
|
|
},
|
|
},
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_ABOX_UDMA)
|
|
{
|
|
.compatible = "samsung,abox-udma-rd",
|
|
.data = &abox_udma_rd_of_data,
|
|
},
|
|
{
|
|
.compatible = "samsung,abox-udma-wr",
|
|
.data = &abox_udma_wr_of_data,
|
|
},
|
|
{
|
|
.compatible = "samsung,abox-udma-wr-dual",
|
|
.data = (void *)&(struct abox_dma_of_data){
|
|
.get_irq = abox_udma_wr_dual_get_irq,
|
|
.get_dai_id = abox_udma_wr_dual_get_dai_id,
|
|
.get_dai_name = abox_udma_wr_dual_get_dai_name,
|
|
.get_src_widget = abox_udma_wr_dual_get_src_widget,
|
|
.dai_drv = &abox_dual_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_udma_wr_dual,
|
|
},
|
|
},
|
|
{
|
|
.compatible = "samsung,abox-udma-wr-debug",
|
|
.data = (void *)&(struct abox_dma_of_data){
|
|
.get_irq = abox_udma_wr_dbg_get_irq,
|
|
.get_dai_id = abox_udma_wr_dbg_get_dai_id,
|
|
.get_dai_name = abox_udma_wr_dbg_get_dai_name,
|
|
.get_src_widget = abox_udma_wr_dbg_get_src_widget,
|
|
.dai_drv = &abox_ddma_dai_drv,
|
|
.num_dai = 1,
|
|
.cmpnt_drv = &abox_udma_wr_dbg,
|
|
},
|
|
},
|
|
#endif
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, samsung_abox_dma_match);
|
|
|
|
static int samsung_abox_dma_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device *dev_abox = dev->parent;
|
|
struct abox_data *abox_data = dev_get_drvdata(dev_abox);
|
|
struct device *dev_gic = abox_data->dev_gic;
|
|
struct device_node *np = dev->of_node;
|
|
struct abox_dma_data *data;
|
|
const struct abox_dma_of_data *of_data;
|
|
const char *type;
|
|
u32 value;
|
|
int i, ret;
|
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, data);
|
|
data->dev = dev;
|
|
data->dev_abox = dev_abox;
|
|
data->abox_data = abox_data;
|
|
init_completion(&data->func_changed);
|
|
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
|
|
|
|
data->sfr_base = devm_get_ioremap(pdev, "sfr", &data->sfr_phys, NULL);
|
|
if (IS_ERR(data->sfr_base))
|
|
return PTR_ERR(data->sfr_base);
|
|
|
|
ret = of_samsung_property_read_u32(dev, np, "id", &data->id);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = of_samsung_property_read_u32(dev, np, "buffer_bytes", &value);
|
|
if (ret < 0)
|
|
value = 0;
|
|
data->dmab.bytes = value;
|
|
|
|
ret = of_samsung_property_read_string(dev, np, "buffer_type", &type);
|
|
if (ret < 0)
|
|
type = "";
|
|
if (!strncmp(type, "ion", sizeof("ion")))
|
|
data->buf_type = BUFFER_TYPE_ION;
|
|
else if (!strncmp(type, "dma", sizeof("dma")))
|
|
data->buf_type = BUFFER_TYPE_DMA;
|
|
else if (!strncmp(type, "ram", sizeof("ram")))
|
|
data->buf_type = BUFFER_TYPE_RAM;
|
|
else
|
|
data->buf_type = BUFFER_TYPE_DMA;
|
|
|
|
of_data = data->of_data = of_device_get_match_data(dev);
|
|
|
|
for (i = DMA_IRQ_BUF_DONE; i < DMA_IRQ_COUNT; i++) {
|
|
enum abox_irq irq = abox_dma_get_irq(data, i);
|
|
|
|
if (irq < 0 || irq > IRQ_COUNT)
|
|
continue;
|
|
|
|
ret = abox_gic_register_irq_handler(dev_gic, irq,
|
|
abox_dma_irq_handlers[i], dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
data->num_dai = of_data->num_dai;
|
|
data->dai_drv = devm_kmemdup(dev, of_data->dai_drv,
|
|
sizeof(*of_data->dai_drv) * data->num_dai,
|
|
GFP_KERNEL);
|
|
if (!data->dai_drv)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < data->num_dai; i++) {
|
|
data->dai_drv[i].id = of_data->get_dai_id(i, data->id);
|
|
data->dai_drv[i].name = of_data->get_dai_name(dev, i, data->id);
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(dev, of_data->cmpnt_drv,
|
|
data->dai_drv, data->num_dai);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pm_runtime_no_callbacks(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int samsung_abox_dma_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void samsung_abox_dma_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
pm_runtime_disable(dev);
|
|
}
|
|
|
|
struct platform_driver samsung_abox_dma_driver = {
|
|
.probe = samsung_abox_dma_probe,
|
|
.remove = samsung_abox_dma_remove,
|
|
.shutdown = samsung_abox_dma_shutdown,
|
|
.driver = {
|
|
.name = "abox-dma",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(samsung_abox_dma_match),
|
|
},
|
|
};
|