sound: usb: Add support to share iova with remote processor

ISOC data transfer for audio device is offloaded to remote
usb audio driver entity. This requires remote entity to access
device context info, event ring, transfer rings and transfer
buffers allocated by APPS XHCI device. Hence add a platform
device which maps the XHCI device physical memory to virtual
memory and shares with remote processor's iommu. All the memory
info is then sent over QMI to remote entity.

Change-Id: I1abc80652a167cefa667b914b3a80a87326c3d44
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/sound/usb/usb_audio_qmi_svc.c b/sound/usb/usb_audio_qmi_svc.c
index 145bc9e..300b602 100644
--- a/sound/usb/usb_audio_qmi_svc.c
+++ b/sound/usb/usb_audio_qmi_svc.c
@@ -24,6 +24,8 @@
 #include <linux/usb.h>
 #include <linux/qmi_encdec.h>
 #include <soc/qcom/msm_qmi_interface.h>
+#include <linux/iommu.h>
+#include <linux/platform_device.h>
 
 #include "usbaudio.h"
 #include "card.h"
@@ -35,14 +37,79 @@
 #define SND_PCM_DEV_NUM_MASK 0xff00
 #define SND_PCM_STREAM_DIRECTION 0xff
 
+#define PREPEND_SID_TO_IOVA(iova, sid) (u64)(((u64)(iova)) | \
+					(((u64)sid) << 32))
+
+/*  event ring iova base address */
+#define IOVA_BASE 0x1000
+
+#define IOVA_DCBA_BASE 0x2000
+#define IOVA_XFER_RING_BASE (IOVA_DCBA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1))
+#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32)
+#define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE)
+#define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE)
+
+#define MAX_XFER_BUFF_LEN (2 * PAGE_SIZE)
+
+struct iova_info {
+	struct list_head list;
+	unsigned long start_iova;
+	size_t size;
+	bool in_use;
+};
+
+struct intf_info {
+	unsigned long data_xfer_ring_va;
+	size_t data_xfer_ring_size;
+	unsigned long sync_xfer_ring_va;
+	size_t sync_xfer_ring_size;
+	unsigned long xfer_buf_va;
+	size_t xfer_buf_size;
+	phys_addr_t xfer_buf_pa;
+	u8 *xfer_buf;
+	bool in_use;
+};
+
 struct uaudio_dev {
+	struct usb_device *udev;
+	unsigned int card_num;
 	atomic_t in_use;
 	struct kref kref;
+	unsigned long dcba_iova;
+	size_t dcba_size;
 	wait_queue_head_t disconnect_wq;
+
+	/* interface specific */
+	int num_intf;
+	struct intf_info *info;
 };
 
 static struct uaudio_dev uadev[SNDRV_CARDS];
 
+struct uaudio_qmi_dev {
+	struct device *dev;
+	u32 sid;
+	u32 intr_num;
+	struct iommu_domain *domain;
+
+	/* list to keep track of available iova */
+	struct list_head dcba_list;
+	size_t dcba_iova_size;
+	unsigned long curr_dcba_iova;
+	struct list_head xfer_ring_list;
+	size_t xfer_ring_iova_size;
+	unsigned long curr_xfer_ring_iova;
+	struct list_head xfer_buf_list;
+	size_t xfer_buf_iova_size;
+	unsigned long curr_xfer_buf_iova;
+	/* bit fields representing pcm card enabled */
+	unsigned long card_slot;
+	/* cache event ring phys addr */
+	u64 er_phys_addr;
+};
+
+static struct uaudio_qmi_dev *uaudio_qdev;
+
 struct uaudio_qmi_svc {
 	struct qmi_handle *uaudio_svc_hdl;
 	void *curr_conn;
@@ -66,9 +133,233 @@
 	.ei_array = qmi_uaudio_stream_resp_msg_v01_ei,
 };
 
