ASoC: msm: qdsp6v2: Add support for OpenDSP

Add support for OpenDSP which allows custom topology
designed using the QACT tool to be sent over to the
Q6.

Change-Id: I8203f8b409870ca406832f279d01cc15a9c8ca4f
Signed-off-by: Ben Romberger <bromberg@codeaurora.org>
diff --git a/include/linux/msm_audio_acdb.h b/include/linux/msm_audio_acdb.h
index 471f10a..f0c4915 100644
--- a/include/linux/msm_audio_acdb.h
+++ b/include/linux/msm_audio_acdb.h
@@ -49,6 +49,10 @@
 			(AUDIO_MAX_COMMON_IOCTL_NUM+21), unsigned)
 #define AUDIO_SET_LSM_CAL		_IOW(AUDIO_IOCTL_MAGIC, \
 			(AUDIO_MAX_COMMON_IOCTL_NUM+22), unsigned)
+#define AUDIO_SET_ADM_CUSTOM_TOPOLOGY	_IOW(AUDIO_IOCTL_MAGIC, \
+			(AUDIO_MAX_COMMON_IOCTL_NUM+23), unsigned)
+#define AUDIO_SET_ASM_CUSTOM_TOPOLOGY	_IOW(AUDIO_IOCTL_MAGIC, \
+			(AUDIO_MAX_COMMON_IOCTL_NUM+24), unsigned)
 
 #define	AUDIO_MAX_ACDB_IOCTL	(AUDIO_MAX_COMMON_IOCTL_NUM+30)
 
diff --git a/include/sound/apr_audio-v2.h b/include/sound/apr_audio-v2.h
index 4555b08..cad737a 100644
--- a/include/sound/apr_audio-v2.h
+++ b/include/sound/apr_audio-v2.h
@@ -6215,6 +6215,22 @@
 #define VOICE_CMD_GET_PARAM				0x0001133E
 #define VOICE_EVT_GET_PARAM_ACK				0x00011008
 
+/* Set Q6 topologies */
+#define ASM_CMD_ADD_TOPOLOGIES				0x00010DBE
+#define ADM_CMD_ADD_TOPOLOGIES				0x00010335
+
+/* structure used for both ioctls */
+struct cmd_set_topologies {
+	struct apr_hdr hdr;
+	u32		payload_addr_lsw;
+	/* LSW of parameter data payload address.*/
+	u32		payload_addr_msw;
+	/* MSW of parameter data payload address.*/
+	u32		mem_map_handle;
+	/* Memory map handle returned by mem map command */
+	u32		payload_size;
+	/* Size in bytes of the variable payload in shared memory */
+} __packed;
 
 /* SRS TRUMEDIA start */
 /* topology */
diff --git a/sound/soc/msm/qdsp6v2/audio_acdb.c b/sound/soc/msm/qdsp6v2/audio_acdb.c
index 58fdc1b..b71132e 100644
--- a/sound/soc/msm/qdsp6v2/audio_acdb.c
+++ b/sound/soc/msm/qdsp6v2/audio_acdb.c
@@ -66,6 +66,12 @@
 	/* VocProc dev cfg cal*/
 	struct acdb_atomic_cal_block	vocproc_dev_cal;
 
+	/* Custom topology */
+	struct acdb_atomic_cal_block	adm_custom_topology;
+	struct acdb_atomic_cal_block	asm_custom_topology;
+	atomic_t			valid_adm_custom_top;
+	atomic_t			valid_asm_custom_top;
+
 	/* AFE cal */
 	struct acdb_atomic_cal_block	afe_cal[MAX_AUDPROC_TYPES];
 
@@ -134,6 +140,106 @@
 	atomic_set(&acdb_data.asm_topology, topology);
 }
 
