aoc: usb: support direct USB access

Support direct USB access to allow APP to access external USB DAC
directly for extending the hi-res USB audio and format support.

Bug: 199034378
Test: build and boot pass
Change-Id: Ib20d2e1a30ef5e0ee01b652cbd033242f84203a7
Signed-off-by: Howard Yen <howardyen@google.com>
diff --git a/usb/Makefile b/usb/Makefile
index 22c117c..5fb6eb9 100644
--- a/usb/Makefile
+++ b/usb/Makefile
@@ -2,14 +2,14 @@
 # ASoC-based USB driver
 #
 obj-$(CONFIG_AOC_DRIVER) += aoc_usb_driver.o
-aoc_usb_driver-objs := aoc_usb_dev.o xhci_hooks_impl_whi.o usb_hooks_impl_whi.o
+aoc_usb_driver-objs := aoc_usb_dev.o xhci_hooks_impl_whi.o usb_hooks_impl_whi.o snd_usb_audio_hook_impl_whi.o
 
 KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
 M ?= $(shell pwd)
 KBUILD_OPTIONS += CONFIG_AOC_DRIVER=m
-EXTRA_CFLAGS=-I$(KERNEL_SRC)/../google-modules/aoc_ipc -I$(KERNEL_SRC)/../google-modules/aoc -I$(KERNEL_SRC)/drivers/usb/host
+EXTRA_CFLAGS=-I$(KERNEL_SRC)/../google-modules/aoc_ipc -I$(KERNEL_SRC)/../google-modules/aoc -I$(KERNEL_SRC)/drivers/usb/host -I$(KERNEL_SRC)/sound/usb
 KBUILD_EXTRA_SYMBOLS=$(OUT_DIR)/../google-modules/aoc/Module.symvers\
 		     $(OUT_DIR)/../google-modules/aoc/alsa/Module.symvers
 
 modules modules_install clean:
-	$(MAKE) -C $(KERNEL_SRC) M=$(M) W=1 -Werror $(KBUILD_OPTIONS) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" KBUILD_EXTRA_SYMBOLS="$(KBUILD_EXTRA_SYMBOLS)" $(@)
+	$(MAKE) -C $(KERNEL_SRC) M=$(M) W=1 $(KBUILD_OPTIONS) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" KBUILD_EXTRA_SYMBOLS="$(KBUILD_EXTRA_SYMBOLS)" $(@)
diff --git a/usb/aoc_usb.h b/usb/aoc_usb.h
index 852f7aa..1787a33 100644
--- a/usb/aoc_usb.h
+++ b/usb/aoc_usb.h
@@ -28,7 +28,8 @@
 	SETUP_DONE,
 	GET_ISOC_TR_INFO,
 	SET_ISOC_TR_INFO,
-	SYNC_CONN_STAT
+	SYNC_CONN_STAT,
+	SET_OFFLOAD_STATE
 };
 
 enum aoc_usb_state {
@@ -64,6 +65,8 @@
 
 	bool usb_accessory_enabled;
 	bool usb_audio_offload;
+	bool dt_direct_usb_access;
+	bool offload_state;
 
 	enum usb_offload_op_mode op_mode;
 
@@ -111,11 +114,14 @@
 
 int xhci_vendor_helper_init(void);
 int usb_vendor_helper_init(void);
+int snd_usb_audio_vendor_helper_init(void);
 
 extern int xhci_handle_event(struct xhci_hcd *xhci);
 extern void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
 				     union xhci_trb *event_ring_deq);
 extern int xhci_exynos_register_vendor_ops(struct xhci_vendor_ops *vendor_ops);
+int xhci_set_offload_state(struct xhci_hcd *xhci, bool enabled);
+struct xhci_hcd *get_xhci_hcd_by_udev(struct usb_device *udev);
 
 int usb_host_mode_state_notify(enum aoc_usb_state usb_state);
 