-static int prepare_qmi_response(struct snd_usb_substream *subs,
-		struct qmi_uaudio_stream_resp_msg_v01 *resp, int card_num)
+enum mem_type {
+	MEM_EVENT_RING,
+	MEM_DCBA,
+	MEM_XFER_RING,
+	MEM_XFER_BUF,
+};
+
+static unsigned long uaudio_get_iova(unsigned long *curr_iova,
+	size_t *curr_iova_size, struct list_head *head, size_t size)
 {
+	struct iova_info *info, *new_info = NULL;
+	struct list_head *curr_head;
+	unsigned long va = 0;
+	size_t tmp_size = size;
+	bool found = false;
+
+	if (size % PAGE_SIZE) {
+		pr_err("%s: size %zu is not page size multiple\n", __func__,
+			size);
+		goto done;
+	}
+
+	if (size > *curr_iova_size) {
+		pr_err("%s: size %zu > curr size %zu\n", __func__, size,
+			*curr_iova_size);
+		goto done;
+	}
+	if (*curr_iova_size == 0) {
+		pr_err("%s: iova mapping is full\n", __func__);
+		goto done;
+	}
+
+	list_for_each_entry(info, head, list) {
+		/* exact size iova_info */
+		if (!info->in_use && info->size == size) {
+			info->in_use = true;
+			va = info->start_iova;
+			*curr_iova_size -= size;
+			found = true;
+			pr_debug("%s: exact size :%zu found\n", __func__, size);
+			goto done;
+		} else if (!info->in_use && tmp_size >= info->size) {
+			if (!new_info)
+				new_info = info;
+			pr_debug("%s: partial size: %zu found\n", __func__,
+				info->size);
+			tmp_size -= info->size;
+			if (tmp_size)
+				continue;
+
+			va = new_info->start_iova;
+			for (curr_head = &new_info->list; curr_head !=
+			&info->list; curr_head = curr_head->next) {
+				new_info = list_entry(curr_head, struct
+						iova_info, list);
+				new_info->in_use = true;
+			}
+			info->in_use = true;
+			*curr_iova_size -= size;
+			found = true;
+			goto done;
+		} else {
+			/* iova region in use */
+			new_info = NULL;
+			tmp_size = size;
+		}
+	}
+
+	info = kzalloc(sizeof(struct iova_info), GFP_KERNEL);
+	if (!info) {
+		va = 0;
+		goto done;
+	}
+
+	va = info->start_iova = *curr_iova;
+	info->size = size;
+	info->in_use = true;
+	*curr_iova += size;
+	*curr_iova_size -= size;
+	found = true;
+	list_add_tail(&info->list, head);
+
+done:
+	if (!found)
+		pr_err("%s: unable to find %zu size iova\n", __func__, size);
+	else
+		pr_debug("%s: va:%lu curr_iova:%lu curr_iova_size:%zu\n",
+		__func__, va, *curr_iova, *curr_iova_size);
+
+	return va;
+}
+
+static unsigned long uaudio_iommu_map(enum mem_type mtype, phys_addr_t pa,
+		size_t size)
+{
+	unsigned long va = 0;
+	bool map = true;
+	int ret;
+
+	switch (mtype) {
+	case MEM_EVENT_RING:
+		va = IOVA_BASE;
+		/* er already mapped */
+		if (uaudio_qdev->er_phys_addr == pa)
+			map = false;
+		break;
+	case MEM_DCBA:
+		va = uaudio_get_iova(&uaudio_qdev->curr_dcba_iova,
+		&uaudio_qdev->dcba_iova_size, &uaudio_qdev->dcba_list, size);
+		break;
+	case MEM_XFER_RING:
+		va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova,
+		&uaudio_qdev->xfer_ring_iova_size, &uaudio_qdev->xfer_ring_list,
+		size);
+		break;
+	case MEM_XFER_BUF:
+		va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova,
+		&uaudio_qdev->xfer_buf_iova_size, &uaudio_qdev->xfer_buf_list,
+		size);
+		break;
+	default:
+		pr_err("%s: unknown mem type %d\n", __func__, mtype);
+	}
+
+	if (!va)
+		map = false;
+
+	if (!map)
+		goto done;
+
+	pr_debug("%s: map pa %pa to iova %lu for memtype %d\n", __func__, &pa,
+		va, mtype);
+	ret = iommu_map(uaudio_qdev->domain, va, pa, size,
+		IOMMU_READ | IOMMU_WRITE | IOMMU_MMIO);
+	if (ret)
+		pr_err("%s:failed to map pa:%pa iova:%lu memtype:%d ret:%d\n",
+			__func__, &pa, va, mtype, ret);
+done:
+	return va;
+}
+
+static void uaudio_put_iova(unsigned long va, size_t size, struct list_head
+	*head, size_t *curr_iova_size)
+{
+	struct iova_info *info;
+	size_t tmp_size = size;
+	bool found = false;
+
+	list_for_each_entry(info, head, list) {
+		if (info->start_iova == va) {
+			if (!info->in_use) {
+				pr_err("%s: va %lu is not in use\n", __func__,
+					va);
+				return;
+			}
+			found = true;
+			info->in_use = false;
+			if (info->size == size)
+				goto done;
+		}
+
+		if (found && tmp_size >= info->size) {
+			info->in_use = false;
+			tmp_size -= info->size;
+			if (!tmp_size)
+				goto done;
+		}
+	}
+
+	if (!found) {
+		pr_err("%s: unable to find the va %lu\n", __func__, va);
+		return;
+	}
+done:
+	*curr_iova_size += size;
+	pr_debug("%s: curr_iova_size %zu\n", __func__, *curr_iova_size);
+}
+
+static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va,
+	size_t size)
+{
+	size_t umap_size;
+	bool unmap = true;
+
+	if (!va || !size)
+		return;
+
+	switch (mtype) {
+	case MEM_EVENT_RING:
+		if (uaudio_qdev->er_phys_addr)
+			uaudio_qdev->er_phys_addr = 0;
+		else
+			unmap = false;
+		break;
+	case MEM_DCBA:
+		uaudio_put_iova(va, size, &uaudio_qdev->dcba_list,
+		&uaudio_qdev->dcba_iova_size);
+		break;
+	case MEM_XFER_RING:
+		uaudio_put_iova(va, size, &uaudio_qdev->xfer_ring_list,
+		&uaudio_qdev->xfer_ring_iova_size);
+		break;
+	case MEM_XFER_BUF:
+		uaudio_put_iova(va, size, &uaudio_qdev->xfer_buf_list,
+		&uaudio_qdev->xfer_buf_iova_size);
+		break;
+	default:
+		pr_err("%s: unknown mem type %d\n", __func__, mtype);
+		unmap = false;
+	}
+
+	if (!unmap)
+		return;
+
+	pr_debug("%s: unmap iova %lu for memtype %d\n", __func__, va, mtype);
+
+	umap_size = iommu_unmap(uaudio_qdev->domain, va, size);
+	if (umap_size != size)
+		pr_err("%s: unmapped size %zu for iova %lu\n", __func__,
+		umap_size, va);
+}
+
+static int prepare_qmi_response(struct snd_usb_substream *subs,
+		struct qmi_uaudio_stream_resp_msg_v01 *resp, u32 xfer_buf_len,
+		int card_num)
+{
+	int ret = -ENODEV;
 	struct usb_interface *iface;
 	struct usb_host_interface *alts;
 	struct usb_interface_descriptor *altsd;
@@ -79,6 +370,11 @@
 	struct uac1_as_header_descriptor *as;
 	struct uac1_ac_header_descriptor *ac;
 	int protocol;
+	u8 *xfer_buf;
+	u32 len, mult, remainder;
+	unsigned long va, tr_data_va = 0, tr_sync_va = 0, dcba_va = 0,
+	xfer_buf_va = 0;
+	phys_addr_t xhci_pa, xfer_buf_pa;
 
 	iface = usb_ifnum_to_if(subs->dev, subs->interface);
 	if (!iface) {
@@ -141,6 +437,14 @@
 	}
 	memcpy(&resp->std_as_data_ep_desc, &ep->desc, sizeof(ep->desc));
 
+	xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep);
+	if (!xhci_pa) {
+		pr_err("%s:failed to get data ep ring dma address\n", __func__);
+		goto err;
+	}
+
+	resp->xhci_mem_info.tr_data.pa = xhci_pa;
+
 	if (subs->sync_endpoint) {
 		ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe);
 		if (!ep) {
@@ -149,27 +453,186 @@
 			goto err;
 		}
 		memcpy(&resp->std_as_sync_ep_desc, &ep->desc, sizeof(ep->desc));