+void reset_custom_topology_flags(void)
+{
+	atomic_set(&acdb_data.valid_adm_custom_top, 1);
+	atomic_set(&acdb_data.valid_asm_custom_top, 1);
+}
+
+void get_adm_custom_topology(struct acdb_cal_block *cal_block)
+{
+	pr_debug("%s\n", __func__);
+
+	if (cal_block == NULL) {
+		pr_err("ACDB=> NULL pointer sent to %s\n", __func__);
+		goto done;
+	}
+
+	if (atomic_read(&acdb_data.valid_adm_custom_top) == 0) {
+		cal_block->cal_size = 0;
+		goto done;
+	}
+	atomic_set(&acdb_data.valid_adm_custom_top, 0);
+
+	cal_block->cal_size =
+		atomic_read(&acdb_data.adm_custom_topology.cal_size);
+	cal_block->cal_paddr =
+		atomic_read(&acdb_data.adm_custom_topology.cal_paddr);
+	cal_block->cal_kvaddr =
+		atomic_read(&acdb_data.adm_custom_topology.cal_kvaddr);
+done:
+	return;
+}
+
+void store_adm_custom_topology(struct cal_block *cal_block)
+{
+	pr_debug("%s,\n", __func__);
+
+	if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) {
+		pr_err("%s: offset %d is > mem_len %ld\n",
+			__func__, cal_block->cal_offset,
+			(long)atomic64_read(&acdb_data.mem_len));
+		goto done;
+	}
+
+	atomic_set(&acdb_data.adm_custom_topology.cal_size,
+		cal_block->cal_size);
+	atomic_set(&acdb_data.adm_custom_topology.cal_paddr,
+		cal_block->cal_offset + atomic64_read(&acdb_data.paddr));
+	atomic_set(&acdb_data.adm_custom_topology.cal_kvaddr,
+		cal_block->cal_offset +
+		atomic64_read(&acdb_data.kvaddr));
+done:
+	return;
+}
+
+void get_asm_custom_topology(struct acdb_cal_block *cal_block)
+{
+	pr_debug("%s\n", __func__);
+
+	if (cal_block == NULL) {
+		pr_err("ACDB=> NULL pointer sent to %s\n", __func__);
+		goto done;
+	}
+
+	if (atomic_read(&acdb_data.valid_asm_custom_top) == 0) {
+		cal_block->cal_size = 0;
+		goto done;
+	}
+	atomic_set(&acdb_data.valid_asm_custom_top, 0);
+
+	cal_block->cal_size =
+		atomic_read(&acdb_data.asm_custom_topology.cal_size);
+	cal_block->cal_paddr =
+		atomic_read(&acdb_data.asm_custom_topology.cal_paddr);
+	cal_block->cal_kvaddr =
+		atomic_read(&acdb_data.asm_custom_topology.cal_kvaddr);
+done:
+	return;
+}
+
+void store_asm_custom_topology(struct cal_block *cal_block)
+{
+	pr_debug("%s,\n", __func__);
+
+	if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) {
+		pr_err("%s: offset %d is > mem_len %ld\n",
+			__func__, cal_block->cal_offset,
+			(long)atomic64_read(&acdb_data.mem_len));
+		goto done;
+	}
+
+	atomic_set(&acdb_data.asm_custom_topology.cal_size,
+		cal_block->cal_size);
+	atomic_set(&acdb_data.asm_custom_topology.cal_paddr,
+		cal_block->cal_offset + atomic64_read(&acdb_data.paddr));
+	atomic_set(&acdb_data.asm_custom_topology.cal_kvaddr,
+		cal_block->cal_offset +
+		atomic64_read(&acdb_data.kvaddr));
+done:
+	return;
+}
+
 void get_voice_cal_allocation(struct acdb_cal_block *cal_block)
 {
 	cal_block->cal_size = ACDB_TOTAL_VOICE_ALLOCATION;
@@ -672,6 +778,8 @@
 			__func__);
 	}
 
+	atomic_set(&acdb_data.valid_adm_custom_top, 1);
+	atomic_set(&acdb_data.valid_asm_custom_top, 1);
 	atomic_inc(&usage_count);
 	return result;
 }
@@ -925,6 +1033,12 @@
 	case AUDIO_SET_LSM_CAL:
 		store_lsm_cal((struct cal_block *)data);
 		goto done;
+	case AUDIO_SET_ADM_CUSTOM_TOPOLOGY:
+		store_adm_custom_topology((struct cal_block *)data);
+		goto done;
+	case AUDIO_SET_ASM_CUSTOM_TOPOLOGY:
+		store_asm_custom_topology((struct cal_block *)data);
+		goto done;
 	default:
 		pr_err("ACDB=> ACDB ioctl not found!\n");
 	}
