diff mbox series

[v5,17/21] ASoC: qdsp6: audioreach: add topology support

Message ID 20210903112032.25834-18-srinivas.kandagatla@linaro.org
State New
Headers show
Series ASoC: qcom: Add AudioReach support | expand

Commit Message

Srinivas Kandagatla Sept. 3, 2021, 11:20 a.m. UTC
Add ASoC topology support in audioreach

Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>

---
 include/uapi/sound/snd_ar_tokens.h |  203 +++++
 sound/soc/qcom/Kconfig             |    1 +
 sound/soc/qcom/qdsp6/Makefile      |    2 +-
 sound/soc/qcom/qdsp6/q6apm.c       |    4 +-
 sound/soc/qcom/qdsp6/topology.c    | 1118 ++++++++++++++++++++++++++++
 5 files changed, 1326 insertions(+), 2 deletions(-)
 create mode 100644 include/uapi/sound/snd_ar_tokens.h
 create mode 100644 sound/soc/qcom/qdsp6/topology.c

-- 
2.21.0

Comments

Pierre-Louis Bossart Sept. 3, 2021, 3:31 p.m. UTC | #1
> +/**

> + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:		Sub Graph Instance Id

> + *

> + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE:		Performance mode of subgraph

> + *						APM_SUB_GRAPH_PERF_MODE_LOW_POWER = 1,

> + *						APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY = 2

> + *

> + * %AR_TKN_U32_SUB_GRAPH_DIRECTION:		Direction of subgraph

> + *						APM_SUB_GRAPH_DIRECTION_TX = 1,

> + *						APM_SUB_GRAPH_DIRECTION_RX = 2


can you have full-duplex? unclear how you would define the direction in
the case of a voice call...

> + *

> + * %AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:		Scenario ID for subgraph

> + *						APM_SUB_GRAPH_SID_AUDIO_PLAYBACK = 1,

> + *						APM_SUB_GRAPH_SID_AUDIO_RECORD = 2,

> + *						APM_SUB_GRAPH_SID_VOICE_CALL = 3

> + *

> + * %AR_TKN_U32_CONTAINER_INSTANCE_ID:		Container Instance ID

> + *

> + * %AR_TKN_U32_CONTAINER_CAPABILITY_ID:		Container capability ID

> + *						APM_CONTAINER_CAP_ID_PP = 1,

> + *						APM_CONTAINER_CAP_ID_CD = 2,

> + *						APM_CONTAINER_CAP_ID_EP = 3,

> + *						APM_CONTAINER_CAP_ID_OLC = 4


Acronyms? PP, CD, EP, OLC?

> + *

> + * %AR_TKN_U32_CONTAINER_STACK_SIZE:		Stack size in the container.

> + *

> + * %AR_TKN_U32_CONTAINER_GRAPH_POS:		Graph Position

> + *						APM_CONT_GRAPH_POS_STREAM = 1,

> + *						APM_CONT_GRAPH_POS_PER_STR_PER_DEV = 2,

> + *						APM_CONT_GRAPH_POS_STR_DEV = 3,

> + *						APM_CONT_GRAPH_POS_GLOBAL_DEV = 4


explain what this means?

> + *

> + * %AR_TKN_U32_CONTAINER_PROC_DOMAIN:		Processor domain of container

> + *						APM_PROC_DOMAIN_ID_MDSP = 1,

> + *						APM_PROC_DOMAIN_ID_ADSP = 2,

> + *						APM_PROC_DOMAIN_ID_SDSP = 4,

> + *						APM_PROC_DOMAIN_ID_CDSP = 5


what happened to 3, is it reserved/illegal?

> + *

> + * %AR_TKN_U32_MODULE_ID:			Module ID

> + *

> + * %AR_TKN_U32_MODULE_INSTANCE_ID:		Module Instance ID.

> + *

> + * %AR_TKN_U32_MODULE_MAX_IP_PORTS:		Module maximum input ports

> + *

> + * %AR_TKN_U32_MODULE_MAX_OP_PORTS:		Module maximum output ports.

> + *

> + * %AR_TKN_U32_MODULE_IN_PORTS:			Number of in ports

> + *

> + * %AR_TKN_U32_MODULE_OUT_PORTS:		Number of out ports.

> + *

> + * %AR_TKN_U32_MODULE_SRC_OP_PORT_ID:		Source module output port ID

> + *

> + * %AR_TKN_U32_MODULE_DST_IN_PORT_ID:		Destination module input port ID

> + *

> + * %AR_TKN_U32_MODULE_HW_IF_IDX:		Interface index types for I2S/LPAIF

> + *

> + * %AR_TKN_U32_MODULE_HW_IF_TYPE:		Interface type

> + *						LPAIF = 0,

> + *						LPAIF_RXTX = 1,

> + *						LPAIF_WSA = 2,

> + *						LPAIF_VA = 3,

> + *						LPAIF_AXI = 4

> + *

> + * %AR_TKN_U32_MODULE_FMT_INTERLEAVE:		PCM Interleaving

> + *						PCM_INTERLEAVED = 1,

> + *						PCM_DEINTERLEAVED_PACKED = 2,

> + *						PCM_DEINTERLEAVED_UNPACKED = 3

> + *

> + * %AR_TKN_U32_MODULE_FMT_DATA:			data format

> + *						FIXED POINT = 1,

> + *						IEC60958 PACKETIZED = 3,

> + *						IEC60958 PACKETIZED NON LINEAR = 8,

> + *						COMPR OVER PCM PACKETIZED = 7,

> + *						IEC61937 PACKETIZED = 2,


isn't this precisely compressed over PCM?

> + *						GENERIC COMPRESSED = 5


??

> + *

> + * %AR_TKN_U32_MODULE_FMT_FREQ:			bit rate


bit rate or frame rate (aka sampling frequency) ?

> + *

> + * %AR_TKN_U32_MODULE_FMT_BIT_DEPTH:		bit depth



> +static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm,

> +					struct snd_soc_tplg_private *private)

> +{

> +	struct audioreach_graph_info *info = NULL;

> +	struct snd_soc_tplg_vendor_array *sg_array;

> +	struct snd_soc_tplg_vendor_value_elem *sg_elem;

> +	struct audioreach_sub_graph *sg;

> +	int graph_id, sub_graph_id, tkn_count = 0;

> +	bool found;

> +

> +	sg_array = audioreach_get_sg_array(private);

> +	sg_elem = sg_array->value;

> +

> +	while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {

> +		switch (le32_to_cpu(sg_elem->token)) {

> +		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:

> +			sub_graph_id = le32_to_cpu(sg_elem->value);

> +			sg = audioreach_tplg_alloc_sub_graph(apm, sub_graph_id, &found);

> +			if (IS_ERR(sg)) {

> +				return sg;

> +			} else if (found) {

> +				/* Already parsed data for this sub-graph */

> +				return sg;

> +			}

> +			dev_err(apm->dev, "%s: New subgraph id : 0x%08x\n", __func__,

> +				sub_graph_id);

> +			break;

> +		case AR_TKN_DAI_INDEX:

> +			/* Sub graph is associated with predefined graph */

> +			graph_id = le32_to_cpu(sg_elem->value);

> +			info = audioreach_tplg_alloc_graph_info(apm, graph_id, &found);

> +			if (IS_ERR(info))

> +				return ERR_CAST(info);

> +			break;

> +		case AR_TKN_U32_SUB_GRAPH_PERF_MODE:

> +			sg->perf_mode = le32_to_cpu(sg_elem->value);

> +			break;

> +		case AR_TKN_U32_SUB_GRAPH_DIRECTION:

> +			sg->direction = le32_to_cpu(sg_elem->value);

> +			break;

> +		case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:

> +			sg->scenario_id = le32_to_cpu(sg_elem->value);

> +			break;

> +		default:

> +			dev_err(apm->dev, "Not a valid token %d for graph\n",

> +				sg_elem->token);

> +		break;


indentation is off

> +

> +		}

> +		tkn_count++;

> +		sg_elem++;

> +	}

> +

> +	/* Sub graph is associated with predefined graph */

> +	if (info) {

> +		dev_err(apm->dev, "%s: adding subgraph id : 0x%08x -> %d\n", __func__,

> +		sub_graph_id, graph_id);

> +

> +		audioreach_tplg_add_sub_graph(sg, info);

> +	}

> +

> +	return sg;

> +}

> +

> +static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *apm,

> +						struct audioreach_sub_graph *sg,

> +						struct snd_soc_tplg_private *private)

> +{

> +	struct snd_soc_tplg_vendor_array *cont_array;

> +	struct snd_soc_tplg_vendor_value_elem *cont_elem;

> +	struct audioreach_container *cont;

> +	int container_id, tkn_count = 0;

> +	bool found = false;

> +

> +	cont_array = audioreach_get_cont_array(private);

> +	cont_elem = cont_array->value;

> +

> +	while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {

> +		switch (le32_to_cpu(cont_elem->token)) {

> +		case AR_TKN_U32_CONTAINER_INSTANCE_ID:

> +			container_id = le32_to_cpu(cont_elem->value);

> +			cont = audioreach_tplg_alloc_container(apm, sg, container_id, &found);

> +			if (IS_ERR(cont))

> +				return ERR_PTR(-ENOMEM);

> +			else if (found) /* Already parsed container data */

> +				return cont;

> +

> +			break;

> +		case AR_TKN_U32_CONTAINER_CAPABILITY_ID:

> +			cont->capability_id = le32_to_cpu(cont_elem->value);

> +			break;

> +		case AR_TKN_U32_CONTAINER_STACK_SIZE:

> +			cont->stack_size = le32_to_cpu(cont_elem->value);

> +			break;

> +		case AR_TKN_U32_CONTAINER_GRAPH_POS:

> +			cont->graph_pos = le32_to_cpu(cont_elem->value);

> +			break;

> +		case AR_TKN_U32_CONTAINER_PROC_DOMAIN:

> +			cont->proc_domain = le32_to_cpu(cont_elem->value);

> +			break;

> +		default:

> +			dev_err(apm->dev, "Not a valid token %d for graph\n",

> +				cont_elem->token);

> +		break;


indentation?

> +

> +		}

> +		tkn_count++;

> +		cont_elem++;

> +	}

> +

> +	return cont;

> +}

