diff mbox series

[v9,06/14] ASoC: Intel: catpt: PCM operations

Message ID 20200926085910.21948-7-cezary.rojewski@intel.com
State Accepted
Commit a126750fc86546bf86c7536923a77cfecc15e5e3
Headers show
Series ASoC: Intel: Catpt - Lynx and Wildcat point | expand

Commit Message

Cezary Rojewski Sept. 26, 2020, 8:59 a.m. UTC
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.

Implementation covers all available pin types:
- system playback and capture
- two offload streams
- loopback (reference)
- bluetooth playback and capture

PCM DAI operations differentiate between those pins as some (mainly
offload) are to be handled differently - DSP expects wp updates on each
notify_position notification.

System playback has no volume control capability as it is routed to
mixer stream directly. Other primary streams - capture and two offloads
- offer individual volume controls.

Compared to sound/soc/intel/haswell this configures SSP device format
automatically on pcm creation.

Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
 sound/soc/intel/catpt/core.h   |    8 +
 sound/soc/intel/catpt/ipc.c    |   37 +
 sound/soc/intel/catpt/loader.c |    6 +
 sound/soc/intel/catpt/pcm.c    | 1175 ++++++++++++++++++++++++++++++++
 4 files changed, 1226 insertions(+)
 create mode 100644 sound/soc/intel/catpt/pcm.c

Comments

Andy Shevchenko Sept. 29, 2020, 11:33 a.m. UTC | #1
On Sat, Sep 26, 2020 at 10:59:02AM +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.
> 
> Implementation covers all available pin types:
> - system playback and capture
> - two offload streams
> - loopback (reference)
> - bluetooth playback and capture
> 
> PCM DAI operations differentiate between those pins as some (mainly
> offload) are to be handled differently - DSP expects wp updates on each
> notify_position notification.
> 
> System playback has no volume control capability as it is routed to
> mixer stream directly. Other primary streams - capture and two offloads
> - offer individual volume controls.
> 
> Compared to sound/soc/intel/haswell this configures SSP device format
> automatically on pcm creation.

Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

Some thoughts below, but it shouldn't prevent v10 to appear.

> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
> ---
>  sound/soc/intel/catpt/core.h   |    8 +
>  sound/soc/intel/catpt/ipc.c    |   37 +
>  sound/soc/intel/catpt/loader.c |    6 +
>  sound/soc/intel/catpt/pcm.c    | 1175 ++++++++++++++++++++++++++++++++
>  4 files changed, 1226 insertions(+)
>  create mode 100644 sound/soc/intel/catpt/pcm.c
> 
> diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h
> index 260e5ae94a2c..a29b4c0232cb 100644
> --- a/sound/soc/intel/catpt/core.h
> +++ b/sound/soc/intel/catpt/core.h
> @@ -175,4 +175,12 @@ struct catpt_stream_runtime {
>  	struct list_head node;
>  };
>  
> +int catpt_register_plat_component(struct catpt_dev *cdev);
> +void catpt_stream_update_position(struct catpt_dev *cdev,
> +				  struct catpt_stream_runtime *stream,
> +				  struct catpt_notify_position *pos);
> +struct catpt_stream_runtime *
> +catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id);
> +int catpt_arm_stream_templates(struct catpt_dev *cdev);
> +
>  #endif
> diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c
> index 4af4de5ae265..f8761006de18 100644
> --- a/sound/soc/intel/catpt/ipc.c
> +++ b/sound/soc/intel/catpt/ipc.c
> @@ -137,6 +137,42 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
>  					  cdev->ipc.default_timeout);
>  }
>  
> +static void
> +catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg)
> +{
> +	struct catpt_stream_runtime *stream;
> +	struct catpt_notify_position pos;
> +	struct catpt_notify_glitch glitch;
> +
> +	stream = catpt_stream_find(cdev, msg.stream_hw_id);
> +	if (!stream) {
> +		dev_warn(cdev->dev, "notify %d for non-existent stream %d\n",
> +			 msg.notify_reason, msg.stream_hw_id);
> +		return;
> +	}
> +
> +	switch (msg.notify_reason) {
> +	case CATPT_NOTIFY_POSITION_CHANGED:
> +		memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos));
> +
> +		catpt_stream_update_position(cdev, stream, &pos);
> +		break;
> +
> +	case CATPT_NOTIFY_GLITCH_OCCURRED:
> +		memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch));
> +
> +		dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n",
> +			 glitch.type, glitch.presentation_pos,
> +			 glitch.write_pos);
> +		break;
> +
> +	default:
> +		dev_warn(cdev->dev, "unknown notification: %d received\n",
> +			 msg.notify_reason);
> +		break;
> +	}
> +}
> +
>  static void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header)
>  {
>  	struct catpt_ipc *ipc = &cdev->ipc;
> @@ -176,6 +212,7 @@ static void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header)
>  	case CATPT_GLB_STREAM_MESSAGE:
>  		switch (msg.stream_msg_type) {
>  		case CATPT_STRM_NOTIFICATION:
> +			catpt_dsp_notify_stream(cdev, msg);
>  			break;
>  		default:
>  			catpt_dsp_copy_rx(cdev, header);
> diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c
> index 473e842e9901..8a5f20abcadb 100644
> --- a/sound/soc/intel/catpt/loader.c
> +++ b/sound/soc/intel/catpt/loader.c
> @@ -658,6 +658,12 @@ int catpt_first_boot_firmware(struct catpt_dev *cdev)
>  	if (ret)
>  		return CATPT_IPC_ERROR(ret);
>  
> +	ret = catpt_arm_stream_templates(cdev);
> +	if (ret) {
> +		dev_err(cdev->dev, "arm templates failed: %d\n", ret);
> +		return ret;
> +	}
> +
>  	/* update dram pg for scratch and restricted regions */
>  	catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
>  
> diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c
> new file mode 100644
> index 000000000000..cf34f0b596d2
> --- /dev/null
> +++ b/sound/soc/intel/catpt/pcm.c
> @@ -0,0 +1,1175 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright(c) 2020 Intel Corporation. All rights reserved.
> +//
> +// Author: Cezary Rojewski <cezary.rojewski@intel.com>
> +//
> +
> +#include <linux/pm_runtime.h>
> +#include <sound/soc.h>
> +#include <sound/pcm_params.h>
> +#include <uapi/sound/tlv.h>
> +#include "core.h"
> +#include "messages.h"
> +
> +struct catpt_stream_template {
> +	enum catpt_path_id path_id;
> +	enum catpt_stream_type type;
> +	u32 persistent_size;
> +	u8 num_entries;
> +	struct catpt_module_entry entries[];
> +};
> +
> +static struct catpt_stream_template system_pb = {
> +	.path_id = CATPT_PATH_SSP0_OUT,
> +	.type = CATPT_STRM_TYPE_SYSTEM,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_PCM_SYSTEM, 0 }},
> +};
> +
> +static struct catpt_stream_template system_cp = {
> +	.path_id = CATPT_PATH_SSP0_IN,
> +	.type = CATPT_STRM_TYPE_CAPTURE,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_PCM_CAPTURE, 0 }},
> +};
> +
> +static struct catpt_stream_template offload_pb = {
> +	.path_id = CATPT_PATH_SSP0_OUT,
> +	.type = CATPT_STRM_TYPE_RENDER,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_PCM, 0 }},
> +};
> +
> +static struct catpt_stream_template loopback_cp = {
> +	.path_id = CATPT_PATH_SSP0_OUT,
> +	.type = CATPT_STRM_TYPE_LOOPBACK,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_PCM_REFERENCE, 0 }},
> +};
> +
> +static struct catpt_stream_template bluetooth_pb = {
> +	.path_id = CATPT_PATH_SSP1_OUT,
> +	.type = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_BLUETOOTH_RENDER, 0 }},
> +};
> +
> +static struct catpt_stream_template bluetooth_cp = {
> +	.path_id = CATPT_PATH_SSP1_IN,
> +	.type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE,
> +	.num_entries = 1,
> +	.entries = {{ CATPT_MODID_BLUETOOTH_CAPTURE, 0 }},
> +};
> +
> +static struct catpt_stream_template *catpt_topology[] = {
> +	[CATPT_STRM_TYPE_RENDER]		= &offload_pb,
> +	[CATPT_STRM_TYPE_SYSTEM]		= &system_pb,
> +	[CATPT_STRM_TYPE_CAPTURE]		= &system_cp,
> +	[CATPT_STRM_TYPE_LOOPBACK]		= &loopback_cp,
> +	[CATPT_STRM_TYPE_BLUETOOTH_RENDER]	= &bluetooth_pb,
> +	[CATPT_STRM_TYPE_BLUETOOTH_CAPTURE]	= &bluetooth_cp,
> +};
> +
> +static struct catpt_stream_template *
> +catpt_get_stream_template(struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtm = substream->private_data;
> +	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
> +	enum catpt_stream_type type;
> +
> +	type = cpu_dai->driver->id;
> +
> +	/* account for capture in bidirectional dais */
> +	switch (type) {
> +	case CATPT_STRM_TYPE_SYSTEM:
> +		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
> +			type = CATPT_STRM_TYPE_CAPTURE;
> +		break;
> +	case CATPT_STRM_TYPE_BLUETOOTH_RENDER:
> +		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
> +			type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE;
> +		break;
> +	default:
> +		break;
> +	};
> +
> +	return catpt_topology[type];
> +}
> +
> +struct catpt_stream_runtime *
> +catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
> +{
> +	struct catpt_stream_runtime *pos, *result = NULL;
> +
> +	spin_lock(&cdev->list_lock);
> +	list_for_each_entry(pos, &cdev->stream_list, node) {
> +		if (pos->info.stream_hw_id == stream_hw_id) {
> +			result = pos;
> +			break;
> +		}
> +	}
> +
> +	spin_unlock(&cdev->list_lock);
> +	return result;
> +}
> +
> +static u32 catpt_stream_read_position(struct catpt_dev *cdev,
> +				      struct catpt_stream_runtime *stream)
> +{
> +	u32 pos;
> +
> +	memcpy_fromio(&pos, cdev->lpe_ba + stream->info.read_pos_regaddr,
> +		      sizeof(pos));
> +	return pos;
> +}
> +
> +static u32 catpt_stream_volume(struct catpt_dev *cdev,
> +			       struct catpt_stream_runtime *stream, u32 channel)
> +{
> +	u32 volume, offset;
> +
> +	if (channel >= CATPT_CHANNELS_MAX)
> +		channel = 0;
> +
> +	offset = stream->info.volume_regaddr[channel];
> +	memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
> +	return volume;
> +}
> +
> +static u32 catpt_mixer_volume(struct catpt_dev *cdev,
> +			      struct catpt_mixer_stream_info *info, u32 channel)
> +{
> +	u32 volume, offset;
> +
> +	if (channel >= CATPT_CHANNELS_MAX)
> +		channel = 0;
> +
> +	offset = info->volume_regaddr[channel];
> +	memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
> +	return volume;
> +}
> +
> +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 = PFN_DOWN(snd_sgbuf_get_addr(databuf, i * PAGE_SIZE));
> +		/* 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 GENMASK(31, 4) | CATPT_CHANNEL_CENTER;

> +	case CATPT_CHANNEL_CONFIG_DUAL_MONO:
> +		return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
> +				      | (CATPT_CHANNEL_LEFT << 4);

Now we know why it used to be at the end of the switch-case. Up to you to leave
here or move back.

> +	case CATPT_CHANNEL_CONFIG_STEREO:
> +		return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
> +				      | (CATPT_CHANNEL_RIGHT << 4);
> +
> +	case CATPT_CHANNEL_CONFIG_2_POINT_1:
> +		return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
> +				       | (CATPT_CHANNEL_RIGHT << 4)
> +				       | (CATPT_CHANNEL_LFE << 8);
> +
> +	case CATPT_CHANNEL_CONFIG_3_POINT_0:
> +		return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
> +				       | (CATPT_CHANNEL_CENTER << 4)
> +				       | (CATPT_CHANNEL_RIGHT << 8);
> +
> +	case CATPT_CHANNEL_CONFIG_3_POINT_1:
> +		return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
> +				       | (CATPT_CHANNEL_CENTER << 4)
> +				       | (CATPT_CHANNEL_RIGHT << 8)
> +				       | (CATPT_CHANNEL_LFE << 12);
> +
> +	case CATPT_CHANNEL_CONFIG_QUATRO:
> +		return GENMASK(31, 16) | 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 GENMASK(31, 16) | CATPT_CHANNEL_LEFT
> +				       | (CATPT_CHANNEL_CENTER << 4)
> +				       | (CATPT_CHANNEL_RIGHT << 8)
> +				       | (CATPT_CHANNEL_CENTER_SURROUND << 12);
> +
> +	case CATPT_CHANNEL_CONFIG_5_POINT_0:
> +		return GENMASK(31, 20) | 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 GENMASK(31, 24) | 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);
> +
> +	default:
> +		return U32_MAX;
> +	}
> +}
> +
> +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 = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0));
> +
> +	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;

Hmm... Seems we lack of something like

/*
 * list_entry_is_head() - Test if the entry points to the head of the list
 * ...
 */
#define list_entry_is_head(pos, head, member)	(&pos->member == head)

Up to you to consider. In above case we may drop pos from above loop and use kctl directly

	struct snd_kcontrol *kctl;
	...
	list_for_each_entry(kctl, &component->card->snd_card->controls, list) {
		if (kctl->private_data == component &&
		    !strncmp(name, kctl->id.name, sizeof(kctl->id.name)))
			break;
	}
	if (list_entry_is_head(kctl, &component->card->snd_card->controls, list))
		return -ENOENT;

Note, you usually don't need any special Acks to modify list.h this way,
although Andrew's name pops up WRT library stuff.