@@ -998,6 +1112,8 @@
 	memset(&acdb_data, 0, sizeof(acdb_data));
 	mutex_init(&acdb_data.acdb_mutex);
 	atomic_set(&usage_count, 0);
+	atomic_set(&acdb_data.valid_adm_custom_top, 1);
+	atomic_set(&acdb_data.valid_asm_custom_top, 1);
 
 	return misc_register(&acdb_misc);
 }
diff --git a/sound/soc/msm/qdsp6v2/audio_acdb.h b/sound/soc/msm/qdsp6v2/audio_acdb.h
index 8528e3c..0b6110d 100644
--- a/sound/soc/msm/qdsp6v2/audio_acdb.h
+++ b/sound/soc/msm/qdsp6v2/audio_acdb.h
@@ -46,6 +46,9 @@
 uint32_t get_adm_rx_topology(void);
 uint32_t get_adm_tx_topology(void);
 uint32_t get_asm_topology(void);
+void reset_custom_topology_flags(void);
+void get_adm_custom_topology(struct acdb_cal_block *cal_block);
+void get_asm_custom_topology(struct acdb_cal_block *cal_block);
 void get_voice_cal_allocation(struct acdb_cal_block *cal_block);
 void get_lsm_cal(struct acdb_cal_block *cal_block);
 void get_anc_cal(struct acdb_cal_block *cal_block);
diff --git a/sound/soc/msm/qdsp6v2/q6adm.c b/sound/soc/msm/qdsp6v2/q6adm.c
index bc11304..685deef 100644
--- a/sound/soc/msm/qdsp6v2/q6adm.c
+++ b/sound/soc/msm/qdsp6v2/q6adm.c
@@ -46,8 +46,11 @@
 
 /* 0 - (MAX_AUDPROC_TYPES -1):				audproc handles */
 /* (MAX_AUDPROC_TYPES -1) - (2 * MAX_AUDPROC_TYPES -1):	audvol handles */
-	atomic_t mem_map_cal_handles[(2 * MAX_AUDPROC_TYPES)];
+/* + 1 for custom ADM topology */
+	atomic_t mem_map_cal_handles[(2 * MAX_AUDPROC_TYPES) + 1];
 	atomic_t mem_map_cal_index;
+
+	int set_custom_topology;
 };
 
 static struct adm_ctl			this_adm;
@@ -309,6 +312,8 @@
 				atomic_set(&this_adm.copp_stat[i], 0);
 			}
 			this_adm.apr = NULL;
+			reset_custom_topology_flags();
+			this_adm.set_custom_topology = 1;
 		}
 		pr_debug("Resetting calibration blocks");
 		for (i = 0; i < MAX_AUDPROC_TYPES; i++) {
@@ -350,6 +355,7 @@
 			case ADM_CMD_DEVICE_CLOSE_V5:
 			case ADM_CMD_SHARED_MEM_UNMAP_REGIONS:
 			case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
+			case ADM_CMD_ADD_TOPOLOGIES:
 				pr_debug("%s: Basic callback received, wake up.\n",
 					__func__);
 				atomic_set(&this_adm.copp_stat[index], 1);
@@ -435,6 +441,87 @@
 	return 0;
 }
 