diff --git a/usb/aoc_usb_dev.c b/usb/aoc_usb_dev.c
index 97693db..2af8e8d 100644
--- a/usb/aoc_usb_dev.c
+++ b/usb/aoc_usb_dev.c
@@ -293,6 +293,31 @@
 	return 0;
 }
 
+static int aoc_usb_set_offload_state(struct aoc_usb_drvdata *drvdata, bool *enabled)
+{
+	int ret = 0;
+	struct CMD_USB_CONTROL_SET_OFFLOAD_STATE *cmd;
+
+	cmd = kzalloc(sizeof(struct CMD_USB_CONTROL_SET_OFFLOAD_STATE), GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+
+	AocCmdHdrSet(&cmd->parent,
+		     CMD_USB_CONTROL_SET_OFFLOAD_STATE_ID,
+		     sizeof(*cmd));
+
+	cmd->offloading = *enabled;
+	ret = aoc_usb_send_command(drvdata, cmd, sizeof(*cmd), cmd, sizeof(*cmd));
+	if (ret < 0) {
+		kfree(cmd);
+		return ret;
+	}
+
+	kfree(cmd);
+
+	return 0;
+}
+
 static int aoc_usb_notify(struct notifier_block *this,
 			  unsigned long code, void *data)
 {
@@ -326,12 +351,18 @@
 	case SYNC_CONN_STAT:
 		ret = aoc_usb_notify_conn_stat(drvdata, data);
 		break;
+	case SET_OFFLOAD_STATE:
+		ret = aoc_usb_set_offload_state(drvdata, data);
+		break;
 	default:
 		dev_warn(&drvdata->adev->dev, "Code %lu is not supported\n", code);
 		ret = -EINVAL;
 		break;
 	}
 
+	if (ret < 0)
+		dev_err(&drvdata->adev->dev, "Fail to handle code %lu, ret = %d", code, ret);
+
 	return ret;
 }
 
@@ -487,6 +518,8 @@
 {
 	xhci_vendor_helper_init();
 	usb_vendor_helper_init();
+	snd_usb_audio_vendor_helper_init();
+
 	INIT_WORK(&usb_recovery_ws, usb_recovery_work);
 	INIT_WORK(&usb_host_mode_checking_ws, usb_host_mode_checking_work);
 
diff --git a/usb/snd_usb_audio_hook_impl_whi.c b/usb/snd_usb_audio_hook_impl_whi.c
new file mode 100644
index 0000000..be9173f
--- /dev/null
+++ b/usb/snd_usb_audio_hook_impl_whi.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Google Corp.
+ *
+ * Author:
+ *  Howard.Yen <howardyen@google.com>
+ */
+
+#include <sound/pcm.h>
+#include <uapi/sound/asound.h>
+#include "usbaudio.h"
+#include "card.h"
+
+#include "aoc_usb.h"
+
+ static int snd_usb_audio_vendor_connect(struct usb_interface *intf)
+{
+	struct usb_device *udev;
+	struct xhci_hcd *xhci;
+
+	if (!intf) {
+		pr_err("%s: Invalid parameter\n", __func__);
+		return -EINVAL;
+	}
+
+	udev = interface_to_usbdev(intf);
+	xhci = get_xhci_hcd_by_udev(udev);
+
+	xhci_set_offload_state(xhci, true);
+
+	return 0;
+}
+
+static void snd_usb_audio_vendor_disconnect(struct usb_interface *intf)
+{
+	struct usb_device *udev;
+	struct xhci_hcd *xhci;
+
+	if (!intf) {
+		pr_err("%s: Invalid parameter\n", __func__);
+		return;
+	}
+
+	udev = interface_to_usbdev(intf);
+	xhci = get_xhci_hcd_by_udev(udev);
+
+	xhci_set_offload_state(xhci, false);
+
+	return;
+}
+
+static int snd_usb_audio_vendor_set_interface(struct usb_device *udev,
+					      struct usb_host_interface *alts,
+					      int iface, int alt)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_set_rate(struct usb_interface *intf, int iface, int rate,
+					 int alt)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_set_pcm_buf(struct usb_device *udev, int iface)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_set_pcm_intf(struct usb_interface *intf, int iface, int alt,
+					     int direction)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_set_pcm_connection(struct usb_device *udev,
+						   enum snd_vendor_pcm_open_close onoff,
+						   int direction)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_set_pcm_binterval(struct audioformat *fp,
+						  struct audioformat *found,
+						  int *cur_attr, int *attr)
+{
+	return 0;
+}
+
+static int snd_usb_audio_vendor_usb_add_ctls(struct snd_usb_audio *chip)
+{
+	return 0;
+}
+
+static struct snd_usb_audio_vendor_ops snd_usb_ops = {
+	.connect = snd_usb_audio_vendor_connect,
+	.disconnect = snd_usb_audio_vendor_disconnect,
+	.set_interface = snd_usb_audio_vendor_set_interface,
+	.set_rate = snd_usb_audio_vendor_set_rate,
+	.set_pcm_buf = snd_usb_audio_vendor_set_pcm_buf,
+	.set_pcm_intf = snd_usb_audio_vendor_set_pcm_intf,
+	.set_pcm_connection = snd_usb_audio_vendor_set_pcm_connection,
+	.set_pcm_binterval = snd_usb_audio_vendor_set_pcm_binterval,
+	.usb_add_ctls = snd_usb_audio_vendor_usb_add_ctls,
+};
+
+int snd_usb_audio_vendor_helper_init(void)
+{
+	return snd_vendor_set_ops(&snd_usb_ops);
+}
diff --git a/usb/xhci_hooks_impl_whi.c b/usb/xhci_hooks_impl_whi.c
index d12f160..811eee4 100644
--- a/usb/xhci_hooks_impl_whi.c
+++ b/usb/xhci_hooks_impl_whi.c
@@ -37,6 +37,27 @@
 	return blocking_notifier_chain_unregister(&aoc_usb_notifier_list, nb);
 }
 
