usb: new attributes implementation to enable/disable usb data

Bug: 184613044
Test: driver probe and attributes access normally
Signed-off-by: Albert Wang <albertccwang@google.com>
Change-Id: Ia34cfd8e76a21f7239e356608e46ddeebd6fa10a
diff --git a/Documentation/ABI/testing/sysfs-class-udc b/Documentation/ABI/testing/sysfs-class-udc
new file mode 100644
index 0000000..1b9c566
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-udc
@@ -0,0 +1,16 @@
+What:		/sys/class/udc/<udc name>/device/usb_data_enabled
+Date:		December 2020
+Contact:	"Ray Chi" <raychi@google.com>
+Description:
+		The attribute can allow user space can check and modify
+		the value to enable or disable usb functionality. Therefore,
+		if the attritube is set to 0, USB host and USB peripheral
+		modes wouldn't be working.
+
+		Example:
+		Enable USB data functionality
+		# echo 1 > /sys/class/udc/.../device/usb_data_enabled
+
+		Disable USB data functionality
+		# echo 0 > /sys/class/udc/.../device/usb_data_enabled
+
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index 872df4f..e1c17e2f 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -304,6 +304,7 @@
 
 	enum usb_device_speed override_usb_speed;
 	u32 auto_vbus_src_sel_threshold;
+	bool usb_data_enabled;
 };
 
 #define USB_HSPHY_3P3_VOL_MIN		3050000 /* uV */
@@ -2987,6 +2988,9 @@
 	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
 	enum dwc3_id_state id;
 
+	if (!mdwc->usb_data_enabled)
+		return NOTIFY_DONE;
+
 	id = event ? DWC3_ID_GROUND : DWC3_ID_FLOAT;
 
 	dev_dbg(mdwc->dev, "host:%ld (id:%d) event received\n", event, id);
@@ -3032,6 +3036,9 @@
 	struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, vbus_nb);
 	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
 
+	if (!mdwc->usb_data_enabled)
+		return NOTIFY_DONE;
+
 	dev_dbg(mdwc->dev, "vbus:%ld event received\n", event);
 
 	if (mdwc->vbus_active == event)
@@ -3388,11 +3395,34 @@
 
 	return ret;
 }
-
 static DEVICE_ATTR_RW(xhci_link_compliance);
 
+static ssize_t usb_data_enabled_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
 
+	return sysfs_emit(buf, "%s\n",
+			  mdwc->usb_data_enabled ? "enabled" : "disabled");
+}
 
+static ssize_t usb_data_enabled_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
+
+	if (kstrtobool(buf, &mdwc->usb_data_enabled))
+		return -EINVAL;
+
+	if (!mdwc->usb_data_enabled) {
+		mdwc->vbus_active = false;
+		mdwc->id_state = DWC3_ID_FLOAT;
+		dwc3_ext_event_notify(mdwc);
+	}
+
+	return count;
+}
+static DEVICE_ATTR_RW(usb_data_enabled);
 
 static int dwc3_msm_probe(struct platform_device *pdev)
 {
@@ -3756,11 +3786,14 @@
 			dev_info(mdwc->dev, "charger detection in progress\n");
 	}
 
+	/* set the initial value */
+	mdwc->usb_data_enabled = true;
 	device_create_file(&pdev->dev, &dev_attr_mode);
 	device_create_file(&pdev->dev, &dev_attr_speed);
 	device_create_file(&pdev->dev, &dev_attr_usb_compliance_mode);
 	device_create_file(&pdev->dev, &dev_attr_auto_vbus_src_sel_threshold);
 	device_create_file(&pdev->dev, &dev_attr_xhci_link_compliance);
+	device_create_file(&pdev->dev, &dev_attr_usb_data_enabled);
 
 	host_mode = usb_get_dr_mode(&mdwc->dwc3->dev) == USB_DR_MODE_HOST;
 	if (!dwc->is_drd && host_mode) {
@@ -3803,6 +3836,8 @@
 	device_remove_file(&pdev->dev, &dev_attr_usb_compliance_mode);
 	device_remove_file(&pdev->dev, &dev_attr_auto_vbus_src_sel_threshold);
 	device_remove_file(&pdev->dev, &dev_attr_xhci_link_compliance);
+	device_create_file(&pdev->dev, &dev_attr_usb_data_enabled);
+
 	if (mdwc->usb_psy)
 		power_supply_put(mdwc->usb_psy);