> +	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);
> +}
> +
> +/* 200 ms for 2 32-bit channels at 48kHz (native format) */
> +#define CATPT_BUFFER_MAX_SIZE	76800
> +#define CATPT_PCM_PERIODS_MAX	4
> +#define CATPT_PCM_PERIODS_MIN	2
> +
> +static const struct snd_pcm_hardware catpt_pcm_hardware = {
> +	.info			= SNDRV_PCM_INFO_MMAP |
> +				  SNDRV_PCM_INFO_MMAP_VALID |
> +				  SNDRV_PCM_INFO_INTERLEAVED |
> +				  SNDRV_PCM_INFO_PAUSE |
> +				  SNDRV_PCM_INFO_RESUME |
> +				  SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
> +	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
> +				  SNDRV_PCM_FMTBIT_S24_LE |
> +				  SNDRV_PCM_FMTBIT_S32_LE,
> +	.period_bytes_min	= PAGE_SIZE,
> +	.period_bytes_max	= CATPT_BUFFER_MAX_SIZE / CATPT_PCM_PERIODS_MIN,
> +	.periods_min		= CATPT_PCM_PERIODS_MIN,
> +	.periods_max		= CATPT_PCM_PERIODS_MAX,
> +	.buffer_bytes_max	= CATPT_BUFFER_MAX_SIZE,
> +};
> +
> +static int catpt_component_pcm_construct(struct snd_soc_component *component,
> +					 struct snd_soc_pcm_runtime *rtm)
> +{
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +
> +	snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG,
> +				       cdev->dev,
> +				       catpt_pcm_hardware.buffer_bytes_max,
> +				       catpt_pcm_hardware.buffer_bytes_max);
> +
> +	return 0;
> +}
> +
> +static int catpt_component_open(struct snd_soc_component *component,
> +				struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtm = substream->private_data;
> +
> +	if (rtm->dai_link->no_pcm)
> +		return 0;
> +	snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware);
> +	return 0;
> +}
> +
> +static snd_pcm_uframes_t
> +catpt_component_pointer(struct snd_soc_component *component,
> +			struct snd_pcm_substream *substream)
> +{
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	struct catpt_stream_runtime *stream;
> +	struct snd_soc_pcm_runtime *rtm = substream->private_data;
> +	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
> +	u32 pos;
> +
> +	if (rtm->dai_link->no_pcm)
> +		return 0;
> +
> +	stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
> +	pos = catpt_stream_read_position(cdev, stream);
> +
> +	return bytes_to_frames(substream->runtime, pos);
> +}
> +
> +static const struct snd_soc_dai_ops catpt_fe_dai_ops = {
> +	.startup = catpt_dai_startup,
> +	.shutdown = catpt_dai_shutdown,
> +	.hw_params = catpt_dai_hw_params,
> +	.hw_free = catpt_dai_hw_free,
> +	.prepare = catpt_dai_prepare,
> +	.trigger = catpt_dai_trigger,
> +};
> +
> +static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm,
> +			     struct snd_soc_dai *dai)
> +{
> +	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0);
> +	struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
> +	struct catpt_ssp_device_format devfmt;
> +	int ret;
> +
> +	devfmt.iface = dai->driver->id;
> +	devfmt.channels = codec_dai->driver->capture.channels_max;
> +
> +	switch (devfmt.iface) {
> +	case CATPT_SSP_IFACE_0:
> +		devfmt.mclk = CATPT_MCLK_FREQ_24_MHZ;
> +
> +		switch (devfmt.channels) {
> +		case 4:
> +			devfmt.mode = CATPT_SSP_MODE_TDM_PROVIDER;
> +			devfmt.clock_divider = 4;
> +			break;
> +		case 2:
> +		default:
> +			devfmt.mode = CATPT_SSP_MODE_I2S_PROVIDER;
> +			devfmt.clock_divider = 9;
> +			break;
> +		}
> +		break;
> +
> +	case CATPT_SSP_IFACE_1:
> +		devfmt.mclk = CATPT_MCLK_OFF;
> +		devfmt.mode = CATPT_SSP_MODE_I2S_CONSUMER;
> +		devfmt.clock_divider = 0;
> +		break;
> +	}
> +
> +	ret = catpt_ipc_set_device_format(cdev, &devfmt);
> +	if (ret)
> +		return CATPT_IPC_ERROR(ret);
> +
> +	/* store device format set for given SSP */
> +	memcpy(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt));
> +	return 0;
> +}
> +
> +static struct snd_soc_dai_driver dai_drivers[] = {
> +/* FE DAIs */
> +{
> +	.name  = "System Pin",
> +	.id = CATPT_STRM_TYPE_SYSTEM,
> +	.ops = &catpt_fe_dai_ops,
> +	.playback = {
> +		.stream_name = "System Playback",
> +		.channels_min = 2,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_48000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
> +	},
> +	.capture = {
> +		.stream_name = "Analog Capture",
> +		.channels_min = 2,
> +		.channels_max = 4,
> +		.rates = SNDRV_PCM_RATE_48000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
> +	},
> +},
> +{
> +	.name  = "Offload0 Pin",
> +	.id = CATPT_STRM_TYPE_RENDER,
> +	.ops = &catpt_fe_dai_ops,
> +	.playback = {
> +		.stream_name = "Offload0 Playback",
> +		.channels_min = 2,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_8000_192000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
> +	},
> +},
> +{
> +	.name  = "Offload1 Pin",
> +	.id = CATPT_STRM_TYPE_RENDER,
> +	.ops = &catpt_fe_dai_ops,
> +	.playback = {
> +		.stream_name = "Offload1 Playback",
> +		.channels_min = 2,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_8000_192000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
> +	},
> +},
> +{
> +	.name  = "Loopback Pin",
> +	.id = CATPT_STRM_TYPE_LOOPBACK,
> +	.ops = &catpt_fe_dai_ops,
> +	.capture = {
> +		.stream_name = "Loopback Capture",
> +		.channels_min = 2,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_48000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
> +	},
> +},
> +{
> +	.name  = "Bluetooth Pin",
> +	.id = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
> +	.ops = &catpt_fe_dai_ops,
> +	.playback = {
> +		.stream_name = "Bluetooth Playback",
> +		.channels_min = 1,
> +		.channels_max = 1,
> +		.rates = SNDRV_PCM_RATE_8000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE,
> +	},
> +	.capture = {
> +		.stream_name = "Bluetooth Capture",
> +		.channels_min = 1,
> +		.channels_max = 1,
> +		.rates = SNDRV_PCM_RATE_8000,
> +		.formats = SNDRV_PCM_FMTBIT_S16_LE,
> +	},
> +},
> +/* BE DAIs */
> +{
> +	.name = "ssp0-port",
> +	.id = CATPT_SSP_IFACE_0,
> +	.pcm_new = catpt_dai_pcm_new,
> +	.playback = {
> +		.channels_min = 1,
> +		.channels_max = 8,
> +	},
> +	.capture = {
> +		.channels_min = 1,
> +		.channels_max = 8,
> +	},
> +},
> +{
> +	.name = "ssp1-port",
> +	.id = CATPT_SSP_IFACE_1,
> +	.pcm_new = catpt_dai_pcm_new,
> +	.playback = {
> +		.channels_min = 1,
> +		.channels_max = 8,
> +	},
> +	.capture = {
> +		.channels_min = 1,
> +		.channels_max = 8,
> +	},
> +},
> +};
> +
> +#define DSP_VOLUME_MAX		S32_MAX /* 0db */
> +#define DSP_VOLUME_STEP_MAX	30
> +
> +static u32 ctlvol_to_dspvol(u32 value)
> +{
> +	if (value > DSP_VOLUME_STEP_MAX)
> +		value = 0;
> +	return DSP_VOLUME_MAX >> (DSP_VOLUME_STEP_MAX - value);
> +}
> +
> +static u32 dspvol_to_ctlvol(u32 volume)
> +{
> +	if (volume > DSP_VOLUME_MAX)
> +		return DSP_VOLUME_STEP_MAX;
> +	return volume ? __fls(volume) : 0;
> +}
> +
> +static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol)
> +{
> +	u32 dspvol;
> +	int ret, i;

> +	for (i = 1; i < CATPT_CHANNELS_MAX; i++)
> +		if (ctlvol[i] != ctlvol[0])
> +			break;

Maybe we need something like memcpy_long() but this is another story...

> +	if (i == CATPT_CHANNELS_MAX) {
> +		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)
> +				break;
> +		}
> +	}
> +
> +	if (ret)
> +		return CATPT_IPC_ERROR(ret);
> +	return 0;
> +}
> +
> +static int catpt_volume_info(struct snd_kcontrol *kcontrol,
> +			     struct snd_ctl_elem_info *uinfo)
> +{
> +	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
> +	uinfo->count = CATPT_CHANNELS_MAX;
> +	uinfo->value.integer.min = 0;
> +	uinfo->value.integer.max = DSP_VOLUME_STEP_MAX;
> +	return 0;
> +}
> +
> +static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component =
> +		snd_soc_kcontrol_component(kcontrol);
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	u32 dspvol;
> +	int i;
> +
> +	pm_runtime_get_sync(cdev->dev);
> +
> +	for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
> +		dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i);
> +		ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
> +	}
> +
> +	pm_runtime_mark_last_busy(cdev->dev);
> +	pm_runtime_put_autosuspend(cdev->dev);
> +
> +	return 0;
> +}
> +
> +static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component =
> +		snd_soc_kcontrol_component(kcontrol);
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	int ret;
> +
> +	pm_runtime_get_sync(cdev->dev);
> +
> +	ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id,
> +			       ucontrol->value.integer.value);
> +
> +	pm_runtime_mark_last_busy(cdev->dev);
> +	pm_runtime_put_autosuspend(cdev->dev);
> +
> +	return ret;
> +}
> +
> +static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol,
> +				   struct snd_ctl_elem_value *ucontrol,
> +				   enum catpt_pin_id pin_id)
> +{
> +	struct snd_soc_component *component =
> +		snd_soc_kcontrol_component(kcontrol);
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	struct catpt_stream_runtime *stream;
> +	long *ctlvol = (long *)kcontrol->private_value;
> +	u32 dspvol;
> +	int i;
> +
> +	stream = catpt_stream_find(cdev, pin_id);
> +	if (!stream) {

> +		for (i = 0; i < CATPT_CHANNELS_MAX; i++)
> +			ucontrol->value.integer.value[i] = ctlvol[i];

...and ditto many times here :-)

> +		return 0;
> +	}
> +
> +	pm_runtime_get_sync(cdev->dev);
> +
> +	for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
> +		dspvol = catpt_stream_volume(cdev, stream, i);
> +		ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
> +	}
> +
> +	pm_runtime_mark_last_busy(cdev->dev);
> +	pm_runtime_put_autosuspend(cdev->dev);
> +
> +	return 0;
> +}
> +
> +static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol,
> +				   struct snd_ctl_elem_value *ucontrol,
> +				   enum catpt_pin_id pin_id)
> +{
> +	struct snd_soc_component *component =
> +		snd_soc_kcontrol_component(kcontrol);
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	struct catpt_stream_runtime *stream;
> +	long *ctlvol = (long *)kcontrol->private_value;
> +	int ret, i;
> +
> +	stream = catpt_stream_find(cdev, pin_id);
> +	if (!stream) {
> +		for (i = 0; i < CATPT_CHANNELS_MAX; i++)
> +			ctlvol[i] = ucontrol->value.integer.value[i];
> +		return 0;
> +	}
> +
> +	pm_runtime_get_sync(cdev->dev);
> +
> +	ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id,
> +			       ucontrol->value.integer.value);
> +
> +	pm_runtime_mark_last_busy(cdev->dev);
> +	pm_runtime_put_autosuspend(cdev->dev);
> +
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < CATPT_CHANNELS_MAX; i++)
> +		ctlvol[i] = ucontrol->value.integer.value[i];
> +	return 0;
> +}
> +
> +static int catpt_offload1_volume_get(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
> +}
> +
> +static int catpt_offload1_volume_put(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
> +}
> +
> +static int catpt_offload2_volume_get(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
> +}
> +
> +static int catpt_offload2_volume_put(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
> +}
> +
> +static int catpt_capture_volume_get(struct snd_kcontrol *kctl,
> +				    struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
> +}
> +
> +static int catpt_capture_volume_put(struct snd_kcontrol *kctl,
> +				    struct snd_ctl_elem_value *uctl)
> +{
> +	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
> +}
> +
> +static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol,
> +				     struct snd_ctl_elem_value *ucontrol)
> +{
> +	ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value;
> +	return 0;
> +}
> +
> +static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol,
> +				     struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component =
> +		snd_soc_kcontrol_component(kcontrol);
> +	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
> +	struct catpt_stream_runtime *stream;
> +	bool mute;
> +	int ret;
> +
> +	mute = (bool)ucontrol->value.integer.value[0];
> +	stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE);
> +	if (!stream) {
> +		*(bool *)kcontrol->private_value = mute;
> +		return 0;
> +	}
> +
> +	pm_runtime_get_sync(cdev->dev);
> +
> +	ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute);
> +
> +	pm_runtime_mark_last_busy(cdev->dev);
> +	pm_runtime_put_autosuspend(cdev->dev);
> +
> +	if (ret)
> +		return CATPT_IPC_ERROR(ret);
> +
> +	*(bool *)kcontrol->private_value = mute;
> +	return 0;
> +}
> +
> +static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	return 0;
> +}
> +
> +static int catpt_waves_switch_put(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	return 0;
> +}
> +
> +static int catpt_waves_param_get(struct snd_kcontrol *kcontrol,
> +				 unsigned int __user *bytes,
> +				 unsigned int size)
> +{
> +	return 0;
> +}
> +
> +static int catpt_waves_param_put(struct snd_kcontrol *kcontrol,
> +				 const unsigned int __user *bytes,
> +				 unsigned int size)
> +{
> +	return 0;
> +}
> +
> +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1);
> +
> +#define CATPT_VOLUME_CTL(kname, sname) \
> +{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
> +	.name = (kname), \
> +	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
> +		  SNDRV_CTL_ELEM_ACCESS_READWRITE, \
> +	.info = catpt_volume_info, \
> +	.get = catpt_##sname##_volume_get, \
> +	.put = catpt_##sname##_volume_put, \
> +	.tlv.p = catpt_volume_tlv, \
> +	.private_value = (unsigned long) \
> +		&(long[CATPT_CHANNELS_MAX]) {0} }
> +
> +static const struct snd_kcontrol_new component_kcontrols[] = {
> +/* Master volume (mixer stream) */
> +CATPT_VOLUME_CTL("Master Playback Volume", mixer),
> +/* Individual volume controls for offload and capture */
> +CATPT_VOLUME_CTL("Media0 Playback Volume", offload1),
> +CATPT_VOLUME_CTL("Media1 Playback Volume", offload2),
> +CATPT_VOLUME_CTL("Mic Capture Volume", capture),
> +SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0},
> +		    catpt_loopback_switch_get, catpt_loopback_switch_put),
> +/* Enable or disable WAVES module */
> +SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
> +		    catpt_waves_switch_get, catpt_waves_switch_put),
> +/* WAVES module parameter control */
> +SND_SOC_BYTES_TLV("Waves Set Param", 128,
> +		  catpt_waves_param_get, catpt_waves_param_put),
> +};
> +
> +static const struct snd_soc_dapm_widget component_widgets[] = {
> +	SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
> +
> +	SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
> +};
> +
> +static const struct snd_soc_dapm_route component_routes[] = {
> +	{"Playback VMixer", NULL, "System Playback"},
> +	{"Playback VMixer", NULL, "Offload0 Playback"},
> +	{"Playback VMixer", NULL, "Offload1 Playback"},
> +
> +	{"SSP0 CODEC OUT", NULL, "Playback VMixer"},
> +
> +	{"Analog Capture", NULL, "SSP0 CODEC IN"},
> +	{"Loopback Capture", NULL, "SSP0 CODEC IN"},
> +
> +	{"SSP1 BT OUT", NULL, "Bluetooth Playback"},
> +	{"Bluetooth Capture", NULL, "SSP1 BT IN"},
> +};
> +
> +static const struct snd_soc_component_driver catpt_comp_driver = {
> +	.name = "catpt-platform",
> +
> +	.pcm_construct = catpt_component_pcm_construct,
> +	.open = catpt_component_open,
> +	.pointer = catpt_component_pointer,
> +
> +	.controls = component_kcontrols,
> +	.num_controls = ARRAY_SIZE(component_kcontrols),
> +	.dapm_widgets = component_widgets,
> +	.num_dapm_widgets = ARRAY_SIZE(component_widgets),
> +	.dapm_routes = component_routes,
> +	.num_dapm_routes = ARRAY_SIZE(component_routes),
> +};
> +
> +int catpt_arm_stream_templates(struct catpt_dev *cdev)
> +{
> +	struct resource *res;
> +	u32 scratch_size = 0;
> +	int i, j;
> +
> +	for (i = 0; i < ARRAY_SIZE(catpt_topology); i++) {
> +		struct catpt_stream_template *template;
> +		struct catpt_module_entry *entry;
> +		struct catpt_module_type *type;
> +
> +		template = catpt_topology[i];
> +		template->persistent_size = 0;
> +
> +		for (j = 0; j < template->num_entries; j++) {
> +			entry = &template->entries[j];
> +			type = &cdev->modules[entry->module_id];
> +
> +			if (!type->loaded)
> +				return -ENOENT;
> +
> +			entry->entry_point = type->entry_point;
> +			template->persistent_size += type->persistent_size;
> +			if (type->scratch_size > scratch_size)
> +				scratch_size = type->scratch_size;
> +		}
> +	}
> +
> +	if (scratch_size) {
> +		/* allocate single scratch area for all modules */
> +		res = catpt_request_region(&cdev->dram, scratch_size);
> +		if (!res)
> +			return -EBUSY;
> +		cdev->scratch = res;
> +	}
> +
> +	return 0;
> +}
> +
> +int catpt_register_plat_component(struct catpt_dev *cdev)
> +{
> +	struct snd_soc_component *component;
> +	int ret;
> +
> +	component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL);
> +	if (!component)
> +		return -ENOMEM;
> +
> +	ret = snd_soc_component_initialize(component, &catpt_comp_driver,
> +					   cdev->dev);
> +	if (ret)
> +		return ret;
> +
> +	component->name = catpt_comp_driver.name;
> +	return snd_soc_add_component(component, dai_drivers,
> +				     ARRAY_SIZE(dai_drivers));
> +}
> -- 
> 2.17.1
>
Cezary Rojewski Sept. 29, 2020, 1:26 p.m. UTC | #2
On 2020-09-29 1:33 PM, Andy Shevchenko wrote:
> On Sat, Sep 26, 2020 at 10:59:02AM +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.
>>
>> Implementation covers all available pin types:
>> - system playback and capture
>> - two offload streams
>> - loopback (reference)
>> - bluetooth playback and capture
>>
>> PCM DAI operations differentiate between those pins as some (mainly
>> offload) are to be handled differently - DSP expects wp updates on each
>> notify_position notification.
>>
>> System playback has no volume control capability as it is routed to
>> mixer stream directly. Other primary streams - capture and two offloads
>> - offer individual volume controls.
>>
>> Compared to sound/soc/intel/haswell this configures SSP device format
>> automatically on pcm creation.
> 
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> 
> Some thoughts below, but it shouldn't prevent v10 to appear.
> 
>> Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>

