summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncan Laurie <dlaurie@google.com>2020-08-28 19:46:35 +0000
committerPatrick Georgi <pgeorgi@google.com>2020-10-19 06:51:40 +0000
commit2cc126be2c3018b09f893e3426c76ad68325c4e8 (patch)
treeb5add00f2dcc5290fa10205129ad2aa7f9d32324
parentb22dc1daf43f935254787daf16fb72755e2a2526 (diff)
drivers/intel/usb4: Add driver for USB4 retimer device
The USB4 retimer device needs to declare a _DSM with specific functions that allow for GPIO control to turn off the power when an external device is not connected. This driver allows the mainboard to provide the GPIO that is connected to the power control. BUG=b:156957424 Change-Id: Icfb85dc3c0885d828aba3855a66109043250ab86 Signed-off-by: Duncan Laurie <dlaurie@google.com> Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org> Reviewed-on: https://review.coreboot.org/c/coreboot/+/44918 Reviewed-by: Angel Pons <th3fanbus@gmail.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
-rw-r--r--Documentation/drivers/retimer.md40
-rw-r--r--Makefile.inc2
-rw-r--r--src/Kconfig1
-rw-r--r--src/drivers/intel/usb4/retimer/Kconfig8
-rw-r--r--src/drivers/intel/usb4/retimer/Makefile.inc1
-rw-r--r--src/drivers/intel/usb4/retimer/chip.h13
-rw-r--r--src/drivers/intel/usb4/retimer/retimer.c136
7 files changed, 200 insertions, 1 deletions
diff --git a/Documentation/drivers/retimer.md b/Documentation/drivers/retimer.md
new file mode 100644
index 0000000000..d83b50b26f
--- /dev/null
+++ b/Documentation/drivers/retimer.md
@@ -0,0 +1,40 @@
+# USB4 Retimers
+
+# Introduction
+As USB speeds continue to increase (up to 5G, 10G, and even 20G or higher in
+newer revisions of the spec), it becomes more difficult to maintain signal
+integrity for longer traces. Devices such as retimers and redrivers can be used
+to help signals maintain their integrity over long distances.
+
+A redriver is a device that boosts the high-frequency content of a signal in
+order to compensate for the attenuation typically caused by travelling through
+various circuit components (PCB, connectors, CPU, etc.). Redrivers are not
+protocol-aware, which makes them relatively simple. However, their effectiveness
+is limited, and may not work at all in some scenarios.
+
+A retimer is a device that retransmits a fresh copy of the signal it receives,
+by doing CDR and retransmitting the data (i.e., it is protocol-aware). Since
+this is a digital component, it may have firmware.
+
+
+# Driver Usage
+
+Some operating systems may have the ability to update firmware on USB4 retimers,
+and ultimately will need some way to power the device on and off so that its new
+firmware can be loaded. This is achieved by providing a GPIO signal that can be
+used for this purpose; its active state must be the one in which power is
+applied to the retimer. This driver will generate the required ACPI AML code
+which will toggle the GPIO in response to the kernel's request (through the
+`_DSM` ACPI method). Simply put something like the following in your devicetree:
+
+```
+device pci 0.0 on
+ chip drivers/intel/usb4/retimer
+ register "power_gpio" = "ACPI_GPIO_OUTPUT_ACTIVE_HIGH(GPP_A0)"
+ device generic 0 on end
+ end
+end
+```
+
+replacing the GPIO with the appropriate pin and polarity.
+
diff --git a/Makefile.inc b/Makefile.inc
index 882673b4c9..297f7b1a16 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -80,7 +80,7 @@ subdirs-y := src/lib src/commonlib/ src/console src/device src/acpi
subdirs-y += src/ec/acpi $(wildcard src/ec/*/*) $(wildcard src/southbridge/*/*)
subdirs-y += $(wildcard src/soc/*/*) $(wildcard src/northbridge/*/*)
subdirs-y += src/superio
-subdirs-y += $(wildcard src/drivers/*) $(wildcard src/drivers/*/*)
+subdirs-y += $(wildcard src/drivers/*) $(wildcard src/drivers/*/*) $(wildcard src/drivers/*/*/*)
subdirs-y += src/cpu src/vendorcode
subdirs-y += util/cbfstool util/sconfig util/nvramtool util/pgtblgen util/amdfwtool
subdirs-y += util/futility util/marvell util/bincfg util/supermicro
diff --git a/src/Kconfig b/src/Kconfig
index 9cc9d31e64..d265da7797 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -554,6 +554,7 @@ source "src/device/Kconfig"
menu "Generic Drivers"
source "src/drivers/*/Kconfig"
source "src/drivers/*/*/Kconfig"
+source "src/drivers/*/*/*/Kconfig"
source "src/commonlib/storage/Kconfig"
endmenu
diff --git a/src/drivers/intel/usb4/retimer/Kconfig b/src/drivers/intel/usb4/retimer/Kconfig
new file mode 100644
index 0000000000..eee8fe1bed
--- /dev/null
+++ b/src/drivers/intel/usb4/retimer/Kconfig
@@ -0,0 +1,8 @@
+config DRIVERS_INTEL_USB4_RETIMER
+ bool
+ depends on HAVE_ACPI_TABLES
+ help
+ A retimer is a device that retransmits a fresh copy of the signal it
+ receives, by doing CDR and retransmitting the data (i.e., it is
+ protocol-aware). If your mainboard has a USB4 retimer (usually
+ located close to the USB4 ports), then select this driver.
diff --git a/src/drivers/intel/usb4/retimer/Makefile.inc b/src/drivers/intel/usb4/retimer/Makefile.inc
new file mode 100644
index 0000000000..bca23aa3bf
--- /dev/null
+++ b/src/drivers/intel/usb4/retimer/Makefile.inc
@@ -0,0 +1 @@
+ramstage-$(CONFIG_DRIVERS_INTEL_USB4_RETIMER) += retimer.c
diff --git a/src/drivers/intel/usb4/retimer/chip.h b/src/drivers/intel/usb4/retimer/chip.h
new file mode 100644
index 0000000000..789d824a81
--- /dev/null
+++ b/src/drivers/intel/usb4/retimer/chip.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __DRIVERS_INTEL_USB4_RETIMER_H__
+#define __DRIVERS_INTEL_USB4_RETIMER_H__
+
+#include <acpi/acpi_device.h>
+
+struct drivers_intel_usb4_retimer_config {
+ /* GPIO used to control power of retimer device. */
+ struct acpi_gpio power_gpio;
+};
+
+#endif /* __DRIVERS_INTEL_USB4_RETIMER_H__ */
diff --git a/src/drivers/intel/usb4/retimer/retimer.c b/src/drivers/intel/usb4/retimer/retimer.c
new file mode 100644
index 0000000000..be9ec35230
--- /dev/null
+++ b/src/drivers/intel/usb4/retimer/retimer.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <acpi/acpigen.h>
+#include <acpi/acpi_device.h>
+#include <console/console.h>
+#include <device/device.h>
+#include <device/path.h>
+#include <gpio.h>
+#include <string.h>
+#include "chip.h"
+
+/* Unique ID for the retimer _DSM. */
+#define INTEL_USB4_RETIMER_DSM_UUID "61788900-C470-42BB-80F0-23A313864593"
+
+/*
+ * Arg0: UUID
+ * Arg1: Revision ID (set to 1)
+ * Arg2: Function Index
+ * 0: Query command implemented
+ * 1: Query force power enable state
+ * 2: Set force power state
+ * Arg3: A package containing parameters for the function specified
+ * by the UUID, revision ID and function index.
+ */
+
+static void usb4_retimer_cb_standard_query(void *arg)
+{
+ /*
+ * ToInteger (Arg1, Local2)
+ * If (Local2 == 1) {
+ * Return(Buffer() {0x07})
+ * }
+ * Return (Buffer() {0x01})
+ */
+ acpigen_write_to_integer(ARG1_OP, LOCAL2_OP);
+
+ /* Revision 1 supports 2 Functions beyond the standard query */
+ acpigen_write_if_lequal_op_int(LOCAL2_OP, 1);
+ acpigen_write_return_singleton_buffer(0x07);
+ acpigen_pop_len(); /* If */
+
+ /* Other revisions support no additional functions */
+ acpigen_write_return_singleton_buffer(0);
+}
+
+static void usb4_retimer_cb_get_power_state(void *arg)
+{
+ struct acpi_gpio *power_gpio = arg;
+
+ /*
+ * // Read power gpio into Local0
+ * Store (\_SB.PCI0.GTXS (power_gpio), Local0)
+ * Return (Local0)
+ */
+ acpigen_get_tx_gpio(power_gpio);
+ acpigen_write_return_op(LOCAL0_OP);
+}
+
+static void usb4_retimer_cb_set_power_state(void *arg)
+{
+ struct acpi_gpio *power_gpio = arg;
+
+ /*
+ * // Get argument for on/off from Arg3[0]
+ * Local0 = DeRefOf (Arg3[0])
+ */
+ acpigen_get_package_op_element(ARG3_OP, 0, LOCAL0_OP);
+
+ /*
+ * If (Local0 == 0) {
+ * // Turn power off
+ * \_SB.PCI0.CTXS (power_gpio)
+ * }
+ */
+ acpigen_write_if_lequal_op_int(LOCAL0_OP, 0);
+ acpigen_disable_tx_gpio(power_gpio);
+ acpigen_pop_len(); /* If */
+
+ /*
+ * Else {
+ * // Turn power on
+ * \_SB.PCI0.STXS (power_gpio)
+ * }
+ */
+ acpigen_write_else();
+ acpigen_enable_tx_gpio(power_gpio);
+ acpigen_pop_len();
+
+ /* Return (Zero) */
+ acpigen_write_return_integer(0);
+}
+
+static void (*usb4_retimer_callbacks[3])(void *) = {
+ usb4_retimer_cb_standard_query, /* Function 0 */
+ usb4_retimer_cb_get_power_state, /* Function 1 */
+ usb4_retimer_cb_set_power_state, /* Function 2 */
+};
+
+static void usb4_retimer_fill_ssdt(const struct device *dev)
+{
+ const struct drivers_intel_usb4_retimer_config *config = dev->chip_info;
+ const char *scope = acpi_device_scope(dev);
+
+ if (!dev->enabled || !scope || !config)
+ return;
+
+ if (!config->power_gpio.pin_count) {
+ printk(BIOS_ERR, "%s: Power GPIO required for %s\n", __func__, dev_path(dev));
+ return;
+ }
+
+ /* Write the _DSM that toggles power with provided GPIO. */
+ acpigen_write_scope(scope);
+ acpigen_write_dsm(INTEL_USB4_RETIMER_DSM_UUID, usb4_retimer_callbacks,
+ ARRAY_SIZE(usb4_retimer_callbacks), (void *)&config->power_gpio);
+ acpigen_pop_len(); /* Scope */
+
+ printk(BIOS_INFO, "%s: %s at %s\n", acpi_device_path(dev), dev->chip_ops->name,
+ dev_path(dev));
+}
+
+static struct device_operations usb4_retimer_dev_ops = {
+ .read_resources = noop_read_resources,
+ .set_resources = noop_set_resources,
+ .acpi_fill_ssdt = usb4_retimer_fill_ssdt,
+};
+
+static void usb4_retimer_enable(struct device *dev)
+{
+ dev->ops = &usb4_retimer_dev_ops;
+}
+
+struct chip_operations drivers_intel_usb4_retimer_ops = {
+ CHIP_NAME("Intel USB4 Retimer")
+ .enable_dev = usb4_retimer_enable
+};