> +


> +static struct audioreach_module *audioreach_find_widget(struct snd_soc_component *comp,

> +							const char *name)

> +{

> +	struct q6apm *apm = dev_get_drvdata(comp->dev);

> +	struct audioreach_module *module;

> +	int id = 0;


unnecessary init?

> +

> +	idr_for_each_entry(&apm->modules_idr, module, id) {

> +		if (!strcmp(name, module->widget->name))

> +			return module;

> +	}

> +

> +	return NULL;

> +}


> +/* DAI link - used for any driver specific init */

> +static int audioreach_link_load(struct snd_soc_component *component, int index,

> +				struct snd_soc_dai_link *link,

> +				struct snd_soc_tplg_link_config *cfg)

> +{

> +	link->nonatomic = true;

> +	link->dynamic = true;

> +	link->platforms->name = NULL;

> +	link->platforms->of_node = of_get_compatible_child(component->dev->of_node,

> +				"qcom,q6apm-dais");

> +	link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST;

> +	link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST;


can you add a comment on why you don't use the default order for FE/BE
triggers?

> +

> +	return 0;

> +}

> +


> +static int audioreach_get_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,

> +				       struct snd_ctl_elem_value *ucontrol)

> +{

> +	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);

> +	struct audioreach_module *mod = dw->dobj.private;

> +

> +	/* Check if the graph is active or not */


that comment seems like a copy/paste, there's no check.

> +	ucontrol->value.integer.value[0] = mod->gain;

> +

> +	return 0;

> +}

> +

> +static int audioreach_put_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,

> +				      struct snd_ctl_elem_value *ucontrol)

> +{

> +	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);

> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);

> +	struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);

> +	struct audioreach_module *mod = dw->dobj.private;

> +	struct q6apm *apm = dev_get_drvdata(c->dev);

> +	int vol = ucontrol->value.integer.value[0];

> +

> +	/* Check if the graph is active or not */

> +	if (dw->power) {

> +		audioreach_gain_set_vol_ctrl(apm, mod, vol);

> +		mod->gain = vol;

> +		return 1;

> +	}


shouldn't you cache the value and apply it when the graph is powered?

Also wondering why this isn't using pm_runtime or something? Is this
re-inventing your own power management?

> +

> +	dev_err(apm->dev, "Unable to set volume as graph is not	active\n");

> +	return 0;

> +

> +}

> +

> +static int audioreach_control_load_mix(struct snd_soc_component *scomp,

> +					  struct snd_ar_control *scontrol,

> +					  struct snd_kcontrol_new *kc,

> +					  struct snd_soc_tplg_ctl_hdr *hdr)

> +{

> +	struct snd_soc_tplg_mixer_control *mc;

> +	struct snd_soc_tplg_vendor_array *c_array;

> +	struct snd_soc_tplg_vendor_value_elem *c_elem;

> +	int tkn_count = 0;

> +

> +	mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);

> +	c_array = (struct snd_soc_tplg_vendor_array *)mc->priv.data;

> +

> +	c_elem = c_array->value;

> +

> +	while (tkn_count <= (le32_to_cpu(c_array->num_elems) - 1)) {

> +		switch (le32_to_cpu(c_elem->token)) {

> +		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:

> +			scontrol->sgid = le32_to_cpu(c_elem->value);

> +			break;

> +		default:

> +			/* Ignore other tokens */

> +		break;


indentation?

> +

> +		}

> +		c_elem++;

> +		tkn_count++;

> +	}

> +

> +	return 0;

> +}


> +int audioreach_tplg_init(struct snd_soc_component *component)

> +{

> +	struct device *dev = component->dev;

> +	const struct firmware *fw;

> +	int ret;

> +

> +	ret = request_firmware(&fw, "audioreach.bin", dev);

> +	if (ret < 0) {

> +		dev_err(dev, "tplg fw audioreach.bin load failed with %d\n", ret);

> +		return ret;

> +	}

> +

> +	ret = snd_soc_tplg_component_load(component, &audioreach_tplg_ops, fw);

> +	if (ret < 0) {

> +		dev_err(dev, "tplg component load failed%d\n", ret);

> +		release_firmware(fw);

> +		return -EINVAL;

> +	}


should you not release the firmware on success as well? that's what we
do for SOF"

	ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw);
	if (ret < 0) {
		dev_err(scomp->dev, "error: tplg component load failed %d\n",
			ret);
		ret = -EINVAL;
	}

	release_firmware(fw);
	return ret;

> +

> +	return 0;

> +}

> +EXPORT_SYMBOL_GPL(audioreach_tplg_init);

>
kernel test robot Sept. 4, 2021, 1:05 a.m. UTC | #2
Hi Srinivas,

I love your patch! Perhaps something to improve:

[auto build test WARNING on asoc/for-next]
[also build test WARNING on robh/for-next sound/for-next linus/master v5.14 next-20210903]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Srinivas-Kandagatla/ASoC-qcom-Add-AudioReach-support/20210903-192325
base:   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
config: hexagon-buildonly-randconfig-r002-20210904 (attached as .config)
compiler: clang version 14.0.0 (https://github.com/llvm/llvm-project 1104e3258b5064e7110cc297e2cec60ac9acfc0a)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/88452a1de12fb46ecf71c3054323a8ed0ed9af21
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Srinivas-Kandagatla/ASoC-qcom-Add-AudioReach-support/20210903-192325
        git checkout 88452a1de12fb46ecf71c3054323a8ed0ed9af21
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=hexagon 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> sound/soc/qcom/qdsp6/topology.c:1048:7: warning: variable 'ret' is used uninitialized whenever switch case is taken [-Wsometimes-uninitialized]

           case SND_SOC_AR_TPLG_VOL_CTL:
                ^~~~~~~~~~~~~~~~~~~~~~~
   include/uapi/sound/snd_ar_tokens.h:52:35: note: expanded from macro 'SND_SOC_AR_TPLG_VOL_CTL'
   #define SND_SOC_AR_TPLG_VOL_CTL                 257
                                                   ^~~
   sound/soc/qcom/qdsp6/topology.c:1060:9: note: uninitialized use occurs here
           return ret;
                  ^~~
   sound/soc/qcom/qdsp6/topology.c:1034:9: note: initialize the variable 'ret' to silence this warning
           int ret;
                  ^
                   = 0
   1 warning generated.


vim +/ret +1048 sound/soc/qcom/qdsp6/topology.c

  1026	
  1027	static int audioreach_control_load(struct snd_soc_component *scomp, int index,
  1028					   struct snd_kcontrol_new *kc,
  1029					   struct snd_soc_tplg_ctl_hdr *hdr)
  1030	{
  1031		struct snd_ar_control *scontrol;
  1032		struct snd_soc_dobj *dobj;
  1033		struct soc_mixer_control *sm;
  1034		int ret;
  1035	
  1036		scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL);
  1037		if (!scontrol)
  1038			return -ENOMEM;
  1039	
  1040		scontrol->scomp = scomp;
  1041	
  1042		switch (le32_to_cpu(hdr->ops.get)) {
  1043		case SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX:
  1044			sm = (struct soc_mixer_control *)kc->private_value;
  1045			dobj = &sm->dobj;
  1046			ret = audioreach_control_load_mix(scomp, scontrol, kc, hdr);
  1047			break;
> 1048		case SND_SOC_AR_TPLG_VOL_CTL:

  1049			sm = (struct soc_mixer_control *)kc->private_value;
  1050			dobj = &sm->dobj;
  1051			break;
  1052		default:
  1053			dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
  1054				 hdr->ops.get, hdr->ops.put, hdr->ops.info);
  1055			kfree(scontrol);
  1056			return -EINVAL;
  1057		}
  1058	
  1059		dobj->private = scontrol;
  1060		return ret;
  1061	}
  1062	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Srinivas Kandagatla Sept. 6, 2021, 4:29 p.m. UTC | #3
thanks Pierre for taking time to review the patches.

On 03/09/2021 16:31, Pierre-Louis Bossart wrote:
> 

> 

> 