...

>> +static u32 catpt_get_channel_map(enum catpt_channel_config config)
>> +{
>> +	switch (config) {
>> +	case CATPT_CHANNEL_CONFIG_MONO:
>> +		return GENMASK(31, 4) | CATPT_CHANNEL_CENTER;
> 
>> +	case CATPT_CHANNEL_CONFIG_DUAL_MONO:
>> +		return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
>> +				      | (CATPT_CHANNEL_LEFT << 4);
> 
> Now we know why it used to be at the end of the switch-case. Up to you to leave
> here or move back.
> 

Reverting to previous relocation in v10.

...

>> +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;
> 
> Hmm... Seems we lack of something like
> 
> /*
>   * list_entry_is_head() - Test if the entry points to the head of the list
>   * ...
>   */
> #define list_entry_is_head(pos, head, member)	(&pos->member == head)
> 
> Up to you to consider. In above case we may drop pos from above loop and use kctl directly
> 
> 	struct snd_kcontrol *kctl;
> 	...
> 	list_for_each_entry(kctl, &component->card->snd_card->controls, list) {
> 		if (kctl->private_data == component &&
> 		    !strncmp(name, kctl->id.name, sizeof(kctl->id.name)))
> 			break;
> 	}
> 	if (list_entry_is_head(kctl, &component->card->snd_card->controls, list))
> 		return -ENOENT;
> 
> Note, you usually don't need any special Acks to modify list.h this way,
> although Andrew's name pops up WRT library stuff.
> 

Since you've already proposed the solution to LKML, once it's added to
the kernel, it will be re-used in catpt just like resource_union().

Good ideas all around, Andy. It's rather surprising to me how many good
ideas came during the upstream process of this small driver.

Czarek
Andy Shevchenko Sept. 29, 2020, 4:46 p.m. UTC | #3
On Tue, Sep 29, 2020 at 01:26:51PM +0000, Rojewski, Cezary wrote:
> On 2020-09-29 1:33 PM, Andy Shevchenko wrote:

...

> Since you've already proposed the solution to LKML, once it's added to
> the kernel, it will be re-used in catpt just like resource_union().

Agree.

> Good ideas all around, Andy. It's rather surprising to me how many good
> ideas came during the upstream process of this small driver.

Thanks! That's how I consider to be a good Samaritan for the project.
diff mbox series

Patch

diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h
index 260e5ae94a2c..a29b4c0232cb 100644
--- a/sound/soc/intel/catpt/core.h
+++ b/sound/soc/intel/catpt/core.h
@@ -175,4 +175,12 @@  struct catpt_stream_runtime {
 	struct list_head node;
 };
 
+int catpt_register_plat_component(struct catpt_dev *cdev);
+void catpt_stream_update_position(struct catpt_dev *cdev,
+				  struct catpt_stream_runtime *stream,
+				  struct catpt_notify_position *pos);
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_arm_stream_templates(struct catpt_dev *cdev);
+
 #endif
diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c
index 4af4de5ae265..f8761006de18 100644
--- a/sound/soc/intel/catpt/ipc.c
+++ b/sound/soc/intel/catpt/ipc.c
@@ -137,6 +137,42 @@  int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
 					  cdev->ipc.default_timeout);
 }
 
