mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Takashi Iwai](/assets/img/avatar_default.png)
Along with the transition to the managed PCM buffers, the driver now
accepts the dynamically allocated buffer, while it still kept the
reference to the old preallocated buffer address. This patch corrects
to the right reference via runtime->dma_addr.
(Although this might have been already buggy before the cleanup with
the managed buffer, let's put Fixes tag to point that; it's a corner
case, after all.)
Fixes: d55894bc27
("ASoC: uniphier: Use managed buffer allocation")
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/20210728112353.6675-5-tiwai@suse.de
Signed-off-by: Mark Brown <broonie@kernel.org>
279 lines
7.5 KiB
C
279 lines
7.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Socionext UniPhier AIO DMA driver.
|
|
//
|
|
// Copyright (c) 2016-2018 Socionext Inc.
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include "aio.h"
|
|
|
|
static struct snd_pcm_hardware uniphier_aiodma_hw = {
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_INTERLEAVED,
|
|
.period_bytes_min = 256,
|
|
.period_bytes_max = 4096,
|
|
.periods_min = 4,
|
|
.periods_max = 1024,
|
|
.buffer_bytes_max = 128 * 1024,
|
|
};
|
|
|
|
static void aiodma_pcm_irq(struct uniphier_aio_sub *sub)
|
|
{
|
|
struct snd_pcm_runtime *runtime = sub->substream->runtime;
|
|
int bytes = runtime->period_size *
|
|
runtime->channels * samples_to_bytes(runtime, 1);
|
|
int ret;
|
|
|
|
spin_lock(&sub->lock);
|
|
ret = aiodma_rb_set_threshold(sub, runtime->dma_bytes,
|
|
sub->threshold + bytes);
|
|
if (!ret)
|
|
sub->threshold += bytes;
|
|
|
|
aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes);
|
|
aiodma_rb_clear_irq(sub);
|
|
spin_unlock(&sub->lock);
|
|
|
|
snd_pcm_period_elapsed(sub->substream);
|
|
}
|
|
|
|
static void aiodma_compr_irq(struct uniphier_aio_sub *sub)
|
|
{
|
|
struct snd_compr_runtime *runtime = sub->cstream->runtime;
|
|
int bytes = runtime->fragment_size;
|
|
int ret;
|
|
|
|
spin_lock(&sub->lock);
|
|
ret = aiodma_rb_set_threshold(sub, sub->compr_bytes,
|
|
sub->threshold + bytes);
|
|
if (!ret)
|
|
sub->threshold += bytes;
|
|
|
|
aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes);
|
|
aiodma_rb_clear_irq(sub);
|
|
spin_unlock(&sub->lock);
|
|
|
|
snd_compr_fragment_elapsed(sub->cstream);
|
|
}
|
|
|
|
static irqreturn_t aiodma_irq(int irq, void *p)
|
|
{
|
|
struct platform_device *pdev = p;
|
|
struct uniphier_aio_chip *chip = platform_get_drvdata(pdev);
|
|
irqreturn_t ret = IRQ_NONE;
|
|
int i, j;
|
|
|
|
for (i = 0; i < chip->num_aios; i++) {
|
|
struct uniphier_aio *aio = &chip->aios[i];
|
|
|
|
for (j = 0; j < ARRAY_SIZE(aio->sub); j++) {
|
|
struct uniphier_aio_sub *sub = &aio->sub[j];
|
|
|
|
/* Skip channel that does not trigger */
|
|
if (!sub->running || !aiodma_rb_is_irq(sub))
|
|
continue;
|
|
|
|
if (sub->substream)
|
|
aiodma_pcm_irq(sub);
|
|
if (sub->cstream)
|
|
aiodma_compr_irq(sub);
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int uniphier_aiodma_open(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &uniphier_aiodma_hw);
|
|
|
|
return snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256);
|
|
}
|
|
|
|
static int uniphier_aiodma_prepare(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 uniphier_aio *aio = uniphier_priv(asoc_rtd_to_cpu(rtd, 0));
|
|
struct uniphier_aio_sub *sub = &aio->sub[substream->stream];
|
|
int bytes = runtime->period_size *
|
|
runtime->channels * samples_to_bytes(runtime, 1);
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
ret = aiodma_ch_set_param(sub);
|
|
if (ret)
|
|
return ret;
|
|
|
|
spin_lock_irqsave(&sub->lock, flags);
|
|
ret = aiodma_rb_set_buffer(sub, runtime->dma_addr,
|
|
runtime->dma_addr + runtime->dma_bytes,
|
|
bytes);
|
|
spin_unlock_irqrestore(&sub->lock, flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uniphier_aiodma_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct uniphier_aio *aio = uniphier_priv(asoc_rtd_to_cpu(rtd, 0));
|
|
struct uniphier_aio_sub *sub = &aio->sub[substream->stream];
|
|
struct device *dev = &aio->chip->pdev->dev;
|
|
int bytes = runtime->period_size *
|
|
runtime->channels * samples_to_bytes(runtime, 1);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&sub->lock, flags);
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes,
|
|
bytes);
|
|
aiodma_ch_set_enable(sub, 1);
|
|
sub->running = 1;
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
sub->running = 0;
|
|
aiodma_ch_set_enable(sub, 0);
|
|
|
|
break;
|
|
default:
|
|
dev_warn(dev, "Unknown trigger(%d) ignored\n", cmd);
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&sub->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t uniphier_aiodma_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 uniphier_aio *aio = uniphier_priv(asoc_rtd_to_cpu(rtd, 0));
|
|
struct uniphier_aio_sub *sub = &aio->sub[substream->stream];
|
|
int bytes = runtime->period_size *
|
|
runtime->channels * samples_to_bytes(runtime, 1);
|
|
unsigned long flags;
|
|
snd_pcm_uframes_t pos;
|
|
|
|
spin_lock_irqsave(&sub->lock, flags);
|
|
aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes);
|
|
|
|
if (sub->swm->dir == PORT_DIR_OUTPUT)
|
|
pos = bytes_to_frames(runtime, sub->rd_offs);
|
|
else
|
|
pos = bytes_to_frames(runtime, sub->wr_offs);
|
|
spin_unlock_irqrestore(&sub->lock, flags);
|
|
|
|
return pos;
|
|
}
|
|
|
|
static int uniphier_aiodma_mmap(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct vm_area_struct *vma)
|
|
{
|
|
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
|
|
|
|
return remap_pfn_range(vma, vma->vm_start,
|
|
substream->runtime->dma_addr >> PAGE_SHIFT,
|
|
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
|
}
|
|
|
|
static int uniphier_aiodma_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct device *dev = rtd->card->snd_card->dev;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
int ret;
|
|
|
|
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(33));
|
|
if (ret)
|
|
return ret;
|
|
|
|
snd_pcm_set_managed_buffer_all(pcm,
|
|
SNDRV_DMA_TYPE_DEV, dev,
|
|
uniphier_aiodma_hw.buffer_bytes_max,
|
|
uniphier_aiodma_hw.buffer_bytes_max);
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver uniphier_soc_platform = {
|
|
.open = uniphier_aiodma_open,
|
|
.prepare = uniphier_aiodma_prepare,
|
|
.trigger = uniphier_aiodma_trigger,
|
|
.pointer = uniphier_aiodma_pointer,
|
|
.mmap = uniphier_aiodma_mmap,
|
|
.pcm_construct = uniphier_aiodma_new,
|
|
.compress_ops = &uniphier_aio_compress_ops,
|
|
};
|
|
|
|
static const struct regmap_config aiodma_regmap_config = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
.max_register = 0x7fffc,
|
|
.cache_type = REGCACHE_NONE,
|
|
};
|
|
|
|
/**
|
|
* uniphier_aiodma_soc_register_platform - register the AIO DMA
|
|
* @pdev: the platform device
|
|
*
|
|
* Register and setup the DMA of AIO to transfer the sound data to device.
|
|
* This function need to call once at driver startup and need NOT to call
|
|
* unregister function.
|
|
*
|
|
* Return: Zero if successful, otherwise a negative value on error.
|
|
*/
|
|
int uniphier_aiodma_soc_register_platform(struct platform_device *pdev)
|
|
{
|
|
struct uniphier_aio_chip *chip = platform_get_drvdata(pdev);
|
|
struct device *dev = &pdev->dev;
|
|
void __iomem *preg;
|
|
int irq, ret;
|
|
|
|
preg = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(preg))
|
|
return PTR_ERR(preg);
|
|
|
|
chip->regmap = devm_regmap_init_mmio(dev, preg,
|
|
&aiodma_regmap_config);
|
|
if (IS_ERR(chip->regmap))
|
|
return PTR_ERR(chip->regmap);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
ret = devm_request_irq(dev, irq, aiodma_irq,
|
|
IRQF_SHARED, dev_name(dev), pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return devm_snd_soc_register_component(dev, &uniphier_soc_platform,
|
|
NULL, 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(uniphier_aiodma_soc_register_platform);
|