+		xhci_pa = usb_get_xfer_ring_dma_addr(subs->dev, ep);
+		if (!xhci_pa) {
+			pr_err("%s:failed to get sync ep ring dma address\n",
+				__func__);
+			goto err;
+		}
+		resp->xhci_mem_info.tr_sync.pa = xhci_pa;
 	}
 
+	resp->interrupter_num = uaudio_qdev->intr_num;
+
+	/*  map xhci data structures PA memory to iova */
+
+	/* event ring */
+	ret = usb_sec_event_ring_setup(subs->dev, resp->interrupter_num);
+	if (ret) {
+		pr_err("%s: failed to setup sec event ring ret %d\n", __func__,
+			ret);
+		goto err;
+	}
+	xhci_pa = usb_get_sec_event_ring_dma_addr(subs->dev,
+			resp->interrupter_num);
+	if (!xhci_pa) {
+		pr_err("%s: failed to get sec event ring dma address\n",
+		__func__);
+		goto err;
+	}
+
+	va = uaudio_iommu_map(MEM_EVENT_RING, xhci_pa, PAGE_SIZE);
+	if (!va)
+		goto err;
+
+	resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va,
+						uaudio_qdev->sid);
+	resp->xhci_mem_info.evt_ring.pa = xhci_pa;
+	resp->xhci_mem_info.evt_ring.size = PAGE_SIZE;
+	uaudio_qdev->er_phys_addr = xhci_pa;
+
+	/* dcba */
+	xhci_pa = usb_get_dcba_dma_addr(subs->dev);
+	if (!xhci_pa) {
+		pr_err("%s:failed to get dcba dma address\n", __func__);
+		goto unmap_er;
+	}
+
+	if (!uadev[card_num].dcba_iova) { /* mappped per usb device */
+		va = uaudio_iommu_map(MEM_DCBA, xhci_pa, PAGE_SIZE);
+		if (!va)
+			goto unmap_er;
+
+		uadev[card_num].dcba_iova = va;
+		uadev[card_num].dcba_size = PAGE_SIZE;
+	}
+
+	dcba_va = uadev[card_num].dcba_iova;
+	resp->xhci_mem_info.dcba.va = PREPEND_SID_TO_IOVA(dcba_va,
+						uaudio_qdev->sid);
+	resp->xhci_mem_info.dcba.pa = xhci_pa;
+	resp->xhci_mem_info.dcba.size = PAGE_SIZE;
+
+	/* data transfer ring */
+	xhci_pa = resp->xhci_mem_info.tr_data.pa;
+	va = uaudio_iommu_map(MEM_XFER_RING, xhci_pa, PAGE_SIZE);
+	if (!va)
+		goto unmap_dcba;
+
+	tr_data_va = va;
+	resp->xhci_mem_info.tr_data.va = PREPEND_SID_TO_IOVA(va,
+						uaudio_qdev->sid);
+	resp->xhci_mem_info.tr_data.size = PAGE_SIZE;
+
+	/* sync transfer ring */
+	if (!resp->xhci_mem_info.tr_sync.pa)
+		goto skip_sync;
+
+	xhci_pa = resp->xhci_mem_info.tr_sync.pa;
+	va = uaudio_iommu_map(MEM_XFER_RING, xhci_pa, PAGE_SIZE);
+	if (!va)
+		goto unmap_data;
+
+	tr_sync_va = va;
+	resp->xhci_mem_info.tr_sync.va = PREPEND_SID_TO_IOVA(va,
+						uaudio_qdev->sid);
+	resp->xhci_mem_info.tr_sync.size = PAGE_SIZE;
+
+skip_sync:
+	/* xfer buffer, multiple of 4K only */
+	if (!xfer_buf_len)
+		xfer_buf_len = PAGE_SIZE;
+
+	mult = xfer_buf_len / PAGE_SIZE;
+	remainder = xfer_buf_len % PAGE_SIZE;
+	len = mult * PAGE_SIZE;
+	len += remainder ? PAGE_SIZE : 0;
+
+	if (len > MAX_XFER_BUFF_LEN) {
+		pr_err("%s: req buf len %d > max buf len %lu, setting %lu\n",
+		__func__, len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN);
+		len = MAX_XFER_BUFF_LEN;
+	}
+
+	xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa);
+	if (!xfer_buf)
+		goto unmap_sync;
+
+	resp->xhci_mem_info.xfer_buff.pa = xfer_buf_pa;
+	resp->xhci_mem_info.xfer_buff.size = len;
+
+	va = uaudio_iommu_map(MEM_XFER_BUF, xfer_buf_pa, len);
+	if (!va)
+		goto unmap_sync;
+
+	xfer_buf_va = va;
+	resp->xhci_mem_info.xfer_buff.va = PREPEND_SID_TO_IOVA(va,
+						uaudio_qdev->sid);
+
 	if (!atomic_read(&uadev[card_num].in_use)) {
 		kref_init(&uadev[card_num].kref);
 		init_waitqueue_head(&uadev[card_num].disconnect_wq);
+		uadev[card_num].num_intf =
+			subs->dev->config->desc.bNumInterfaces;
+		uadev[card_num].info =
+			kzalloc(sizeof(struct intf_info) *
+			uadev[card_num].num_intf, GFP_KERNEL);
+		if (!uadev[card_num].info) {
+			ret = -ENOMEM;
+			goto unmap_xfer_buf;
+		}
+		uadev[card_num].udev = subs->dev;
 		atomic_set(&uadev[card_num].in_use, 1);
 	} else {
 		kref_get(&uadev[card_num].kref);
 	}
 
