[PATCH 03/13][SRU][OEM-5.13] UBUNTU: SAUCE: IPU driver release WW48 with MCU

You-Sheng Yang vicamo.yang at canonical.com
Thu Jul 29 06:48:22 UTC 2021


From: Wang Yating <yating.wang at intel.com>

BugLink: https://bugs.launchpad.net/bugs/1921345

Signed-off-by: Wang Yating <yating.wang at intel.com>
(cherry picked from
https://github.com/intel/ipu6-drivers/commit/d127576fe1f1ea9a138618d88ce694b7ddb650f8)
Signed-off-by: You-Sheng Yang <vicamo.yang at canonical.com>
---
 drivers/usb/intel_ulpss/Kconfig               |  11 +
 drivers/usb/intel_ulpss/Makefile              |   3 +
 drivers/usb/intel_ulpss/diag_stub.c           | 116 ++++
 drivers/usb/intel_ulpss/diag_stub.h           |  19 +
 drivers/usb/intel_ulpss/gpio_stub.c           | 459 +++++++++++++++
 drivers/usb/intel_ulpss/gpio_stub.h           |  13 +
 drivers/usb/intel_ulpss/i2c_stub.c            | 456 +++++++++++++++
 drivers/usb/intel_ulpss/i2c_stub.h            |  21 +
 drivers/usb/intel_ulpss/mng_stub.c            | 244 ++++++++
 drivers/usb/intel_ulpss/mng_stub.h            |  18 +
 .../usb/intel_ulpss/protocol_intel_ulpss.h    | 173 ++++++
 drivers/usb/intel_ulpss/ulpss_bridge.c        | 529 ++++++++++++++++++
 drivers/usb/intel_ulpss/ulpss_bridge.h        |  77 +++
 drivers/usb/intel_ulpss/usb_stub.c            | 285 ++++++++++
 drivers/usb/intel_ulpss/usb_stub.h            |  49 ++
 15 files changed, 2473 insertions(+)
 create mode 100644 drivers/usb/intel_ulpss/Kconfig
 create mode 100644 drivers/usb/intel_ulpss/Makefile
 create mode 100644 drivers/usb/intel_ulpss/diag_stub.c
 create mode 100644 drivers/usb/intel_ulpss/diag_stub.h
 create mode 100644 drivers/usb/intel_ulpss/gpio_stub.c
 create mode 100644 drivers/usb/intel_ulpss/gpio_stub.h
 create mode 100644 drivers/usb/intel_ulpss/i2c_stub.c
 create mode 100644 drivers/usb/intel_ulpss/i2c_stub.h
 create mode 100644 drivers/usb/intel_ulpss/mng_stub.c
 create mode 100644 drivers/usb/intel_ulpss/mng_stub.h
 create mode 100644 drivers/usb/intel_ulpss/protocol_intel_ulpss.h
 create mode 100644 drivers/usb/intel_ulpss/ulpss_bridge.c
 create mode 100644 drivers/usb/intel_ulpss/ulpss_bridge.h
 create mode 100644 drivers/usb/intel_ulpss/usb_stub.c
 create mode 100644 drivers/usb/intel_ulpss/usb_stub.h

diff --git a/drivers/usb/intel_ulpss/Kconfig b/drivers/usb/intel_ulpss/Kconfig
new file mode 100644
index 000000000000..565f1750df20
--- /dev/null
+++ b/drivers/usb/intel_ulpss/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config INTEL_LPSS_USB
+	tristate "Intel Low Power Subsystem support as USB devices"
+	depends on USB
+	help
+	  This driver supports USB-interfaced Intel Low Power Subsystem
+	  (LPSS) devices such as I2C, GPIO.
+	  Say Y or M here if you have LPSS USB devices.
+	  To compile this driver as a module, choose M here: the
+	  module will be called intel_lpss_usb.ko.
diff --git a/drivers/usb/intel_ulpss/Makefile b/drivers/usb/intel_ulpss/Makefile
new file mode 100644
index 000000000000..fbda28bd676f
--- /dev/null
+++ b/drivers/usb/intel_ulpss/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_INTEL_LPSS_USB)		+= intel_lpss_usb.o
+intel_lpss_usb-y			:= ulpss_bridge.o usb_stub.o mng_stub.o i2c_stub.o gpio_stub.o diag_stub.o
diff --git a/drivers/usb/intel_ulpss/diag_stub.c b/drivers/usb/intel_ulpss/diag_stub.c
new file mode 100644
index 000000000000..dcf7ac46f3a9
--- /dev/null
+++ b/drivers/usb/intel_ulpss/diag_stub.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/types.h>
+
+#include "diag_stub.h"
+
+struct diag_stub_priv {
+	struct usb_stub *stub;
+};
+
+int diag_get_state(struct usb_stub *stub)
+{
+	if (!stub)
+		return -EINVAL;
+
+	return usb_stub_write(stub, DIAG_GET_STATE, NULL, 0, true,
+			      USB_WRITE_ACK_TIMEOUT);
+}
+
+int diag_get_fw_log(struct usb_stub *stub, u8 *buf, ssize_t *len)
+{
+	int ret;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, DIAG_GET_FW_LOG, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	*len = stub->len;
+	if (!ret && stub->len > 0)
+		memcpy(buf, stub->buf, stub->len);
+
+	mutex_unlock(&stub->stub_mutex);
+	return ret;
+}
+
+int diag_get_coredump(struct usb_stub *stub, u8 *buf, ssize_t *len)
+{
+	int ret;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, DIAG_GET_FW_COREDUMP, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	*len = stub->len;
+	if (!ret && !stub->len)
+		memcpy(buf, stub->buf, stub->len);
+
+	mutex_unlock(&stub->stub_mutex);
+
+	return ret;
+}
+
+int diag_get_statistic_info(struct usb_stub *stub)
+{
+	int ret;
+
+	if (stub == NULL)
+		return -EINVAL;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, DIAG_GET_STATISTIC, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+	mutex_unlock(&stub->stub_mutex);
+
+	return ret;
+}
+
+int diag_set_trace_level(struct usb_stub *stub, u8 level)
+{
+	int ret;
+
+	if (stub == NULL)
+		return -EINVAL;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, DIAG_SET_TRACE_LEVEL, &level, sizeof(level),
+			     true, USB_WRITE_ACK_TIMEOUT);
+	mutex_unlock(&stub->stub_mutex);
+
+	return ret;
+}
+
+static void diag_stub_cleanup(struct usb_stub *stub)
+{
+	BUG_ON(!stub);
+	if (stub->priv)
+		kfree(stub->priv);
+
+	return;
+}
+
+int diag_stub_init(struct usb_interface *intf, void *cookie, u8 len)
+{
+	struct usb_stub *stub = usb_stub_alloc(intf);
+	struct diag_stub_priv *priv;
+
+	if (!intf || !stub)
+		return -EINVAL;
+
+	priv = kzalloc(sizeof(struct diag_stub_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->stub = stub;
+
+	stub->priv = priv;
+	stub->type = DIAG_CMD_TYPE;
+	stub->intf = intf;
+	stub->cleanup = diag_stub_cleanup;
+	return 0;
+}
diff --git a/drivers/usb/intel_ulpss/diag_stub.h b/drivers/usb/intel_ulpss/diag_stub.h
new file mode 100644
index 000000000000..b52b11e85ec7
--- /dev/null
+++ b/drivers/usb/intel_ulpss/diag_stub.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __DIAG_STUB_H__
+#define __DIAG_STUB_H__
+
+#include "usb_stub.h"
+
+int diag_set_trace_level(struct usb_stub *stub, u8 level);
+int diag_get_statistic_info(struct usb_stub *stub);
+int diag_stub_init(struct usb_interface *intf, void *cookie, u8 len);
+int diag_get_state(struct usb_stub *stub);
+int diag_get_fw_log(struct usb_stub *stub, u8 *buf, ssize_t *len);
+int diag_get_coredump(struct usb_stub *stub, u8 *buf, ssize_t *len);
+#endif
diff --git a/drivers/usb/intel_ulpss/gpio_stub.c b/drivers/usb/intel_ulpss/gpio_stub.c
new file mode 100644
index 000000000000..fd0aee9b8466
--- /dev/null
+++ b/drivers/usb/intel_ulpss/gpio_stub.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include "gpio_stub.h"
+#include "protocol_intel_ulpss.h"
+#include "usb_stub.h"
+
+#define USB_BRIDGE_GPIO_HID "INTC1074"
+struct pin_info {
+	u8 bank_id;
+	bool valid;
+	u8 connect_mode;
+};
+
+struct gpio_stub_priv {
+	u32 id;
+	struct usb_stub *stub;
+
+	/** gpio descriptor */
+	struct gpio_descriptor descriptor;
+	struct pin_info *pin_info_table;
+
+	struct device dev;
+	u32 valid_gpio_num;
+	u32 total_gpio_num;
+	struct gpio_chip gpio_chip;
+
+	bool ready;
+};
+
+/** stub function */
+static int gpio_stub_parse(struct usb_stub *stub, u8 cmd, u8 flags, u8 *data,
+			   u32 len)
+{
+	if (!stub)
+		return -EINVAL;
+
+	if (cmd == GPIO_INTR_NOTIFY)
+		if (stub->notify)
+			stub->notify(stub, GPIO_INTR_EVENT, NULL);
+
+	return 0;
+}
+
+static void gpio_stub_cleanup(struct usb_stub *stub)
+{
+	struct gpio_stub_priv *priv = stub->priv;
+
+	if (!stub || !priv)
+		return;
+
+	dev_dbg(&stub->intf->dev, "%s unregister gpio dev\n", __func__);
+
+	if (priv->ready) {
+		gpiochip_remove(&priv->gpio_chip);
+		device_unregister(&priv->dev);
+	}
+
+	if (priv->pin_info_table) {
+		kfree(priv->pin_info_table);
+		priv->pin_info_table = NULL;
+	}
+	kfree(priv);
+
+	return;
+}
+
+static int gpio_stub_update_descriptor(struct usb_stub *stub,
+				       struct gpio_descriptor *descriptor,
+				       u8 len)
+{
+	struct gpio_stub_priv *priv = stub->priv;
+	u32 i, j;
+	int pin_id;
+
+	if (!priv || !descriptor ||
+	    len != offsetof(struct gpio_descriptor, bank_table) +
+			    sizeof(struct bank_descriptor) *
+				    descriptor->banks ||
+	    len > sizeof(priv->descriptor))
+		return -EINVAL;
+
+	if ((descriptor->pins_per_bank <= 0) || (descriptor->banks <= 0)) {
+		dev_err(&stub->intf->dev, "%s pins_per_bank:%d bans:%d\n",
+			__func__, descriptor->pins_per_bank, descriptor->banks);
+		return -EINVAL;
+	}
+
+	priv->total_gpio_num = descriptor->pins_per_bank * descriptor->banks;
+	memcpy(&priv->descriptor, descriptor, len);
+
+	priv->pin_info_table =
+		kzalloc(sizeof(struct pin_info) * descriptor->pins_per_bank *
+				descriptor->banks,
+			GFP_KERNEL);
+	if (!priv->pin_info_table)
+		return -ENOMEM;
+
+	for (i = 0; i < descriptor->banks; i++) {
+		for (j = 0; j < descriptor->pins_per_bank; j++) {
+			pin_id = descriptor->pins_per_bank * i + j;
+			if ((descriptor->bank_table[i].bitmap & (1 << j))) {
+				priv->pin_info_table[pin_id].valid = true;
+				priv->valid_gpio_num++;
+				dev_dbg(&stub->intf->dev,
+					"%s found one valid pin (%d %d %d %d %d)\n",
+					__func__, i, j,
+					descriptor->bank_table[i].pin_num,
+					descriptor->pins_per_bank,
+					priv->valid_gpio_num);
+			} else {
+				priv->pin_info_table[pin_id].valid = false;
+			}
+			priv->pin_info_table[pin_id].bank_id = i;
+		}
+	}
+
+	dev_dbg(&stub->intf->dev, "%s valid_gpio_num:%d total_gpio_num:%d\n",
+		__func__, priv->valid_gpio_num, priv->total_gpio_num);
+	return 0;
+}
+
+static struct pin_info *gpio_stub_get_pin_info(struct usb_stub *stub, u8 pin_id)
+{
+	struct pin_info *pin_info = NULL;
+	struct gpio_stub_priv *priv = stub->priv;
+
+	BUG_ON(!priv);
+
+	if (!(pin_id <
+	      priv->descriptor.banks * priv->descriptor.pins_per_bank)) {
+		dev_err(&stub->intf->dev,
+			"pin_id:%d banks:%d, pins_per_bank:%d\n", pin_id,
+			priv->descriptor.banks, priv->descriptor.pins_per_bank);
+		return NULL;
+	}
+
+	pin_info = &priv->pin_info_table[pin_id];
+	if (!pin_info || !pin_info->valid) {
+		dev_err(&stub->intf->dev,
+			"%s pin_id:%d banks:%d, pins_per_bank:%d valid:%d",
+			__func__, pin_id, priv->descriptor.banks,
+			priv->descriptor.pins_per_bank, pin_info->valid);
+
+		return NULL;
+	}
+
+	return pin_info;
+}
+
+static int gpio_stub_ready(struct usb_stub *stub, void *cookie, u8 len);
+int gpio_stub_init(struct usb_interface *intf, void *cookie, u8 len)
+{
+	struct usb_stub *stub = usb_stub_alloc(intf);
+	struct gpio_stub_priv *priv;
+
+	if (!intf || !stub)
+		return -EINVAL;
+
+	priv = kzalloc(sizeof(struct gpio_stub_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->stub = stub;
+	priv->id = DEFAULT_GPIO_CONTROLLER_ID;
+
+	stub->priv = priv;
+	stub->type = GPIO_CMD_TYPE;
+	stub->intf = intf;
+	stub->parse = gpio_stub_parse;
+	stub->cleanup = gpio_stub_cleanup;
+
+	return gpio_stub_ready(stub, cookie, len);
+}
+
+/** gpio function */
+static u8 gpio_get_payload_len(u8 pin_num)
+{
+	return sizeof(struct gpio_packet) + pin_num * sizeof(struct gpio_op);
+}
+
+static int gpio_config(struct usb_stub *stub, u8 pin_id, u8 config)
+{
+	struct gpio_packet *packet;
+	struct pin_info *pin_info;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+
+	if (!stub)
+		return -EINVAL;
+
+	dev_dbg(&stub->intf->dev, "%s pin_id:%u\n", __func__, pin_id);
+
+	packet = (struct gpio_packet *)buf;
+
+	pin_info = gpio_stub_get_pin_info(stub, pin_id);
+	if (!pin_info) {
+		dev_err(&stub->intf->dev, "invalid gpio pin pin_id:%d\n",
+			pin_id);
+		return -EINVAL;
+	}
+
+	packet->item[0].index = pin_id;
+	packet->item[0].value = config | pin_info->connect_mode;
+	packet->num = 1;
+
+	return usb_stub_write(stub, GPIO_CONFIG, (u8 *)packet,
+			      (u8)gpio_get_payload_len(packet->num), true,
+			      USB_WRITE_ACK_TIMEOUT);
+}
+
+static int intel_ulpss_gpio_read(struct usb_stub *stub, u8 pin_id, int *data)
+{
+	struct gpio_packet *packet;
+	struct pin_info *pin_info;
+	struct gpio_packet *ack_packet;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	if (!stub)
+		return -EINVAL;
+
+	packet = (struct gpio_packet *)buf;
+	packet->num = 1;
+
+	pin_info = gpio_stub_get_pin_info(stub, pin_id);
+	if (!pin_info) {
+		dev_err(&stub->intf->dev, "invalid gpio pin_id:[%u]", pin_id);
+		return -EINVAL;
+	}
+
+	packet->item[0].index = pin_id;
+	ret = usb_stub_write(stub, GPIO_READ, (u8 *)packet,
+			     (u8)gpio_get_payload_len(packet->num), true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	ack_packet = (struct gpio_packet *)stub->buf;
+
+	BUG_ON(!ack_packet);
+	if (ret || !stub->len || ack_packet->num != packet->num) {
+		dev_err(&stub->intf->dev,
+			"%s usb_stub_write failed pin_id:%d ret %d", __func__,
+			pin_id, ret);
+		return -EIO;
+	}
+
+	*data = (ack_packet->item[0].value > 0) ? 1 : 0;
+
+	return ret;
+}
+
+static int intel_ulpss_gpio_write(struct usb_stub *stub, u8 pin_id, int value)
+{
+	struct gpio_packet *packet;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+
+	BUG_ON(!stub);
+
+	packet = (struct gpio_packet *)buf;
+	packet->num = 1;
+
+	packet->item[0].index = pin_id;
+	packet->item[0].value = (value & 1);
+
+	return usb_stub_write(stub, GPIO_WRITE, buf,
+			      gpio_get_payload_len(packet->num), true,
+			      USB_WRITE_ACK_TIMEOUT);
+}
+
+/* gpio chip*/
+static int intel_ulpss_gpio_get_value(struct gpio_chip *chip, unsigned off)
+{
+	struct gpio_stub_priv *priv = gpiochip_get_data(chip);
+	int value = 0;
+	int ret;
+
+	dev_dbg(chip->parent, "%s off:%u\n", __func__, off);
+	ret = intel_ulpss_gpio_read(priv->stub, off, &value);
+	if (ret) {
+		dev_err(chip->parent, "%s off:%d get vaule failed %d\n",
+			__func__, off, ret);
+	}
+	return value;
+}
+
+static void intel_ulpss_gpio_set_value(struct gpio_chip *chip, unsigned off, int val)
+{
+	struct gpio_stub_priv *priv = gpiochip_get_data(chip);
+	int ret;
+
+	dev_dbg(chip->parent, "%s off:%u val:%d\n", __func__, off, val);
+	ret = intel_ulpss_gpio_write(priv->stub, off, val);
+	if (ret) {
+		dev_err(chip->parent, "%s off:%d val:%d set vaule failed %d\n",
+			__func__, off, val, ret);
+	}
+}
+
+static int intel_ulpss_gpio_direction_input(struct gpio_chip *chip, unsigned off)
+{
+	struct gpio_stub_priv *priv = gpiochip_get_data(chip);
+	u8 config = GPIO_CONF_INPUT | GPIO_CONF_CLR;
+
+	dev_dbg(chip->parent, "%s off:%u\n", __func__, off);
+	return gpio_config(priv->stub, off, config);
+}
+
+static int intel_ulpss_gpio_direction_output(struct gpio_chip *chip, unsigned off,
+				     int val)
+{
+	struct gpio_stub_priv *priv = gpiochip_get_data(chip);
+	u8 config = GPIO_CONF_OUTPUT | GPIO_CONF_CLR;
+	int ret;
+
+	dev_dbg(chip->parent, "%s off:%u\n", __func__, off);
+	ret = gpio_config(priv->stub, off, config);
+	if (ret)
+		return ret;
+
+	intel_ulpss_gpio_set_value(chip, off, val);
+	return ret;
+}
+
+static int intel_ulpss_gpio_set_config(struct gpio_chip *chip, unsigned int off,
+			       unsigned long config)
+{
+	struct gpio_stub_priv *priv = gpiochip_get_data(chip);
+	struct pin_info *pin_info;
+
+	dev_dbg(chip->parent, "%s off:%d\n", __func__, off);
+
+	pin_info = gpio_stub_get_pin_info(priv->stub, off);
+	if (!pin_info) {
+		dev_err(chip->parent, "invalid gpio pin off:%d pin_id:%d\n",
+			off, off);
+
+		return -EINVAL;
+	}
+
+	dev_dbg(chip->parent, " %s off:%d config:%d\n", __func__, off,
+		pinconf_to_config_param(config));
+
+	pin_info->connect_mode = 0;
+	switch (pinconf_to_config_param(config)) {
+	case PIN_CONFIG_BIAS_PULL_UP:
+		pin_info->connect_mode |= GPIO_CONF_PULLUP;
+		break;
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+		pin_info->connect_mode |= GPIO_CONF_PULLDOWN;
+		break;
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	dev_dbg(chip->parent, " %s off:%d connect_mode:%d\n", __func__, off,
+		pin_info->connect_mode);
+	return 0;
+}
+
+static void gpio_dev_release(struct device *dev)
+{
+	dev_dbg(dev, "%s\n", __func__);
+}
+
+static int intel_ulpss_gpio_chip_setup(struct usb_interface *intf,
+			       struct usb_stub *stub)
+{
+	struct gpio_stub_priv *priv;
+	struct gpio_chip *gc;
+	struct acpi_device *adev;
+	int ret;
+
+	priv = stub->priv;
+	priv->dev.parent = &intf->dev;
+	priv->dev.init_name = "intel-ulpss-gpio";
+	priv->dev.release = gpio_dev_release;
+	adev = find_adev_by_hid(
+		ACPI_COMPANION(&(interface_to_usbdev(intf)->dev)),
+		USB_BRIDGE_GPIO_HID);
+	if (adev) {
+		ACPI_COMPANION_SET(&priv->dev, adev);
+		dev_info(&intf->dev, "found: %s -> %s\n", dev_name(&intf->dev),
+			 acpi_device_hid(adev));
+	} else {
+		dev_err(&intf->dev, "not found: %s\n", USB_BRIDGE_GPIO_HID);
+	}
+
+	ret = device_register(&priv->dev);
+	if (ret) {
+		dev_err(&intf->dev, "device register failed\n");
+		device_unregister(&priv->dev);
+
+		return ret;
+	}
+
+	gc = &priv->gpio_chip;
+	gc->direction_input = intel_ulpss_gpio_direction_input;
+	gc->direction_output = intel_ulpss_gpio_direction_output;
+	gc->get = intel_ulpss_gpio_get_value;
+	gc->set = intel_ulpss_gpio_set_value;
+	gc->set_config = intel_ulpss_gpio_set_config;
+	gc->can_sleep = true;
+	gc->parent = &priv->dev;
+
+	gc->base = -1;
+	gc->ngpio = priv->total_gpio_num;
+	gc->label = "intel_ulpss gpiochip";
+	gc->owner = THIS_MODULE;
+
+	ret = gpiochip_add_data(gc, priv);
+	if (ret) {
+		dev_err(&intf->dev, "%s gpiochip add failed ret:%d\n", __func__,
+			ret);
+	} else {
+		priv->ready = true;
+		dev_info(&intf->dev,
+			 "%s gpiochip add success, base:%d ngpio:%d\n",
+			 __func__, gc->base, gc->ngpio);
+	}
+
+	return ret;
+}
+
+static int gpio_stub_ready(struct usb_stub *stub, void *cookie, u8 len)
+{
+	struct gpio_descriptor *descriptor = cookie;
+	int ret;
+
+	if (!descriptor || (descriptor->pins_per_bank <= 0) ||
+	    (descriptor->banks <= 0)) {
+		dev_err(&stub->intf->dev,
+			"%s gpio stub descriptor not correct\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = gpio_stub_update_descriptor(stub, descriptor, len);
+	if (ret) {
+		dev_err(&stub->intf->dev,
+			"%s gpio stub update descriptor failed\n", __func__);
+		return ret;
+	}
+
+	ret = intel_ulpss_gpio_chip_setup(stub->intf, stub);
+
+	return ret;
+}
diff --git a/drivers/usb/intel_ulpss/gpio_stub.h b/drivers/usb/intel_ulpss/gpio_stub.h
new file mode 100644
index 000000000000..a4ddb618c83b
--- /dev/null
+++ b/drivers/usb/intel_ulpss/gpio_stub.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __GPIO_STUB_H__
+#define __GPIO_STUB_H__
+
+int gpio_stub_init(struct usb_interface *intf, void *cookie, u8 len);
+
+#endif
diff --git a/drivers/usb/intel_ulpss/i2c_stub.c b/drivers/usb/intel_ulpss/i2c_stub.c
new file mode 100644
index 000000000000..f0dcf6413016
--- /dev/null
+++ b/drivers/usb/intel_ulpss/i2c_stub.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bug.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include "i2c_stub.h"
+#include "protocol_intel_ulpss.h"
+#include "usb_stub.h"
+
+#define USB_BRIDGE_I2C_HID_0 "INTC1075"
+#define USB_BRIDGE_I2C_HID_1 "INTC1076"
+
+static char *usb_bridge_i2c_hids[] = { USB_BRIDGE_I2C_HID_0,
+				       USB_BRIDGE_I2C_HID_1 };
+
+struct i2c_stub_priv {
+	struct usb_stub *stub;
+	struct i2c_descriptor descriptor;
+	struct i2c_adapter *adap;
+
+	bool ready;
+};
+
+static u8 i2c_format_slave_addr(u8 slave_addr, enum i2c_address_mode mode)
+{
+	if (mode == I2C_ADDRESS_MODE_7Bit)
+		return slave_addr << 1;
+
+	return 0xFF;
+}
+
+static void i2c_stub_cleanup(struct usb_stub *stub)
+{
+	struct i2c_stub_priv *priv = stub->priv;
+	int i;
+
+	if (!priv)
+		return;
+
+	if (priv->ready) {
+		for (i = 0; i < priv->descriptor.num; i++)
+			if (priv->adap[i].nr != -1)
+				i2c_del_adapter(&priv->adap[i]);
+
+		if (priv->adap)
+			kfree(priv->adap);
+	}
+
+	kfree(priv);
+}
+
+static int i2c_stub_update_descriptor(struct usb_stub *stub,
+				      struct i2c_descriptor *descriptor, u8 len)
+{
+	struct i2c_stub_priv *priv = stub->priv;
+
+	if (!priv || !descriptor ||
+	    len != offsetof(struct i2c_descriptor, info) +
+			    descriptor->num *
+				    sizeof(struct i2c_controller_info) ||
+	    len > sizeof(priv->descriptor))
+		return -EINVAL;
+
+	memcpy(&priv->descriptor, descriptor, len);
+
+	return 0;
+}
+
+static int i2c_stub_ready(struct usb_stub *stub, void *cookie, u8 len);
+int i2c_stub_init(struct usb_interface *intf, void *cookie, u8 len)
+{
+	struct usb_stub *stub = usb_stub_alloc(intf);
+	struct i2c_stub_priv *priv;
+
+	if (!intf || !stub)
+		return -EINVAL;
+
+	priv = kzalloc(sizeof(struct i2c_stub_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	stub->priv = priv;
+	stub->type = I2C_CMD_TYPE;
+	stub->intf = intf;
+	stub->cleanup = i2c_stub_cleanup;
+
+	priv->stub = stub;
+
+	return i2c_stub_ready(stub, cookie, len);
+}
+
+/** i2c intf */
+static int intel_ulpss_i2c_init(struct usb_stub *stub, u8 id)
+{
+	struct i2c_raw_io *i2c_config;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	i2c_config = (struct i2c_raw_io *)buf;
+
+	i2c_config->id = id;
+	i2c_config->len = 1;
+	i2c_config->data[0] = I2C_FLAG_FREQ_400K;
+
+	ret = usb_stub_write(stub, I2C_INIT, (u8 *)i2c_config,
+			     sizeof(struct i2c_raw_io) + i2c_config->len, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	return ret;
+}
+
+static int intel_ulpss_i2c_start(struct usb_stub *stub, u8 id, u8 slave_addr,
+				 enum xfer_type type)
+{
+	struct i2c_raw_io *raw_io;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	BUG_ON(!stub);
+
+	raw_io = (struct i2c_raw_io *)buf;
+	raw_io->id = id;
+	raw_io->len = 1;
+	raw_io->data[0] =
+		i2c_format_slave_addr(slave_addr, I2C_ADDRESS_MODE_7Bit);
+	raw_io->data[0] |= (type == READ_XFER_TYPE) ? I2C_SLAVE_TRANSFER_READ :
+						      I2C_SLAVE_TRANSFER_WRITE;
+
+	ret = usb_stub_write(stub, I2C_START, (u8 *)raw_io,
+			     sizeof(struct i2c_raw_io) + raw_io->len, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	if (stub->len < sizeof(struct i2c_raw_io))
+		return -EIO;
+
+	raw_io = (struct i2c_raw_io *)stub->buf;
+	if (raw_io->len < 0 || raw_io->id != id) {
+		dev_err(&stub->intf->dev,
+			"%s i2c start failed len:%d id:%d %d\n", __func__,
+			raw_io->len, raw_io->id, id);
+		return -EIO;
+	}
+
+	return ret;
+}
+
+static int intel_ulpss_i2c_stop(struct usb_stub *stub, u8 id, u8 slave_addr)
+{
+	struct i2c_raw_io *raw_io;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	BUG_ON(!stub);
+
+	raw_io = (struct i2c_raw_io *)buf;
+	raw_io->id = id;
+	raw_io->len = 1;
+	raw_io->data[0] = 0;
+
+	ret = usb_stub_write(stub, I2C_STOP, (u8 *)raw_io,
+			     sizeof(struct i2c_raw_io) + 1, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	if (stub->len < sizeof(struct i2c_raw_io))
+		return -EIO;
+
+	raw_io = (struct i2c_raw_io *)stub->buf;
+	if (raw_io->len < 0 || raw_io->id != id) {
+		dev_err(&stub->intf->dev,
+			"%s i2c stop failed len:%d id:%d %d\n", __func__,
+			raw_io->len, raw_io->id, id);
+		return -EIO;
+	}
+
+	return ret;
+}
+
+static int intel_ulpss_i2c_pure_read(struct usb_stub *stub, u8 id, u8 *data,
+				     u8 len)
+{
+	struct i2c_raw_io *raw_io;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	raw_io = (struct i2c_raw_io *)buf;
+
+	BUG_ON(!stub);
+	if (len > MAX_PAYLOAD_SIZE - sizeof(struct i2c_raw_io)) {
+		return -EINVAL;
+	}
+
+	raw_io->id = id;
+	raw_io->len = len;
+	raw_io->data[0] = 0;
+	ret = usb_stub_write(stub, I2C_READ, (u8 *)raw_io,
+			     sizeof(struct i2c_raw_io) + 1, true,
+			     USB_WRITE_ACK_TIMEOUT);
+	if (ret) {
+		dev_err(&stub->intf->dev, "%s ret:%d\n", __func__, ret);
+		return ret;
+	}
+
+	if (stub->len < sizeof(struct i2c_raw_io))
+		return -EIO;
+
+	raw_io = (struct i2c_raw_io *)stub->buf;
+	if (raw_io->len < 0 || raw_io->id != id) {
+		dev_err(&stub->intf->dev,
+			"%s i2 raw read failed len:%d id:%d %d\n", __func__,
+			raw_io->len, raw_io->id, id);
+		return -EIO;
+	}
+
+	BUG_ON(raw_io->len != len);
+	memcpy(data, raw_io->data, raw_io->len);
+
+	return 0;
+}
+
+static int intel_ulpss_i2c_read(struct usb_stub *stub, u8 id, u8 slave_addr,
+				u8 *data, u8 len)
+{
+	int ret;
+
+	BUG_ON(!stub);
+	ret = intel_ulpss_i2c_start(stub, id, slave_addr, READ_XFER_TYPE);
+	if (ret) {
+		dev_err(&stub->intf->dev, "%s i2c start failed ret:%d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = intel_ulpss_i2c_pure_read(stub, id, data, len);
+	if (ret) {
+		dev_err(&stub->intf->dev, "%s i2c raw read failed ret:%d\n",
+			__func__, ret);
+
+		return ret;
+	}
+
+	ret = intel_ulpss_i2c_stop(stub, id, slave_addr);
+	if (ret) {
+		dev_err(&stub->intf->dev, "%s i2c stop failed ret:%d\n",
+			__func__, ret);
+
+		return ret;
+	}
+
+	return ret;
+}
+
+static int intel_ulpss_i2c_pure_write(struct usb_stub *stub, u8 id, u8 *data,
+				      u8 len)
+{
+	struct i2c_raw_io *raw_io;
+	u8 buf[MAX_PAYLOAD_SIZE] = { 0 };
+	int ret;
+
+	if (len > MAX_PAYLOAD_SIZE - sizeof(struct i2c_raw_io)) {
+		dev_err(&stub->intf->dev, "%s unexpected long data, len: %d",
+			__func__, len);
+		return -EINVAL;
+	}
+
+	raw_io = (struct i2c_raw_io *)buf;
+	raw_io->id = id;
+	raw_io->len = len;
+	memcpy(raw_io->data, data, len);
+
+	ret = usb_stub_write(stub, I2C_WRITE, (u8 *)raw_io,
+			     sizeof(struct i2c_raw_io) + raw_io->len, true,
+			     USB_WRITE_ACK_TIMEOUT);
+
+	if (stub->len < sizeof(struct i2c_raw_io))
+		return -EIO;
+
+	raw_io = (struct i2c_raw_io *)stub->buf;
+	if (raw_io->len < 0 || raw_io->id != id) {
+		dev_err(&stub->intf->dev,
+			"%s i2c write failed len:%d id:%d %d\n", __func__,
+			raw_io->len, raw_io->id, id);
+		return -EIO;
+	}
+	return ret;
+}
+
+static int intel_ulpss_i2c_write(struct usb_stub *stub, u8 id, u8 slave_addr,
+				 u8 *data, u8 len)
+{
+	int ret;
+	BUG_ON(!stub);
+
+	ret = intel_ulpss_i2c_start(stub, id, slave_addr, WRITE_XFER_TYPE);
+	if (ret)
+		return ret;
+
+	ret = intel_ulpss_i2c_pure_write(stub, id, data, len);
+	if (ret)
+		return ret;
+
+	ret = intel_ulpss_i2c_stop(stub, id, slave_addr);
+
+	return ret;
+}
+
+static int intel_ulpss_i2c_xfer(struct i2c_adapter *adapter,
+				struct i2c_msg *msg, int num)
+{
+	struct i2c_stub_priv *priv;
+	struct i2c_msg *cur_msg;
+	struct usb_stub *stub;
+	int id = -1;
+	int i, ret;
+
+	priv = i2c_get_adapdata(adapter);
+	stub = priv->stub;
+
+	if (!stub || !priv) {
+		dev_err(&adapter->dev, "%s num:%d stub:0x%lx priv:0x%lx\n",
+			__func__, num, (long)stub, (long)priv);
+		return 0;
+	}
+
+	for (i = 0; i < priv->descriptor.num; i++)
+		if (&priv->adap[i] == adapter)
+			id = i;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = intel_ulpss_i2c_init(stub, id);
+	if (ret) {
+		dev_err(&adapter->dev, "%s i2c init failed id:%d\n", __func__,
+			adapter->nr);
+		mutex_unlock(&stub->stub_mutex);
+		return 0;
+	}
+
+	for (i = 0; !ret && i < num && id >= 0; i++) {
+		cur_msg = &msg[i];
+		dev_dbg(&adapter->dev, "%s i:%d id:%d msg:(%d %d)\n", __func__,
+			i, id, cur_msg->flags, cur_msg->len);
+		if (cur_msg->flags & I2C_M_RD)
+			ret = intel_ulpss_i2c_read(priv->stub, id,
+						   cur_msg->addr, cur_msg->buf,
+						   cur_msg->len);
+
+		else
+			ret = intel_ulpss_i2c_write(priv->stub, id,
+						    cur_msg->addr, cur_msg->buf,
+						    cur_msg->len);
+	}
+
+	mutex_unlock(&stub->stub_mutex);
+	dev_dbg(&adapter->dev, "%s id:%d ret:%d\n", __func__, id, ret);
+
+	/* return the number of messages processed, or the error code */
+	if (ret)
+		return ret;
+	return num;
+}
+
+static u32 intel_ulpss_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+static const struct i2c_algorithm intel_ulpss_i2c_algo = {
+	.master_xfer = intel_ulpss_i2c_xfer,
+	.functionality = intel_ulpss_i2c_func,
+};
+
+static int intel_ulpss_i2c_adapter_register(struct usb_interface *intf,
+					    struct i2c_adapter *adap, int index)
+{
+	struct acpi_device *adev;
+
+	if (!adap || index < 0 || index > sizeof(usb_bridge_i2c_hids))
+		return -EINVAL;
+
+	adev = find_adev_by_hid(
+		ACPI_COMPANION(&(interface_to_usbdev(intf)->dev)),
+		usb_bridge_i2c_hids[index]);
+	if (adev) {
+		dev_info(&intf->dev, "Found: %s -> %s\n", dev_name(&intf->dev),
+			 acpi_device_hid(adev));
+		ACPI_COMPANION_SET(&adap->dev, adev);
+	} else {
+		dev_err(&intf->dev, "Not Found: %s\n",
+			usb_bridge_i2c_hids[index]);
+	}
+
+	return i2c_add_adapter(adap);
+}
+
+static int intel_ulpss_i2c_adapter_setup(struct usb_interface *intf,
+					 struct usb_stub *stub)
+{
+	struct i2c_stub_priv *priv;
+	int ret;
+	int i;
+
+	if (!intf || !stub)
+		return -EINVAL;
+
+	priv = stub->priv;
+	if (!priv)
+		return -EINVAL;
+
+	priv->adap = kzalloc(sizeof(struct i2c_adapter) * priv->descriptor.num,
+			     GFP_KERNEL);
+
+	for (i = 0; i < priv->descriptor.num; i++) {
+		priv->adap[i].owner = THIS_MODULE;
+		snprintf(priv->adap[i].name, sizeof(priv->adap[i].name),
+			 "intel-ulpss-i2c-%d", i);
+		priv->adap[i].algo = &intel_ulpss_i2c_algo;
+		priv->adap[i].dev.parent = &intf->dev;
+
+		i2c_set_adapdata(&priv->adap[i], priv);
+
+		ret = intel_ulpss_i2c_adapter_register(intf, &priv->adap[i], i);
+		if (ret)
+			return ret;
+	}
+
+	priv->ready = true;
+	return ret;
+}
+
+static int i2c_stub_ready(struct usb_stub *stub, void *cookie, u8 len)
+{
+	struct i2c_descriptor *descriptor = cookie;
+	int ret;
+
+	if (!descriptor || descriptor->num <= 0) {
+		dev_err(&stub->intf->dev,
+			"%s i2c stub descriptor not correct\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = i2c_stub_update_descriptor(stub, descriptor, len);
+	if (ret) {
+		dev_err(&stub->intf->dev,
+			"%s i2c stub update descriptor failed\n", __func__);
+		return ret;
+	}
+
+	ret = intel_ulpss_i2c_adapter_setup(stub->intf, stub);
+	return ret;
+}
diff --git a/drivers/usb/intel_ulpss/i2c_stub.h b/drivers/usb/intel_ulpss/i2c_stub.h
new file mode 100644
index 000000000000..2049af4aa05d
--- /dev/null
+++ b/drivers/usb/intel_ulpss/i2c_stub.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __I2C_STUB_H__
+#define __I2C_STUB_H__
+
+#include <linux/types.h>
+
+enum i2c_address_mode { I2C_ADDRESS_MODE_7Bit, I2C_ADDRESS_MODE_10Bit };
+enum xfer_type {
+	READ_XFER_TYPE,
+	WRITE_XFER_TYPE,
+};
+
+int i2c_stub_init(struct usb_interface *intf, void *cookie, u8 len);
+
+#endif
diff --git a/drivers/usb/intel_ulpss/mng_stub.c b/drivers/usb/intel_ulpss/mng_stub.c
new file mode 100644
index 000000000000..ad949471c53c
--- /dev/null
+++ b/drivers/usb/intel_ulpss/mng_stub.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include "ulpss_bridge.h"
+#include "mng_stub.h"
+#include "protocol_intel_ulpss.h"
+#include "usb_stub.h"
+#include "i2c_stub.h"
+#include "gpio_stub.h"
+
+struct mng_stub_priv {
+	long reset_id;
+	struct usb_stub *stub;
+	bool synced;
+};
+
+static void mng_cleanup(struct usb_stub *stub)
+{
+	struct mng_stub_priv *priv = stub->priv;
+
+	if (priv)
+		kfree(priv);
+}
+
+static int mng_reset_ack(struct usb_stub *stub, u32 reset_id)
+{
+	return usb_stub_write(stub, MNG_RESET_NOTIFY, (u8 *)&reset_id,
+			      (u8)sizeof(reset_id), false,
+			      USB_WRITE_ACK_TIMEOUT);
+}
+
+static int mng_stub_parse(struct usb_stub *stub, u8 cmd, u8 ack, u8 *data,
+			  u32 len)
+{
+	struct mng_stub_priv *priv = stub->priv;
+	int ret = 0;
+
+	if (!stub || !stub->intf)
+		return -EINVAL;
+
+	switch (cmd) {
+	case MNG_RESET_NOTIFY:
+		if (data && (len >= sizeof(u32))) {
+			u32 reset_id = *(u32 *)data;
+
+			if (!ack)
+				ret = mng_reset_ack(stub, reset_id);
+
+			priv->synced = (!ret) ? true : false;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+int mng_stub_init(struct usb_interface *intf, void *cookie, u8 len)
+{
+	struct usb_stub *stub = usb_stub_alloc(intf);
+	struct mng_stub_priv *priv;
+
+	if (!intf || !stub)
+		return -EINVAL;
+
+	priv = kzalloc(sizeof(struct mng_stub_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->reset_id = 0;
+	priv->stub = stub;
+
+	stub->priv = priv;
+	stub->type = MNG_CMD_TYPE;
+	stub->intf = intf;
+	stub->parse = mng_stub_parse;
+	stub->cleanup = mng_cleanup;
+
+	return 0;
+}
+
+static int mng_reset_handshake(struct usb_stub *stub)
+{
+	int ret;
+	struct mng_stub_priv *priv = stub->priv;
+	long reset_id;
+
+	if (!stub)
+		return -EINVAL;
+
+	reset_id = priv->reset_id++;
+	ret = usb_stub_write(stub, MNG_RESET_NOTIFY, (u8 *)&reset_id,
+			     (u8)sizeof(reset_id), true, USB_WRITE_ACK_TIMEOUT);
+
+	if (ret || !priv->synced) {
+		dev_err(&stub->intf->dev, "%s priv->synced:%d ret:%d\n",
+			__func__, priv->synced, ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int mng_reset(struct usb_stub *stub)
+{
+	if (!stub)
+		return -EINVAL;
+
+	return usb_stub_write(stub, MNG_RESET, NULL, 0, true,
+			      USB_WRITE_ACK_TIMEOUT);
+}
+
+static int mng_enum_gpio(struct usb_stub *stub)
+{
+	int ret;
+
+	if (!stub)
+		return -EINVAL;
+
+	ret = usb_stub_write(stub, MNG_ENUM_GPIO, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+	if (ret || stub->len <= 0) {
+		dev_err(&stub->intf->dev, "%s enum gpio failed ret:%d len:%d\n",
+			__func__, ret, stub->len);
+		return ret;
+	}
+
+	return gpio_stub_init(stub->intf, stub->buf, stub->len);
+}
+
+static int mng_enum_i2c(struct usb_stub *stub)
+{
+	int ret;
+
+	if (!stub)
+		return -EINVAL;
+
+	ret = usb_stub_write(stub, MNG_ENUM_I2C, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+	if (ret || stub->len <= 0) {
+		dev_err(&stub->intf->dev, "%s enum gpio failed ret:%d len:%d\n",
+			__func__, ret, stub->len);
+		ret = -EIO;
+		return ret;
+	}
+
+	ret = i2c_stub_init(stub->intf, stub->buf, stub->len);
+	return ret;
+}
+
+int mng_get_version(struct usb_stub *stub, struct fw_version *version)
+{
+	int ret;
+
+	if (!stub || !version)
+		return -EINVAL;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, MNG_GET_VERSION, NULL, 0, true,
+			     USB_WRITE_ACK_TIMEOUT);
+	if (ret || stub->len < sizeof(struct fw_version)) {
+		mutex_unlock(&stub->stub_mutex);
+		dev_err(&stub->intf->dev,
+			"%s MNG_GET_VERSION failed ret:%d len:%d\n", __func__,
+			ret, stub->len);
+		ret = -EIO;
+		return ret;
+	}
+
+	memcpy(version, stub->buf, sizeof(struct fw_version));
+	mutex_unlock(&stub->stub_mutex);
+
+	return 0;
+}
+
+int mng_get_version_string(struct usb_stub *stub, char *buf)
+{
+	int ret;
+	struct fw_version version;
+	if (!buf)
+		return -EINVAL;
+
+	ret = mng_get_version(stub, &version);
+	if (ret) {
+		dev_err(&stub->intf->dev, "%s mng get fw version failed ret:%d",
+			__func__, ret);
+
+		ret = sprintf(buf, "%d.%d.%d.%d\n", 1, 1, 1, 1);
+		return ret;
+	}
+
+	ret = sprintf(buf, "%d.%d.%d.%d\n", version.major, version.minor,
+		      version.patch, version.build);
+
+	return ret;
+}
+
+int mng_set_dfu_mode(struct usb_stub *stub)
+{
+	int ret;
+	struct mng_stub_priv *priv = NULL;
+	if (!stub)
+		return -EINVAL;
+
+	priv = stub->priv;
+
+	mutex_lock(&stub->stub_mutex);
+	ret = usb_stub_write(stub, MNG_SET_DFU_MODE, NULL, 0, false,
+			     USB_WRITE_ACK_TIMEOUT);
+	mutex_unlock(&stub->stub_mutex);
+
+	return ret;
+}
+
+int mng_stub_link(struct usb_interface *intf, struct usb_stub *mng_stub)
+{
+	int ret;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+
+	BUG_ON(!intel_ulpss_dev || !mng_stub);
+
+	ret = mng_reset_handshake(mng_stub);
+	if (ret)
+		return ret;
+	intel_ulpss_dev->state = USB_BRIDGE_RESET_SYNCED;
+
+	ret = mng_enum_gpio(mng_stub);
+	if (ret)
+		return ret;
+	intel_ulpss_dev->state = USB_BRIDGE_ENUM_GPIO_COMPLETE;
+
+	ret = mng_enum_i2c(mng_stub);
+	if (ret)
+		return ret;
+	intel_ulpss_dev->state = USB_BRIDGE_ENUM_I2C_COMPLETE;
+	intel_ulpss_dev->state = USB_BRIDGE_STARTED;
+
+	return ret;
+}
diff --git a/drivers/usb/intel_ulpss/mng_stub.h b/drivers/usb/intel_ulpss/mng_stub.h
new file mode 100644
index 000000000000..1a7a49304c75
--- /dev/null
+++ b/drivers/usb/intel_ulpss/mng_stub.h
@@ -0,0 +1,18 @@
+
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __MNG_STUB_H__
+#define __MNG_STUB_H__
+#include "usb_stub.h"
+
+int mng_stub_init(struct usb_interface *intf, void *cookie, u8 len);
+int mng_get_version_string(struct usb_stub *stub, char *buf);
+int mng_set_dfu_mode(struct usb_stub *stub);
+int mng_stub_link(struct usb_interface *intf, struct usb_stub *mng_stub);
+int mng_reset(struct usb_stub *stub);
+#endif
diff --git a/drivers/usb/intel_ulpss/protocol_intel_ulpss.h b/drivers/usb/intel_ulpss/protocol_intel_ulpss.h
new file mode 100644
index 000000000000..50a5e24d9f8f
--- /dev/null
+++ b/drivers/usb/intel_ulpss/protocol_intel_ulpss.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __PROTOCOL_INTEL_ULPSS_H__
+#define __PROTOCOL_INTEL_ULPSS_H__
+
+#include <linux/types.h>
+
+/*
+* Define FW Communication protocol
+*/
+#define MAX_GPIO_NUM 20
+#define MAX_GPIO_BANK_NUM 5
+
+#define MAX_I2C_CONTROLLER_NUM 2
+
+/* command types */
+#define MNG_CMD_TYPE 1
+#define DIAG_CMD_TYPE 2
+#define GPIO_CMD_TYPE 3
+#define I2C_CMD_TYPE 4
+#define SPI_CMD_TYPE 5
+
+/* command Flags */
+#define ACK_FLAG BIT(0)
+#define RESP_FLAG BIT(1)
+#define CMPL_FLAG BIT(2)
+
+/* MNG commands */
+#define MNG_GET_VERSION 1
+#define MNG_RESET_NOTIFY 2
+#define MNG_RESET 3
+#define MNG_ENUM_GPIO 4
+#define MNG_ENUM_I2C 5
+#define MNG_POWER_STATE_CHANGE 6
+#define MNG_SET_DFU_MODE 7
+
+/* DIAG commands */
+#define DIAG_GET_STATE 0x01
+#define DIAG_GET_STATISTIC 0x02
+#define DIAG_SET_TRACE_LEVEL 0x03
+#define DIAG_SET_ECHO_MODE 0x04
+#define DIAG_GET_FW_LOG 0x05
+#define DIAG_GET_FW_COREDUMP 0x06
+#define DIAG_TRIGGER_WDT 0x07
+#define DIAG_TRIGGER_FAULT 0x08
+#define DIAG_FEED_WDT 0x09
+#define DIAG_GET_SECURE_STATE 0x0A
+
+/* GPIO commands */
+#define GPIO_CONFIG 1
+#define GPIO_READ 2
+#define GPIO_WRITE 3
+#define GPIO_INTR_NOTIFY 4
+
+/* I2C commands */
+#define I2C_INIT 1
+#define I2C_XFER 2
+#define I2C_START 3
+#define I2C_STOP 4
+#define I2C_READ 5
+#define I2C_WRITE 6
+
+#define GPIO_CONF_DISABLE BIT(0)
+#define GPIO_CONF_INPUT BIT(1)
+#define GPIO_CONF_OUTPUT BIT(2)
+#define GPIO_CONF_PULLUP BIT(3)
+#define GPIO_CONF_PULLDOWN BIT(4)
+
+/* Intentional overlap with PULLUP / PULLDOWN */
+#define GPIO_CONF_SET BIT(3)
+#define GPIO_CONF_CLR BIT(4)
+
+struct cmd_header {
+	u8 type;
+	u8 cmd;
+	u8 flags;
+	u8 len;
+	u8 data[];
+} __attribute__((packed));
+
+struct fw_version {
+	u8 major;
+	u8 minor;
+	u16 patch;
+	u16 build;
+} __attribute__((packed));
+
+struct bank_descriptor {
+	u8 bank_id;
+	u8 pin_num;
+
+	/* 1 bit for each gpio, 1 means valid */
+	u32 bitmap;
+} __attribute__((packed));
+
+struct gpio_descriptor {
+	u8 pins_per_bank;
+	u8 banks;
+	struct bank_descriptor bank_table[MAX_GPIO_BANK_NUM];
+} __attribute__((packed));
+
+struct i2c_controller_info {
+	u8 id;
+	u8 capacity;
+	u8 intr_pin;
+} __attribute__((packed));
+
+struct i2c_descriptor {
+	u8 num;
+	struct i2c_controller_info info[MAX_I2C_CONTROLLER_NUM];
+} __attribute__((packed));
+
+struct gpio_op {
+	u8 index;
+	u8 value;
+} __attribute__((packed));
+
+struct gpio_packet {
+	u8 num;
+	struct gpio_op item[0];
+} __attribute__((packed));
+
+/* I2C Transfer */
+struct i2c_xfer {
+	u8 id;
+	u8 slave;
+	u16 flag; /* speed, 8/16bit addr, addr increase, etc */
+	u16 addr;
+	u16 len;
+	u8 data[0];
+} __attribute__((packed));
+
+/* I2C raw commands: Init/Start/Read/Write/Stop */
+struct i2c_raw_io {
+	u8 id;
+	s16 len;
+	u8 data[0];
+} __attribute__((packed));
+
+#define MAX_PACKET_SIZE 64
+#define MAX_PAYLOAD_SIZE (MAX_PACKET_SIZE - sizeof(struct cmd_header))
+
+#define USB_WRITE_TIMEOUT 20
+#define USB_WRITE_ACK_TIMEOUT 100
+
+#define DEFAULT_GPIO_CONTROLLER_ID 1
+#define DEFAULT_GPIO_PIN_COUNT_PER_BANK 32
+
+#define DEFAULT_I2C_CONTROLLER_ID 1
+#define DEFAULT_I2C_CAPACITY 0
+#define DEFAULT_I2C_INTR_PIN 0
+
+/* I2C r/w Flags */
+#define I2C_SLAVE_TRANSFER_WRITE (0)
+#define I2C_SLAVE_TRANSFER_READ (1)
+
+/* i2c init flags */
+#define I2C_INIT_FLAG_MODE_MASK (1 << 0)
+#define I2C_INIT_FLAG_MODE_POLLING (0 << 0)
+#define I2C_INIT_FLAG_MODE_INTERRUPT (1 << 0)
+
+#define I2C_FLAG_ADDR_16BIT (1 << 0)
+#define I2C_INIT_FLAG_FREQ_MASK (3 << 1)
+#define I2C_FLAG_FREQ_100K (0 << 1)
+#define I2C_FLAG_FREQ_400K (1 << 1)
+#define I2C_FLAG_FREQ_1M (2 << 1)
+
+#endif
diff --git a/drivers/usb/intel_ulpss/ulpss_bridge.c b/drivers/usb/intel_ulpss/ulpss_bridge.c
new file mode 100644
index 000000000000..55a29f2edad2
--- /dev/null
+++ b/drivers/usb/intel_ulpss/ulpss_bridge.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#include "diag_stub.h"
+#include "i2c_stub.h"
+#include "mng_stub.h"
+#include "ulpss_bridge.h"
+
+/* Define these values to match your devices */
+#define USB_BRIDGE_VENDOR_ID 0x8086
+#define USB_BRIDGE_PRODUCT_ID 0x0b63
+
+/* table of devices that work with this driver */
+static const struct usb_device_id intel_ulpss_bridge_table[] = {
+	{ USB_DEVICE(USB_BRIDGE_VENDOR_ID, USB_BRIDGE_PRODUCT_ID) },
+	{} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, intel_ulpss_bridge_table);
+
+static void intel_ulpss_bridge_read_cb(struct urb *urb)
+{
+	struct usb_bridge *intel_ulpss_dev;
+	struct bridge_msg msg;
+	unsigned long flags;
+	bool need_sched;
+	int ret;
+
+	intel_ulpss_dev = urb->context;
+	BUG_ON(!intel_ulpss_dev);
+	dev_dbg(&intel_ulpss_dev->intf->dev,
+		"%s bulk read urb got message from fw, status:%d data_len:%d\n",
+		__func__, urb->status, urb->actual_length);
+
+	if (urb->status || intel_ulpss_dev->errors) {
+		/* sync/async unlink faults aren't errors */
+		if (urb->status == -ENOENT || urb->status == -ECONNRESET ||
+		    urb->status == -ESHUTDOWN) {
+			dev_dbg(&intel_ulpss_dev->intf->dev,
+				"%s read bulk urb unlink: %d %d\n", __func__,
+				urb->status, intel_ulpss_dev->errors);
+			return;
+		} else {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"%s read bulk urb transfer failed: %d %d\n",
+				__func__, urb->status, intel_ulpss_dev->errors);
+			goto resubmmit;
+		}
+	}
+
+	msg.len = urb->actual_length;
+	memcpy(msg.buf, intel_ulpss_dev->bulk_in_buffer, urb->actual_length);
+
+	spin_lock_irqsave(&intel_ulpss_dev->msg_fifo_spinlock, flags);
+	need_sched = kfifo_is_empty(&intel_ulpss_dev->msg_fifo);
+
+	if (kfifo_put(&intel_ulpss_dev->msg_fifo, msg)) {
+		if (need_sched)
+			schedule_work(&intel_ulpss_dev->event_work);
+	} else {
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s put msg faild full:%d\n", __func__,
+			kfifo_is_full(&intel_ulpss_dev->msg_fifo));
+	}
+
+	spin_unlock_irqrestore(&intel_ulpss_dev->msg_fifo_spinlock, flags);
+
+resubmmit:
+	/* resubmmit urb to receive fw msg */
+	ret = usb_submit_urb(intel_ulpss_dev->bulk_in_urb, GFP_KERNEL);
+	if (ret) {
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s failed submitting read urb, error %d\n", __func__,
+			ret);
+	}
+}
+
+static int intel_ulpss_bridge_read_start(struct usb_bridge *intel_ulpss_dev)
+{
+	int ret;
+
+	/* prepare a read */
+	usb_fill_bulk_urb(
+		intel_ulpss_dev->bulk_in_urb, intel_ulpss_dev->udev,
+		usb_rcvbulkpipe(intel_ulpss_dev->udev,
+				intel_ulpss_dev->bulk_in_endpointAddr),
+		intel_ulpss_dev->bulk_in_buffer, intel_ulpss_dev->bulk_in_size,
+		intel_ulpss_bridge_read_cb, intel_ulpss_dev);
+
+	/* submit read urb */
+	ret = usb_submit_urb(intel_ulpss_dev->bulk_in_urb, GFP_KERNEL);
+	if (ret) {
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s - failed submitting read urb, error %d\n", __func__,
+			ret);
+	}
+	return ret;
+}
+
+static void intel_ulpss_bridge_write_cb(struct urb *urb)
+{
+	struct usb_bridge *intel_ulpss_dev;
+
+	intel_ulpss_dev = urb->context;
+
+	if (!intel_ulpss_dev)
+		return;
+
+	if (urb->status) {
+		/* sync/async unlink faults aren't errors */
+		if (urb->status == -ENOENT || urb->status == -ECONNRESET ||
+		    urb->status == -ESHUTDOWN) {
+			dev_warn(&intel_ulpss_dev->intf->dev,
+				 "%s write bulk urb unlink: %d\n", __func__,
+				 urb->status);
+		} else {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"%s write bulk urb transfer failed: %d\n",
+				__func__, urb->status);
+
+			intel_ulpss_dev->errors = urb->status;
+		}
+	}
+
+	/* free up our allocated buffer */
+	usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+			  urb->transfer_buffer, urb->transfer_dma);
+
+	dev_dbg(&intel_ulpss_dev->intf->dev, "%s write callback out\n",
+		__func__);
+}
+
+ssize_t intel_ulpss_bridge_write(struct usb_interface *intf, void *data,
+				 size_t len, unsigned int timeout)
+{
+	struct urb *urb = NULL;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+	char *buf = NULL;
+	int time;
+	int ret;
+
+	if (!len || len > MAX_PACKET_SIZE) {
+		dev_err(&intf->dev, "%s write len not correct len:%ld\n",
+			__func__, len);
+		return -EINVAL;
+	}
+
+	mutex_lock(&intel_ulpss_dev->write_mutex);
+	usb_autopm_get_interface(intf);
+
+	if (intel_ulpss_dev->errors) {
+		dev_err(&intf->dev, "%s dev error %d\n", __func__,
+			intel_ulpss_dev->errors);
+		intel_ulpss_dev->errors = 0;
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* create a urb, and a buffer for it, and copy the data to the urb */
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	buf = usb_alloc_coherent(intel_ulpss_dev->udev, len, GFP_KERNEL,
+				 &urb->transfer_dma);
+
+	if (!buf) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	memcpy(buf, data, len);
+
+	if (intel_ulpss_dev->disconnected) { /* disconnect() was called */
+		ret = -ENODEV;
+		goto error;
+	}
+
+	/* initialize the urb properly */
+	usb_fill_bulk_urb(
+		urb, intel_ulpss_dev->udev,
+		usb_sndbulkpipe(intel_ulpss_dev->udev,
+				intel_ulpss_dev->bulk_out_endpointAddr),
+		buf, len, intel_ulpss_bridge_write_cb, intel_ulpss_dev);
+	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	usb_anchor_urb(urb, &intel_ulpss_dev->write_submitted);
+	/* send the data out the bulk port */
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret) {
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s - failed submitting write urb, error %d\n",
+			__func__, ret);
+		goto error_unanchor;
+	}
+
+	/*
+	 * release our reference to this urb, the USB core will eventually free
+	 * it entirely
+	 */
+	usb_free_urb(urb);
+
+	time = usb_wait_anchor_empty_timeout(&intel_ulpss_dev->write_submitted,
+					     timeout);
+	if (!time) {
+		usb_kill_anchored_urbs(&intel_ulpss_dev->write_submitted);
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s waiting out urb sending timeout, error %d %d\n",
+			__func__, time, timeout);
+	}
+
+	usb_autopm_put_interface(intf);
+	mutex_unlock(&intel_ulpss_dev->write_mutex);
+	return len;
+
+error_unanchor:
+	usb_unanchor_urb(urb);
+error:
+	if (urb) {
+		/* free up our allocated buffer */
+		usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+				  urb->transfer_buffer, urb->transfer_dma);
+		usb_free_urb(urb);
+	}
+
+	usb_autopm_put_interface(intf);
+	mutex_unlock(&intel_ulpss_dev->write_mutex);
+	return ret;
+}
+
+static void intel_ulpss_bridge_delete(struct usb_bridge *intel_ulpss_dev)
+{
+	usb_free_urb(intel_ulpss_dev->bulk_in_urb);
+	usb_put_intf(intel_ulpss_dev->intf);
+	usb_put_dev(intel_ulpss_dev->udev);
+	kfree(intel_ulpss_dev->bulk_in_buffer);
+	kfree(intel_ulpss_dev);
+}
+
+static int intel_ulpss_bridge_init(struct usb_bridge *intel_ulpss_dev)
+{
+	mutex_init(&intel_ulpss_dev->write_mutex);
+	init_usb_anchor(&intel_ulpss_dev->write_submitted);
+	init_waitqueue_head(&intel_ulpss_dev->bulk_out_ack);
+	INIT_LIST_HEAD(&intel_ulpss_dev->stubs_list);
+	INIT_KFIFO(intel_ulpss_dev->msg_fifo);
+	spin_lock_init(&intel_ulpss_dev->msg_fifo_spinlock);
+
+	intel_ulpss_dev->state = USB_BRIDGE_INITED;
+
+	return 0;
+}
+
+static ssize_t cmd_store(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct usb_stub *mng_stub = usb_stub_find(intf, MNG_CMD_TYPE);
+	struct usb_stub *diag_stub = usb_stub_find(intf, DIAG_CMD_TYPE);
+	int ret;
+
+	dev_dbg(dev, "%s:%u %s\n", __func__, __LINE__, buf);
+	if (sysfs_streq(buf, "dfu")) {
+		ret = mng_set_dfu_mode(mng_stub);
+	} else if (sysfs_streq(buf, "reset")) {
+		ret = mng_reset(mng_stub);
+	} else if (sysfs_streq(buf, "debug")) {
+		ret = diag_set_trace_level(diag_stub, 3);
+	}
+
+	return count;
+}
+
+static ssize_t cmd_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	dev_dbg(dev, "%s:%u \n", __func__, __LINE__);
+
+	return sprintf(buf, "%s\n", "supported cmd: [dfu, reset, debug]");
+}
+static DEVICE_ATTR_RW(cmd);
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct usb_stub *mng_stub = usb_stub_find(intf, MNG_CMD_TYPE);
+
+	dev_dbg(dev, "%s:%u\n", __func__, __LINE__);
+	return mng_get_version_string(mng_stub, buf);
+}
+static DEVICE_ATTR_RO(version);
+
+static ssize_t log_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	int ret;
+	ssize_t len;
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct usb_stub *diag_stub = usb_stub_find(intf, DIAG_CMD_TYPE);
+
+	ret = diag_get_fw_log(diag_stub, buf, &len);
+	dev_dbg(dev, "%s:%u len %ld\n", __func__, __LINE__, len);
+
+	if (ret)
+		return ret;
+	else
+		return len;
+}
+static DEVICE_ATTR_RO(log);
+
+static ssize_t coredump_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	int ret;
+	ssize_t len;
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct usb_stub *diag_stub = usb_stub_find(intf, DIAG_CMD_TYPE);
+
+	ret = diag_get_coredump(diag_stub, buf, &len);
+	dev_dbg(dev, "%s:%u len %ld\n", __func__, __LINE__, len);
+
+	if (ret)
+		return ret;
+	else
+		return len;
+}
+static DEVICE_ATTR_RO(coredump);
+
+static struct attribute *intel_ulpss_bridge_attrs[] = {
+	&dev_attr_version.attr,
+	&dev_attr_cmd.attr,
+	&dev_attr_log.attr,
+	&dev_attr_coredump.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(intel_ulpss_bridge);
+
+static int intel_ulpss_bridge_probe(struct usb_interface *intf,
+				    const struct usb_device_id *id)
+{
+	struct usb_bridge *intel_ulpss_dev;
+	struct usb_endpoint_descriptor *bulk_in, *bulk_out;
+	struct usb_stub *stub;
+	int ret;
+
+	/* allocate memory for our device state and initialize it */
+	intel_ulpss_dev = kzalloc(sizeof(*intel_ulpss_dev), GFP_KERNEL);
+	if (!intel_ulpss_dev)
+		return -ENOMEM;
+
+	intel_ulpss_bridge_init(intel_ulpss_dev);
+	intel_ulpss_dev->udev = usb_get_dev(interface_to_usbdev(intf));
+	intel_ulpss_dev->intf = usb_get_intf(intf);
+
+	/* set up the endpoint information */
+	/* use only the first bulk-in and bulk-out endpoints */
+	ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in,
+					&bulk_out, NULL, NULL);
+	if (ret) {
+		dev_err(&intf->dev,
+			"Could not find both bulk-in and bulk-out endpoints\n");
+		goto error;
+	}
+
+	intel_ulpss_dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
+	intel_ulpss_dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
+	intel_ulpss_dev->bulk_in_buffer =
+		kzalloc(intel_ulpss_dev->bulk_in_size, GFP_KERNEL);
+	if (!intel_ulpss_dev->bulk_in_buffer) {
+		ret = -ENOMEM;
+		goto error;
+	}
+	intel_ulpss_dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!intel_ulpss_dev->bulk_in_urb) {
+		ret = -ENOMEM;
+		goto error;
+	}
+	intel_ulpss_dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
+
+	dev_dbg(&intf->dev, "bulk_in size:%ld addr:%d bulk_out addr:%d\n",
+		intel_ulpss_dev->bulk_in_size,
+		intel_ulpss_dev->bulk_in_endpointAddr,
+		intel_ulpss_dev->bulk_out_endpointAddr);
+
+	/* save our data pointer in this intf device */
+	usb_set_intfdata(intf, intel_ulpss_dev);
+
+	ret = intel_ulpss_bridge_read_start(intel_ulpss_dev);
+	if (ret) {
+		dev_err(&intf->dev, "%s bridge read start failed ret %d\n",
+			__func__, ret);
+		goto error;
+	}
+
+	ret = usb_stub_init(intf);
+	if (ret) {
+		dev_err(&intf->dev, "%s usb stub init failed ret %d\n",
+			__func__, ret);
+		usb_set_intfdata(intf, NULL);
+		goto error;
+	}
+
+	ret = mng_stub_init(intf, NULL, 0);
+	if (ret) {
+		dev_err(&intf->dev, "%s register mng stub failed ret %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = diag_stub_init(intf, NULL, 0);
+	if (ret) {
+		dev_err(&intf->dev, "%s register diag stub failed ret %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	stub = usb_stub_find(intf, MNG_CMD_TYPE);
+	if (!stub) {
+		ret = -EINVAL;
+		return ret;
+	}
+
+	ret = mng_stub_link(intf, stub);
+	if (intel_ulpss_dev->state != USB_BRIDGE_STARTED) {
+		dev_err(&intf->dev, "%s mng stub link done ret:%d state:%d\n",
+			__func__, ret, intel_ulpss_dev->state);
+		return ret;
+	}
+
+	usb_enable_autosuspend(intel_ulpss_dev->udev);
+	dev_info(&intf->dev, "intel_ulpss USB bridge device init success\n");
+	return 0;
+
+error:
+	/* this frees allocated memory */
+	intel_ulpss_bridge_delete(intel_ulpss_dev);
+
+	return ret;
+}
+
+static void intel_ulpss_bridge_disconnect(struct usb_interface *intf)
+{
+	struct usb_bridge *intel_ulpss_dev;
+
+	intel_ulpss_dev = usb_get_intfdata(intf);
+	intel_ulpss_dev->disconnected = 1;
+
+	usb_kill_urb(intel_ulpss_dev->bulk_in_urb);
+	usb_kill_anchored_urbs(&intel_ulpss_dev->write_submitted);
+
+	usb_stub_cleanup(intf);
+	intel_ulpss_dev->state = USB_BRIDGE_STOPPED;
+
+	cancel_work_sync(&intel_ulpss_dev->event_work);
+
+	usb_set_intfdata(intf, NULL);
+	/* decrement our usage len */
+	intel_ulpss_bridge_delete(intel_ulpss_dev);
+
+	dev_dbg(&intf->dev, "USB bridge now disconnected\n");
+}
+
+static void intel_ulpss_bridge_draw_down(struct usb_bridge *intel_ulpss_dev)
+{
+	int time;
+
+	time = usb_wait_anchor_empty_timeout(&intel_ulpss_dev->write_submitted,
+					     1000);
+	if (!time)
+		usb_kill_anchored_urbs(&intel_ulpss_dev->write_submitted);
+	usb_kill_urb(intel_ulpss_dev->bulk_in_urb);
+}
+
+static int intel_ulpss_bridge_suspend(struct usb_interface *intf,
+				      pm_message_t message)
+{
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+	dev_dbg(&intf->dev, "USB bridge now suspend\n");
+
+	intel_ulpss_bridge_draw_down(intel_ulpss_dev);
+	return 0;
+}
+
+static int intel_ulpss_bridge_resume(struct usb_interface *intf)
+{
+	int ret;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+	dev_dbg(&intf->dev, "USB bridge now resume\n");
+
+	ret = intel_ulpss_bridge_read_start(intel_ulpss_dev);
+	if (ret) {
+		dev_err(&intf->dev, "%s bridge read start failed ret %d\n",
+			__func__, ret);
+	}
+	return ret;
+}
+static struct usb_driver bridge_driver = {
+	.name = "intel_ulpss",
+	.probe = intel_ulpss_bridge_probe,
+	.disconnect = intel_ulpss_bridge_disconnect,
+	.suspend = intel_ulpss_bridge_suspend,
+	.resume = intel_ulpss_bridge_resume,
+	.id_table = intel_ulpss_bridge_table,
+	.dev_groups = intel_ulpss_bridge_groups,
+	.supports_autosuspend = 1,
+};
+
+module_usb_driver(bridge_driver);
+
+MODULE_AUTHOR("Ye Xiang <xiang.ye at intel.com>");
+MODULE_AUTHOR("Zhang Lixu <lixu.zhang at intel.com>");
+MODULE_DESCRIPTION("Intel LPSS USB driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/intel_ulpss/ulpss_bridge.h b/drivers/usb/intel_ulpss/ulpss_bridge.h
new file mode 100644
index 000000000000..bcdf15e79f3c
--- /dev/null
+++ b/drivers/usb/intel_ulpss/ulpss_bridge.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __ULPSS_BRIDGE_H__
+#define __ULPSS_BRIDGE_H__
+
+#include <linux/kfifo.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include "usb_stub.h"
+
+struct bridge_msg {
+	size_t len;
+	u8 buf[MAX_PACKET_SIZE];
+	unsigned long read_time;
+};
+
+enum USB_BRIDGE_STATE {
+	USB_BRIDGE_STOPPED = 0,
+	USB_BRIDGE_INITED,
+	USB_BRIDGE_START_SYNCING,
+	USB_BRIDGE_START_DISPATCH_STARTED,
+	USB_BRIDGE_START_READ_STARTED,
+	USB_BRIDGE_RESET_HANDSHAKE,
+	USB_BRIDGE_RESET_SYNCED,
+	USB_BRIDGE_ENUM_GPIO_PENDING,
+	USB_BRIDGE_ENUM_GPIO_COMPLETE,
+	USB_BRIDGE_ENUM_I2C_PENDING,
+	USB_BRIDGE_ENUM_I2C_COMPLETE,
+	USB_BRIDGE_STARTED,
+	USB_BRIDGE_FAILED,
+};
+
+struct usb_bridge {
+	struct usb_device *udev;
+	struct usb_interface *intf;
+	u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
+	u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
+	/* in case we need to retract our submissions */
+	struct usb_anchor write_submitted;
+
+	/* the urb to read data with */
+	struct urb *bulk_in_urb;
+	/* the buffer to receive data */
+	unsigned char *bulk_in_buffer;
+	size_t bulk_in_size;
+
+	/* bridge status */
+	int errors; /* the last request tanked */
+	unsigned int disconnected;
+	int state;
+
+	struct mutex write_mutex;
+
+	/* stub */
+	size_t stub_count;
+	struct list_head stubs_list;
+
+	/* buffers to store bridge msg temporary */
+	DECLARE_KFIFO(msg_fifo, struct bridge_msg, 16);
+	spinlock_t msg_fifo_spinlock;
+
+	/* dispatch message from fw */
+	struct work_struct event_work;
+
+	/* to wait for an ongoing write ack */
+	wait_queue_head_t bulk_out_ack;
+};
+
+ssize_t intel_ulpss_bridge_write(struct usb_interface *intf, void *data, size_t len,
+		     unsigned int timeout);
+
+#endif /* __ULPSS_BRIDGE_H__ */
diff --git a/drivers/usb/intel_ulpss/usb_stub.c b/drivers/usb/intel_ulpss/usb_stub.c
new file mode 100644
index 000000000000..77fd3ef1f53a
--- /dev/null
+++ b/drivers/usb/intel_ulpss/usb_stub.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include "protocol_intel_ulpss.h"
+#include "ulpss_bridge.h"
+
+#define MAKE_CMD_ID(type, cmd) ((type << 8) | cmd)
+static bool usb_stub_validate(u8 *data, u32 data_len)
+{
+	bool is_valid = true;
+	struct cmd_header *header = (struct cmd_header *)data;
+
+	/* verify the respone flag */
+	if (header->cmd != GPIO_INTR_NOTIFY &&
+	    ((header->flags & RESP_FLAG) == 0))
+		is_valid = false;
+
+	/* verify the payload len */
+	is_valid = is_valid && (header->len + sizeof(*header) == data_len);
+
+	return is_valid;
+}
+
+static int usb_stub_parse(struct usb_stub *stub, struct cmd_header *header)
+{
+	int ret = 0;
+
+	if (!stub || !header || (header->len < 0))
+		return -EINVAL;
+
+	stub->len = header->len;
+
+	if (header->len == 0)
+		return 0;
+
+	memcpy(stub->buf, header->data, header->len);
+	if (stub->parse)
+		ret = stub->parse(stub, header->cmd, header->flags,
+				  header->data, header->len);
+
+	return ret;
+}
+
+/*
+ * Bottom half processing work function (instead of thread handler)
+ * for processing fw messages
+ */
+static void event_work_cb(struct work_struct *work)
+{
+	struct usb_bridge *intel_ulpss_dev;
+	struct bridge_msg msg_in_proc = { 0 };
+	struct usb_stub *stub;
+	struct cmd_header *header;
+	int rcv_cmd_id;
+	int ret;
+
+	intel_ulpss_dev = container_of(work, struct usb_bridge, event_work);
+	BUG_ON(!intel_ulpss_dev);
+
+	while (kfifo_get(&intel_ulpss_dev->msg_fifo, &msg_in_proc)) {
+		if (!msg_in_proc.len)
+			continue;
+
+		header = (struct cmd_header *)msg_in_proc.buf;
+
+		dev_dbg(&intel_ulpss_dev->intf->dev,
+			"receive: type:%d cmd:%d flags:%d len:%d\n",
+			header->type, header->cmd, header->flags, header->len);
+
+		/* verify the data */
+		if (!usb_stub_validate(msg_in_proc.buf, msg_in_proc.len)) {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"%s header->len:%d payload_len:%ld\n ",
+				__func__, header->len, msg_in_proc.len);
+			continue;
+		}
+
+		stub = usb_stub_find(intel_ulpss_dev->intf, header->type);
+		ret = usb_stub_parse(stub, header);
+		if (ret) {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"%s failed to parse data: ret:%d type:%d len: %d",
+				__func__, ret, header->type, header->len);
+			continue;
+		}
+
+		rcv_cmd_id = MAKE_CMD_ID(stub->type, header->cmd);
+		if (rcv_cmd_id == stub->cmd_id) {
+			stub->acked = true;
+			wake_up_interruptible(&intel_ulpss_dev->bulk_out_ack);
+
+		} else {
+			dev_warn(&intel_ulpss_dev->intf->dev,
+				 "%s rcv_cmd_id:%x != stub->cmd_id:%x",
+				 __func__, rcv_cmd_id, stub->cmd_id);
+		}
+	}
+}
+
+struct usb_stub *usb_stub_alloc(struct usb_interface *intf)
+{
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+	struct usb_stub *cur_stub;
+
+	cur_stub = kzalloc(sizeof(struct usb_stub), GFP_KERNEL);
+	if (!cur_stub) {
+		dev_err(&intf->dev, "%s no memory for new stub", __func__);
+		return NULL;
+	}
+
+	mutex_init(&cur_stub->stub_mutex);
+	INIT_LIST_HEAD(&cur_stub->list);
+
+	list_add_tail(&cur_stub->list, &intel_ulpss_dev->stubs_list);
+	intel_ulpss_dev->stub_count++;
+	dev_dbg(&intf->dev,
+		"%s enuming stub intel_ulpss_dev->stub_count:%ld type:%d success\n",
+		__func__, intel_ulpss_dev->stub_count, cur_stub->type);
+
+	return cur_stub;
+}
+
+int usb_stub_init(struct usb_interface *intf)
+{
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+
+	if (!intel_ulpss_dev)
+		return -EINVAL;
+
+	INIT_WORK(&intel_ulpss_dev->event_work, event_work_cb);
+
+	return 0;
+}
+
+struct usb_stub *usb_stub_find(struct usb_interface *intf, u8 type)
+{
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+	struct usb_stub *cur_stub;
+
+	if (!intel_ulpss_dev)
+		return NULL;
+
+	list_for_each_entry (cur_stub, &intel_ulpss_dev->stubs_list, list) {
+		if (cur_stub->type == type)
+			return cur_stub;
+	}
+
+	dev_err(&intf->dev, "%s usb stub not find, type: %d", __func__, type);
+	return NULL;
+}
+
+int usb_stub_write(struct usb_stub *stub, u8 cmd, u8 *data, u8 len,
+		   bool wait_ack, long timeout)
+{
+	int ret;
+	u8 flags = 0;
+	u8 buff[MAX_PACKET_SIZE] = { 0 };
+
+	struct cmd_header *header;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(stub->intf);
+
+	if (!stub || !intel_ulpss_dev)
+		return -EINVAL;
+
+	header = (struct cmd_header *)buff;
+	if (len >= MAX_PAYLOAD_SIZE)
+		return -ENOMEM;
+
+	if (wait_ack)
+		flags |= ACK_FLAG;
+
+	flags |= CMPL_FLAG;
+
+	header->type = stub->type;
+	header->cmd = cmd;
+	header->flags = flags;
+	header->len = len;
+
+	memcpy(header->data, data, len);
+	dev_dbg(&intel_ulpss_dev->intf->dev,
+		"send: type:%d cmd:%d flags:%d len:%d\n", header->type,
+		header->cmd, header->flags, header->len);
+
+	stub->cmd_id = MAKE_CMD_ID(stub->type, cmd);
+
+	ret = intel_ulpss_bridge_write(
+		stub->intf, header, sizeof(struct cmd_header) + len, timeout);
+
+	if (ret != sizeof(struct cmd_header) + len) {
+		dev_err(&intel_ulpss_dev->intf->dev,
+			"%s bridge write failed ret:%d total_len:%ld\n ",
+			__func__, ret, sizeof(struct cmd_header) + len);
+		return -EIO;
+	}
+
+	if (flags & ACK_FLAG) {
+		ret = wait_event_interruptible_timeout(
+			intel_ulpss_dev->bulk_out_ack, (stub->acked), timeout);
+		stub->acked = false;
+
+		if (ret < 0) {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"acked wait interrupted ret:%d timeout:%ld ack:%d\n",
+				ret, timeout, stub->acked);
+			return ret;
+
+		} else if (ret == 0) {
+			dev_err(&intel_ulpss_dev->intf->dev,
+				"acked sem wait timed out ret:%d timeout:%ld ack:%d\n",
+				ret, timeout, stub->acked);
+			return -ETIMEDOUT;
+		}
+	}
+
+	return 0;
+}
+
+void usb_stub_broadcast(struct usb_interface *intf, long event,
+			void *event_data)
+{
+	int ret = 0;
+	struct usb_stub *cur_stub = NULL;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+
+	if (!intel_ulpss_dev)
+		return;
+
+	list_for_each_entry (cur_stub, &intel_ulpss_dev->stubs_list, list) {
+		if (cur_stub && cur_stub->notify) {
+			ret = cur_stub->notify(cur_stub, event, event_data);
+			if (ret)
+				continue;
+		}
+	}
+}
+
+void usb_stub_cleanup(struct usb_interface *intf)
+{
+	struct usb_stub *cur_stub = NULL;
+	struct usb_stub *next = NULL;
+	struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
+
+	if (!intel_ulpss_dev)
+		return;
+
+	list_for_each_entry_safe (cur_stub, next, &intel_ulpss_dev->stubs_list,
+				  list) {
+		if (!cur_stub)
+			continue;
+
+		list_del_init(&cur_stub->list);
+		dev_dbg(&intf->dev, "%s type:%d\n ", __func__, cur_stub->type);
+		if (cur_stub->cleanup)
+			cur_stub->cleanup(cur_stub);
+
+		mutex_destroy(&cur_stub->stub_mutex);
+		kfree(cur_stub);
+
+		intel_ulpss_dev->stub_count--;
+	}
+}
+
+struct acpi_device *find_adev_by_hid(struct acpi_device *parent,
+				     const char *hid)
+{
+	struct acpi_device *adev;
+
+	if (!parent)
+		return NULL;
+
+	list_for_each_entry (adev, &parent->children, node) {
+		if (!strcmp(hid, acpi_device_hid(adev)))
+			return adev;
+	}
+
+	return NULL;
+}
diff --git a/drivers/usb/intel_ulpss/usb_stub.h b/drivers/usb/intel_ulpss/usb_stub.h
new file mode 100644
index 000000000000..9efa047079ca
--- /dev/null
+++ b/drivers/usb/intel_ulpss/usb_stub.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel LPSS USB driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __USB_STUB_H__
+#define __USB_STUB_H__
+
+#include <linux/acpi.h>
+#include <linux/types.h>
+
+#include "protocol_intel_ulpss.h"
+
+#define GPIO_INTR_EVENT 2
+
+struct usb_stub {
+	struct list_head list;
+	u8 type;
+	struct usb_interface *intf;
+
+	struct mutex stub_mutex;
+	u8 buf[MAX_PAYLOAD_SIZE];
+	u32 len;
+
+	bool acked;
+	/** for identify ack */
+	int cmd_id;
+
+	int (*parse)(struct usb_stub *stub, u8 cmd, u8 flags, u8 *data,
+		     u32 len);
+	int (*notify)(struct usb_stub *stub, long event, void *evt_data);
+	void (*cleanup)(struct usb_stub *stub);
+	void *priv;
+};
+
+int usb_stub_init(struct usb_interface *intf);
+struct usb_stub *usb_stub_find(struct usb_interface *intf, u8 type);
+int usb_stub_write(struct usb_stub *stub, u8 cmd, u8 *data, u8 len,
+		   bool wait_ack, long timeout);
+void usb_stub_broadcast(struct usb_interface *intf, long event,
+			void *event_data);
+void usb_stub_cleanup(struct usb_interface *intf);
+struct usb_stub *usb_stub_alloc(struct usb_interface *intf);
+
+struct acpi_device *find_adev_by_hid(struct acpi_device *parent,
+				     const char *hid);
+#endif
-- 
2.31.1




More information about the kernel-team mailing list