+void send_adm_custom_topology(int port_id)
+{
+	struct acdb_cal_block		cal_block;
+	struct cmd_set_topologies	adm_top;
+	int				index;
+	int				result;
+	int				size = 4096;
+
+	get_adm_custom_topology(&cal_block);
+	if (cal_block.cal_size == 0) {
+		pr_debug("%s: no cal to send addr= 0x%x\n",
+				__func__, cal_block.cal_paddr);
+		goto done;
+	}
+
+	index = afe_get_port_index(port_id);
+	if (index < 0 || index >= AFE_MAX_PORTS) {
+		pr_err("%s: invalid port idx %d portid %#x\n",
+				__func__, index, port_id);
+		goto done;
+	}
+
+	if (this_adm.set_custom_topology) {
+		/* specific index 4 for adm topology memory */
+		atomic_set(&this_adm.mem_map_cal_index, 4);
+
+		/* Only call this once */
+		this_adm.set_custom_topology = 0;
+
+		result = adm_memory_map_regions(port_id, &cal_block.cal_paddr,
+					0, &size, 1);
+		if (result < 0) {
+			pr_err("%s: mmap did not work! addr = 0x%x, size = %d\n",
+				__func__, cal_block.cal_paddr,
+			       cal_block.cal_size);
+			goto done;
+		}
+	}
+
+
+	adm_top.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+		APR_HDR_LEN(20), APR_PKT_VER);
+	adm_top.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE,
+		sizeof(adm_top));
+	adm_top.hdr.src_svc = APR_SVC_ADM;
+	adm_top.hdr.src_domain = APR_DOMAIN_APPS;
+	adm_top.hdr.src_port = port_id;
+	adm_top.hdr.dest_svc = APR_SVC_ADM;
+	adm_top.hdr.dest_domain = APR_DOMAIN_ADSP;
+	adm_top.hdr.dest_port = atomic_read(&this_adm.copp_id[index]);
+	adm_top.hdr.token = port_id;
+	adm_top.hdr.opcode = ADM_CMD_ADD_TOPOLOGIES;
+	adm_top.payload_addr_lsw = cal_block.cal_paddr;
+	adm_top.payload_addr_msw = 0;
+	adm_top.mem_map_handle = atomic_read(&this_adm.mem_map_cal_handles[4]);
+	adm_top.payload_size = cal_block.cal_size;
+
+	atomic_set(&this_adm.copp_stat[index], 0);
+	pr_debug("%s: Sending ADM_CMD_ADD_TOPOLOGIES payload = 0x%x, size = %d\n",
+		__func__, adm_top.payload_addr_lsw,
+		adm_top.payload_size);
+	result = apr_send_pkt(this_adm.apr, (uint32_t *)&adm_top);
+	if (result < 0) {
+		pr_err("%s: Set topologies failed port = 0x%x payload = 0x%x\n",
+			__func__, port_id, cal_block.cal_paddr);
+		goto done;
+	}
+	/* Wait for the callback */
+	result = wait_event_timeout(this_adm.wait[index],
+		atomic_read(&this_adm.copp_stat[index]),
+		msecs_to_jiffies(TIMEOUT_MS));
+	if (!result) {
+		pr_err("%s: Set topologies timed out port = 0x%x, payload = 0x%x\n",
+			__func__, port_id, cal_block.cal_paddr);
+		goto done;
+	}
+
+done:
+	return;
+}
+
 static int send_adm_cal_block(int port_id, struct acdb_cal_block *aud_cal)
 {
 	s32				result = 0;
@@ -684,6 +771,7 @@
 		rtac_set_adm_handle(this_adm.apr);
 	}
 
