[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