+	if (uadev[card_num].info[subs->interface].in_use) {
+		pr_err("%s interface# %d already in use card# %d\n", __func__,
+			subs->interface, card_num);
+		goto unmap_xfer_buf;
+	}
+
+	uadev[card_num].card_num = card_num;
+
+	/* cache intf specific info to use it for unmap and free xfer buf */
+	uadev[card_num].info[subs->interface].data_xfer_ring_va = tr_data_va;
+	uadev[card_num].info[subs->interface].data_xfer_ring_size = PAGE_SIZE;
+	uadev[card_num].info[subs->interface].sync_xfer_ring_va = tr_sync_va;
+	uadev[card_num].info[subs->interface].sync_xfer_ring_size = PAGE_SIZE;
+	uadev[card_num].info[subs->interface].xfer_buf_va = xfer_buf_va;
+	uadev[card_num].info[subs->interface].xfer_buf_pa = xfer_buf_pa;
+	uadev[card_num].info[subs->interface].xfer_buf_size = len;
+	uadev[card_num].info[subs->interface].xfer_buf = xfer_buf;
+	uadev[card_num].info[subs->interface].in_use = true;
+
+	set_bit(card_num, &uaudio_qdev->card_slot);
+
 	return 0;
+
+unmap_xfer_buf:
+	uaudio_iommu_unmap(MEM_XFER_BUF, xfer_buf_va, len);
+unmap_sync:
+	usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa);
+	uaudio_iommu_unmap(MEM_XFER_RING, tr_sync_va, PAGE_SIZE);
+unmap_data:
+	uaudio_iommu_unmap(MEM_XFER_RING, tr_data_va, PAGE_SIZE);
+unmap_dcba:
+	uaudio_iommu_unmap(MEM_DCBA, dcba_va, PAGE_SIZE);
+unmap_er:
+	uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE);
 err:
