[PATCH v7 06/14] ASoC: Intel: catpt: PCM operations
Rojewski, Cezary
cezary.rojewski at intel.com
Mon Sep 21 20:50:27 CEST 2020
On 2020-09-21 4:08 PM, Andy Shevchenko wrote:
> On Mon, Sep 21, 2020 at 01:54:16PM +0200, Cezary Rojewski wrote:
>> DSP designed for Lynxpoint and Wildcat Point offers no dynamic topology
>> i.e. all pipelines are already defined within firmware and host is
>> relegated to allocing stream for predefined pins. This is represented by
>> 'catpt_topology' member.
>>
...
>> +
>> +static void catpt_arrange_page_table(struct snd_pcm_substream *substream,
>> + struct snd_dma_buffer *pgtbl)
>> +{
>> + struct snd_pcm_runtime *rtm = substream->runtime;
>> + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream);
>> + int i, pages;
>> +
>> + pages = snd_sgbuf_aligned_pages(rtm->dma_bytes);
>> +
>> + for (i = 0; i < pages; i++) {
>> + u32 pfn, offset;
>> + u32 *page_table;
>> +
>> + pfn = snd_sgbuf_get_addr(databuf, i * PAGE_SIZE) >> PAGE_SHIFT;
>
> PFN_DOWN()
>
Ack. Updating in both cases where explicit right shift by PAGE_SHIFT is used.
>> + /* incrementing by 2 on even and 3 on odd */
>> + offset = ((i << 2) + i) >> 1;
>> + page_table = (u32 *)(pgtbl->area + offset);
>> +
>> + if (i & 1)
>> + *page_table |= (pfn << 4);
>> + else
>> + *page_table |= pfn;
>> + }
>> +}
>> +
>> +static u32 catpt_get_channel_map(enum catpt_channel_config config)
>> +{
>> + switch (config) {
>> + case CATPT_CHANNEL_CONFIG_MONO:
>> + return (0xFFFFFFF0 | CATPT_CHANNEL_CENTER);
>> +
>> + case CATPT_CHANNEL_CONFIG_STEREO:
>> + return (0xFFFFFF00 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_RIGHT << 4));
>> +
>> + case CATPT_CHANNEL_CONFIG_2_POINT_1:
>> + return (0xFFFFF000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_RIGHT << 4)
>> + | (CATPT_CHANNEL_LFE << 8));
>> +
>> + case CATPT_CHANNEL_CONFIG_3_POINT_0:
>> + return (0xFFFFF000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_CENTER << 4)
>> + | (CATPT_CHANNEL_RIGHT << 8));
>> +
>> + case CATPT_CHANNEL_CONFIG_3_POINT_1:
>> + return (0xFFFF0000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_CENTER << 4)
>> + | (CATPT_CHANNEL_RIGHT << 8)
>> + | (CATPT_CHANNEL_LFE << 12));
>> +
>> + case CATPT_CHANNEL_CONFIG_QUATRO:
>> + return (0xFFFF0000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_RIGHT << 4)
>> + | (CATPT_CHANNEL_LEFT_SURROUND << 8)
>> + | (CATPT_CHANNEL_RIGHT_SURROUND << 12));
>> +
>> + case CATPT_CHANNEL_CONFIG_4_POINT_0:
>> + return (0xFFFF0000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_CENTER << 4)
>> + | (CATPT_CHANNEL_RIGHT << 8)
>> + | (CATPT_CHANNEL_CENTER_SURROUND << 12));
>> +
>> + case CATPT_CHANNEL_CONFIG_5_POINT_0:
>> + return (0xFFF00000 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_CENTER << 4)
>> + | (CATPT_CHANNEL_RIGHT << 8)
>> + | (CATPT_CHANNEL_LEFT_SURROUND << 12)
>> + | (CATPT_CHANNEL_RIGHT_SURROUND << 16));
>> +
>> + case CATPT_CHANNEL_CONFIG_5_POINT_1:
>> + return (0xFF000000 | CATPT_CHANNEL_CENTER
>> + | (CATPT_CHANNEL_LEFT << 4)
>> + | (CATPT_CHANNEL_RIGHT << 8)
>> + | (CATPT_CHANNEL_LEFT_SURROUND << 12)
>> + | (CATPT_CHANNEL_RIGHT_SURROUND << 16)
>> + | (CATPT_CHANNEL_LFE << 20));
>> +
>> + case CATPT_CHANNEL_CONFIG_DUAL_MONO:
>> + return (0xFFFFFF00 | CATPT_CHANNEL_LEFT
>> + | (CATPT_CHANNEL_LEFT << 4));
>> +
>> + default:
>> + return 0xFFFFFFFF;
>> + }
>
> GENMASK() for all above?
>
Ack. GENMASK + U32_MAX for the 'default' label.
>> +}
>> +
>> +static enum catpt_channel_config catpt_get_channel_config(u32 num_channels)
>> +{
>> + switch (num_channels) {
>> + case 6:
>> + return CATPT_CHANNEL_CONFIG_5_POINT_1;
>> + case 5:
>> + return CATPT_CHANNEL_CONFIG_5_POINT_0;
>> + case 4:
>> + return CATPT_CHANNEL_CONFIG_QUATRO;
>> + case 3:
>> + return CATPT_CHANNEL_CONFIG_2_POINT_1;
>> + case 1:
>> + return CATPT_CHANNEL_CONFIG_MONO;
>> + case 2:
>> + default:
>> + return CATPT_CHANNEL_CONFIG_STEREO;
>> + }
>> +}
>> +
>> +static int catpt_dai_startup(struct snd_pcm_substream *substream,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_template *template;
>> + struct catpt_stream_runtime *stream;
>> + struct resource *res;
>> + int ret;
>> +
>> + template = catpt_get_stream_template(substream);
>> +
>> + stream = kzalloc(sizeof(*stream), GFP_KERNEL);
>> + if (!stream)
>> + return -ENOMEM;
>> +
>> + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, cdev->dev, PAGE_SIZE,
>> + &stream->pgtbl);
>> + if (ret)
>> + goto err_pgtbl;
>> +
>> + res = catpt_request_region(&cdev->dram, template->persistent_size);
>> + if (!res) {
>> + ret = -EBUSY;
>> + goto err_request;
>> + }
>> +
>> + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
>> +
>> + stream->template = template;
>> + stream->persistent = res;
>> + stream->substream = substream;
>> + INIT_LIST_HEAD(&stream->node);
>> + snd_soc_dai_set_dma_data(dai, substream, stream);
>> +
>> + spin_lock(&cdev->list_lock);
>> + list_add_tail(&stream->node, &cdev->stream_list);
>> + spin_unlock(&cdev->list_lock);
>> +
>> + return 0;
>> +
>> +err_request:
>> + snd_dma_free_pages(&stream->pgtbl);
>> +err_pgtbl:
>> + kfree(stream);
>> + return ret;
>> +}
>> +
>> +static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_runtime *stream;
>> +
>> + stream = snd_soc_dai_get_dma_data(dai, substream);
>> +
>> + spin_lock(&cdev->list_lock);
>> + list_del(&stream->node);
>> + spin_unlock(&cdev->list_lock);
>> +
>> + release_resource(stream->persistent);
>> + kfree(stream->persistent);
>> + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
>> +
>> + snd_dma_free_pages(&stream->pgtbl);
>> + kfree(stream);
>> + snd_soc_dai_set_dma_data(dai, substream, NULL);
>> +}
>> +
>> +static int catpt_dai_hw_params(struct snd_pcm_substream *substream,
>> + struct snd_pcm_hw_params *params,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_runtime *stream;
>> + struct catpt_audio_format afmt;
>> + struct catpt_ring_info rinfo;
>> + struct snd_pcm_runtime *rtm = substream->runtime;
>> + struct snd_dma_buffer *dmab;
>> + int ret;
>> +
>> + stream = snd_soc_dai_get_dma_data(dai, substream);
>> + if (stream->allocated)
>> + return 0;
>> +
>> + memset(&afmt, 0, sizeof(afmt));
>> + afmt.sample_rate = params_rate(params);
>> + afmt.bit_depth = params_physical_width(params);
>> + afmt.valid_bit_depth = params_width(params);
>> + afmt.num_channels = params_channels(params);
>> + afmt.channel_config = catpt_get_channel_config(afmt.num_channels);
>> + afmt.channel_map = catpt_get_channel_map(afmt.channel_config);
>> + afmt.interleaving = CATPT_INTERLEAVING_PER_CHANNEL;
>> +
>> + dmab = snd_pcm_get_dma_buf(substream);
>> + catpt_arrange_page_table(substream, &stream->pgtbl);
>> +
>> + memset(&rinfo, 0, sizeof(rinfo));
>> + rinfo.page_table_addr = stream->pgtbl.addr;
>> + rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE);
>> + rinfo.size = rtm->dma_bytes;
>> + rinfo.offset = 0;
>> + rinfo.ring_first_page_pfn = snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT;
>> +
>> + ret = catpt_ipc_alloc_stream(cdev, stream->template->path_id,
>> + stream->template->type,
>> + &afmt, &rinfo,
>> + stream->template->num_entries,
>> + stream->template->entries,
>> + stream->persistent,
>> + cdev->scratch,
>> + &stream->info);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> +
>> + stream->allocated = true;
>> + return 0;
>> +}
>> +
>> +static int catpt_dai_hw_free(struct snd_pcm_substream *substream,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_runtime *stream;
>> +
>> + stream = snd_soc_dai_get_dma_data(dai, substream);
>> + if (!stream->allocated)
>> + return 0;
>> +
>> + catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
>> + catpt_ipc_free_stream(cdev, stream->info.stream_hw_id);
>> +
>> + stream->allocated = false;
>> + return 0;
>> +}
>> +
>> +static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol);
>> +
>> +static int catpt_dai_apply_usettings(struct snd_soc_dai *dai,
>> + struct catpt_stream_runtime *stream)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct snd_soc_component *component = dai->component;
>> + struct snd_kcontrol *pos, *kctl = NULL;
>> + const char *name;
>> + int ret;
>> + u32 id = stream->info.stream_hw_id;
>> +
>> + /* only selected streams have individual controls */
>> + switch (id) {
>> + case CATPT_PIN_ID_OFFLOAD1:
>> + name = "Media0 Playback Volume";
>> + break;
>> + case CATPT_PIN_ID_OFFLOAD2:
>> + name = "Media1 Playback Volume";
>> + break;
>> + case CATPT_PIN_ID_CAPTURE1:
>> + name = "Mic Capture Volume";
>> + break;
>> + case CATPT_PIN_ID_REFERENCE:
>> + name = "Loopback Mute";
>> + break;
>> + default:
>> + return 0;
>> + };
>> +
>> + list_for_each_entry(pos, &component->card->snd_card->controls, list) {
>> + if (pos->private_data == component &&
>> + !strncmp(name, pos->id.name, sizeof(pos->id.name))) {
>> + kctl = pos;
>> + break;
>> + }
>> + }
>> + if (!kctl)
>> + return -ENOENT;
>> +
>> + if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK)
>> + return catpt_set_dspvol(cdev, id, (long *)kctl->private_value);
>> + ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)kctl->private_value);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> + return 0;
>> +}
>> +
>> +static int catpt_dai_prepare(struct snd_pcm_substream *substream,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_runtime *stream;
>> + int ret;
>> +
>> + stream = snd_soc_dai_get_dma_data(dai, substream);
>> + if (stream->prepared)
>> + return 0;
>> +
>> + ret = catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> +
>> + ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> +
>> + ret = catpt_dsp_update_lpclock(cdev);
>> + if (ret)
>> + return ret;
>> +
>> + ret = catpt_dai_apply_usettings(dai, stream);
>> + if (ret)
>> + return ret;
>> +
>> + stream->prepared = true;
>> + return 0;
>> +}
>> +
>> +static int catpt_dai_trigger(struct snd_pcm_substream *substream, int cmd,
>> + struct snd_soc_dai *dai)
>> +{
>> + struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
>> + struct catpt_stream_runtime *stream;
>> + struct snd_pcm_runtime *runtime = substream->runtime;
>> + snd_pcm_uframes_t pos;
>> + int ret;
>> +
>> + stream = snd_soc_dai_get_dma_data(dai, substream);
>> +
>> + switch (cmd) {
>> + case SNDRV_PCM_TRIGGER_START:
>> + /* only offload is set_write_pos driven */
>> + if (stream->template->type != CATPT_STRM_TYPE_RENDER)
>> + goto resume_stream;
>> +
>> + pos = frames_to_bytes(runtime, runtime->start_threshold);
>> + /*
>> + * Dsp operates on buffer halves, thus max 2x set_write_pos
>> + * (entire buffer filled) prior to stream start.
>> + */
>> + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
>> + pos, false, false);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> + fallthrough;
>> + case SNDRV_PCM_TRIGGER_RESUME:
>> + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>> + resume_stream:
>> + ret = catpt_ipc_resume_stream(cdev, stream->info.stream_hw_id);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> + break;
>> +
>> + case SNDRV_PCM_TRIGGER_STOP:
>> + stream->prepared = false;
>> + catpt_dsp_update_lpclock(cdev);
>> + fallthrough;
>> + case SNDRV_PCM_TRIGGER_SUSPEND:
>> + case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>> + ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
>> + if (ret)
>> + return CATPT_IPC_ERROR(ret);
>> + break;
>> +
>> + default:
>> + break;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +void catpt_stream_update_position(struct catpt_dev *cdev,
>> + struct catpt_stream_runtime *stream,
>> + struct catpt_notify_position *pos)
>> +{
>> + struct snd_pcm_substream *substream = stream->substream;
>> + struct snd_pcm_runtime *r = substream->runtime;
>> + snd_pcm_uframes_t dsppos, newpos;
>> + int ret;
>> +
>> + dsppos = bytes_to_frames(r, pos->stream_position);
>> +
>> + /* only offload is set_write_pos driven */
>> + if (stream->template->type != CATPT_STRM_TYPE_RENDER)
>> + goto exit;
>> +
>> + if (dsppos >= r->buffer_size / 2)
>> + newpos = r->buffer_size / 2;
>> + else
>> + newpos = 0;
>> + /*
>> + * Dsp operates on buffer halves, thus on every notify position
>> + * (buffer half consumed) update wp to allow stream progression.
>> + */
>> + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
>> + frames_to_bytes(r, newpos),
>> + false, false);
>> + if (ret) {
>> + dev_err(cdev->dev, "update position for stream %d failed: %d\n",
>> + stream->info.stream_hw_id, ret);
>> + return;
>> + }
>> +exit:
>> + snd_pcm_period_elapsed(substream);
>> +}
>> +
>> +#define CATPT_BUFFER_MAX_SIZE 76800 /* 200ms native format */
>
> I don't understand 'native format'. Please, give clearer comment, like
>
> /* 200ms for 8 8-bit channels at 48kHz (native format) */
>
Sure, will expand the description. Just so you know, it's 2/24(32)/48kHz
for LPT/WPT solution.
>> +#define CATPT_PCM_PERIODS_MAX 4
>> +#define CATPT_PCM_PERIODS_MIN 2
>> +
...
>
>> +#define DSP_VOLUME_MAX 0x7FFFFFFF /* 0db */
>
> S32_MAX ?
>
Ack.
>> +static const u32 volume_map[] = {
>> + DSP_VOLUME_MAX >> 30,
>> + DSP_VOLUME_MAX >> 29,
>> + DSP_VOLUME_MAX >> 28,
>> + DSP_VOLUME_MAX >> 27,
>> + DSP_VOLUME_MAX >> 26,
>> + DSP_VOLUME_MAX >> 25,
>> + DSP_VOLUME_MAX >> 24,
>> + DSP_VOLUME_MAX >> 23,
>> + DSP_VOLUME_MAX >> 22,
>> + DSP_VOLUME_MAX >> 21,
>> + DSP_VOLUME_MAX >> 20,
>> + DSP_VOLUME_MAX >> 19,
>> + DSP_VOLUME_MAX >> 18,
>> + DSP_VOLUME_MAX >> 17,
>> + DSP_VOLUME_MAX >> 16,
>> + DSP_VOLUME_MAX >> 15,
>> + DSP_VOLUME_MAX >> 14,
>> + DSP_VOLUME_MAX >> 13,
>> + DSP_VOLUME_MAX >> 12,
>> + DSP_VOLUME_MAX >> 11,
>> + DSP_VOLUME_MAX >> 10,
>> + DSP_VOLUME_MAX >> 9,
>> + DSP_VOLUME_MAX >> 8,
>> + DSP_VOLUME_MAX >> 7,
>> + DSP_VOLUME_MAX >> 6,
>> + DSP_VOLUME_MAX >> 5,
>> + DSP_VOLUME_MAX >> 4,
>> + DSP_VOLUME_MAX >> 3,
>> + DSP_VOLUME_MAX >> 2,
>> + DSP_VOLUME_MAX >> 1,
>> + DSP_VOLUME_MAX >> 0,
>> +};
>
> Why do you have table? Does it have any gaps? Why can't be formula used?
>
No gaps, just a lookup table. As catpt aims to keep 100% backward
compatibility with what sound/soc/intel/haswell solution exposes, it is
also useful for filling kcontrol info: catpt_volume_info() function.
>> +static u32 ctlvol_to_dspvol(u32 value)
>> +{
>> + if (value >= ARRAY_SIZE(volume_map))
>> + value = 0;
>> + return volume_map[value];
>> +}
>> +
>> +static u32 dspvol_to_ctlvol(u32 volume)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < ARRAY_SIZE(volume_map); i++)
>> + if (volume_map[i] >= volume)
>> + return i;
>
>> + return i - 1;
>
> It seems okay in this case, but in general the code looks dangerous (for
> example, if ARRAY_SIZE is 0).
>
Agreed, although volume_map will never change so I've decided to drop
additional safety.
...
>> +}
>> +
>> +static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol)
>> +{
>> + bool all_equal = true;
>> + u32 dspvol;
>> + int ret, i;
>
>> + for (i = 1; all_equal && i < CATPT_CHANNELS_MAX; i++)
>> + all_equal = (ctlvol[i] == ctlvol[0]);
>
> for (i = 1; i < CATPT_CHANNELS_MAX; i++)
> if (ctlvol[i] != ctlvol[0])
> break;
>
>> + if (all_equal) {
>
> if (i == _MAX)
>
> and one variable less.
>
Keen eye, thanks for this tip! Ack.
...
>> + dspvol = ctlvol_to_dspvol(ctlvol[0]);
>> +
>> + ret = catpt_ipc_set_volume(cdev, stream_id,
>> + CATPT_ALL_CHANNELS_MASK, dspvol,
>> + 0, CATPT_AUDIO_CURVE_NONE);
>> + } else {
>> + for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
>> + dspvol = ctlvol_to_dspvol(ctlvol[i]);
>> +
>> + ret = catpt_ipc_set_volume(cdev, stream_id,
>> + i, dspvol,
>> + 0, CATPT_AUDIO_CURVE_NONE);
>
>> + if (ret)
>> + goto exit;
>
> Don't see necessity of this in this patch...
>
>> + }
>> + }
>
>> +exit:
>
> ...neither this.
>
Believe simple 'break' suffices. 'exit' label becomes redundant too.
Thanks,
Czarek
More information about the Alsa-devel
mailing list