>> +/**

>> + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:		Sub Graph Instance Id

>> + *

>> + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE:		Performance mode of subgraph

>> + *						APM_SUB_GRAPH_PERF_MODE_LOW_POWER = 1,

>> + *						APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY = 2

>> + *

>> + * %AR_TKN_U32_SUB_GRAPH_DIRECTION:		Direction of subgraph

>> + *						APM_SUB_GRAPH_DIRECTION_TX = 1,

>> + *						APM_SUB_GRAPH_DIRECTION_RX = 2

> 

> can you have full-duplex? unclear how you would define the direction in

> the case of a voice call...



> 

>> + *

>> + * %AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:		Scenario ID for subgraph

>> + *						APM_SUB_GRAPH_SID_AUDIO_PLAYBACK = 1,

>> + *						APM_SUB_GRAPH_SID_AUDIO_RECORD = 2,

>> + *						APM_SUB_GRAPH_SID_VOICE_CALL = 3

>> + *

>> + * %AR_TKN_U32_CONTAINER_INSTANCE_ID:		Container Instance ID

>> + *

>> + * %AR_TKN_U32_CONTAINER_CAPABILITY_ID:		Container capability ID

>> + *						APM_CONTAINER_CAP_ID_PP = 1,

>> + *						APM_CONTAINER_CAP_ID_CD = 2,

>> + *						APM_CONTAINER_CAP_ID_EP = 3,

>> + *						APM_CONTAINER_CAP_ID_OLC = 4

> 

> Acronyms? PP, CD, EP, OLC?

> 

Post Processing, Compress/Decompress, Endpoint and Offload.
I will add some comments to make this more clear.

>> + *

>> + * %AR_TKN_U32_CONTAINER_STACK_SIZE:		Stack size in the container.

>> + *

>> + * %AR_TKN_U32_CONTAINER_GRAPH_POS:		Graph Position

>> + *						APM_CONT_GRAPH_POS_STREAM = 1,

>> + *						APM_CONT_GRAPH_POS_PER_STR_PER_DEV = 2,

>> + *						APM_CONT_GRAPH_POS_STR_DEV = 3,

>> + *						APM_CONT_GRAPH_POS_GLOBAL_DEV = 4

> 

> explain what this means?

This defines container's relative position in the graph. Containers 
provide scheduling context for the graph.

There is some documentation at 
https://source.codeaurora.org/quic/la/platform/vendor/opensource/arspf-headers/tree/apm_container_api.h

> 


>> + *

>> + * %AR_TKN_U32_CONTAINER_PROC_DOMAIN:		Processor domain of container

>> + *						APM_PROC_DOMAIN_ID_MDSP = 1,

>> + *						APM_PROC_DOMAIN_ID_ADSP = 2,

>> + *						APM_PROC_DOMAIN_ID_SDSP = 4,

>> + *						APM_PROC_DOMAIN_ID_CDSP = 5

> 

> what happened to 3, is it reserved/illegal?


As per 
https://source.codeaurora.org/quic/la/platform/vendor/opensource/arspf-headers/tree/apm_graph_properties.h
this is not defined. DSP commands will timeout if we try to run with 3 I 
guess.
> 

>> + *

>> + * %AR_TKN_U32_MODULE_ID:			Module ID

>> + *

>> + * %AR_TKN_U32_MODULE_INSTANCE_ID:		Module Instance ID.

>> + *

>> + * %AR_TKN_U32_MODULE_MAX_IP_PORTS:		Module maximum input ports

>> + *

>> + * %AR_TKN_U32_MODULE_MAX_OP_PORTS:		Module maximum output ports.

>> + *

>> + * %AR_TKN_U32_MODULE_IN_PORTS:			Number of in ports

>> + *

>> + * %AR_TKN_U32_MODULE_OUT_PORTS:		Number of out ports.

>> + *

>> + * %AR_TKN_U32_MODULE_SRC_OP_PORT_ID:		Source module output port ID

>> + *

>> + * %AR_TKN_U32_MODULE_DST_IN_PORT_ID:		Destination module input port ID

>> + *

>> + * %AR_TKN_U32_MODULE_HW_IF_IDX:		Interface index types for I2S/LPAIF

>> + *

>> + * %AR_TKN_U32_MODULE_HW_IF_TYPE:		Interface type

>> + *						LPAIF = 0,

>> + *						LPAIF_RXTX = 1,

>> + *						LPAIF_WSA = 2,

>> + *						LPAIF_VA = 3,

>> + *						LPAIF_AXI = 4

>> + *

>> + * %AR_TKN_U32_MODULE_FMT_INTERLEAVE:		PCM Interleaving

>> + *						PCM_INTERLEAVED = 1,

>> + *						PCM_DEINTERLEAVED_PACKED = 2,

>> + *						PCM_DEINTERLEAVED_UNPACKED = 3

>> + *

>> + * %AR_TKN_U32_MODULE_FMT_DATA:			data format

>> + *						FIXED POINT = 1,

>> + *						IEC60958 PACKETIZED = 3,

>> + *						IEC60958 PACKETIZED NON LINEAR = 8,

>> + *						COMPR OVER PCM PACKETIZED = 7,

>> + *						IEC61937 PACKETIZED = 2,

> 

> isn't this precisely compressed over PCM?


These values are more specifically for I2S endpoint. 
(https://source.codeaurora.org/quic/la/platform/vendor/opensource/arspf-headers/tree/i2s_api.h#n232)

Am guessing that COMPR OVER PCM PACKETIZED mean audio stream formats 
other than IEC60958/61937, If you are keen on more details, I can ask 
DSP team for more info.


> 

>> + *						GENERIC COMPRESSED = 5

> 

> ??

> 

>> + *

>> + * %AR_TKN_U32_MODULE_FMT_FREQ:			bit rate

> 

> bit rate or frame rate (aka sampling frequency) ?


Yes, Will change this to AR_TKN_U32_MODULE_BIT_RATE to be more obvious.

> 

>> + *

>> + * %AR_TKN_U32_MODULE_FMT_BIT_DEPTH:		bit depth

> 

> 

>> +static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm,

>> +					struct snd_soc_tplg_private *private)

>> +{

>> +	struct audioreach_graph_info *info = NULL;

>> +	struct snd_soc_tplg_vendor_array *sg_array;

>> +	struct snd_soc_tplg_vendor_value_elem *sg_elem;

>> +	struct audioreach_sub_graph *sg;

>> +	int graph_id, sub_graph_id, tkn_count = 0;

>> +	bool found;

>> +

>> +	sg_array = audioreach_get_sg_array(private);

>> +	sg_elem = sg_array->value;

>> +

>> +	while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {

>> +		switch (le32_to_cpu(sg_elem->token)) {

>> +		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:

>> +			sub_graph_id = le32_to_cpu(sg_elem->value);

>> +			sg = audioreach_tplg_alloc_sub_graph(apm, sub_graph_id, &found);

>> +			if (IS_ERR(sg)) {

>> +				return sg;

>> +			} else if (found) {

>> +				/* Already parsed data for this sub-graph */

>> +				return sg;

>> +			}

>> +			dev_err(apm->dev, "%s: New subgraph id : 0x%08x\n", __func__,

>> +				sub_graph_id);

>> +			break;

>> +		case AR_TKN_DAI_INDEX:

>> +			/* Sub graph is associated with predefined graph */

>> +			graph_id = le32_to_cpu(sg_elem->value);

>> +			info = audioreach_tplg_alloc_graph_info(apm, graph_id, &found);

>> +			if (IS_ERR(info))

>> +				return ERR_CAST(info);

>> +			break;

>> +		case AR_TKN_U32_SUB_GRAPH_PERF_MODE:

>> +			sg->perf_mode = le32_to_cpu(sg_elem->value);

>> +			break;

>> +		case AR_TKN_U32_SUB_GRAPH_DIRECTION:

>> +			sg->direction = le32_to_cpu(sg_elem->value);

>> +			break;

>> +		case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:

>> +			sg->scenario_id = le32_to_cpu(sg_elem->value);

>> +			break;

>> +		default:

>> +			dev_err(apm->dev, "Not a valid token %d for graph\n",

>> +				sg_elem->token);

>> +		break;

> 

> indentation is off

> 

>> +

>> +		}

>> +		tkn_count++;

>> +		sg_elem++;

>> +	}

>> +

>> +	/* Sub graph is associated with predefined graph */

>> +	if (info) {

>> +		dev_err(apm->dev, "%s: adding subgraph id : 0x%08x -> %d\n", __func__,

>> +		sub_graph_id, graph_id);

>> +

>> +		audioreach_tplg_add_sub_graph(sg, info);

>> +	}

>> +

>> +	return sg;

>> +}

>> +

>> +static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *apm,

>> +						struct audioreach_sub_graph *sg,

>> +						struct snd_soc_tplg_private *private)

>> +{

>> +	struct snd_soc_tplg_vendor_array *cont_array;

>> +	struct snd_soc_tplg_vendor_value_elem *cont_elem;

>> +	struct audioreach_container *cont;

>> +	int container_id, tkn_count = 0;

>> +	bool found = false;

>> +

>> +	cont_array = audioreach_get_cont_array(private);

>> +	cont_elem = cont_array->value;

>> +

>> +	while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {

>> +		switch (le32_to_cpu(cont_elem->token)) {

>> +		case AR_TKN_U32_CONTAINER_INSTANCE_ID:

>> +			container_id = le32_to_cpu(cont_elem->value);

>> +			cont = audioreach_tplg_alloc_container(apm, sg, container_id, &found);

>> +			if (IS_ERR(cont))

>> +				return ERR_PTR(-ENOMEM);

>> +			else if (found) /* Already parsed container data */

>> +				return cont;

>> +

>> +			break;

>> +		case AR_TKN_U32_CONTAINER_CAPABILITY_ID:

>> +			cont->capability_id = le32_to_cpu(cont_elem->value);

>> +			break;

>> +		case AR_TKN_U32_CONTAINER_STACK_SIZE:

>> +			cont->stack_size = le32_to_cpu(cont_elem->value);

>> +			break;

>> +		case AR_TKN_U32_CONTAINER_GRAPH_POS:

>> +			cont->graph_pos = le32_to_cpu(cont_elem->value);

>> +			break;

>> +		case AR_TKN_U32_CONTAINER_PROC_DOMAIN:

>> +			cont->proc_domain = le32_to_cpu(cont_elem->value);

>> +			break;

>> +		default:

>> +			dev_err(apm->dev, "Not a valid token %d for graph\n",

>> +				cont_elem->token);

>> +		break;

> 

> indentation?

> 

>> +

>> +		}

>> +		tkn_count++;

>> +		cont_elem++;

>> +	}

>> +

>> +	return cont;

>> +}

>> +

> 

>> +static struct audioreach_module *audioreach_find_widget(struct snd_soc_component *comp,

>> +							const char *name)

>> +{

>> +	struct q6apm *apm = dev_get_drvdata(comp->dev);

>> +	struct audioreach_module *module;

>> +	int id = 0;

> 

> unnecessary init?

> 

>> +

>> +	idr_for_each_entry(&apm->modules_idr, module, id) {

>> +		if (!strcmp(name, module->widget->name))

>> +			return module;

>> +	}

>> +

>> +	return NULL;

>> +}

> 

>> +/* DAI link - used for any driver specific init */

>> +static int audioreach_link_load(struct snd_soc_component *component, int index,

>> +				struct snd_soc_dai_link *link,

>> +				struct snd_soc_tplg_link_config *cfg)