+	send_adm_custom_topology(port_id);
 
 	/* Create a COPP if port id are not enabled */
 	if (atomic_read(&this_adm.copp_cnt[index]) == 0) {
@@ -1154,6 +1242,7 @@
 {
 	int i = 0;
 	this_adm.apr = NULL;
+	this_adm.set_custom_topology = 1;
 
 	for (i = 0; i < AFE_MAX_PORTS; i++) {
 		atomic_set(&this_adm.copp_id[i], RESET_COPP_ID);
diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c
index 3d8d5eb..b832c41 100644
--- a/sound/soc/msm/qdsp6v2/q6asm.c
+++ b/sound/soc/msm/qdsp6v2/q6asm.c
@@ -106,6 +106,9 @@
 static int out_cold_index;
 static char *out_buffer;
 static char *in_buffer;
+static int set_custom_topology;
+static int topology_map_handle;
+
 static int audio_output_latency_dbgfs_open(struct inode *inode,
 							struct file *file)
 {
@@ -341,6 +344,90 @@
 	return;
 }
 
+void send_asm_custom_topology(struct audio_client *ac)
+{
+	struct acdb_cal_block		cal_block;
+	struct cmd_set_topologies	asm_top;
+	struct audio_buffer		*buf;
+	struct asm_buffer_node		*buf_node = NULL;
+	struct list_head		*ptr, *next;
+	int				result;
+	int				size = 4096;
+
+	get_asm_custom_topology(&cal_block);
+	if (cal_block.cal_size == 0) {
+		pr_debug("%s: no cal to send addr= 0x%x\n",
+				__func__, cal_block.cal_paddr);
+		goto done;
+	}
+
+	if (set_custom_topology) {
+		/* Only call this once */
+		set_custom_topology = 0;
+
+		/* Use first asm buf to map memory */
+		buf = kzalloc(sizeof(struct audio_buffer), GFP_KERNEL);
+		if (!buf) {
+			pr_debug("%s: could not allocate temp memory\n",
+				__func__);
+			goto done;
+		}
+		buf[0].phys = cal_block.cal_paddr;
+		ac->port[0].buf = buf;
+
+		result = q6asm_memory_map_regions(ac, 0, size, 1, 1);
+		if (result < 0) {
+			pr_err("%s: mmap did not work! addr = 0x%x, size = %d\n",
+				__func__, cal_block.cal_paddr,
+				cal_block.cal_size);
+			goto done;
+		}
+
+		list_for_each_safe(ptr, next, &ac->port[IN].mem_map_handle) {
+			buf_node = list_entry(ptr, struct asm_buffer_node,
+						list);
+			if (buf_node->buf_addr_lsw == cal_block.cal_paddr) {
+				topology_map_handle =  buf_node->mmap_hdl;
+				break;
+			}
+		}
+
+		kfree(buf);
+	}
+
+	q6asm_add_hdr(ac, &asm_top.hdr, APR_PKT_SIZE(APR_HDR_SIZE,
+						sizeof(asm_top)), TRUE);
+
+	asm_top.hdr.opcode = ASM_CMD_ADD_TOPOLOGIES;
+	asm_top.payload_addr_lsw = cal_block.cal_paddr;
+	asm_top.payload_addr_msw = 0;
+	asm_top.mem_map_handle = topology_map_handle;
+	asm_top.payload_size = cal_block.cal_size;
+
+	 pr_debug("%s: Sending ASM_CMD_ADD_TOPOLOGIES payload = 0x%x, size = %d, map handle = 0x%x\n",
+		__func__, asm_top.payload_addr_lsw,
+		asm_top.payload_size, asm_top.mem_map_handle);
+
+	result = apr_send_pkt(ac->apr, (uint32_t *) &asm_top);
+	if (result < 0) {
+		pr_err("%s: Set topologies failed payload = 0x%x\n",
+			__func__, cal_block.cal_paddr);
+		goto done;
+	}
+
+	result = wait_event_timeout(ac->cmd_wait,
+			(atomic_read(&ac->cmd_state) == 0), 5*HZ);
+	if (result < 0) {
+		pr_err("%s: Set topologies failed payload = 0x%x\n",
+			__func__, cal_block.cal_paddr);
+		goto done;
+	}
+
+
+done:
+	return;
+}
+
 int q6asm_audio_client_buf_free(unsigned int dir,
 			struct audio_client *ac)
 {
@@ -563,6 +650,8 @@
 	atomic_set(&ac->cmd_state, 0);
 	atomic_set(&ac->nowait_cmd_cnt, 0);
 
+	send_asm_custom_topology(ac);
+
 	pr_debug("%s: session[%d]\n", __func__, ac->session);
 
 	return ac;
@@ -822,6 +911,8 @@
 		apr_reset(this_mmap.apr);
 		atomic_set(&this_mmap.ref_cnt, 0);
 		this_mmap.apr = NULL;
+		reset_custom_topology_flags();
+		set_custom_topology = 1;
 		return 0;
 	}
 	sid = (data->token >> 8) & 0x0F;
@@ -956,6 +1047,8 @@
 					(uint32_t *)data->payload, ac->priv);
 		apr_reset(ac->apr);
 		ac->apr = NULL;
+		reset_custom_topology_flags();
+		set_custom_topology = 1;
 		return 0;
 	}
 
@@ -997,6 +1090,7 @@
 		case ASM_STREAM_CMD_OPEN_READWRITE_V2:
 		case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2:
 		case ASM_STREAM_CMD_SET_ENCDEC_PARAM:
+		case ASM_CMD_ADD_TOPOLOGIES:
 		pr_debug("%s:Payload = [0x%x]stat[0x%x]\n",
 				__func__, payload[0], payload[1]);
 			if (atomic_read(&ac->cmd_state) && wakeup_flag) {
@@ -3564,6 +3658,7 @@
 {
 	pr_debug("%s\n", __func__);
 	memset(session, 0, sizeof(session));
+	set_custom_topology = 1;
 
 	config_debug_fs_init();