+static void
+catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg)
+{
+	struct catpt_stream_runtime *stream;
+	struct catpt_notify_position pos;
+	struct catpt_notify_glitch glitch;
+
+	stream = catpt_stream_find(cdev, msg.stream_hw_id);
+	if (!stream) {
+		dev_warn(cdev->dev, "notify %d for non-existent stream %d\n",
+			 msg.notify_reason, msg.stream_hw_id);
+		return;
+	}
+
+	switch (msg.notify_reason) {
+	case CATPT_NOTIFY_POSITION_CHANGED:
+		memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos));
+
+		catpt_stream_update_position(cdev, stream, &pos);
+		break;
+
+	case CATPT_NOTIFY_GLITCH_OCCURRED:
+		memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch));
+
+		dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n",
+			 glitch.type, glitch.presentation_pos,
+			 glitch.write_pos);
+		break;
+
+	default:
+		dev_warn(cdev->dev, "unknown notification: %d received\n",
+			 msg.notify_reason);
+		break;
+	}
+}
+
 static void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header)
 {
 	struct catpt_ipc *ipc = &cdev->ipc;
@@ -176,6 +212,7 @@  static void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header)
 	case CATPT_GLB_STREAM_MESSAGE:
 		switch (msg.stream_msg_type) {
 		case CATPT_STRM_NOTIFICATION:
+			catpt_dsp_notify_stream(cdev, msg);
 			break;
 		default:
 			catpt_dsp_copy_rx(cdev, header);
diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c
index 473e842e9901..8a5f20abcadb 100644
--- a/sound/soc/intel/catpt/loader.c
+++ b/sound/soc/intel/catpt/loader.c
@@ -658,6 +658,12 @@  int catpt_first_boot_firmware(struct catpt_dev *cdev)
 	if (ret)
 		return CATPT_IPC_ERROR(ret);
 
+	ret = catpt_arm_stream_templates(cdev);
+	if (ret) {
+		dev_err(cdev->dev, "arm templates failed: %d\n", ret);
+		return ret;
+	}
+
 	/* update dram pg for scratch and restricted regions */
 	catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
 
diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c
new file mode 100644
index 000000000000..cf34f0b596d2
--- /dev/null
+++ b/sound/soc/intel/catpt/pcm.c
@@ -0,0 +1,1175 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <uapi/sound/tlv.h>
+#include "core.h"
+#include "messages.h"
+
+struct catpt_stream_template {
+	enum catpt_path_id path_id;
+	enum catpt_stream_type type;
+	u32 persistent_size;
+	u8 num_entries;
+	struct catpt_module_entry entries[];
+};
+
+static struct catpt_stream_template system_pb = {
+	.path_id = CATPT_PATH_SSP0_OUT,
+	.type = CATPT_STRM_TYPE_SYSTEM,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_PCM_SYSTEM, 0 }},
+};
+
+static struct catpt_stream_template system_cp = {
+	.path_id = CATPT_PATH_SSP0_IN,
+	.type = CATPT_STRM_TYPE_CAPTURE,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_PCM_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template offload_pb = {
+	.path_id = CATPT_PATH_SSP0_OUT,
+	.type = CATPT_STRM_TYPE_RENDER,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_PCM, 0 }},
+};
+
+static struct catpt_stream_template loopback_cp = {
+	.path_id = CATPT_PATH_SSP0_OUT,
+	.type = CATPT_STRM_TYPE_LOOPBACK,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_PCM_REFERENCE, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_pb = {
+	.path_id = CATPT_PATH_SSP1_OUT,
+	.type = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_BLUETOOTH_RENDER, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_cp = {
+	.path_id = CATPT_PATH_SSP1_IN,
+	.type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE,
+	.num_entries = 1,
+	.entries = {{ CATPT_MODID_BLUETOOTH_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template *catpt_topology[] = {
+	[CATPT_STRM_TYPE_RENDER]		= &offload_pb,
+	[CATPT_STRM_TYPE_SYSTEM]		= &system_pb,
+	[CATPT_STRM_TYPE_CAPTURE]		= &system_cp,
+	[CATPT_STRM_TYPE_LOOPBACK]		= &loopback_cp,
+	[CATPT_STRM_TYPE_BLUETOOTH_RENDER]	= &bluetooth_pb,
+	[CATPT_STRM_TYPE_BLUETOOTH_CAPTURE]	= &bluetooth_cp,
+};
+
+static struct catpt_stream_template *
+catpt_get_stream_template(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtm = substream->private_data;
+	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+	enum catpt_stream_type type;
+
+	type = cpu_dai->driver->id;
+
+	/* account for capture in bidirectional dais */
+	switch (type) {
+	case CATPT_STRM_TYPE_SYSTEM:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			type = CATPT_STRM_TYPE_CAPTURE;
+		break;
+	case CATPT_STRM_TYPE_BLUETOOTH_RENDER:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE;
+		break;
+	default:
+		break;
+	};
+
+	return catpt_topology[type];
+}
+
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+	struct catpt_stream_runtime *pos, *result = NULL;
+
+	spin_lock(&cdev->list_lock);
+	list_for_each_entry(pos, &cdev->stream_list, node) {
+		if (pos->info.stream_hw_id == stream_hw_id) {
+			result = pos;
+			break;
+		}
+	}
+
+	spin_unlock(&cdev->list_lock);
+	return result;
+}
+
+static u32 catpt_stream_read_position(struct catpt_dev *cdev,
+				      struct catpt_stream_runtime *stream)
+{
+	u32 pos;
+
+	memcpy_fromio(&pos, cdev->lpe_ba + stream->info.read_pos_regaddr,
+		      sizeof(pos));
+	return pos;
+}
+
+static u32 catpt_stream_volume(struct catpt_dev *cdev,
+			       struct catpt_stream_runtime *stream, u32 channel)
+{
+	u32 volume, offset;
+
+	if (channel >= CATPT_CHANNELS_MAX)
+		channel = 0;
+
+	offset = stream->info.volume_regaddr[channel];
+	memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+	return volume;
+}
+
+static u32 catpt_mixer_volume(struct catpt_dev *cdev,
+			      struct catpt_mixer_stream_info *info, u32 channel)
+{
+	u32 volume, offset;
+
+	if (channel >= CATPT_CHANNELS_MAX)
+		channel = 0;
+
+	offset = info->volume_regaddr[channel];
+	memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+	return volume;
+}
+
+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 = PFN_DOWN(snd_sgbuf_get_addr(databuf, i * PAGE_SIZE));
+		/* 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 GENMASK(31, 4) | CATPT_CHANNEL_CENTER;
+
+	case CATPT_CHANNEL_CONFIG_DUAL_MONO:
+		return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+				      | (CATPT_CHANNEL_LEFT << 4);
+
+	case CATPT_CHANNEL_CONFIG_STEREO:
+		return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+				      | (CATPT_CHANNEL_RIGHT << 4);
+
+	case CATPT_CHANNEL_CONFIG_2_POINT_1:
+		return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+				       | (CATPT_CHANNEL_RIGHT << 4)
+				       | (CATPT_CHANNEL_LFE << 8);
+
+	case CATPT_CHANNEL_CONFIG_3_POINT_0:
+		return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+				       | (CATPT_CHANNEL_CENTER << 4)
+				       | (CATPT_CHANNEL_RIGHT << 8);
+
+	case CATPT_CHANNEL_CONFIG_3_POINT_1:
+		return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+				       | (CATPT_CHANNEL_CENTER << 4)
+				       | (CATPT_CHANNEL_RIGHT << 8)
+				       | (CATPT_CHANNEL_LFE << 12);
+
+	case CATPT_CHANNEL_CONFIG_QUATRO:
+		return GENMASK(31, 16) | 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 GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+				       | (CATPT_CHANNEL_CENTER << 4)
+				       | (CATPT_CHANNEL_RIGHT << 8)
+				       | (CATPT_CHANNEL_CENTER_SURROUND << 12);
+
+	case CATPT_CHANNEL_CONFIG_5_POINT_0:
+		return GENMASK(31, 20) | 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 GENMASK(31, 24) | 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);
+
+	default:
+		return U32_MAX;
+	}
+}
+
+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 = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0));
+
+	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);
+}
+
+/* 200 ms for 2 32-bit channels at 48kHz (native format) */
+#define CATPT_BUFFER_MAX_SIZE	76800
+#define CATPT_PCM_PERIODS_MAX	4
+#define CATPT_PCM_PERIODS_MIN	2
+
+static const struct snd_pcm_hardware catpt_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME |
+				  SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
+				  SNDRV_PCM_FMTBIT_S24_LE |
+				  SNDRV_PCM_FMTBIT_S32_LE,
+	.period_bytes_min	= PAGE_SIZE,
+	.period_bytes_max	= CATPT_BUFFER_MAX_SIZE / CATPT_PCM_PERIODS_MIN,
+	.periods_min		= CATPT_PCM_PERIODS_MIN,
+	.periods_max		= CATPT_PCM_PERIODS_MAX,
+	.buffer_bytes_max	= CATPT_BUFFER_MAX_SIZE,
+};
+
+static int catpt_component_pcm_construct(struct snd_soc_component *component,
+					 struct snd_soc_pcm_runtime *rtm)
+{
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+
+	snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG,
+				       cdev->dev,
+				       catpt_pcm_hardware.buffer_bytes_max,
+				       catpt_pcm_hardware.buffer_bytes_max);
+
+	return 0;
+}
+
+static int catpt_component_open(struct snd_soc_component *component,
+				struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtm = substream->private_data;
+
+	if (rtm->dai_link->no_pcm)
+		return 0;
+	snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware);
+	return 0;
+}
+
+static snd_pcm_uframes_t
+catpt_component_pointer(struct snd_soc_component *component,
+			struct snd_pcm_substream *substream)
+{
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	struct catpt_stream_runtime *stream;
+	struct snd_soc_pcm_runtime *rtm = substream->private_data;
+	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+	u32 pos;
+
+	if (rtm->dai_link->no_pcm)
+		return 0;
+
+	stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
+	pos = catpt_stream_read_position(cdev, stream);
+
+	return bytes_to_frames(substream->runtime, pos);
+}
+
+static const struct snd_soc_dai_ops catpt_fe_dai_ops = {
+	.startup = catpt_dai_startup,
+	.shutdown = catpt_dai_shutdown,
+	.hw_params = catpt_dai_hw_params,
+	.hw_free = catpt_dai_hw_free,
+	.prepare = catpt_dai_prepare,
+	.trigger = catpt_dai_trigger,
+};
+
+static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0);
+	struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+	struct catpt_ssp_device_format devfmt;
+	int ret;
+
+	devfmt.iface = dai->driver->id;
+	devfmt.channels = codec_dai->driver->capture.channels_max;
+
+	switch (devfmt.iface) {
+	case CATPT_SSP_IFACE_0:
+		devfmt.mclk = CATPT_MCLK_FREQ_24_MHZ;
+
+		switch (devfmt.channels) {
+		case 4:
+			devfmt.mode = CATPT_SSP_MODE_TDM_PROVIDER;
+			devfmt.clock_divider = 4;
+			break;
+		case 2:
+		default:
+			devfmt.mode = CATPT_SSP_MODE_I2S_PROVIDER;
+			devfmt.clock_divider = 9;
+			break;
+		}
+		break;
+
+	case CATPT_SSP_IFACE_1:
+		devfmt.mclk = CATPT_MCLK_OFF;
+		devfmt.mode = CATPT_SSP_MODE_I2S_CONSUMER;
+		devfmt.clock_divider = 0;
+		break;
+	}
+
+	ret = catpt_ipc_set_device_format(cdev, &devfmt);
+	if (ret)
+		return CATPT_IPC_ERROR(ret);
+
+	/* store device format set for given SSP */
+	memcpy(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt));
+	return 0;
+}
+
+static struct snd_soc_dai_driver dai_drivers[] = {
+/* FE DAIs */
+{
+	.name  = "System Pin",
+	.id = CATPT_STRM_TYPE_SYSTEM,
+	.ops = &catpt_fe_dai_ops,
+	.playback = {
+		.stream_name = "System Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.capture = {
+		.stream_name = "Analog Capture",
+		.channels_min = 2,
+		.channels_max = 4,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+},
+{
+	.name  = "Offload0 Pin",
+	.id = CATPT_STRM_TYPE_RENDER,
+	.ops = &catpt_fe_dai_ops,
+	.playback = {
+		.stream_name = "Offload0 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+},
+{
+	.name  = "Offload1 Pin",
+	.id = CATPT_STRM_TYPE_RENDER,
+	.ops = &catpt_fe_dai_ops,
+	.playback = {
+		.stream_name = "Offload1 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+},
+{
+	.name  = "Loopback Pin",
+	.id = CATPT_STRM_TYPE_LOOPBACK,
+	.ops = &catpt_fe_dai_ops,
+	.capture = {
+		.stream_name = "Loopback Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+},
+{
+	.name  = "Bluetooth Pin",
+	.id = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+	.ops = &catpt_fe_dai_ops,
+	.playback = {
+		.stream_name = "Bluetooth Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "Bluetooth Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+},
+/* BE DAIs */
+{
+	.name = "ssp0-port",
+	.id = CATPT_SSP_IFACE_0,
+	.pcm_new = catpt_dai_pcm_new,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 8,
+	},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 8,
+	},
+},
+{
+	.name = "ssp1-port",
+	.id = CATPT_SSP_IFACE_1,
+	.pcm_new = catpt_dai_pcm_new,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 8,
+	},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 8,
+	},
+},
+};
+
+#define DSP_VOLUME_MAX		S32_MAX /* 0db */
+#define DSP_VOLUME_STEP_MAX	30
+
+static u32 ctlvol_to_dspvol(u32 value)
+{
+	if (value > DSP_VOLUME_STEP_MAX)
+		value = 0;
+	return DSP_VOLUME_MAX >> (DSP_VOLUME_STEP_MAX - value);
+}
+
+static u32 dspvol_to_ctlvol(u32 volume)
+{
+	if (volume > DSP_VOLUME_MAX)
+		return DSP_VOLUME_STEP_MAX;
+	return volume ? __fls(volume) : 0;
+}
+
+static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol)
+{
+	u32 dspvol;
+	int ret, i;
+
+	for (i = 1; i < CATPT_CHANNELS_MAX; i++)
+		if (ctlvol[i] != ctlvol[0])
+			break;
+
+	if (i == CATPT_CHANNELS_MAX) {
+		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)
+				break;
+		}
+	}
+
+	if (ret)
+		return CATPT_IPC_ERROR(ret);
+	return 0;
+}
+
+static int catpt_volume_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = CATPT_CHANNELS_MAX;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = DSP_VOLUME_STEP_MAX;
+	return 0;
+}
+
+static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	u32 dspvol;
+	int i;
+
+	pm_runtime_get_sync(cdev->dev);
+
+	for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+		dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i);
+		ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+	}
+
+	pm_runtime_mark_last_busy(cdev->dev);
+	pm_runtime_put_autosuspend(cdev->dev);
+
+	return 0;
+}
+
+static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	int ret;
+
+	pm_runtime_get_sync(cdev->dev);
+
+	ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id,
+			       ucontrol->value.integer.value);
+
+	pm_runtime_mark_last_busy(cdev->dev);
+	pm_runtime_put_autosuspend(cdev->dev);
+
+	return ret;
+}
+
+static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol,
+				   enum catpt_pin_id pin_id)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	struct catpt_stream_runtime *stream;
+	long *ctlvol = (long *)kcontrol->private_value;
+	u32 dspvol;
+	int i;
+
+	stream = catpt_stream_find(cdev, pin_id);
+	if (!stream) {
+		for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+			ucontrol->value.integer.value[i] = ctlvol[i];
+		return 0;
+	}
+
+	pm_runtime_get_sync(cdev->dev);
+
+	for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+		dspvol = catpt_stream_volume(cdev, stream, i);
+		ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+	}
+
+	pm_runtime_mark_last_busy(cdev->dev);
+	pm_runtime_put_autosuspend(cdev->dev);
+
+	return 0;
+}
+
+static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol,
+				   enum catpt_pin_id pin_id)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	struct catpt_stream_runtime *stream;
+	long *ctlvol = (long *)kcontrol->private_value;
+	int ret, i;
+
+	stream = catpt_stream_find(cdev, pin_id);
+	if (!stream) {
+		for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+			ctlvol[i] = ucontrol->value.integer.value[i];
+		return 0;
+	}
+
+	pm_runtime_get_sync(cdev->dev);
+
+	ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id,
+			       ucontrol->value.integer.value);
+
+	pm_runtime_mark_last_busy(cdev->dev);
+	pm_runtime_put_autosuspend(cdev->dev);
+
+	if (ret)
+		return ret;
+
+	for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+		ctlvol[i] = ucontrol->value.integer.value[i];
+	return 0;
+}
+
+static int catpt_offload1_volume_get(struct snd_kcontrol *kctl,
+				     struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload1_volume_put(struct snd_kcontrol *kctl,
+				     struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload2_volume_get(struct snd_kcontrol *kctl,
+				     struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_offload2_volume_put(struct snd_kcontrol *kctl,
+				     struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_capture_volume_get(struct snd_kcontrol *kctl,
+				    struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_capture_volume_put(struct snd_kcontrol *kctl,
+				    struct snd_ctl_elem_value *uctl)
+{
+	return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value;
+	return 0;
+}
+
+static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+		snd_soc_kcontrol_component(kcontrol);
+	struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+	struct catpt_stream_runtime *stream;
+	bool mute;
+	int ret;
+
+	mute = (bool)ucontrol->value.integer.value[0];
+	stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE);
+	if (!stream) {
+		*(bool *)kcontrol->private_value = mute;
+		return 0;
+	}
+
+	pm_runtime_get_sync(cdev->dev);
+
+	ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute);
+
+	pm_runtime_mark_last_busy(cdev->dev);
+	pm_runtime_put_autosuspend(cdev->dev);
+
+	if (ret)
+		return CATPT_IPC_ERROR(ret);
+
+	*(bool *)kcontrol->private_value = mute;
+	return 0;
+}
+
+static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	return 0;
+}
+
+static int catpt_waves_switch_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	return 0;
+}
+
+static int catpt_waves_param_get(struct snd_kcontrol *kcontrol,
+				 unsigned int __user *bytes,
+				 unsigned int size)
+{
+	return 0;
+}
+
+static int catpt_waves_param_put(struct snd_kcontrol *kcontrol,
+				 const unsigned int __user *bytes,
+				 unsigned int size)
+{
+	return 0;
+}
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1);
+
+#define CATPT_VOLUME_CTL(kname, sname) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = (kname), \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+		  SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+	.info = catpt_volume_info, \
+	.get = catpt_##sname##_volume_get, \
+	.put = catpt_##sname##_volume_put, \
+	.tlv.p = catpt_volume_tlv, \
+	.private_value = (unsigned long) \
+		&(long[CATPT_CHANNELS_MAX]) {0} }
+
+static const struct snd_kcontrol_new component_kcontrols[] = {
+/* Master volume (mixer stream) */
+CATPT_VOLUME_CTL("Master Playback Volume", mixer),
+/* Individual volume controls for offload and capture */
+CATPT_VOLUME_CTL("Media0 Playback Volume", offload1),
+CATPT_VOLUME_CTL("Media1 Playback Volume", offload2),
+CATPT_VOLUME_CTL("Mic Capture Volume", capture),
+SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0},
+		    catpt_loopback_switch_get, catpt_loopback_switch_put),
+/* Enable or disable WAVES module */
+SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
+		    catpt_waves_switch_get, catpt_waves_switch_put),
+/* WAVES module parameter control */
+SND_SOC_BYTES_TLV("Waves Set Param", 128,
+		  catpt_waves_param_get, catpt_waves_param_put),
+};
+
+static const struct snd_soc_dapm_widget component_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route component_routes[] = {
+	{"Playback VMixer", NULL, "System Playback"},
+	{"Playback VMixer", NULL, "Offload0 Playback"},
+	{"Playback VMixer", NULL, "Offload1 Playback"},
+
+	{"SSP0 CODEC OUT", NULL, "Playback VMixer"},
+
+	{"Analog Capture", NULL, "SSP0 CODEC IN"},
+	{"Loopback Capture", NULL, "SSP0 CODEC IN"},
+
+	{"SSP1 BT OUT", NULL, "Bluetooth Playback"},
+	{"Bluetooth Capture", NULL, "SSP1 BT IN"},
+};
+
+static const struct snd_soc_component_driver catpt_comp_driver = {
+	.name = "catpt-platform",
+
+	.pcm_construct = catpt_component_pcm_construct,
+	.open = catpt_component_open,
+	.pointer = catpt_component_pointer,
+
+	.controls = component_kcontrols,
+	.num_controls = ARRAY_SIZE(component_kcontrols),
+	.dapm_widgets = component_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(component_widgets),
+	.dapm_routes = component_routes,
+	.num_dapm_routes = ARRAY_SIZE(component_routes),
+};
+
+int catpt_arm_stream_templates(struct catpt_dev *cdev)
+{
+	struct resource *res;
+	u32 scratch_size = 0;
+	int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(catpt_topology); i++) {
+		struct catpt_stream_template *template;
+		struct catpt_module_entry *entry;
+		struct catpt_module_type *type;
+
+		template = catpt_topology[i];
+		template->persistent_size = 0;
+
+		for (j = 0; j < template->num_entries; j++) {
+			entry = &template->entries[j];
+			type = &cdev->modules[entry->module_id];
+
+			if (!type->loaded)
+				return -ENOENT;
+
+			entry->entry_point = type->entry_point;
+			template->persistent_size += type->persistent_size;
+			if (type->scratch_size > scratch_size)
+				scratch_size = type->scratch_size;
+		}
+	}
+
+	if (scratch_size) {
+		/* allocate single scratch area for all modules */
+		res = catpt_request_region(&cdev->dram, scratch_size);
+		if (!res)
+			return -EBUSY;
+		cdev->scratch = res;
+	}
+
+	return 0;
+}
+
+int catpt_register_plat_component(struct catpt_dev *cdev)
+{
+	struct snd_soc_component *component;
+	int ret;
+
+	component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL);
+	if (!component)
+		return -ENOMEM;
+
+	ret = snd_soc_component_initialize(component, &catpt_comp_driver,
+					   cdev->dev);
+	if (ret)
+		return ret;
+
+	component->name = catpt_comp_driver.name;
+	return snd_soc_add_component(component, dai_drivers,
+				     ARRAY_SIZE(dai_drivers));
+}