-	return -ENODEV;
+	return ret;
 }
 
 void uaudio_disconnect_cb(struct snd_usb_audio *chip)
 {
-	int ret;
+	int ret, if_idx;
 	struct uaudio_dev *dev;
 	int card_num = chip->card_num;
 
+	pr_debug("%s: for card# %d\n", __func__, card_num);
+
 	mutex_lock(&chip->dev_lock);
 	if (card_num >=  SNDRV_CARDS) {
 		pr_err("%s: invalid card number\n", __func__);
@@ -185,6 +648,64 @@
 			goto done;
 		}
 	}
+
+	/* clean up */
+	if (!dev->udev) {
+		pr_debug("%s: no clean up required\n", __func__);
+		goto done;
+	}
+
+	/* free xfer buffer and unmap xfer ring and buf per interface */
+	for (if_idx = 0; if_idx < dev->num_intf; if_idx++) {
+		if (!dev->info[if_idx].in_use)
+			continue;
+		usb_free_coherent(dev->udev,
+		dev->info[if_idx].xfer_buf_size,
+		dev->info[if_idx].xfer_buf,
+		dev->info[if_idx].xfer_buf_pa);
+
+		uaudio_iommu_unmap(MEM_XFER_RING,
+		dev->info[if_idx].data_xfer_ring_va,
+		dev->info[if_idx].data_xfer_ring_size);
+		dev->info[if_idx].data_xfer_ring_va = 0;
+		dev->info[if_idx].data_xfer_ring_size = 0;
+
+		uaudio_iommu_unmap(MEM_XFER_RING,
+		dev->info[if_idx].sync_xfer_ring_va,
+		dev->info[if_idx].sync_xfer_ring_size);
+		dev->info[if_idx].sync_xfer_ring_va = 0;
+		dev->info[if_idx].sync_xfer_ring_size = 0;
+
+		uaudio_iommu_unmap(MEM_XFER_BUF,
+		dev->info[if_idx].xfer_buf_va,
+		dev->info[if_idx].xfer_buf_size);
+		dev->info[if_idx].xfer_buf_va = 0;
+		dev->info[if_idx].xfer_buf_size = 0;
+		pr_debug("%s: release resources: intf# %d card# %d\n", __func__,
+			if_idx, card_num);
+	}
+
+	/* iommu_unmap dcba iova for a usb device */
+	uaudio_iommu_unmap(MEM_DCBA, dev->dcba_iova, dev->dcba_size);
+
+	dev->dcba_iova = 0;
+	dev->dcba_size = 0;
+	dev->num_intf = 0;
+
+	/* free interface info */
+	kfree(dev->info);
+	dev->info = NULL;
+
+	clear_bit(card_num, &uaudio_qdev->card_slot);
+
+	/* all audio devices are disconnected */
+	if (!uaudio_qdev->card_slot) {
+		uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE);
+		usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num);
+		pr_debug("%s: all audio devices disconnected\n", __func__);
+	}
+
+	dev->udev = NULL;
 done:
 	mutex_unlock(&chip->dev_lock);
 }
