mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-15 06:55:13 +08:00
1587a061ef
For Spreadtrum audio platform driver, it need allocate a larger DMA buffer dynamically to copy audio data between userspace and kernel space, but that will increase the risk of memory allocation failure especially the system is under heavy load situation. To make sure the audio can work in this scenario, we usually reserve one region of memory to be used as a shared pool of DMA buffers for the platform component. So add of_reserved_mem_device_init_by_idx() function to initialize the shared pool of DMA buffers to be used by the platform component. Signed-off-by: Baolin Wang <baolin.wang@linaro.org> Signed-off-by: Mark Brown <broonie@kernel.org>
570 lines
15 KiB
C
570 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2019 Spreadtrum Communications Inc.
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/dma/sprd-dma.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/platform_device.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include "sprd-pcm-dma.h"
|
|
|
|
#define SPRD_PCM_DMA_LINKLIST_SIZE 64
|
|
#define SPRD_PCM_DMA_BRUST_LEN 640
|
|
|
|
struct sprd_pcm_dma_data {
|
|
struct dma_chan *chan;
|
|
struct dma_async_tx_descriptor *desc;
|
|
dma_cookie_t cookie;
|
|
dma_addr_t phys;
|
|
void *virt;
|
|
int pre_pointer;
|
|
};
|
|
|
|
struct sprd_pcm_dma_private {
|
|
struct snd_pcm_substream *substream;
|
|
struct sprd_pcm_dma_params *params;
|
|
struct sprd_pcm_dma_data data[SPRD_PCM_CHANNEL_MAX];
|
|
int hw_chan;
|
|
int dma_addr_offset;
|
|
};
|
|
|
|
static const struct snd_pcm_hardware sprd_pcm_hardware = {
|
|
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
|
|
.period_bytes_min = 1,
|
|
.period_bytes_max = 64 * 1024,
|
|
.periods_min = 1,
|
|
.periods_max = PAGE_SIZE / SPRD_PCM_DMA_LINKLIST_SIZE,
|
|
.buffer_bytes_max = 64 * 1024,
|
|
};
|
|
|
|
static int sprd_pcm_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
struct sprd_pcm_dma_private *dma_private;
|
|
int hw_chan = SPRD_PCM_CHANNEL_MAX;
|
|
int size, ret, i;
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware);
|
|
|
|
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
SPRD_PCM_DMA_BRUST_LEN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
|
SPRD_PCM_DMA_BRUST_LEN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_pcm_hw_constraint_integer(runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dma_private = devm_kzalloc(dev, sizeof(*dma_private), GFP_KERNEL);
|
|
if (!dma_private)
|
|
return -ENOMEM;
|
|
|
|
size = runtime->hw.periods_max * SPRD_PCM_DMA_LINKLIST_SIZE;
|
|
|
|
for (i = 0; i < hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
data->virt = dmam_alloc_coherent(dev, size, &data->phys,
|
|
GFP_KERNEL);
|
|
if (!data->virt) {
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
dma_private->hw_chan = hw_chan;
|
|
runtime->private_data = dma_private;
|
|
dma_private->substream = substream;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
for (i = 0; i < hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (data->virt)
|
|
dmam_free_coherent(dev, size, data->virt, data->phys);
|
|
}
|
|
|
|
devm_kfree(dev, dma_private);
|
|
return ret;
|
|
}
|
|
|
|
static int sprd_pcm_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct sprd_pcm_dma_private *dma_private = runtime->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
int size = runtime->hw.periods_max * SPRD_PCM_DMA_LINKLIST_SIZE;
|
|
int i;
|
|
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
dmam_free_coherent(dev, size, data->virt, data->phys);
|
|
}
|
|
|
|
devm_kfree(dev, dma_private);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sprd_pcm_dma_complete(void *data)
|
|
{
|
|
struct sprd_pcm_dma_private *dma_private = data;
|
|
struct snd_pcm_substream *substream = dma_private->substream;
|
|
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
|
|
static void sprd_pcm_release_dma_channel(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct sprd_pcm_dma_private *dma_private = runtime->private_data;
|
|
int i;
|
|
|
|
for (i = 0; i < SPRD_PCM_CHANNEL_MAX; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (data->chan) {
|
|
dma_release_channel(data->chan);
|
|
data->chan = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sprd_pcm_request_dma_channel(struct snd_pcm_substream *substream,
|
|
int channels)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct sprd_pcm_dma_private *dma_private = runtime->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
struct sprd_pcm_dma_params *dma_params = dma_private->params;
|
|
int i;
|
|
|
|
if (channels > SPRD_PCM_CHANNEL_MAX) {
|
|
dev_err(dev, "invalid dma channel number:%d\n", channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < channels; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
data->chan = dma_request_slave_channel(dev,
|
|
dma_params->chan_name[i]);
|
|
if (!data->chan) {
|
|
dev_err(dev, "failed to request dma channel:%s\n",
|
|
dma_params->chan_name[i]);
|
|
sprd_pcm_release_dma_channel(substream);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct sprd_pcm_dma_private *dma_private = runtime->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct sprd_pcm_dma_params *dma_params;
|
|
size_t totsize = params_buffer_bytes(params);
|
|
size_t period = params_period_bytes(params);
|
|
int channels = params_channels(params);
|
|
int is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
struct scatterlist *sg;
|
|
unsigned long flags;
|
|
int ret, i, j, sg_num;
|
|
|
|
dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
|
if (!dma_params) {
|
|
dev_warn(component->dev, "no dma parameters setting\n");
|
|
dma_private->params = NULL;
|
|
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
|
runtime->dma_bytes = totsize;
|
|
return 0;
|
|
}
|
|
|
|
if (!dma_private->params) {
|
|
dma_private->params = dma_params;
|
|
ret = sprd_pcm_request_dma_channel(substream, channels);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
|
|
|
runtime->dma_bytes = totsize;
|
|
sg_num = totsize / period;
|
|
dma_private->dma_addr_offset = totsize / channels;
|
|
|
|
sg = devm_kcalloc(component->dev, sg_num, sizeof(*sg), GFP_KERNEL);
|
|
if (!sg) {
|
|
ret = -ENOMEM;
|
|
goto sg_err;
|
|
}
|
|
|
|
for (i = 0; i < channels; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
struct dma_chan *chan = data->chan;
|
|
struct dma_slave_config config = { };
|
|
struct sprd_dma_linklist link = { };
|
|
enum dma_transfer_direction dir;
|
|
struct scatterlist *sgt = sg;
|
|
|
|
config.src_maxburst = dma_params->fragment_len[i];
|
|
config.src_addr_width = dma_params->datawidth[i];
|
|
config.dst_addr_width = dma_params->datawidth[i];
|
|
if (is_playback) {
|
|
config.src_addr = runtime->dma_addr +
|
|
i * dma_private->dma_addr_offset;
|
|
config.dst_addr = dma_params->dev_phys[i];
|
|
dir = DMA_MEM_TO_DEV;
|
|
} else {
|
|
config.src_addr = dma_params->dev_phys[i];
|
|
config.dst_addr = runtime->dma_addr +
|
|
i * dma_private->dma_addr_offset;
|
|
dir = DMA_DEV_TO_MEM;
|
|
}
|
|
|
|
sg_init_table(sgt, sg_num);
|
|
for (j = 0; j < sg_num; j++, sgt++) {
|
|
u32 sg_len = period / channels;
|
|
|
|
sg_dma_len(sgt) = sg_len;
|
|
sg_dma_address(sgt) = runtime->dma_addr +
|
|
i * dma_private->dma_addr_offset + sg_len * j;
|
|
}
|
|
|
|
/*
|
|
* Configure the link-list address for the DMA engine link-list
|
|
* mode.
|
|
*/
|
|
link.virt_addr = (unsigned long)data->virt;
|
|
link.phy_addr = data->phys;
|
|
|
|
ret = dmaengine_slave_config(chan, &config);
|
|
if (ret) {
|
|
dev_err(component->dev,
|
|
"failed to set slave configuration: %d\n", ret);
|
|
goto config_err;
|
|
}
|
|
|
|
/*
|
|
* We configure the DMA request mode, interrupt mode, channel
|
|
* mode and channel trigger mode by the flags.
|
|
*/
|
|
flags = SPRD_DMA_FLAGS(SPRD_DMA_CHN_MODE_NONE, SPRD_DMA_NO_TRG,
|
|
SPRD_DMA_FRAG_REQ, SPRD_DMA_TRANS_INT);
|
|
data->desc = chan->device->device_prep_slave_sg(chan, sg,
|
|
sg_num, dir,
|
|
flags, &link);
|
|
if (!data->desc) {
|
|
dev_err(component->dev, "failed to prepare slave sg\n");
|
|
ret = -ENOMEM;
|
|
goto config_err;
|
|
}
|
|
|
|
if (!runtime->no_period_wakeup) {
|
|
data->desc->callback = sprd_pcm_dma_complete;
|
|
data->desc->callback_param = dma_private;
|
|
}
|
|
}
|
|
|
|
devm_kfree(component->dev, sg);
|
|
|
|
return 0;
|
|
|
|
config_err:
|
|
devm_kfree(component->dev, sg);
|
|
sg_err:
|
|
sprd_pcm_release_dma_channel(substream);
|
|
return ret;
|
|
}
|
|
|
|
static int sprd_pcm_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
snd_pcm_set_runtime_buffer(substream, NULL);
|
|
sprd_pcm_release_dma_channel(substream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct sprd_pcm_dma_private *dma_private =
|
|
substream->runtime->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
int ret = 0, i;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (!data->desc)
|
|
continue;
|
|
|
|
data->cookie = dmaengine_submit(data->desc);
|
|
ret = dma_submit_error(data->cookie);
|
|
if (ret) {
|
|
dev_err(component->dev,
|
|
"failed to submit dma request: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
dma_async_issue_pending(data->chan);
|
|
}
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (data->chan)
|
|
dmaengine_resume(data->chan);
|
|
}
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (data->chan)
|
|
dmaengine_terminate_async(data->chan);
|
|
}
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (data->chan)
|
|
dmaengine_pause(data->chan);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct sprd_pcm_dma_private *dma_private = runtime->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
int pointer[SPRD_PCM_CHANNEL_MAX];
|
|
int bytes_of_pointer = 0, sel_max = 0, i;
|
|
snd_pcm_uframes_t x;
|
|
struct dma_tx_state state;
|
|
enum dma_status status;
|
|
|
|
for (i = 0; i < dma_private->hw_chan; i++) {
|
|
struct sprd_pcm_dma_data *data = &dma_private->data[i];
|
|
|
|
if (!data->chan)
|
|
continue;
|
|
|
|
status = dmaengine_tx_status(data->chan, data->cookie, &state);
|
|
if (status == DMA_ERROR) {
|
|
dev_err(component->dev,
|
|
"failed to get dma channel %d status\n", i);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We just get current transfer address from the DMA engine, so
|
|
* we need convert to current pointer.
|
|
*/
|
|
pointer[i] = state.residue - runtime->dma_addr -
|
|
i * dma_private->dma_addr_offset;
|
|
|
|
if (i == 0) {
|
|
bytes_of_pointer = pointer[i];
|
|
sel_max = pointer[i] < data->pre_pointer ? 1 : 0;
|
|
} else {
|
|
sel_max ^= pointer[i] < data->pre_pointer ? 1 : 0;
|
|
|
|
if (sel_max)
|
|
bytes_of_pointer =
|
|
max(pointer[i], pointer[i - 1]) << 1;
|
|
else
|
|
bytes_of_pointer =
|
|
min(pointer[i], pointer[i - 1]) << 1;
|
|
}
|
|
|
|
data->pre_pointer = pointer[i];
|
|
}
|
|
|
|
x = bytes_to_frames(runtime, bytes_of_pointer);
|
|
if (x == runtime->buffer_size)
|
|
x = 0;
|
|
|
|
return x;
|
|
}
|
|
|
|
static int sprd_pcm_mmap(struct snd_pcm_substream *substream,
|
|
struct vm_area_struct *vma)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
|
|
return remap_pfn_range(vma, vma->vm_start,
|
|
runtime->dma_addr >> PAGE_SHIFT,
|
|
vma->vm_end - vma->vm_start,
|
|
vma->vm_page_prot);
|
|
}
|
|
|
|
static struct snd_pcm_ops sprd_pcm_ops = {
|
|
.open = sprd_pcm_open,
|
|
.close = sprd_pcm_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = sprd_pcm_hw_params,
|
|
.hw_free = sprd_pcm_hw_free,
|
|
.trigger = sprd_pcm_trigger,
|
|
.pointer = sprd_pcm_pointer,
|
|
.mmap = sprd_pcm_mmap,
|
|
};
|
|
|
|
static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_card *card = rtd->card->snd_card;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
struct snd_pcm_substream *substream;
|
|
int ret;
|
|
|
|
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
|
if (ret)
|
|
return ret;
|
|
|
|
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
|
if (substream) {
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
|
|
sprd_pcm_hardware.buffer_bytes_max,
|
|
&substream->dma_buffer);
|
|
if (ret) {
|
|
dev_err(card->dev,
|
|
"can't alloc playback dma buffer: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
|
|
if (substream) {
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
|
|
sprd_pcm_hardware.buffer_bytes_max,
|
|
&substream->dma_buffer);
|
|
if (ret) {
|
|
dev_err(card->dev,
|
|
"can't alloc capture dma buffer: %d\n", ret);
|
|
snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sprd_pcm_free(struct snd_pcm *pcm)
|
|
{
|
|
struct snd_pcm_substream *substream;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
|
|
substream = pcm->streams[i].substream;
|
|
if (substream) {
|
|
snd_dma_free_pages(&substream->dma_buffer);
|
|
substream->dma_buffer.area = NULL;
|
|
substream->dma_buffer.addr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct snd_soc_component_driver sprd_soc_component = {
|
|
.name = DRV_NAME,
|
|
.ops = &sprd_pcm_ops,
|
|
.compr_ops = &sprd_platform_compr_ops,
|
|
.pcm_new = sprd_pcm_new,
|
|
.pcm_free = sprd_pcm_free,
|
|
};
|
|
|
|
static int sprd_soc_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret;
|
|
|
|
ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0);
|
|
if (ret)
|
|
dev_warn(&pdev->dev,
|
|
"no reserved DMA memory for audio platform device\n");
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component,
|
|
NULL, 0);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "could not register platform:%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id sprd_pcm_of_match[] = {
|
|
{ .compatible = "sprd,pcm-platform", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sprd_pcm_of_match);
|
|
|
|
static struct platform_driver sprd_pcm_driver = {
|
|
.driver = {
|
|
.name = "sprd-pcm-audio",
|
|
.of_match_table = sprd_pcm_of_match,
|
|
},
|
|
|
|
.probe = sprd_soc_platform_probe,
|
|
};
|
|
|
|
module_platform_driver(sprd_pcm_driver);
|
|
|
|
MODULE_DESCRIPTION("Spreadtrum ASoC PCM DMA");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:sprd-audio");
|