>> +{

>> +	link->nonatomic = true;

>> +	link->dynamic = true;

>> +	link->platforms->name = NULL;

>> +	link->platforms->of_node = of_get_compatible_child(component->dev->of_node,

>> +				"qcom,q6apm-dais");

>> +	link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST;

>> +	link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST;

> 

> can you add a comment on why you don't use the default order for FE/BE

> triggers?

> 

>> +

>> +	return 0;

>> +}

>> +

> 

>> +static int audioreach_get_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,

>> +				       struct snd_ctl_elem_value *ucontrol)

>> +{

>> +	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);

>> +	struct audioreach_module *mod = dw->dobj.private;

>> +

>> +	/* Check if the graph is active or not */

> 

> that comment seems like a copy/paste, there's no check.

> 

>> +	ucontrol->value.integer.value[0] = mod->gain;

>> +

>> +	return 0;

>> +}

>> +

>> +static int audioreach_put_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,

>> +				      struct snd_ctl_elem_value *ucontrol)

>> +{

>> +	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);

>> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);

>> +	struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);

>> +	struct audioreach_module *mod = dw->dobj.private;

>> +	struct q6apm *apm = dev_get_drvdata(c->dev);

>> +	int vol = ucontrol->value.integer.value[0];

>> +

>> +	/* Check if the graph is active or not */

>> +	if (dw->power) {

>> +		audioreach_gain_set_vol_ctrl(apm, mod, vol);

>> +		mod->gain = vol;

>> +		return 1;

>> +	}

> 

> shouldn't you cache the value and apply it when the graph is powered?


we could do that.

> 

> Also wondering why this isn't using pm_runtime or something? Is this

> re-inventing your own power management?

If you mean using dapm event callback, I can give that a try.

> 

>> +

>> +	dev_err(apm->dev, "Unable to set volume as graph is not	active\n");

>> +	return 0;

>> +

>> +}

>> +

>> +static int audioreach_control_load_mix(struct snd_soc_component *scomp,

>> +					  struct snd_ar_control *scontrol,

>> +					  struct snd_kcontrol_new *kc,

>> +					  struct snd_soc_tplg_ctl_hdr *hdr)

>> +{

>> +	struct snd_soc_tplg_mixer_control *mc;

>> +	struct snd_soc_tplg_vendor_array *c_array;

>> +	struct snd_soc_tplg_vendor_value_elem *c_elem;

>> +	int tkn_count = 0;

>> +

>> +	mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);

>> +	c_array = (struct snd_soc_tplg_vendor_array *)mc->priv.data;

>> +

>> +	c_elem = c_array->value;

>> +

>> +	while (tkn_count <= (le32_to_cpu(c_array->num_elems) - 1)) {

>> +		switch (le32_to_cpu(c_elem->token)) {

>> +		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:

>> +			scontrol->sgid = le32_to_cpu(c_elem->value);

>> +			break;

>> +		default:

>> +			/* Ignore other tokens */

>> +		break;

> 

> indentation?

> 

>> +

>> +		}

>> +		c_elem++;

>> +		tkn_count++;

>> +	}

>> +

>> +	return 0;

>> +}

> 

>> +int audioreach_tplg_init(struct snd_soc_component *component)

>> +{

>> +	struct device *dev = component->dev;

>> +	const struct firmware *fw;

>> +	int ret;

>> +

>> +	ret = request_firmware(&fw, "audioreach.bin", dev);

>> +	if (ret < 0) {

>> +		dev_err(dev, "tplg fw audioreach.bin load failed with %d\n", ret);

>> +		return ret;

>> +	}

>> +

>> +	ret = snd_soc_tplg_component_load(component, &audioreach_tplg_ops, fw);

>> +	if (ret < 0) {

>> +		dev_err(dev, "tplg component load failed%d\n", ret);

>> +		release_firmware(fw);

>> +		return -EINVAL;

>> +	}

> 

> should you not release the firmware on success as well? that's what we

> do for SOF"

> 

> 	ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw);

> 	if (ret < 0) {

> 		dev_err(scomp->dev, "error: tplg component load failed %d\n",

> 			ret);

> 		ret = -EINVAL;

> 	}

> 

> 	release_firmware(fw);

> 	return ret;


Thanks for hints, I updated it as suggested.

--srini
> 

>> +

>> +	return 0;

>> +}

>> +EXPORT_SYMBOL_GPL(audioreach_tplg_init);

>>
diff mbox series

Patch

diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h
new file mode 100644
index 000000000000..1a2582af0251
--- /dev/null
+++ b/include/uapi/sound/snd_ar_tokens.h
@@ -0,0 +1,203 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef __SND_AR_TOKENS_H__
+#define __SND_AR_TOKENS_H__
+
+#define APM_SUB_GRAPH_PERF_MODE_LOW_POWER	0x1
+#define APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY	0x2
+
+#define APM_SUB_GRAPH_DIRECTION_TX		0x1
+#define APM_SUB_GRAPH_DIRECTION_RX		0x2
+
+/** Scenario ID Audio Playback */
+#define APM_SUB_GRAPH_SID_AUDIO_PLAYBACK          0x1
+/* Scenario ID Audio Record */
+#define APM_SUB_GRAPH_SID_AUDIO_RECORD            0x2
+/* Scenario ID Voice call. */
+#define APM_SUB_GRAPH_SID_VOICE_CALL              0x3
+
+/* container capability ID Pre/Post Processing (PP) */
+#define APM_CONTAINER_CAP_ID_PP                   0x1
+/* container capability ID Compression/Decompression (CD) */
+#define APM_CONTAINER_CAP_ID_CD                   0x2
+/* container capability ID End Point(EP) */
+#define APM_CONTAINER_CAP_ID_EP                   0x3
+/* container capability ID Offload (OLC) */
+#define APM_CONTAINER_CAP_ID_OLC                  0x4
+
+/* container graph position Stream */
+#define APM_CONT_GRAPH_POS_STREAM                 0x1
+/* container graph position Per Stream Per Device*/
+#define APM_CONT_GRAPH_POS_PER_STR_PER_DEV        0x2
+/* container graph position Stream-Device */
+#define APM_CONT_GRAPH_POS_STR_DEV                0x3
+/* container graph position Global Device */
+#define APM_CONT_GRAPH_POS_GLOBAL_DEV             0x4
+
+#define APM_PROC_DOMAIN_ID_MDSP			0x1
+#define APM_PROC_DOMAIN_ID_ADSP			0x2
+#define APM_PROC_DOMAIN_ID_SDSP			0x4
+#define APM_PROC_DOMAIN_ID_CDSP			0x5
+
+#define PCM_INTERLEAVED			1
+#define PCM_DEINTERLEAVED_PACKED	2
+#define PCM_DEINTERLEAVED_UNPACKED	3
+#define AR_I2S_WS_SRC_EXTERNAL	0
+#define AR_I2S_WS_SRC_INTERNAL	1
+
+/*
+ * Kcontrol IDs
+ */
+#define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX	256
+#define SND_SOC_AR_TPLG_VOL_CTL			257
+
+/**
+ * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:		Sub Graph Instance Id
+ *
+ * %AR_TKN_U32_SUB_GRAPH_PERF_MODE:		Performance mode of subgraph
+ *						APM_SUB_GRAPH_PERF_MODE_LOW_POWER = 1,
+ *						APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY = 2
+ *
+ * %AR_TKN_U32_SUB_GRAPH_DIRECTION:		Direction of subgraph
+ *						APM_SUB_GRAPH_DIRECTION_TX = 1,
+ *						APM_SUB_GRAPH_DIRECTION_RX = 2
+ *
+ * %AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:		Scenario ID for subgraph
+ *						APM_SUB_GRAPH_SID_AUDIO_PLAYBACK = 1,
+ *						APM_SUB_GRAPH_SID_AUDIO_RECORD = 2,
+ *						APM_SUB_GRAPH_SID_VOICE_CALL = 3
+ *
+ * %AR_TKN_U32_CONTAINER_INSTANCE_ID:		Container Instance ID
+ *
+ * %AR_TKN_U32_CONTAINER_CAPABILITY_ID:		Container capability ID
+ *						APM_CONTAINER_CAP_ID_PP = 1,
+ *						APM_CONTAINER_CAP_ID_CD = 2,
+ *						APM_CONTAINER_CAP_ID_EP = 3,
+ *						APM_CONTAINER_CAP_ID_OLC = 4
+ *
+ * %AR_TKN_U32_CONTAINER_STACK_SIZE:		Stack size in the container.
+ *
+ * %AR_TKN_U32_CONTAINER_GRAPH_POS:		Graph Position
+ *						APM_CONT_GRAPH_POS_STREAM = 1,
+ *						APM_CONT_GRAPH_POS_PER_STR_PER_DEV = 2,
+ *						APM_CONT_GRAPH_POS_STR_DEV = 3,
+ *						APM_CONT_GRAPH_POS_GLOBAL_DEV = 4
+ *
+ * %AR_TKN_U32_CONTAINER_PROC_DOMAIN:		Processor domain of container
+ *						APM_PROC_DOMAIN_ID_MDSP = 1,
+ *						APM_PROC_DOMAIN_ID_ADSP = 2,
+ *						APM_PROC_DOMAIN_ID_SDSP = 4,
+ *						APM_PROC_DOMAIN_ID_CDSP = 5
+ *
+ * %AR_TKN_U32_MODULE_ID:			Module ID
+ *
+ * %AR_TKN_U32_MODULE_INSTANCE_ID:		Module Instance ID.
+ *
+ * %AR_TKN_U32_MODULE_MAX_IP_PORTS:		Module maximum input ports
+ *
+ * %AR_TKN_U32_MODULE_MAX_OP_PORTS:		Module maximum output ports.
+ *
+ * %AR_TKN_U32_MODULE_IN_PORTS:			Number of in ports
+ *
+ * %AR_TKN_U32_MODULE_OUT_PORTS:		Number of out ports.
+ *
+ * %AR_TKN_U32_MODULE_SRC_OP_PORT_ID:		Source module output port ID
+ *
+ * %AR_TKN_U32_MODULE_DST_IN_PORT_ID:		Destination module input port ID
+ *
+ * %AR_TKN_U32_MODULE_HW_IF_IDX:		Interface index types for I2S/LPAIF
+ *
+ * %AR_TKN_U32_MODULE_HW_IF_TYPE:		Interface type
+ *						LPAIF = 0,
+ *						LPAIF_RXTX = 1,
+ *						LPAIF_WSA = 2,
+ *						LPAIF_VA = 3,
+ *						LPAIF_AXI = 4
+ *
+ * %AR_TKN_U32_MODULE_FMT_INTERLEAVE:		PCM Interleaving
+ *						PCM_INTERLEAVED = 1,
+ *						PCM_DEINTERLEAVED_PACKED = 2,
+ *						PCM_DEINTERLEAVED_UNPACKED = 3
+ *
+ * %AR_TKN_U32_MODULE_FMT_DATA:			data format
+ *						FIXED POINT = 1,
+ *						IEC60958 PACKETIZED = 3,
+ *						IEC60958 PACKETIZED NON LINEAR = 8,
+ *						COMPR OVER PCM PACKETIZED = 7,
+ *						IEC61937 PACKETIZED = 2,
+ *						GENERIC COMPRESSED = 5
+ *
+ * %AR_TKN_U32_MODULE_FMT_FREQ:			bit rate
+ *
+ * %AR_TKN_U32_MODULE_FMT_BIT_DEPTH:		bit depth
+ *
+ * %AR_TKN_U32_MODULE_SD_LINE_IDX:		I2S serial data line idx
+ *						I2S_SD0 = 1,
+ *						I2S_SD1 = 2,
+ *						I2S_SD2 = 3,
+ *						I2S_SD3 = 4,
+ *						I2S_QUAD01 = 5,
+ *						I2S_QUAD23 = 6,
+ *						I2S_6CHS = 7,
+ *						I2S_8CHS = 8
+ *
+ * %AR_TKN_U32_MODULE_WS_SRC:			Word Select Source
+ *						AR_I2S_WS_SRC_EXTERNAL = 0,
+ *						AR_I2S_WS_SRC_INTERNAL = 1,
+ *
+ * %AR_TKN_U32_MODULE_FRAME_SZ_FACTOR:		Frame size factor
+ *
+ * %AR_TKN_U32_MODULE_LOG_CODE:			Log Module Code
+ *
+ * %AR_TKN_U32_MODULE_LOG_TAP_POINT_ID:		logging tap point of this module
+ *
+ * %AR_TKN_U32_MODULE_LOG_MODE:			logging mode
+ *						LOG_WAIT = 0,
+ *						LOG_IMMEDIATELY = 1
+ *
+ * %AR_TKN_DAI_INDEX:				dai index
+ *
+ */
+
+/* DAI Tokens */
+#define AR_TKN_DAI_INDEX			1
+/* SUB GRAPH Tokens */
+#define AR_TKN_U32_SUB_GRAPH_INSTANCE_ID	2
+#define AR_TKN_U32_SUB_GRAPH_PERF_MODE		3
+#define AR_TKN_U32_SUB_GRAPH_DIRECTION		4
+#define AR_TKN_U32_SUB_GRAPH_SCENARIO_ID	5
+
+/* Container Tokens */
+#define AR_TKN_U32_CONTAINER_INSTANCE_ID		100
+#define AR_TKN_U32_CONTAINER_CAPABILITY_ID	101
+#define AR_TKN_U32_CONTAINER_STACK_SIZE		102
+#define AR_TKN_U32_CONTAINER_GRAPH_POS		103
+#define AR_TKN_U32_CONTAINER_PROC_DOMAIN		104
+
+/* Module Tokens */
+#define AR_TKN_U32_MODULE_ID			200
+#define AR_TKN_U32_MODULE_INSTANCE_ID		201
+#define AR_TKN_U32_MODULE_MAX_IP_PORTS		202
+#define AR_TKN_U32_MODULE_MAX_OP_PORTS		203
+#define AR_TKN_U32_MODULE_IN_PORTS		204
+#define AR_TKN_U32_MODULE_OUT_PORTS		205
+#define AR_TKN_U32_MODULE_SRC_OP_PORT_ID	206
+#define AR_TKN_U32_MODULE_DST_IN_PORT_ID	207
+#define AR_TKN_U32_MODULE_SRC_INSTANCE_ID	208
+#define AR_TKN_U32_MODULE_DST_INSTANCE_ID	209
+
+
+#define AR_TKN_U32_MODULE_HW_IF_IDX		250
+#define AR_TKN_U32_MODULE_HW_IF_TYPE		251
+#define AR_TKN_U32_MODULE_FMT_INTERLEAVE	252
+#define AR_TKN_U32_MODULE_FMT_DATA		253
+#define AR_TKN_U32_MODULE_FMT_FREQ		254
+#define AR_TKN_U32_MODULE_FMT_BIT_DEPTH		255
+#define AR_TKN_U32_MODULE_SD_LINE_IDX		256
+#define AR_TKN_U32_MODULE_WS_SRC		257
+#define AR_TKN_U32_MODULE_FRAME_SZ_FACTOR	258
+#define AR_TKN_U32_MODULE_LOG_CODE		259
+#define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID	260
+#define AR_TKN_U32_MODULE_LOG_MODE		261
+
+#endif /* __SND_AR_TOKENS_H__ */
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 5a693f83fd6c..66d8436ab0a8 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -100,6 +100,7 @@  config SND_SOC_QDSP6
 	select SND_SOC_QDSP6_ROUTING
 	select SND_SOC_QDSP6_ASM
 	select SND_SOC_QDSP6_ASM_DAI