@@ -193,7 +714,19 @@
 {
 	struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref);
 
+	pr_debug("%s for dev %p\n", __func__, dev);
+
 	atomic_set(&dev->in_use, 0);
+
+	clear_bit(dev->card_num, &uaudio_qdev->card_slot);
+
+	/* all audio devices are disconnected */
+	if (!uaudio_qdev->card_slot) {
+		usb_sec_event_ring_cleanup(dev->udev, uaudio_qdev->intr_num);
+		uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE);
+		pr_debug("%s: all audio devices disconnected\n", __func__);
+	}
+
 	wake_up(&dev->disconnect_wq);
 }
 
@@ -204,8 +737,9 @@
 	struct snd_usb_substream *subs;
 	struct snd_usb_audio *chip = NULL;
 	struct uaudio_qmi_svc *svc = uaudio_svc;
+	struct intf_info *info;
 	u8 pcm_card_num, pcm_dev_num, direction;
-	int ret = 0;
+	int intf_num = -1, ret = 0;
 
 	req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)req;
 
@@ -234,6 +768,7 @@
 	}
 
 	mutex_lock(&chip->dev_lock);
+	intf_num = subs->interface;
 	if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm
 			|| !subs->stream->chip) {
 		ret = -ENODEV;
@@ -248,12 +783,45 @@
 	ret = snd_usb_enable_audio_stream(subs, req_msg->enable);
 
 	if (!ret && req_msg->enable)
-		ret = prepare_qmi_response(subs, &resp, pcm_card_num);
+		ret = prepare_qmi_response(subs, &resp, req_msg->xfer_buff_size,
+			pcm_card_num);
 
 	mutex_unlock(&chip->dev_lock);
 
 response:
 	if (!req_msg->enable && ret != -EINVAL) {
+		if (intf_num >= 0) {
+			mutex_lock(&chip->dev_lock);
+			info = &uadev[pcm_card_num].info[intf_num];
+			uaudio_iommu_unmap(MEM_XFER_RING,
+			info->data_xfer_ring_va,
+			info->data_xfer_ring_size);
+			info->data_xfer_ring_va = 0;
+			info->data_xfer_ring_size = 0;
+
+			uaudio_iommu_unmap(MEM_XFER_RING,
+			info->sync_xfer_ring_va,
+			info->sync_xfer_ring_size);
+			info->sync_xfer_ring_va = 0;
+			info->sync_xfer_ring_size = 0;
+
+			uaudio_iommu_unmap(MEM_XFER_BUF,
+			info->xfer_buf_va,
+			info->xfer_buf_size);
+			info->xfer_buf_va = 0;
+
+			usb_free_coherent(uadev[pcm_card_num].udev,
+				info->xfer_buf_size,
+				info->xfer_buf,
+				info->xfer_buf_pa);
+			info->xfer_buf_size = 0;
+			info->xfer_buf = NULL;
+			info->xfer_buf_pa = 0;
+			info->in_use = false;
+			pr_debug("%s:release resources: intf# %d card# %d\n",
+				__func__, intf_num, pcm_card_num);
+			mutex_unlock(&chip->dev_lock);
+		}
 		if (atomic_read(&uadev[pcm_card_num].in_use))
 			kref_put(&uadev[pcm_card_num].kref,
 					uaudio_dev_release);
@@ -387,7 +955,94 @@
 	.req_cb = uaudio_qmi_svc_req_cb,
 };
 
-static int __init uaudio_qmi_svc_init(void)
+static int uaudio_qmi_plat_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct device_node *node = pdev->dev.of_node;
+
+	uaudio_qdev = devm_kzalloc(&pdev->dev, sizeof(struct uaudio_qmi_dev),
+		GFP_KERNEL);
+	if (!uaudio_qdev)
+		return -ENOMEM;
+
+	uaudio_qdev->dev = &pdev->dev;
+
+	ret = of_property_read_u32(node, "qcom,usb-audio-stream-id",
+				&uaudio_qdev->sid);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to read sid.\n");
+		return -ENODEV;
+	}
+
+	ret = of_property_read_u32(node, "qcom,usb-audio-intr-num",
+				&uaudio_qdev->intr_num);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to read intr num.\n");
+		return -ENODEV;
+	}
+
+	uaudio_qdev->domain = iommu_domain_alloc(pdev->dev.bus);
+	if (!uaudio_qdev) {
+		dev_err(&pdev->dev, "failed to callocate iommu domin\n");
+		return -ENODEV;
+	}
+
+	/* attach to external processor iommu */
+	ret = iommu_attach_device(uaudio_qdev->domain, &pdev->dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to attach device ret = %d\n", ret);
+		goto free_domain;
+	}
+
+	/* initialize dcba, xfer ring and xfer buf iova list */
+	INIT_LIST_HEAD(&uaudio_qdev->dcba_list);
+	uaudio_qdev->curr_dcba_iova = IOVA_DCBA_BASE;
+	uaudio_qdev->dcba_iova_size = SNDRV_CARDS * PAGE_SIZE;
+
+	INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list);
+	uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE;
+	uaudio_qdev->xfer_ring_iova_size =
+			IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE;
+
+	INIT_LIST_HEAD(&uaudio_qdev->xfer_buf_list);
+	uaudio_qdev->curr_xfer_buf_iova = IOVA_XFER_BUF_BASE;
+	uaudio_qdev->xfer_buf_iova_size =
+		IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE;
+
+	return 0;
+
+free_domain:
+	iommu_domain_free(uaudio_qdev->domain);
+	return ret;
+}
+
+static int uaudio_qmi_plat_remove(struct platform_device *pdev)
+{
+	iommu_detach_device(uaudio_qdev->domain, &pdev->dev);
+	iommu_domain_free(uaudio_qdev->domain);
+	uaudio_qdev->domain = NULL;
+
+	return 0;
+}
+
+static const struct of_device_id of_uaudio_matach[] = {
+	{
+		.compatible = "qcom,usb-audio-qmi-dev",
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, of_uaudio_matach);
+
+static struct platform_driver uaudio_qmi_driver = {
+	.probe		= uaudio_qmi_plat_probe,
+	.remove		= uaudio_qmi_plat_remove,
+	.driver		= {
+		.name	= "uaudio-qmi",
+		.of_match_table	= of_uaudio_matach,
+	},
+};
+
+static int uaudio_qmi_svc_init(void)
 {
 	int ret;
 	struct uaudio_qmi_svc *svc;
@@ -430,7 +1085,7 @@
 	return ret;
 }
 
-static void __exit uaudio_qmi_svc_exit(void)
+static void uaudio_qmi_svc_exit(void)
 {
 	struct uaudio_qmi_svc *svc = uaudio_svc;
 
@@ -442,8 +1097,25 @@
 	uaudio_svc = NULL;
 }
 
-module_init(uaudio_qmi_svc_init);
-module_exit(uaudio_qmi_svc_exit);
+static int __init uaudio_qmi_plat_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&uaudio_qmi_driver);
+	if (ret)
+		return ret;
+
+	return uaudio_qmi_svc_init();
+}
+
+static void __exit uaudio_qmi_plat_exit(void)
+{
+	uaudio_qmi_svc_exit();
+	platform_driver_unregister(&uaudio_qmi_driver);
+}
+
+module_init(uaudio_qmi_plat_init);
+module_exit(uaudio_qmi_plat_exit);
 
 MODULE_DESCRIPTION("USB AUDIO QMI Service Driver");
 MODULE_LICENSE("GPL v2");