summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 000000000000..d83b50b26f7d
--- /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 882673b4c9e9..297f7b1a16a1 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 9cc9d31e6468..d265da7797fd 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 000000000000..eee8fe1bedfe
--- /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 000000000000..bca23aa3bfec
--- /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 000000000000..789d824a8102
--- /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 000000000000..be9ec35230e9
--- /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
+};