1124 lines
27 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Rockchip DLP (Digital Loopback) Driver
*
* Copyright (c) 2022 Rockchip Electronics Co., Ltd.
* Author: Sugar Zhang <sugar.zhang@rock-chips.com>
*
*/
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "rockchip_dlp.h"
#define PBUF_CNT 2
/* MUST: dlp_text should be match to enum dlp_mode */
static const char *const dlp_text[] = {
"Disabled",
"2CH: 1 Loopback + 1 Mic",
"2CH: 1 Mic + 1 Loopback",
"2CH: 1 Mic + 1 Loopback-mixed",
"2CH: 2 Loopbacks",
"4CH: 2 Mics + 2 Loopbacks",
"4CH: 2 Mics + 1 Loopback-mixed",
"4CH: 4 Loopbacks",
"6CH: 4 Mics + 2 Loopbacks",
"6CH: 4 Mics + 1 Loopback-mixed",
"6CH: 6 Loopbacks",
"8CH: 6 Mics + 2 Loopbacks",
"8CH: 6 Mics + 1 Loopback-mixed",
"8CH: 8 Loopbacks",
"10CH: 8 Mics + 2 Loopbacks",
"10CH: 8 Mics + 1 Loopback-mixed",
"16CH: 8 Mics + 8 Loopbacks",
};
static inline void drd_buf_free(struct dlp_runtime_data *drd)
{
if (drd && drd->buf) {
dev_dbg(drd->parent->dev, "%s: stream[%d]: 0x%px\n",
__func__, drd->stream, drd->buf);
kvfree(drd->buf);
drd->buf = NULL;
}
}
static inline int drd_buf_alloc(struct dlp_runtime_data *drd, int size)
{
if (drd) {
if (snd_BUG_ON(drd->buf))
return -EINVAL;
drd->buf = kvzalloc(size, GFP_KERNEL);
if (!drd->buf)
return -ENOMEM;
dev_dbg(drd->parent->dev, "%s: stream[%d]: 0x%px\n",
__func__, drd->stream, drd->buf);
}
return 0;
}
static inline void dlp_activate(struct dlp *dlp)
{
atomic_inc(&dlp->active);
}
static inline void dlp_deactivate(struct dlp *dlp)
{
atomic_dec(&dlp->active);
}
static inline bool dlp_mode_channels_match(struct dlp *dlp,
int ch, int *expected)
{
*expected = 0;
switch (dlp->mode) {
case DLP_MODE_DISABLED:
return true;
case DLP_MODE_2CH_1LP_1MIC:
case DLP_MODE_2CH_1MIC_1LP:
case DLP_MODE_2CH_1MIC_1LP_MIX:
case DLP_MODE_2CH_2LP:
*expected = 2;
return (ch == 2);
case DLP_MODE_4CH_2MIC_2LP:
case DLP_MODE_4CH_2MIC_1LP_MIX:
case DLP_MODE_4CH_4LP:
*expected = 4;
return (ch == 4);
case DLP_MODE_6CH_4MIC_2LP:
case DLP_MODE_6CH_4MIC_1LP_MIX:
case DLP_MODE_6CH_6LP:
*expected = 6;
return (ch == 6);
case DLP_MODE_8CH_6MIC_2LP:
case DLP_MODE_8CH_6MIC_1LP_MIX:
case DLP_MODE_8CH_8LP:
*expected = 8;
return (ch == 8);
case DLP_MODE_10CH_8MIC_2LP:
case DLP_MODE_10CH_8MIC_1LP_MIX:
*expected = 10;
return (ch == 10);
case DLP_MODE_16CH_8MIC_8LP:
*expected = 16;
return (ch == 16);
default:
return false;
}
}
static int dlp_get_offset_size(struct dlp_runtime_data *drd,
enum dlp_mode mode, int *ofs, int *size, bool *mix)
{
bool is_playback = drd->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
switch (mode) {
case DLP_MODE_2CH_1LP_1MIC:
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 1);
break;
case DLP_MODE_2CH_1MIC_1LP:
*ofs = dlp_channels_to_bytes(drd, 1);
*size = dlp_channels_to_bytes(drd, 1);
break;
case DLP_MODE_2CH_1MIC_1LP_MIX:
if (is_playback) {
*ofs = 0;
*size = dlp_frames_to_bytes(drd, 1);
if (mix)
*mix = true;
} else {
*ofs = dlp_channels_to_bytes(drd, 1);
*size = dlp_channels_to_bytes(drd, 1);
}
break;
case DLP_MODE_2CH_2LP:
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 2);
break;
case DLP_MODE_4CH_2MIC_2LP:
if (is_playback) {
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 2);
} else {
*ofs = dlp_channels_to_bytes(drd, 2);
*size = dlp_channels_to_bytes(drd, 2);
}
break;
case DLP_MODE_4CH_2MIC_1LP_MIX:
if (is_playback) {
*ofs = 0;
*size = dlp_frames_to_bytes(drd, 1);
if (mix)
*mix = true;
} else {
*ofs = dlp_channels_to_bytes(drd, 2);
*size = dlp_channels_to_bytes(drd, 1);
}
break;
case DLP_MODE_4CH_4LP:
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 4);
break;
case DLP_MODE_6CH_4MIC_2LP:
if (is_playback) {
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 2);
} else {
*ofs = dlp_channels_to_bytes(drd, 4);
*size = dlp_channels_to_bytes(drd, 2);
}
break;
case DLP_MODE_6CH_4MIC_1LP_MIX:
if (is_playback) {
*ofs = 0;
*size = dlp_frames_to_bytes(drd, 1);
if (mix)
*mix = true;
} else {
*ofs = dlp_channels_to_bytes(drd, 4);
*size = dlp_channels_to_bytes(drd, 1);
}
break;
case DLP_MODE_6CH_6LP:
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 6);
break;
case DLP_MODE_8CH_6MIC_2LP:
if (is_playback) {
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 2);
} else {
*ofs = dlp_channels_to_bytes(drd, 6);
*size = dlp_channels_to_bytes(drd, 2);
}
break;
case DLP_MODE_8CH_6MIC_1LP_MIX:
if (is_playback) {
*ofs = 0;
*size = dlp_frames_to_bytes(drd, 1);
if (mix)
*mix = true;
} else {
*ofs = dlp_channels_to_bytes(drd, 6);
*size = dlp_channels_to_bytes(drd, 1);
}
break;
case DLP_MODE_8CH_8LP:
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 8);
break;
case DLP_MODE_10CH_8MIC_2LP:
if (is_playback) {
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 2);
} else {
*ofs = dlp_channels_to_bytes(drd, 8);
*size = dlp_channels_to_bytes(drd, 2);
}
break;
case DLP_MODE_10CH_8MIC_1LP_MIX:
if (is_playback) {
*ofs = 0;
*size = dlp_frames_to_bytes(drd, 1);
if (mix)
*mix = true;
} else {
*ofs = dlp_channels_to_bytes(drd, 8);
*size = dlp_channels_to_bytes(drd, 1);
}
break;
case DLP_MODE_16CH_8MIC_8LP:
if (is_playback) {
*ofs = 0;
*size = dlp_channels_to_bytes(drd, 8);
} else {
*ofs = dlp_channels_to_bytes(drd, 8);
*size = dlp_channels_to_bytes(drd, 8);
}
break;
default:
*ofs = 0;
*size = 0;
if (mix)
*mix = false;
ret = -EINVAL;
}
return ret;
}
static int dlp_mix_frame_buffer(struct dlp_runtime_data *drd, void *buf)
{
int sample_bytes = dlp_channels_to_bytes(drd, 1);
int16_t *p16 = (int16_t *)buf, v16 = 0;
int32_t *p32 = (int32_t *)buf, v32 = 0;
int i = 0;
switch (sample_bytes) {
case 2:
for (i = 0; i < drd->channels; i++)
v16 += (p16[i] / drd->channels);
p16[0] = v16;
break;
case 4:
for (i = 0; i < drd->channels; i++)
v32 += (p32[i] / drd->channels);
p32[0] = v32;
break;
default:
return -EINVAL;
}
return 0;
}
static inline int drd_init_from(struct dlp_runtime_data *drd, struct dlp_runtime_data *src)
{
memset(drd, 0x0, sizeof(*drd));
drd->parent = src->parent;
drd->buf_sz = src->buf_sz;
drd->period_sz = src->period_sz;
drd->frame_bytes = src->frame_bytes;
drd->channels = src->channels;
drd->stream = src->stream;
INIT_LIST_HEAD(&drd->node);
kref_init(&drd->refcount);
dev_dbg(drd->parent->dev, "%s: drd: 0x%px\n", __func__, drd);
return 0;
}
static void drd_avl_list_add(struct dlp *dlp, struct dlp_runtime_data *drd)
{
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
list_add(&drd->node, &dlp->drd_avl_list);
dlp->drd_avl_count++;
spin_unlock_irqrestore(&dlp->lock, flags);
}
static struct dlp_runtime_data *drd_avl_list_get(struct dlp *dlp)
{
struct dlp_runtime_data *drd = NULL;
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
if (!list_empty(&dlp->drd_avl_list)) {
drd = list_first_entry(&dlp->drd_avl_list, struct dlp_runtime_data, node);
list_del(&drd->node);
dlp->drd_avl_count--;
}
spin_unlock_irqrestore(&dlp->lock, flags);
return drd;
}
static void drd_release(struct kref *ref)
{
struct dlp_runtime_data *drd =
container_of(ref, struct dlp_runtime_data, refcount);
dev_dbg(drd->parent->dev, "%s: drd: 0x%px\n", __func__, drd);
drd_buf_free(drd);
/* move to available list */
drd_avl_list_add(drd->parent, drd);
}
static inline struct dlp_runtime_data *drd_get(struct dlp_runtime_data *drd)
{
if (!drd)
return NULL;
return kref_get_unless_zero(&drd->refcount) ? drd : NULL;
}
static inline void drd_put(struct dlp_runtime_data *drd)
{
if (!drd)
return;
kref_put(&drd->refcount, drd_release);
}
static void drd_rdy_list_add(struct dlp *dlp, struct dlp_runtime_data *drd)
{
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
list_add(&drd->node, &dlp->drd_rdy_list);
spin_unlock_irqrestore(&dlp->lock, flags);
}
static struct dlp_runtime_data *drd_rdy_list_get(struct dlp *dlp)
{
struct dlp_runtime_data *drd = NULL;
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
if (!list_empty(&dlp->drd_rdy_list)) {
/* the newest one */
drd = list_first_entry(&dlp->drd_rdy_list, struct dlp_runtime_data, node);
list_del(&drd->node);
}
spin_unlock_irqrestore(&dlp->lock, flags);
return drd;
}
static bool drd_rdy_list_found(struct dlp *dlp, struct dlp_runtime_data *drd)
{
struct dlp_runtime_data *_drd;
unsigned long flags;
bool found = false;
if (!drd)
return false;
spin_lock_irqsave(&dlp->lock, flags);
list_for_each_entry(_drd, &dlp->drd_rdy_list, node) {
if (_drd == drd) {
found = true;
break;
}
}
spin_unlock_irqrestore(&dlp->lock, flags);
return found;
}
static void drd_rdy_list_free(struct dlp *dlp)
{
struct list_head drd_list;
struct dlp_runtime_data *drd;
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
list_replace_init(&dlp->drd_rdy_list, &drd_list);
spin_unlock_irqrestore(&dlp->lock, flags);
while (!list_empty(&drd_list)) {
drd = list_first_entry(&drd_list, struct dlp_runtime_data, node);
list_del(&drd->node);
drd_put(drd);
}
}
static void drd_ref_list_add(struct dlp *dlp, struct dlp_runtime_data *drd)
{
unsigned long flags;
/* push valid playback into ref list */
spin_lock_irqsave(&dlp->lock, flags);
list_add_tail(&drd->node, &dlp->drd_ref_list);
spin_unlock_irqrestore(&dlp->lock, flags);
}
static struct dlp_runtime_data *drd_ref_list_first(struct dlp *dlp)
{
struct dlp_runtime_data *drd = NULL;
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
if (!list_empty(&dlp->drd_ref_list))
drd = list_first_entry(&dlp->drd_ref_list, struct dlp_runtime_data, node);
spin_unlock_irqrestore(&dlp->lock, flags);
return drd;
}
static struct dlp_runtime_data *drd_ref_list_del(struct dlp *dlp,
struct dlp_runtime_data *drd)
{
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
list_del(&drd->node);
spin_unlock_irqrestore(&dlp->lock, flags);
return drd;
}
static void drd_ref_list_free(struct dlp *dlp)
{
struct list_head drd_list;
struct dlp_runtime_data *drd;
unsigned long flags;
spin_lock_irqsave(&dlp->lock, flags);
list_replace_init(&dlp->drd_ref_list, &drd_list);
spin_unlock_irqrestore(&dlp->lock, flags);
while (!list_empty(&drd_list)) {
drd = list_first_entry(&drd_list, struct dlp_runtime_data, node);
list_del(&drd->node);
if (!atomic_read(&drd->stop))
drd_rdy_list_add(dlp, drd);
else
drd_put(drd);
}
}
int dlp_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct dlp *dlp = soc_component_to_dlp(component);
struct dlp_runtime_data *drd = substream_to_drd(substream);
bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ch_req = params_channels(params), ch_exp = 0;
if (unlikely(!dlp || !drd))
return -EINVAL;
/* mode should match to channels */
if (!is_playback && !dlp_mode_channels_match(dlp, ch_req, &ch_exp)) {
dev_err(dlp->dev,
"capture %d ch, expected: %d ch for loopback mode-%d\n",
ch_req, ch_exp, dlp->mode);
return -EINVAL;
}
drd->frame_bytes = snd_pcm_format_size(params_format(params),
params_channels(params));
drd->period_sz = params_period_size(params);
drd->buf_sz = params_buffer_size(params);
drd->channels = params_channels(params);
if (is_playback)
drd->buf_sz *= PBUF_CNT;
return 0;
}
EXPORT_SYMBOL_GPL(dlp_hw_params);
int dlp_open(struct dlp *dlp, struct dlp_runtime_data *drd,
struct snd_pcm_substream *substream)
{
if (unlikely(!dlp || !drd))
return -EINVAL;
drd->parent = dlp;
drd->stream = substream->stream;
substream->runtime->private_data = drd;
dlp_activate(dlp);
return 0;
}
EXPORT_SYMBOL_GPL(dlp_open);
int dlp_close(struct dlp *dlp, struct dlp_runtime_data *drd,
struct snd_pcm_substream *substream)
{
if (unlikely(!dlp || !drd))
return -EINVAL;
/*
* In case: open -> hw_params -> prepare -> close flow
* should check and free all.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
drd_put(dlp->drd_pb_shadow);
dlp->drd_pb_shadow = NULL;
} else {
drd_buf_free(drd);
}
dlp_deactivate(dlp);
return 0;
}
EXPORT_SYMBOL_GPL(dlp_close);
void dlp_dma_complete(struct dlp *dlp, struct dlp_runtime_data *drd)
{
if (unlikely(!dlp || !drd))
return;
atomic64_inc(&drd->period_elapsed);
}
EXPORT_SYMBOL_GPL(dlp_dma_complete);
int dlp_start(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct device *dev,
dma_pointer_f dma_pointer)
{
struct dlp *dlp = soc_component_to_dlp(component);
int bstream = SNDRV_PCM_STREAM_LAST - substream->stream;
struct snd_pcm_str *bro = &substream->pcm->streams[bstream];
struct snd_pcm_substream *bsubstream = bro->substream;
struct dlp_runtime_data *adrd = substream_to_drd(substream);
struct dlp_runtime_data *bdrd = substream_to_drd(bsubstream);
struct dlp_runtime_data *drd_ref;
bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
uint64_t a = 0, b = 0;
snd_pcm_uframes_t fifo_a = 0, fifo_b = 0;
snd_pcm_sframes_t delta = 0;
if (unlikely(!dlp || !adrd || !dma_pointer))
return -EINVAL;
if (dlp->mode == DLP_MODE_DISABLED)
return -EINVAL;
fifo_a = dlp->config->get_fifo_count(dev, substream);
a = dma_pointer(component, substream) % adrd->period_sz;
if (bsubstream->runtime && snd_pcm_running(bsubstream)) {
if (unlikely(!bdrd))
return -EINVAL;
fifo_b = dlp->config->get_fifo_count(dev, bsubstream);
b = dma_pointer(component, bsubstream) % bdrd->period_sz;
drd_ref = drd_rdy_list_get(dlp);
if (unlikely(!drd_ref)) {
dev_err(dev, "Failed to get rdy drd\n");
return -EINVAL;
}
a += (atomic64_read(&adrd->period_elapsed) * adrd->period_sz);
b += (atomic64_read(&bdrd->period_elapsed) * bdrd->period_sz);
fifo_a = dlp_bytes_to_frames(adrd, fifo_a * 4);
fifo_b = dlp_bytes_to_frames(bdrd, fifo_b * 4);
delta = is_playback ? (a - fifo_a) - (b + fifo_b) : (b - fifo_b) - (a + fifo_a);
drd_ref->hw_ptr_delta = delta;
drd_ref_list_add(dlp, drd_ref);
}
if (is_playback)
dev_dbg(dev, "START-P: DMA-P: %llu, DMA-C: %llu, FIFO-P: %lu, FIFO-C: %lu, DELTA: %ld\n",
a, b, fifo_a, fifo_b, delta);
else
dev_dbg(dev, "START-C: DMA-P: %llu, DMA-C: %llu, FIFO-P: %lu, FIFO-C: %lu, DELTA: %ld\n",
b, a, fifo_b, fifo_a, delta);
return 0;
}
EXPORT_SYMBOL_GPL(dlp_start);
void dlp_stop(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
dma_pointer_f dma_pointer)
{
struct dlp *dlp = soc_component_to_dlp(component);
struct dlp_runtime_data *drd = substream_to_drd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
uint64_t appl_ptr, hw_ptr;
if (unlikely(!dlp || !drd || !runtime || !dma_pointer))
return;
if (dlp->mode == DLP_MODE_DISABLED)
return;
/* any data in FIFOs will be gone ,so don't care */
appl_ptr = READ_ONCE(runtime->control->appl_ptr);
hw_ptr = dma_pointer(component, substream) % drd->period_sz;
hw_ptr += (atomic64_read(&drd->period_elapsed) * drd->period_sz);
/*
* playback:
*
* snd_pcm_drop: hw_ptr will be smaller than appl_ptr
* snd_pcm_drain, hw_ptr will be equal to appl_ptr
*
* anyway, we should use the smaller one, obviously, it's hw_ptr.
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (dlp->drd_pb_shadow) {
dlp->drd_pb_shadow->hw_ptr = min(hw_ptr, appl_ptr);
atomic_set(&dlp->drd_pb_shadow->stop, 1);
}
drd_rdy_list_free(dlp);
} else {
/* free residue playback ref list for capture when stop */
drd_ref_list_free(dlp);
}
atomic64_set(&drd->period_elapsed, 0);
dev_dbg(dlp->dev, "STOP-%s: applptr: %llu, hwptr: %llu\n",
substream->stream ? "C" : "P", appl_ptr, hw_ptr);
}
EXPORT_SYMBOL_GPL(dlp_stop);
static int process_capture(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned long hwoff,
void __user *buf, unsigned long bytes)
{
struct dlp *dlp = soc_component_to_dlp(component);
struct snd_pcm_runtime *runtime = substream->runtime;
struct dlp_runtime_data *drd = substream_to_drd(substream);
struct dlp_runtime_data *drd_ref = NULL;
snd_pcm_sframes_t frames = 0;
snd_pcm_sframes_t frames_consumed = 0, frames_residue = 0, frames_tmp = 0;
snd_pcm_sframes_t ofs = 0;
snd_pcm_uframes_t appl_ptr;
int ofs_cap, ofs_play, size_cap, size_play;
int i = 0, j = 0, ret = 0;
bool free_ref = false, mix = false;
char *cbuf = NULL, *pbuf = NULL;
void *dma_ptr;
if (unlikely(!drd || !runtime || !buf))
return -EINVAL;
frames = dlp_bytes_to_frames(drd, bytes);
dma_ptr = runtime->dma_area + hwoff;
cbuf = drd->buf;
appl_ptr = READ_ONCE(runtime->control->appl_ptr);
memcpy(cbuf, dma_ptr, bytes);
#ifdef DLP_DBG
/* DBG: mark STUB in ch-REC for trace each read */
memset(cbuf, 0x22, dlp_channels_to_bytes(drd, 1));
#endif
ret = dlp_get_offset_size(drd, dlp->mode, &ofs_cap, &size_cap, NULL);
if (ret) {
dev_err(dlp->dev, "Failed to get dlp cap offset\n");
return -EINVAL;
}
/* clear channel-LP_CHN */
for (i = 0; i < frames; i++) {
cbuf = drd->buf + dlp_frames_to_bytes(drd, i) + ofs_cap;
memset(cbuf, 0x0, size_cap);
}
start:
drd_ref = drd_get(drd_ref_list_first(dlp));
if (!drd_ref)
return 0;
ret = dlp_get_offset_size(drd_ref, dlp->mode, &ofs_play, &size_play, &mix);
if (ret) {
dev_err(dlp->dev, "Failed to get dlp play offset\n");
goto _drd_put;
}
ofs = appl_ptr + drd_ref->hw_ptr_delta;
/*
* if playback stop, process the data tail and then
* free drd_ref if data consumed.
*/
if (atomic_read(&drd_ref->stop)) {
if (ofs >= drd_ref->hw_ptr) {
drd_put(drd_ref_list_del(dlp, drd_ref));
goto _drd_put;
} else if ((ofs + frames) > drd_ref->hw_ptr) {
dev_dbg(dlp->dev, "applptr: %8lu, ofs': %7ld, refhwptr: %lld, frames: %ld (*)\n",
appl_ptr, ofs, drd_ref->hw_ptr, frames);
/*
* should ignore the data that after play stop
* and care about if the next ref start in the
* same window
*/
frames_tmp = drd_ref->hw_ptr - ofs;
frames_residue = frames - frames_tmp;
frames = frames_tmp;
free_ref = true;
}
}
/*
* should ignore the data that before play start:
*
* frames:
* +---------------------------------------------+
* | ofs<0 | ofs>0 |
* +---------------------------------------------+
*
*/
if ((ofs + frames) <= 0)
goto _drd_put;
/* skip if ofs < 0 and fixup ofs */
j = 0;
if (ofs < 0) {
dev_dbg(dlp->dev, "applptr: %8lu, ofs: %8ld, frames: %ld (*)\n",
appl_ptr, ofs, frames);
j = -ofs;
frames += ofs;
ofs = 0;
appl_ptr += j;
}
ofs %= drd_ref->buf_sz;
dev_dbg(dlp->dev, "applptr: %8lu, ofs: %8ld, frames: %5ld, refc: %u\n",
appl_ptr, ofs, frames, kref_read(&drd_ref->refcount));
for (i = 0; i < frames; i++, j++) {
cbuf = drd->buf + dlp_frames_to_bytes(drd, j + frames_consumed) + ofs_cap;
pbuf = drd_ref->buf + dlp_frames_to_bytes(drd_ref, ((i + ofs) % drd_ref->buf_sz)) + ofs_play;
if (mix)
dlp_mix_frame_buffer(drd_ref, pbuf);
memcpy(cbuf, pbuf, size_cap);
}
appl_ptr += frames;
frames_consumed += frames;
if (free_ref) {
drd_put(drd_ref_list_del(dlp, drd_ref));
drd_put(drd_ref);
drd_ref = NULL;
free_ref = false;
if (frames_residue) {
frames = frames_residue;
frames_residue = 0;
goto start;
}
}
_drd_put:
drd_put(drd_ref);
drd_ref = NULL;
return 0;
}
static int process_playback(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned long hwoff,
void __user *buf, unsigned long bytes)
{
struct dlp *dlp = soc_component_to_dlp(component);
struct dlp_runtime_data *drd;
char *pbuf;
int ret = 0;
drd = drd_get(dlp->drd_pb_shadow);
if (!drd)
return 0;
pbuf = drd->buf + drd->buf_ofs;
if (copy_from_user(pbuf, buf, bytes)) {
ret = -EFAULT;
goto err_put;
}
drd->buf_ofs += bytes;
drd->buf_ofs %= dlp_frames_to_bytes(drd, drd->buf_sz);
err_put:
drd_put(drd);
return ret;
}
static int dlp_process(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned long hwoff,
void __user *buf, unsigned long bytes)
{
struct dlp *dlp = soc_component_to_dlp(component);
int ret = 0;
if (dlp->mode == DLP_MODE_DISABLED)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = process_playback(component, substream, hwoff, buf, bytes);
else
ret = process_capture(component, substream, hwoff, buf, bytes);
return ret;
}
/*
* MSB check
* channel id
* as much as more 0/1 stress
*/
#define PATTERN8(x) (0xa0 | (x))
#define PATTERN16(x) (0xab00 | (x))
#define PATTERN32(x) (0xabcabc00 | (x))
static void snd_fill_pattern_frame(char *buf, int bits, int ch)
{
unsigned char *ptr8 = (unsigned char *)buf;
unsigned short *ptr16 = (unsigned short *)buf;
unsigned int *ptr32 = (unsigned int *)buf;
int i = 0;
switch (bits) {
case 8:
for (i = 0; i < ch; i++)
ptr8[i] = PATTERN8(i + 1);
break;
case 16:
for (i = 0; i < ch; i++)
ptr16[i] = PATTERN16(i + 1);
break;
case 32:
for (i = 0; i < ch; i++)
ptr32[i] = PATTERN32(i + 1);
break;
default:
pr_err("invalid bits: %d\n", bits);
break;
}
}
static int snd_fill_pattern(struct snd_pcm_substream *substream,
int channels, unsigned long hwoff,
unsigned long bytes)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t frames;
void *buf;
int i = 0;
buf = runtime->dma_area + hwoff +
channels * (runtime->dma_bytes / runtime->channels);
frames = bytes_to_frames(runtime, bytes);
for (i = 0; i < frames; i++) {
snd_fill_pattern_frame(buf, runtime->sample_bits, runtime->channels);
buf += frames_to_bytes(runtime, 1);
}
return 0;
}
int dlp_copy_user(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
int channel, unsigned long hwoff,
void __user *buf, unsigned long bytes)
{
struct dlp_runtime_data *drd = substream_to_drd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
void *dma_ptr;
int ret;
if (unlikely(!drd || !runtime || !buf))
return -EINVAL;
dma_ptr = runtime->dma_area + hwoff +
channel * (runtime->dma_bytes / runtime->channels);
if (is_playback) {
if (IS_ENABLED(CONFIG_SND_PCM_PATTERN_DEBUG))
snd_fill_pattern(substream, channel, hwoff, bytes);
else if (copy_from_user(dma_ptr, buf, bytes))
return -EFAULT;
}
ret = dlp_process(component, substream, hwoff, buf, bytes);
if (!ret)
dma_ptr = drd->buf;
if (!is_playback)
if (copy_to_user(buf, dma_ptr, bytes))
return -EFAULT;
return 0;
}
EXPORT_SYMBOL_GPL(dlp_copy_user);
static SOC_ENUM_SINGLE_EXT_DECL(dlp_mode, dlp_text);
static int dlp_mode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct dlp *dlp = soc_component_to_dlp(component);
ucontrol->value.enumerated.item[0] = dlp->mode;
return 0;
}
static int dlp_mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
struct dlp *dlp = soc_component_to_dlp(component);
unsigned int mode = ucontrol->value.enumerated.item[0];
/* MUST: do not update mode while stream is running */
if (atomic_read(&dlp->active)) {
dev_err(dlp->dev, "Should set this mode before pcm open\n");
return -EPERM;
}
if (mode == dlp->mode)
return 0;
dlp->mode = mode;
return 1;
}
static const struct snd_kcontrol_new dlp_controls[] = {
SOC_ENUM_EXT("Software Digital Loopback Mode", dlp_mode,
dlp_mode_get, dlp_mode_put),
};
int dlp_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct dlp *dlp = soc_component_to_dlp(component);
struct dlp_runtime_data *drd = substream_to_drd(substream);
struct dlp_runtime_data *drd_new = NULL;
int buf_bytes, last_buf_bytes;
int ret;
if (unlikely(!dlp || !drd))
return -EINVAL;
if (dlp->mode == DLP_MODE_DISABLED)
return 0;
buf_bytes = dlp_frames_to_bytes(drd, drd->buf_sz);
last_buf_bytes = dlp_frames_to_bytes(drd, drd->last_buf_sz);
if (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN)
dev_dbg(dlp->dev, "stream[%d]: prepare from XRUN\n",
substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dev_dbg(dlp->dev, "avl count: %d\n", dlp->drd_avl_count);
if (snd_BUG_ON(!dlp->drd_avl_count))
return -EINVAL;
/*
* There might be multiple calls hw_params -> prepare
* before start stream, so, should check buf size status
* to determine whether to re-create buf or do nothing.
*/
if (drd_rdy_list_found(dlp, dlp->drd_pb_shadow)) {
if (buf_bytes == last_buf_bytes)
return 0;
drd_rdy_list_free(dlp);
}
/* release the old one, re-create for new params */
drd_put(dlp->drd_pb_shadow);
dlp->drd_pb_shadow = NULL;
drd_new = drd_avl_list_get(dlp);
if (!drd_new)
return -ENOMEM;
drd_init_from(drd_new, drd);
ret = drd_buf_alloc(drd_new, buf_bytes);
if (ret)
return -ENOMEM;
if (snd_BUG_ON(!drd_get(drd_new)))
return -EINVAL;
drd_rdy_list_add(dlp, drd_new);
dlp->drd_pb_shadow = drd_new;
} else {
/*
* There might be multiple calls hw_params -> prepare
* before start stream, so, should check buf size status
* to determine whether to re-create buf or do nothing.
*/
if (drd->buf && buf_bytes == last_buf_bytes)
return 0;
drd_buf_free(drd);
ret = drd_buf_alloc(drd, buf_bytes);
if (ret)
return ret;
}
/* update last after all done success */
drd->last_buf_sz = drd->buf_sz;
return 0;
}
EXPORT_SYMBOL_GPL(dlp_prepare);
int dlp_probe(struct snd_soc_component *component)
{
snd_soc_add_component_controls(component, dlp_controls,
ARRAY_SIZE(dlp_controls));
return 0;
}
EXPORT_SYMBOL_GPL(dlp_probe);
int dlp_register(struct dlp *dlp, struct device *dev,
const struct snd_soc_component_driver *driver,
const struct snd_dlp_config *config)
{
struct dlp_runtime_data *drd;
int ret = 0, i = 0;
if (unlikely(!dlp || !dev || !driver || !config))
return -EINVAL;
dlp->dev = dev;
dlp->config = config;
#ifdef CONFIG_DEBUG_FS
dlp->component.debugfs_prefix = "dma";
#endif
INIT_LIST_HEAD(&dlp->drd_avl_list);
INIT_LIST_HEAD(&dlp->drd_rdy_list);
INIT_LIST_HEAD(&dlp->drd_ref_list);
dlp->drd_avl_count = ARRAY_SIZE(dlp->drds);
for (i = 0; i < dlp->drd_avl_count; i++) {
drd = &dlp->drds[i];
list_add_tail(&drd->node, &dlp->drd_avl_list);
}
spin_lock_init(&dlp->lock);
atomic_set(&dlp->active, 0);
ret = snd_soc_component_initialize(&dlp->component, driver, dev);
if (ret)
return ret;
ret = snd_soc_add_component(&dlp->component, NULL, 0);
return ret;
}
EXPORT_SYMBOL_GPL(dlp_register);
MODULE_DESCRIPTION("Rockchip Digital Loopback Core Driver");
MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>");
MODULE_LICENSE("GPL");