From patchwork Thu Jul 29 06:48:22 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: You-Sheng Yang X-Patchwork-Id: 1511084 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.ubuntu.com (client-ip=91.189.94.19; helo=huckleberry.canonical.com; envelope-from=kernel-team-bounces@lists.ubuntu.com; receiver=) Received: from huckleberry.canonical.com (huckleberry.canonical.com [91.189.94.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Gb1PG4mMMz9sWd; Thu, 29 Jul 2021 16:50:10 +1000 (AEST) Received: from localhost ([127.0.0.1] helo=huckleberry.canonical.com) by huckleberry.canonical.com with esmtp (Exim 4.86_2) (envelope-from ) id 1m8zrr-00076I-8y; Thu, 29 Jul 2021 06:50:07 +0000 Received: from mail-pj1-f42.google.com ([209.85.216.42]) by huckleberry.canonical.com with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1m8zqa-0006xq-LM for kernel-team@lists.ubuntu.com; Thu, 29 Jul 2021 06:48:49 +0000 Received: by mail-pj1-f42.google.com with SMTP id k4-20020a17090a5144b02901731c776526so14062342pjm.4 for ; Wed, 28 Jul 2021 23:48:48 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=qWkTFsGkH7tKMtwHf5Wvo7/xHYgSYHwPY8GayuEfDDA=; b=ZYe6A+tJz92BKVlLewpfvQhTLCFP8GHdRTjCm6p/68AlIHRkQcnXmvpGA1RE48d9vD KTyEfNHfcae5Cdi2ihgRrEDUvr3J/H8UquCFBtGpabLpSSc2bZkXo9K2wMpUg51kkFCx GNIgM+aAEnsIuIJzrSDyOHfdxOa7il0766lCQpuQv6ll6uvotgP7y2dkCzXrjEyUYX2V g2CVYuQ/eM7Cb9z/Cl576t8YleXr+FJ610Ryvhi5LAIOVGDXlpSxNQ6w9+y6fARpZAKV K3kYW89NNMWHwn/ACgw8gGdm/HLJFG3//YEt3QyHmWnkKTi2Rrd7TRSkis4OcLJgoiFu FNeg== X-Gm-Message-State: AOAM532x3EO46E1VfbnaweLpToIdJkGPdktiTRa7DCa8KO1Eq1R7orzh dT0T3vOnyj4FeMZGyh/ZfFUveBupIZ4RJg== X-Google-Smtp-Source: ABdhPJz8JfFLXICnVyCucNDdbljq+rnGyI5i8vM9dA2/5MClnD1+YAyXPfaVLsVivc7rjPdPEcPZaA== X-Received: by 2002:a17:902:6ac9:b029:12c:3bac:8d78 with SMTP id i9-20020a1709026ac9b029012c3bac8d78mr3251441plt.34.1627541325893; Wed, 28 Jul 2021 23:48:45 -0700 (PDT) Received: from localhost (61-220-137-37.HINET-IP.hinet.net. [61.220.137.37]) by smtp.gmail.com with ESMTPSA id z21sm8160681pjh.19.2021.07.28.23.48.45 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Jul 2021 23:48:45 -0700 (PDT) From: You-Sheng Yang To: kernel-team@lists.ubuntu.com Subject: [PATCH 03/13][SRU][OEM-5.13] UBUNTU: SAUCE: IPU driver release WW48 with MCU Date: Thu, 29 Jul 2021 14:48:22 +0800 Message-Id: <20210729064832.25656-4-vicamo.yang@canonical.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210729064832.25656-1-vicamo.yang@canonical.com> References: <20210729064832.25656-1-vicamo.yang@canonical.com> MIME-Version: 1.0 Received-SPF: pass client-ip=209.85.216.42; envelope-from=vicamo@gmail.com; helo=mail-pj1-f42.google.com X-BeenThere: kernel-team@lists.ubuntu.com X-Mailman-Version: 2.1.20 Precedence: list List-Id: Kernel team discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: kernel-team-bounces@lists.ubuntu.com Sender: "kernel-team" From: Wang Yating BugLink: https://bugs.launchpad.net/bugs/1921345 Signed-off-by: Wang Yating (cherry picked from https://github.com/intel/ipu6-drivers/commit/d127576fe1f1ea9a138618d88ce694b7ddb650f8) Signed-off-by: You-Sheng Yang --- 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 + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 + +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 + +/* +* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_AUTHOR("Zhang Lixu "); +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 +#include +#include +#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 +#include +#include + +#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 +#include + +#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