+int xhci_set_offload_state(struct xhci_hcd *xhci, bool enabled)
+{
+	struct xhci_vendor_data *vendor_data;
+
+	if (!xhci)
+		return -EINVAL;
+
+	vendor_data = xhci_to_priv(xhci)->vendor_data;
+
+	if (!vendor_data->dt_direct_usb_access)
+		return -EPERM;
+
+	xhci_info(xhci, "Set offloading state %s\n", enabled ? "true" : "false");
+
+	blocking_notifier_call_chain(&aoc_usb_notifier_list, SET_OFFLOAD_STATE,
+				     &enabled);
+	vendor_data->offload_state = enabled;
+
+	return 0;
+}
+
 static int xhci_sync_dev_ctx(struct xhci_hcd *xhci, unsigned int slot_id)
 {
 	struct xhci_virt_device *dev;
@@ -246,7 +267,7 @@
 	return is_video;
 }
 
-static struct xhci_hcd *get_xhci_hcd_by_udev(struct usb_device *udev)
+struct xhci_hcd *get_xhci_hcd_by_udev(struct usb_device *udev)
 {
 	struct usb_hcd *uhcd = container_of(udev->bus, struct usb_hcd, self);
 
@@ -475,6 +496,13 @@
 		return ret;
 	}
 
+	vendor_data->dt_direct_usb_access =
+		of_property_read_bool(dev->of_node, "direct-usb-access") ? true : false;
+	if (!vendor_data->dt_direct_usb_access)
+		dev_warn(dev, "Direct USB access is not supported\n");
+
+	vendor_data->offload_state = true;
+
 	usb_register_notify(&xhci_udev_nb);
 	vendor_data->op_mode = USB_OFFLOAD_DRAM;
 	vendor_data->xhci = xhci;
@@ -539,7 +567,7 @@
 			if (is_usb_video_device(udev))
 				return false;
 			else if (ep_ring->type == TYPE_ISOC)
-				return true;
+				return vendor_data->offload_state;
 		}
 	}