+	select SND_SOC_TOPOLOGY
 	select SND_SOC_QDSP6_APM
 	help
 	 To add support for MSM QDSP6 Soc Audio.
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index 1a0803d97eec..766b824f6597 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -1,6 +1,6 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 snd-q6dsp-common-objs := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o
-snd-q6apm-objs := q6apm.o audioreach.o
+snd-q6apm-objs := q6apm.o audioreach.o topology.o
 
 obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o
 obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c
index acb9c7ad6fcd..1b7ba454f87d 100644
--- a/sound/soc/qcom/qdsp6/q6apm.c
+++ b/sound/soc/qcom/qdsp6/q6apm.c
@@ -785,11 +785,13 @@  EXPORT_SYMBOL_GPL(q6apm_graph_flush);
 
 static int q6apm_audio_probe(struct snd_soc_component *component)
 {
-	return 0;
+	return audioreach_tplg_init(component);
 }
 
 static void q6apm_audio_remove(struct snd_soc_component *component)
 {
+	/* remove topology */
+	snd_soc_tplg_component_remove(component);
 }
 
 #define APM_AUDIO_DRV_NAME "q6apm-audio"
diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c
new file mode 100644
index 000000000000..37d21d7898f1
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/topology.c
@@ -0,0 +1,1118 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asound.h>
+#include <linux/firmware.h>
+#include <sound/soc-topology.h>
+#include <sound/soc-dpcm.h>
+#include <uapi/sound/snd_ar_tokens.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include "q6apm.h"
+#include "audioreach.h"
+
+struct snd_ar_control {
+	u32 sgid; /* Sub Graph ID */
+	struct snd_soc_component *scomp;
+};
+
+static struct audioreach_graph_info *audioreach_tplg_alloc_graph_info(
+					struct q6apm *apm, uint32_t graph_id,
+					bool *found)
+{
+	struct audioreach_graph_info *info;
+	int ret;
+
+	spin_lock(&apm->lock);
+	info = idr_find(&apm->graph_info_idr, graph_id);
+	spin_unlock(&apm->lock);
+
+	if (info) {
+		*found = true;
+		return info;
+	}
+
+	*found = false;
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return ERR_PTR(-ENOMEM);
+
+
+	INIT_LIST_HEAD(&info->sg_list);
+	spin_lock_init(&info->sg_list_lock);
+
+	spin_lock(&apm->lock);
+	ret = idr_alloc(&apm->graph_info_idr, info, graph_id,
+			graph_id + 1, GFP_ATOMIC);
+	spin_unlock(&apm->lock);
+
+	if (ret < 0) {
+		dev_err(apm->dev, "Failed to allocate Graph ID (%x)\n", graph_id);
+		kfree(info);
+		return ERR_PTR(ret);
+	}
+
+	info->id = ret;
+
+	return info;
+}
+
+static void audioreach_tplg_add_sub_graph(struct audioreach_sub_graph *sg,
+					  struct audioreach_graph_info *info)
+{
+	list_add_tail(&sg->node, &info->sg_list);
+	sg->info = info;
+	info->num_sub_graphs++;
+}
+
+
+static struct audioreach_sub_graph *audioreach_tplg_alloc_sub_graph(
+						struct q6apm *apm,
+						uint32_t sub_graph_id,
+						bool *found)
+{
+	struct audioreach_sub_graph *sg = NULL;
+	int ret;
+
+	if (!sub_graph_id)
+		return ERR_PTR(-EINVAL);
+
+	/* Find if there is already a matching sub-graph */
+	spin_lock(&apm->lock);
+	sg = idr_find(&apm->sub_graphs_idr, sub_graph_id);
+	spin_unlock(&apm->lock);
+
+
+	if (sg) {
+		*found = true;
+		return sg;
+	}
+
+	*found = false;
+	sg = kzalloc(sizeof(*sg), GFP_KERNEL);
+	if (!sg)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&sg->container_list);
+
+	spin_lock(&apm->lock);
+	ret = idr_alloc(&apm->sub_graphs_idr, sg, sub_graph_id,
+			sub_graph_id + 1, GFP_ATOMIC);
+	spin_unlock(&apm->lock);
+
+	if (ret < 0) {
+		dev_err(apm->dev,
+			"Failed to allocate Sub-Graph Instance ID (%x)\n",
+			sub_graph_id);
+		kfree(sg);
+		return ERR_PTR(ret);
+	}
+
+	sg->sub_graph_id = ret;
+
+	return sg;
+}
+
+static struct audioreach_container *audioreach_tplg_alloc_container(struct q6apm *apm,
+						struct audioreach_sub_graph *sg,
+						uint32_t container_id,
+						bool *found)
+{
+	struct audioreach_container *cont = NULL;
+	int ret;
+
+	if (!container_id)
+		return ERR_PTR(-EINVAL);
+
+	spin_lock(&apm->lock);
+	cont = idr_find(&apm->containers_idr, container_id);
+	spin_unlock(&apm->lock);
+
+	if (cont) {
+		*found = true;
+		return cont;
+	}
+	*found = false;
+
+	cont = kzalloc(sizeof(*cont), GFP_KERNEL);
+	if (!cont)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&cont->modules_list);
+
+	spin_lock(&apm->lock);
+	ret = idr_alloc(&apm->containers_idr, cont, container_id,
+				container_id + 1, GFP_ATOMIC);
+	spin_unlock(&apm->lock);
+
+	if (ret < 0) {
+		dev_err(apm->dev,
+			"Failed to allocate Container Instance ID (%x)\n",
+			container_id);
+		kfree(cont);
+		return ERR_PTR(ret);
+	}
+
+	cont->container_id = ret;
+	cont->sub_graph = sg;
+	/* add to container list */
+	list_add_tail(&cont->node, &sg->container_list);
+	sg->num_containers++;
+
+	return cont;
+}
+
+static struct audioreach_module *audioreach_tplg_alloc_module(struct q6apm *apm,
+					struct audioreach_container *cont,
+					struct snd_soc_dapm_widget *w,
+					uint32_t module_id,
+					bool *found)
+{
+	struct audioreach_module *mod;
+	int ret;
+
+	spin_lock(&apm->lock);
+	mod = idr_find(&apm->modules_idr, module_id);
+	spin_unlock(&apm->lock);
+
+	if (mod) {
+		*found = true;
+		return mod;
+	}
+	*found = false;
+	mod = kzalloc(sizeof(*mod), GFP_KERNEL);
+	if (!mod)
+		return ERR_PTR(-ENOMEM);
+
+	spin_lock(&apm->lock);
+	if (!module_id) { /* alloc module id dynamically */
+		ret = idr_alloc_cyclic(&apm->modules_idr, mod,
+				       AR_MODULE_DYNAMIC_INSTANCE_ID_START,
+				       AR_MODULE_DYNAMIC_INSTANCE_ID_END,
+				       GFP_ATOMIC);
+	} else {
+		ret = idr_alloc(&apm->modules_idr, mod, module_id,
+			  module_id + 1, GFP_ATOMIC);
+	}
+	spin_unlock(&apm->lock);
+
+	if (ret < 0) {
+		dev_err(apm->dev,
+			"Failed to allocate Module Instance ID (%x)\n", module_id);
+		kfree(mod);
+		return ERR_PTR(ret);
+	}
+
+	mod->instance_id = ret;
+	dev_err(apm->dev, "Module Instance ID (0x%08x) allocated\n", ret);
+	/* add to module list */
+	list_add_tail(&mod->node, &cont->modules_list);
+	mod->container = cont;
+	mod->widget = w;
+	cont->num_modules++;
+
+	return mod;
+}
+
+static struct snd_soc_tplg_vendor_array *audioreach_get_sg_array(
+					struct snd_soc_tplg_private *private)
+{
+	struct snd_soc_tplg_vendor_array *sg_array = NULL;
+	bool found = false;
+	int sz;
+
+	for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+		struct snd_soc_tplg_vendor_value_elem *sg_elem;
+		int tkn_count = 0;
+
+		sg_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+		sg_elem = sg_array->value;
+		sz = sz + le32_to_cpu(sg_array->size);
+		while (!found && tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {
+			switch (le32_to_cpu(sg_elem->token)) {
+			case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+				found = true;
+				break;
+			default:
+				break;
+			}
+			tkn_count++;
+			sg_elem++;
+		}
+	}
+
+	if (found)
+		return sg_array;
+
+	return NULL;
+}
+
+static struct snd_soc_tplg_vendor_array *audioreach_get_cont_array(
+					struct snd_soc_tplg_private *private)
+{
+	struct snd_soc_tplg_vendor_array *cont_array = NULL;
+	bool found = false;
+	int sz;
+
+	for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+		struct snd_soc_tplg_vendor_value_elem *cont_elem;
+		int tkn_count = 0;
+
+		cont_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+		cont_elem = cont_array->value;
+		sz = sz + le32_to_cpu(cont_array->size);
+		while (!found && tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {
+			switch (le32_to_cpu(cont_elem->token)) {
+			case AR_TKN_U32_CONTAINER_INSTANCE_ID:
+				found = true;
+				break;
+			default:
+				break;
+			}
+			tkn_count++;
+			cont_elem++;
+		}
+	}
+
+	if (found)
+		return cont_array;
+
+	return NULL;
+}
+
+static struct snd_soc_tplg_vendor_array *audioreach_get_module_array(
+					struct snd_soc_tplg_private *private)
+{
+	struct snd_soc_tplg_vendor_array *mod_array = NULL;
+	bool found = false;
+	int sz = 0;
+
+	for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+		struct snd_soc_tplg_vendor_value_elem *mod_elem;
+		int tkn_count = 0;
+
+		mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+		mod_elem = mod_array->value;
+		sz = sz + le32_to_cpu(mod_array->size);
+		while (!found && tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+			switch (le32_to_cpu(mod_elem->token)) {
+			case AR_TKN_U32_MODULE_INSTANCE_ID:
+				found = true;
+				break;
+			default:
+				break;
+			}
+			tkn_count++;
+			mod_elem++;
+		}
+	}
+
+	if (found)
+		return mod_array;
+
+	return NULL;
+}
+
+static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm,
+					struct snd_soc_tplg_private *private)
+{
+	struct audioreach_graph_info *info = NULL;
+	struct snd_soc_tplg_vendor_array *sg_array;
+	struct snd_soc_tplg_vendor_value_elem *sg_elem;
+	struct audioreach_sub_graph *sg;
+	int graph_id, sub_graph_id, tkn_count = 0;
+	bool found;
+
+	sg_array = audioreach_get_sg_array(private);
+	sg_elem = sg_array->value;
+
+	while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {
+		switch (le32_to_cpu(sg_elem->token)) {
+		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+			sub_graph_id = le32_to_cpu(sg_elem->value);
+			sg = audioreach_tplg_alloc_sub_graph(apm, sub_graph_id, &found);
+			if (IS_ERR(sg)) {
+				return sg;
+			} else if (found) {
+				/* Already parsed data for this sub-graph */
+				return sg;
+			}
+			dev_err(apm->dev, "%s: New subgraph id : 0x%08x\n", __func__,
+				sub_graph_id);
+			break;
+		case AR_TKN_DAI_INDEX:
+			/* Sub graph is associated with predefined graph */
+			graph_id = le32_to_cpu(sg_elem->value);
+			info = audioreach_tplg_alloc_graph_info(apm, graph_id, &found);
+			if (IS_ERR(info))
+				return ERR_CAST(info);
+			break;
+		case AR_TKN_U32_SUB_GRAPH_PERF_MODE:
+			sg->perf_mode = le32_to_cpu(sg_elem->value);
+			break;
+		case AR_TKN_U32_SUB_GRAPH_DIRECTION:
+			sg->direction = le32_to_cpu(sg_elem->value);
+			break;
+		case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:
+			sg->scenario_id = le32_to_cpu(sg_elem->value);
+			break;
+		default:
+			dev_err(apm->dev, "Not a valid token %d for graph\n",
+				sg_elem->token);
+		break;
+
+		}
+		tkn_count++;
+		sg_elem++;
+	}
+
+	/* Sub graph is associated with predefined graph */
+	if (info) {
+		dev_err(apm->dev, "%s: adding subgraph id : 0x%08x -> %d\n", __func__,
+		sub_graph_id, graph_id);
+
+		audioreach_tplg_add_sub_graph(sg, info);
+	}
+
+	return sg;
+}
+
+static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *apm,
+						struct audioreach_sub_graph *sg,
+						struct snd_soc_tplg_private *private)
+{
+	struct snd_soc_tplg_vendor_array *cont_array;
+	struct snd_soc_tplg_vendor_value_elem *cont_elem;
+	struct audioreach_container *cont;
+	int container_id, tkn_count = 0;
+	bool found = false;
+
+	cont_array = audioreach_get_cont_array(private);
+	cont_elem = cont_array->value;
+
+	while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {
+		switch (le32_to_cpu(cont_elem->token)) {
+		case AR_TKN_U32_CONTAINER_INSTANCE_ID:
+			container_id = le32_to_cpu(cont_elem->value);
+			cont = audioreach_tplg_alloc_container(apm, sg, container_id, &found);
+			if (IS_ERR(cont))
+				return ERR_PTR(-ENOMEM);
+			else if (found) /* Already parsed container data */
+				return cont;
+
+			break;
+		case AR_TKN_U32_CONTAINER_CAPABILITY_ID:
+			cont->capability_id = le32_to_cpu(cont_elem->value);
+			break;
+		case AR_TKN_U32_CONTAINER_STACK_SIZE:
+			cont->stack_size = le32_to_cpu(cont_elem->value);
+			break;
+		case AR_TKN_U32_CONTAINER_GRAPH_POS:
+			cont->graph_pos = le32_to_cpu(cont_elem->value);
+			break;
+		case AR_TKN_U32_CONTAINER_PROC_DOMAIN:
+			cont->proc_domain = le32_to_cpu(cont_elem->value);
+			break;
+		default:
+			dev_err(apm->dev, "Not a valid token %d for graph\n",
+				cont_elem->token);
+		break;
+
+		}
+		tkn_count++;
+		cont_elem++;
+	}
+
+	return cont;
+}
+
+static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *apm,
+							struct audioreach_container *cont,
+							struct snd_soc_tplg_private *private,
+							struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_tplg_vendor_array *mod_array;
+	struct snd_soc_tplg_vendor_value_elem *mod_elem;
+	uint32_t max_ip_port = 0, max_op_port = 0, in_port = 0, out_port = 0;
+	uint32_t src_mod_inst_id = 0, src_mod_op_port_id = 0;
+	uint32_t dst_mod_inst_id = 0, dst_mod_ip_port_id = 0;
+	int module_id = 0, instance_id = 0, tkn_count = 0;
+	struct audioreach_module *mod = NULL;
+	bool found;
+
+	mod_array = audioreach_get_module_array(private);
+	mod_elem = mod_array->value;
+
+	while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+		switch (le32_to_cpu(mod_elem->token)) {
+		/* common module info */
+		case AR_TKN_U32_MODULE_ID:
+			module_id = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_INSTANCE_ID:
+			instance_id = le32_to_cpu(mod_elem->value);
+			mod = audioreach_tplg_alloc_module(apm, cont, w,
+							   instance_id, &found);
+			if (IS_ERR(mod)) {
+				return ERR_PTR(-ENOMEM);
+			} else if (found) {
+				dev_err(apm->dev, "Duplicate Module Instance ID 0x%08x found\n",
+					instance_id);
+				return ERR_PTR(-EINVAL);
+			}
+
+			break;
+		case AR_TKN_U32_MODULE_MAX_IP_PORTS:
+			max_ip_port = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_MAX_OP_PORTS:
+			max_op_port = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_IN_PORTS:
+			in_port = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_OUT_PORTS:
+			out_port = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_SRC_OP_PORT_ID:
+			src_mod_op_port_id = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_SRC_INSTANCE_ID:
+			src_mod_inst_id = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_DST_INSTANCE_ID:
+			dst_mod_inst_id = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_DST_IN_PORT_ID:
+			dst_mod_ip_port_id = le32_to_cpu(mod_elem->value);
+
+		default:
+			break;
+
+		}
+		tkn_count++;
+		mod_elem++;
+	}
+
+	if (mod) {
+		mod->module_id = module_id;
+		mod->max_ip_port = max_ip_port;
+		mod->max_op_port = max_op_port;
+		mod->in_port = in_port;
+		mod->out_port = out_port;
+		mod->src_mod_inst_id = src_mod_inst_id;
+		mod->src_mod_op_port_id = src_mod_op_port_id;
+		mod->dst_mod_inst_id = dst_mod_inst_id;
+		mod->dst_mod_ip_port_id = dst_mod_ip_port_id;
+	}
+
+	return mod;
+}
+
+static int audioreach_widget_load_module_common(struct snd_soc_component *component,
+				      int index, struct snd_soc_dapm_widget *w,
+				      struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	struct q6apm *apm = dev_get_drvdata(component->dev);
+	struct audioreach_sub_graph *sg;
+	struct audioreach_container *cont;
+	struct audioreach_module *mod;
+	struct snd_soc_dobj *dobj;
+
+	sg = audioreach_parse_sg_tokens(apm, &tplg_w->priv);
+	if (IS_ERR(sg))
+		return PTR_ERR(sg);
+
+	cont = audioreach_parse_cont_tokens(apm, sg, &tplg_w->priv);
+	if (IS_ERR(cont))
+		return PTR_ERR(cont);
+
+	mod = audioreach_parse_common_tokens(apm, cont, &tplg_w->priv, w);
+	if (IS_ERR(mod))
+		return PTR_ERR(mod);
+
+	dobj = &w->dobj;
+	dobj->private = mod;
+
+	return 0;
+}
+
+static int audioreach_widget_load_enc_dec_cnv(struct snd_soc_component *component,
+					  int index, struct snd_soc_dapm_widget *w,
+					  struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	struct	snd_soc_tplg_vendor_array *mod_array;
+	struct snd_soc_tplg_vendor_value_elem *mod_elem;
+	struct audioreach_module *mod;
+	struct snd_soc_dobj *dobj;
+	int tkn_count = 0;
+	int ret;
+
+	ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+	if (ret)
+		return ret;
+
+	dobj = &w->dobj;
+	mod = dobj->private;
+	mod_array = audioreach_get_module_array(&tplg_w->priv);
+	mod_elem = mod_array->value;
+
+	while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+		switch (le32_to_cpu(mod_elem->token)) {
+		case AR_TKN_U32_MODULE_FMT_INTERLEAVE:
+			mod->interleave_type = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_FMT_FREQ:
+			mod->rate = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_FMT_BIT_DEPTH:
+			mod->bit_depth = le32_to_cpu(mod_elem->value);
+			break;
+		default:
+			break;
+		}
+		tkn_count++;
+		mod_elem++;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_log_module_load(struct audioreach_module *mod,
+					     struct snd_soc_tplg_vendor_array *mod_array)
+{
+	struct snd_soc_tplg_vendor_value_elem *mod_elem;
+	int tkn_count = 0;
+
+	mod_elem = mod_array->value;
+
+	while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+		switch (le32_to_cpu(mod_elem->token)) {
+
+		case AR_TKN_U32_MODULE_LOG_CODE:
+			mod->log_code = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_LOG_TAP_POINT_ID:
+			mod->log_tap_point_id = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_LOG_MODE:
+			mod->mode = le32_to_cpu(mod_elem->value);
+			break;
+		default:
+			break;
+		}
+		tkn_count++;
+		mod_elem++;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_dma_module_load(struct audioreach_module *mod,
+					     struct snd_soc_tplg_vendor_array *mod_array)
+{
+	struct snd_soc_tplg_vendor_value_elem *mod_elem;
+	int tkn_count = 0;
+
+	mod_elem = mod_array->value;
+
+	while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+		switch (le32_to_cpu(mod_elem->token)) {
+		case AR_TKN_U32_MODULE_HW_IF_IDX:
+			mod->hw_interface_idx = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_FMT_DATA:
+			mod->data_format = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_HW_IF_TYPE:
+			mod->hw_interface_type = le32_to_cpu(mod_elem->value);
+			break;
+		default:
+			break;
+		}
+		tkn_count++;
+		mod_elem++;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_i2s_module_load(struct audioreach_module *mod,
+					     struct snd_soc_tplg_vendor_array *mod_array)
+{
+	struct snd_soc_tplg_vendor_value_elem *mod_elem;
+	int tkn_count = 0;
+
+	mod_elem = mod_array->value;
+
+	while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+		switch (le32_to_cpu(mod_elem->token)) {
+		case AR_TKN_U32_MODULE_HW_IF_IDX:
+			mod->hw_interface_idx = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_FMT_DATA:
+			mod->data_format = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_HW_IF_TYPE:
+			mod->hw_interface_type = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_SD_LINE_IDX:
+			mod->sd_line_idx = le32_to_cpu(mod_elem->value);
+			break;
+		case AR_TKN_U32_MODULE_WS_SRC:
+			mod->ws_src = le32_to_cpu(mod_elem->value);
+			break;
+		default:
+			break;
+		}
+		tkn_count++;
+		mod_elem++;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_load_buffer(struct snd_soc_component *component,
+					 int index, struct snd_soc_dapm_widget *w,
+					 struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	struct snd_soc_tplg_vendor_array *mod_array;
+	struct audioreach_module *mod;
+	struct snd_soc_dobj *dobj;
+	int ret;
+
+	ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+	if (ret)
+		return ret;
+
+	dobj = &w->dobj;
+	mod = dobj->private;
+
+	mod_array = audioreach_get_module_array(&tplg_w->priv);
+
+	switch (mod->module_id) {
+	case MODULE_ID_CODEC_DMA_SINK:
+	case MODULE_ID_CODEC_DMA_SOURCE:
+		audioreach_widget_dma_module_load(mod, mod_array);
+		break;
+	case MODULE_ID_DATA_LOGGING:
+		audioreach_widget_log_module_load(mod, mod_array);
+		break;
+	case MODULE_ID_I2S_SINK:
+	case MODULE_ID_I2S_SOURCE:
+		audioreach_widget_i2s_module_load(mod, mod_array);
+		break;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_load_mixer(struct snd_soc_component *component,
+					int index, struct snd_soc_dapm_widget *w,
+					struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	struct snd_soc_tplg_vendor_value_elem *w_elem;
+	struct snd_soc_tplg_vendor_array *w_array;
+	struct snd_ar_control *scontrol;
+	struct snd_soc_dobj *dobj;
+	int tkn_count = 0;
+
+	w_array = &tplg_w->priv.array[0];
+
+	scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL);
+	if (!scontrol)
+		return -ENOMEM;
+
+	scontrol->scomp = component;
+	dobj = &w->dobj;
+	dobj->private = scontrol;
+
+	w_elem = w_array->value;
+	while (tkn_count <= (le32_to_cpu(w_array->num_elems) - 1)) {
+		switch (le32_to_cpu(w_elem->token)) {
+		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+			scontrol->sgid = le32_to_cpu(w_elem->value);
+			break;
+		default: /* ignore other tokens */
+			break;
+		}
+		tkn_count++;
+		w_elem++;
+	}
+
+	return 0;
+}
+
+static int audioreach_widget_load_pga(struct snd_soc_component *component,
+					int index, struct snd_soc_dapm_widget *w,
+					struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	struct audioreach_module *mod;
+	struct snd_soc_dobj *dobj;
+	int ret;
+
+	ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+	if (ret)
+		return ret;
+
+	dobj = &w->dobj;
+	mod = dobj->private;
+	mod->gain = VOL_CTRL_DEFAULT_GAIN;
+
+	return 0;
+}
+
+static int audioreach_widget_ready(struct snd_soc_component *component,
+				   int index, struct snd_soc_dapm_widget *w,
+				   struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+	switch (w->id) {
+	case snd_soc_dapm_aif_in:
+	case snd_soc_dapm_aif_out:
+		audioreach_widget_load_buffer(component, index, w, tplg_w);
+		break;
+	case snd_soc_dapm_decoder:
+	case snd_soc_dapm_encoder:
+	case snd_soc_dapm_src:
+		audioreach_widget_load_enc_dec_cnv(component, index, w, tplg_w);
+		break;
+	case snd_soc_dapm_buffer:
+		audioreach_widget_load_buffer(component, index, w, tplg_w);
+		break;
+	case snd_soc_dapm_mixer:
+		return audioreach_widget_load_mixer(component, index, w, tplg_w);
+	case snd_soc_dapm_pga:
+		return audioreach_widget_load_pga(component, index, w, tplg_w);
+	case snd_soc_dapm_dai_link:
+	case snd_soc_dapm_scheduler:
+	case snd_soc_dapm_out_drv:
+	default:
+		dev_err(component->dev, "Widget type (0x%x) not yet supported\n",
+			w->id);
+		break;
+	}
+
+	return 0;
+}
+
+
+static int audioreach_widget_unload(struct snd_soc_component *scomp,
+				    struct snd_soc_dobj *dobj)
+{
+	struct q6apm *apm = dev_get_drvdata(scomp->dev);
+	struct audioreach_container *cont;
+	struct audioreach_module *mod;
+	struct snd_soc_dapm_widget *w =
+		container_of(dobj, struct snd_soc_dapm_widget, dobj);
+
+	mod = dobj->private;
+	cont = mod->container;
+
+	if (w->id == snd_soc_dapm_mixer) {
+		/* virtual widget */
+		kfree(dobj->private);
+		return 0;
+	}
+
+	spin_lock(&apm->lock);
+	idr_remove(&apm->modules_idr, mod->instance_id);
+	cont->num_modules--;
+
+	/* delete list */
+	list_del(&mod->node);
+	/* free memory */
+	kfree(mod);
+
+	if (list_empty(&cont->modules_list)) { /* remove container */
+		struct audioreach_sub_graph *sg = cont->sub_graph;
+
+		idr_remove(&apm->containers_idr, cont->container_id);
+		list_del(&cont->node);
+		sg->num_containers--;
+		kfree(cont);
+		if (list_empty(&sg->container_list)) { /* remove sg */
+			struct audioreach_graph_info *info = sg->info;
+
+			idr_remove(&apm->sub_graphs_idr, sg->sub_graph_id);
+			list_del(&sg->node);
+			info->num_sub_graphs--;
+			kfree(sg);
+			if (list_empty(&info->sg_list)) { /* remove graph info */
+				idr_remove(&apm->graph_info_idr, info->id);
+				kfree(info);
+			}
+		}
+	}
+
+	spin_unlock(&apm->lock);
+
+	return 0;
+}
+
+static struct audioreach_module *audioreach_find_widget(struct snd_soc_component *comp,
+							const char *name)
+{
+	struct q6apm *apm = dev_get_drvdata(comp->dev);
+	struct audioreach_module *module;
+	int id = 0;
+
+	idr_for_each_entry(&apm->modules_idr, module, id) {
+		if (!strcmp(name, module->widget->name))
+			return module;
+	}
+
+	return NULL;
+}
+
+static int audioreach_route_load(struct snd_soc_component *scomp, int index,
+			  struct snd_soc_dapm_route *route)
+{
+	struct audioreach_module *src, *sink;
+
+	src = audioreach_find_widget(scomp, route->source);
+	sink = audioreach_find_widget(scomp, route->sink);
+
+	if (src && sink) {
+		src->dst_mod_inst_id = sink->instance_id;
+		sink->src_mod_inst_id = src->instance_id;
+	}
+
+	return 0;
+}
+
+static int audioreach_route_unload(struct snd_soc_component *scomp,
+			    struct snd_soc_dobj *dobj)
+{
+
+	return 0;
+}
+
+static void audioreach_tplg_complete(struct snd_soc_component *component)
+{
+	/* TBD */
+}
+
+/* DAI link - used for any driver specific init */
+static int audioreach_link_load(struct snd_soc_component *component, int index,
+				struct snd_soc_dai_link *link,
+				struct snd_soc_tplg_link_config *cfg)
+{
+	link->nonatomic = true;
+	link->dynamic = true;
+	link->platforms->name = NULL;
+	link->platforms->of_node = of_get_compatible_child(component->dev->of_node,
+				"qcom,q6apm-dais");
+	link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST;
+	link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST;
+
+	return 0;
+}
+
+static int audioreach_get_audio_mixer(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_ar_control *scontrol = mc->dobj.private;
+	struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+	struct q6apm *data = dev_get_drvdata(c->dev);
+	bool connected;
+
+	connected = q6apm_is_sub_graphs_connected(data, scontrol->sgid,
+						  dapm_scontrol->sgid);
+	if (connected)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+static int audioreach_put_audio_mixer(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_ar_control *scontrol = mc->dobj.private;
+	struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+	struct q6apm *data = dev_get_drvdata(c->dev);
+	struct snd_soc_dapm_update *update = NULL;
+
+	if (ucontrol->value.integer.value[0]) {
+		q6apm_connect_sub_graphs(data, scontrol->sgid,
+					 dapm_scontrol->sgid, true);
+		snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, update);
+	} else {
+		q6apm_connect_sub_graphs(data, scontrol->sgid,
+					 dapm_scontrol->sgid, false);
+		snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, update);
+	}
+	return 0;
+}
+
+static int audioreach_get_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct audioreach_module *mod = dw->dobj.private;
+
+	/* Check if the graph is active or not */
+	ucontrol->value.integer.value[0] = mod->gain;
+
+	return 0;
+}
+
+static int audioreach_put_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+	struct audioreach_module *mod = dw->dobj.private;
+	struct q6apm *apm = dev_get_drvdata(c->dev);
+	int vol = ucontrol->value.integer.value[0];
+
+	/* Check if the graph is active or not */
+	if (dw->power) {
+		audioreach_gain_set_vol_ctrl(apm, mod, vol);
+		mod->gain = vol;
+		return 1;
+	}
+
+	dev_err(apm->dev, "Unable to set volume as graph is not	active\n");
+	return 0;
+
+}
+
+static int audioreach_control_load_mix(struct snd_soc_component *scomp,
+					  struct snd_ar_control *scontrol,
+					  struct snd_kcontrol_new *kc,
+					  struct snd_soc_tplg_ctl_hdr *hdr)
+{
+	struct snd_soc_tplg_mixer_control *mc;
+	struct snd_soc_tplg_vendor_array *c_array;
+	struct snd_soc_tplg_vendor_value_elem *c_elem;
+	int tkn_count = 0;
+
+	mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);
+	c_array = (struct snd_soc_tplg_vendor_array *)mc->priv.data;
+
+	c_elem = c_array->value;
+
+	while (tkn_count <= (le32_to_cpu(c_array->num_elems) - 1)) {
+		switch (le32_to_cpu(c_elem->token)) {
+		case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+			scontrol->sgid = le32_to_cpu(c_elem->value);
+			break;
+		default:
+			/* Ignore other tokens */
+		break;
+
+		}
+		c_elem++;
+		tkn_count++;
+	}
+
+	return 0;
+}
+
+static int audioreach_control_load(struct snd_soc_component *scomp, int index,
+				   struct snd_kcontrol_new *kc,
+				   struct snd_soc_tplg_ctl_hdr *hdr)
+{
+	struct snd_ar_control *scontrol;
+	struct snd_soc_dobj *dobj;
+	struct soc_mixer_control *sm;
+	int ret;
+
+	scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL);
+	if (!scontrol)
+		return -ENOMEM;
+
+	scontrol->scomp = scomp;
+
+	switch (le32_to_cpu(hdr->ops.get)) {
+	case SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX:
+		sm = (struct soc_mixer_control *)kc->private_value;
+		dobj = &sm->dobj;
+		ret = audioreach_control_load_mix(scomp, scontrol, kc, hdr);
+		break;
+	case SND_SOC_AR_TPLG_VOL_CTL:
+		sm = (struct soc_mixer_control *)kc->private_value;
+		dobj = &sm->dobj;
+		break;
+	default:
+		dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
+			 hdr->ops.get, hdr->ops.put, hdr->ops.info);
+		kfree(scontrol);
+		return -EINVAL;
+	}
+
+	dobj->private = scontrol;
+	return ret;
+}
+
+static int audioreach_control_unload(struct snd_soc_component *scomp,
+				     struct snd_soc_dobj *dobj)
+{
+	struct snd_ar_control *scontrol = dobj->private;
+
+	kfree(scontrol);
+
+	return 0;
+}
+
+static const struct snd_soc_tplg_kcontrol_ops audioreach_io_ops[] = {
+	{SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX, audioreach_get_audio_mixer,
+		audioreach_put_audio_mixer, snd_soc_info_volsw},
+	{SND_SOC_AR_TPLG_VOL_CTL, audioreach_get_vol_ctrl_audio_mixer,
+		audioreach_put_vol_ctrl_audio_mixer, snd_soc_info_volsw},
+};
+
+static struct snd_soc_tplg_ops audioreach_tplg_ops  = {
+	.io_ops = audioreach_io_ops,
+	.io_ops_count = ARRAY_SIZE(audioreach_io_ops),
+
+	.control_load	= audioreach_control_load,
+	.control_unload	= audioreach_control_unload,
+
+	.widget_ready = audioreach_widget_ready,
+	.widget_unload = audioreach_widget_unload,
+
+	.complete = audioreach_tplg_complete,
+	.link_load = audioreach_link_load,
+
+	.dapm_route_load	= audioreach_route_load,
+	.dapm_route_unload	= audioreach_route_unload,
+};
+
+int audioreach_tplg_init(struct snd_soc_component *component)
+{
+	struct device *dev = component->dev;
+	const struct firmware *fw;
+	int ret;
+
+	ret = request_firmware(&fw, "audioreach.bin", dev);
+	if (ret < 0) {
+		dev_err(dev, "tplg fw audioreach.bin load failed with %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_tplg_component_load(component, &audioreach_tplg_ops, fw);
+	if (ret < 0) {
+		dev_err(dev, "tplg component load failed%d\n", ret);
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(audioreach_tplg_init);