[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