// SPDX-License-Identifier: GPL-2.0-or-later /* * Rockchip DLP (Digital Loopback) Driver * * Copyright (c) 2022 Rockchip Electronics Co., Ltd. * Author: Sugar Zhang * */ #include #include #include #include #include #include #include #include #include #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 "); MODULE_LICENSE("GPL");