diff options
-rw-r--r-- | src/drivers/intel/usb4/retimer/chip.h | 13 | ||||
-rw-r--r-- | src/drivers/intel/usb4/retimer/retimer.c | 402 | ||||
-rw-r--r-- | src/drivers/intel/usb4/retimer/retimer.h | 27 |
3 files changed, 368 insertions, 74 deletions
diff --git a/src/drivers/intel/usb4/retimer/chip.h b/src/drivers/intel/usb4/retimer/chip.h index 789d824a81..5b1c76f53c 100644 --- a/src/drivers/intel/usb4/retimer/chip.h +++ b/src/drivers/intel/usb4/retimer/chip.h @@ -4,10 +4,19 @@ #define __DRIVERS_INTEL_USB4_RETIMER_H__ #include <acpi/acpi_device.h> +#include <acpi/acpi.h> +#include <acpi/acpi_pld.h> + +#define DFP_NUM_MAX 2 struct drivers_intel_usb4_retimer_config { - /* GPIO used to control power of retimer device. */ - struct acpi_gpio power_gpio; + /* Downstream facing port(DFP) */ + struct { + /* GPIO used to control power of retimer device */ + struct acpi_gpio power_gpio; + /* _PLD setting */ + struct acpi_pld_group group; + } dfp[DFP_NUM_MAX]; }; #endif /* __DRIVERS_INTEL_USB4_RETIMER_H__ */ diff --git a/src/drivers/intel/usb4/retimer/retimer.c b/src/drivers/intel/usb4/retimer/retimer.c index fad353bebe..e84d29b6c6 100644 --- a/src/drivers/intel/usb4/retimer/retimer.c +++ b/src/drivers/intel/usb4/retimer/retimer.c @@ -2,141 +2,401 @@ #include <acpi/acpigen.h> #include <acpi/acpi_device.h> +#include <acpi/acpi_pld.h> #include <console/console.h> #include <device/device.h> #include <device/path.h> #include <gpio.h> - +#include <string.h> #include "chip.h" #include "retimer.h" /* Unique ID for the retimer _DSM. */ -#define INTEL_USB4_RETIMER_DSM_UUID "61788900-C470-42BB-80F0-23A313864593" +#define INTEL_USB4_RETIMER_DSM_UUID "E0053122-795B-4122-8A5E-57BE1D26ACB3" + +static const char *usb4_retimer_scope; +static const char *usb4_retimer_path_arg(const char *arg) +{ + /* \\_SB.PCI0.TDMx.ARG */ + static char name[DEVICE_PATH_MAX]; + snprintf(name, sizeof(name), "%s%c%s", usb4_retimer_scope, '.', arg); + return name; +} + +/* Each polling cycle takes up to 25 ms with a total of 12 of these iterations */ +#define USB4_RETIMER_ITERATION_NUM 12 +#define USB4_RETIMER_POLL_CYCLE_MS 25 +static void usb4_retimer_execute_ec_cmd(uint8_t port, uint8_t cmd, uint8_t expected_value) +{ + const char *RFWU = ec_retimer_fw_update_path(); + const uint8_t data = cmd << USB_RETIMER_FW_UPDATE_OP_SHIFT | port; + + /* Invoke EC Retimer firmware update command execution */ + ec_retimer_fw_update(data); + /* If RFWU has return value 0xfe, return error -1 */ + acpigen_write_if_lequal_namestr_int(RFWU, USB_RETIMER_FW_UPDATE_ERROR); + acpigen_write_return_integer(-1); + acpigen_pop_len(); /* If */ + + acpigen_write_store_int_to_op(USB4_RETIMER_ITERATION_NUM, LOCAL2_OP); + acpigen_emit_byte(WHILE_OP); + acpigen_write_len_f(); + acpigen_emit_byte(LGREATER_OP); + acpigen_emit_byte(LOCAL2_OP); + acpigen_emit_byte(ZERO_OP); + acpigen_write_if_lequal_namestr_int(RFWU, expected_value); + acpigen_emit_byte(BREAK_OP); + acpigen_pop_len(); /* If */ + + if (cmd == USB_RETIMER_FW_UPDATE_SET_TBT) { + /* + * EC return either USB_PD_MUX_USB4_ENABLED or USB_PD_MUX_TBT_COMPAT_ENABLED + * to RFWU after the USB_RETIMER_FW_UPDATE_SET_TBT command execution. It is + * needed to add additional check for USB_PD_MUX_TBT_COMPAT_ENABLED. + */ + acpigen_write_if_lequal_namestr_int(RFWU, USB_PD_MUX_TBT_COMPAT_ENABLED); + acpigen_emit_byte(BREAK_OP); + acpigen_pop_len(); /* If */ + } + + acpigen_write_sleep(USB4_RETIMER_POLL_CYCLE_MS); + acpigen_emit_byte(DECREMENT_OP); + acpigen_emit_byte(LOCAL2_OP); + acpigen_pop_len(); /* While */ + + /* + * Check whether there is timeout error + * Return: -1 if timeout error occurring + */ + acpigen_write_if_lequal_op_int(LOCAL2_OP, 0); + acpigen_write_return_integer(-1); + acpigen_pop_len(); /* If */ +} + +static void enable_retimer_online_state(uint8_t port, struct acpi_gpio *power_gpio) +{ + uint8_t expected_value; + + /* + * Enable_retimer_online_state under NDA + * 1. Force power on + * 2. Check if there is a device connected + * 3. Suspend PD + * 4. Set Mux to USB mode + * 5. Set Mux to Safe mode + * 6. Set Mux to TBT mode + */ + + /* Force power on for the retimer on the port */ + acpigen_enable_tx_gpio(power_gpio); + + /* + * Get MUX mode state + * Return -1 if there is a device connected on the port. + * Otherwise proceed Retimer firmware upgrade operation. + */ + expected_value = USB_PD_MUX_NONE; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_GET_MUX, expected_value); + + /* + * Suspend PD + * Command: USB_RETIMER_FW_UPDATE_SUSPEND_PD + * Expect return value: 0 + */ + expected_value = 0; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_SUSPEND_PD, expected_value); + + /* + * Set MUX USB Mode + * Command: USB_RETIMER_FW_UPDATE_SUSPEND_PD + * Expect return value: USB_PD_MUX_USB_ENABLED + */ + expected_value = USB_PD_MUX_USB_ENABLED; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_SET_USB, expected_value); + + /* + * Set MUX Safe Mode + * Command: USB_RETIMER_FW_UPDATE_SET_SAFE + * Expect return value: USB_PD_MUX_SAFE_MODE + */ + expected_value = USB_PD_MUX_SAFE_MODE; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_SET_SAFE, expected_value); + + /* + * Set MUX TBT Mode + * Command: USB_RETIMER_FW_UPDATE_SET_TBT + * Expect return value: USB_PD_MUX_USB4_ENABLED or USB_PD_MUX_TBT_COMPAT_ENABLED + */ + expected_value = USB_PD_MUX_USB4_ENABLED; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_SET_TBT, expected_value); +} + +static void disable_retimer_online_state(uint8_t port, struct acpi_gpio *power_gpio) +{ + uint8_t expected_value; + + /* + * Disable_retimer_online_state + * 1. Set Mux to disconnect mode + * 2. Resume PD + * 3. Force power off + */ + + /* + * Set MUX Disconnect Mode + * Command: USB_RETIMER_FW_UPDATE_DISCONNECT + * Expect return value: 0 + */ + expected_value = 0; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_DISCONNECT, expected_value); + + /* + * Resume PD + * Command: USB_RETIMER_FW_UPDATE_RESUME_PD + * Expect return value: 1 + */ + expected_value = 1; + usb4_retimer_execute_ec_cmd(port, USB_RETIMER_FW_UPDATE_RESUME_PD, expected_value); + + /* Force power off */ + acpigen_disable_tx_gpio(power_gpio); +} /* - * Arg0: UUID + * Arg0: UUID e0053122-795b-4122-8a5e-57be1d26acb3 * Arg1: Revision ID (set to 1) * Arg2: Function Index * 0: Query command implemented - * 1: Query force power enable state - * 2: Set force power state - * 3: Get Retimer FW Update EC Ram value - * 4: Set Retimer FW Update EC Ram value + * 1: Get power state + * 2: Set power state * Arg3: A package containing parameters for the function specified - * by the UUID, revision ID and function index. + * by the UUID, revision ID, function index and port index. */ - -static void usb4_retimer_cb_standard_query(void *arg) +static void usb4_retimer_cb_standard_query(uint8_t port, void *arg) { /* - * ToInteger (Arg1, Local2) - * If (Local2 == 1) { - * Return(Buffer() {0x1f}) + * ToInteger (Arg1, Local1) + * If (Local1 == 1) { + * Return(Buffer() {0x7}) * } * Return (Buffer() {0x01}) */ - acpigen_write_to_integer(ARG1_OP, LOCAL2_OP); + acpigen_write_to_integer(ARG1_OP, LOCAL1_OP); - /* Revision 1 supports 4 Functions beyond the standard query */ - acpigen_write_if_lequal_op_int(LOCAL2_OP, 1); - acpigen_write_return_singleton_buffer(0x1f); + /* Revision 1 supports 2 Functions beyond the standard query */ + acpigen_write_if_lequal_op_int(LOCAL1_OP, 1); + acpigen_write_return_singleton_buffer(0x7); acpigen_pop_len(); /* If */ /* Other revisions support no additional functions */ - acpigen_write_return_singleton_buffer(0); + acpigen_write_return_singleton_buffer(0x1); } -static void usb4_retimer_cb_get_power_state(void *arg) +static void usb4_retimer_cb_get_power_state(uint8_t port, void *arg) { - struct acpi_gpio *power_gpio = arg; + const char *PWR; + char pwr[DEVICE_PATH_MAX]; + + snprintf(pwr, sizeof(pwr), "HR.DFP%1d.PWR", port); + PWR = usb4_retimer_path_arg(pwr); + + /* + * If (PWR > 0) { + * Return (1) + * } + */ + acpigen_write_if(); + acpigen_emit_byte(LGREATER_OP); + acpigen_emit_namestring(PWR); + acpigen_emit_byte(0); + acpigen_write_return_integer(1); /* - * Read power gpio into Local0 - * Store (\_SB.PCI0.GTXS (power_gpio), Local0) - * Return (Local0) + * Else { + * Return (0) + * } */ - acpigen_get_tx_gpio(power_gpio); - acpigen_write_return_op(LOCAL0_OP); + acpigen_write_else(); + acpigen_write_return_integer(0); + acpigen_pop_len(); } -static void usb4_retimer_cb_set_power_state(void *arg) +static void usb4_retimer_cb_set_power_state(uint8_t port, void *arg) { struct acpi_gpio *power_gpio = arg; + const char *PWR; + char pwr[DEVICE_PATH_MAX]; + + snprintf(pwr, sizeof(pwr), "HR.DFP%1d.PWR", port); + PWR = usb4_retimer_path_arg(pwr); /* - * Get information to set to retimer info from Arg3[0] - * Local0 = DeRefOf (Arg3[0]) + * Get information to set retimer power state from Arg3[0] + * Local1 = DeRefOf (Arg3[0]) */ - acpigen_get_package_op_element(ARG3_OP, 0, LOCAL0_OP); + acpigen_get_package_op_element(ARG3_OP, 0, LOCAL1_OP); /* - * If (Local0 == 0) { - * // Turn power off - * \_SB.PCI0.CTXS (power_gpio) + * If ((Local1 == 0) && (PWR > 0)) { + * PWR-- + * If (PWR == 0) { + * // Disable retimer online state + * } * } */ - acpigen_write_if_lequal_op_int(LOCAL0_OP, 0); - acpigen_disable_tx_gpio(power_gpio); + acpigen_write_if(); + acpigen_emit_byte(LAND_OP); + acpigen_emit_byte(LEQUAL_OP); + acpigen_emit_byte(LOCAL1_OP); + acpigen_emit_byte(0); + acpigen_emit_byte(LGREATER_OP); + acpigen_emit_namestring(PWR); + acpigen_emit_byte(0); + /* PWR-- */ + acpigen_emit_byte(DECREMENT_OP); + acpigen_emit_namestring(PWR); + acpigen_write_if_lequal_namestr_int(PWR, 0); /* If (PWR == 0) */ + disable_retimer_online_state(port, power_gpio); + acpigen_pop_len(); /* If (PWR == 0) */ /* - * Else { - * // Turn power on - * \_SB.PCI0.STXS (power_gpio) + * Else If ((Local1 == 1) && (PWR == 0)) { + * // Enable retimer online state + * PWR++ * } */ acpigen_write_else(); - acpigen_enable_tx_gpio(power_gpio); - acpigen_pop_len(); + acpigen_write_if(); + acpigen_emit_byte(LAND_OP); + acpigen_emit_byte(LEQUAL_OP); + acpigen_emit_byte(LOCAL1_OP); + acpigen_emit_byte(1); + acpigen_emit_byte(LEQUAL_OP); + acpigen_emit_namestring(PWR); + acpigen_emit_byte(0); + enable_retimer_online_state(port, power_gpio); + /* PWR++ */ + acpigen_emit_byte(INCREMENT_OP); + acpigen_emit_namestring(PWR); - /* Return (Zero) */ + /* + * Else { + * Return (0) + * } + */ + acpigen_write_else(); acpigen_write_return_integer(0); -} - -static void usb4_retimer_cb_get_retimer_info(void *arg) -{ - const char *RFWU = ec_retimer_fw_update_path(); + acpigen_pop_len(); /* Else */ + acpigen_pop_len(); /* If */ /* - * Read Mux Retimer info from EC RAM - * Return RFWU if RFWU is not NULL. Otherwise return -1 to - * inform kernel about error. + * If (PWR == 1) { + * Return (1) + * } */ - if (!RFWU) - acpigen_write_return_byte(-1); - else - acpigen_write_return_namestr(RFWU); -} + acpigen_write_if_lequal_namestr_int(PWR, 1); + acpigen_write_return_integer(1); -static void usb4_retimer_cb_set_retimer_info(void *arg) -{ - ec_retimer_fw_update(arg); + /* + * Else { + * Return (0) + * } + */ + acpigen_write_else(); + acpigen_write_return_integer(0); + acpigen_pop_len(); } -static void (*usb4_retimer_callbacks[5])(void *) = { +static void (*usb4_retimer_callbacks[3])(uint8_t port, void *) = { usb4_retimer_cb_standard_query, /* Function 0 */ usb4_retimer_cb_get_power_state, /* Function 1 */ usb4_retimer_cb_set_power_state, /* Function 2 */ - usb4_retimer_cb_get_retimer_info, /* Function 3 */ - usb4_retimer_cb_set_retimer_info, /* Function 4 */ }; +static void usb4_retimer_write_dsm(uint8_t port, const char *uuid, + void (**callbacks)(uint8_t port, void *), size_t count, void *arg) +{ + struct usb4_retimer_dsm_uuid id = DSM_UUID(uuid, callbacks, count, arg); + size_t i; + + acpigen_write_to_integer(ARG2_OP, LOCAL0_OP); + + for (i = 0; i < id.count; i++) { + /* If (LEqual (Local0, i)) */ + acpigen_write_if_lequal_op_int(LOCAL0_OP, i); + + /* Callback to write if handler. */ + if (id.callbacks[i]) + id.callbacks[i](port, id.arg); + + acpigen_pop_len(); /* If */ + } +} + 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); + struct drivers_intel_usb4_retimer_config *config = dev->chip_info; + static char dfp[DEVICE_PATH_MAX]; + struct acpi_pld pld; + uint8_t port; - if (!scope || !config) + usb4_retimer_scope = acpi_device_scope(dev); + if (!usb4_retimer_scope || !config) return; - if (!config->power_gpio.pin_count) { - printk(BIOS_ERR, "%s: Power GPIO required for %s\n", __func__, dev_path(dev)); - return; - } + /* Scope */ + acpigen_write_scope(usb4_retimer_scope); + + /* Host router */ + acpigen_write_device("HR"); + acpigen_write_ADR(0); + acpigen_write_STA(ACPI_STATUS_DEVICE_ALL_ON); - /* 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); + for (port = 0; port < DFP_NUM_MAX; port++) { + if (!config->dfp[port].power_gpio.pin_count) { + printk(BIOS_ERR, "%s: No DFP%1d power GPIO for %s\n", __func__, + port, dev_path(dev)); + continue; + } + + /* DFPx */ + snprintf(dfp, sizeof(dfp), "DFP%1d", port); + acpigen_write_device(dfp); + /* _ADR part is for the lane adapter */ + acpigen_write_ADR(port*2 + 1); + + /* Fill _PLD with the same USB 3.x object on the Type-C connector */ + acpi_pld_fill_usb(&pld, UPC_TYPE_PROPRIETARY, &config->dfp[port].group); + pld.shape = PLD_SHAPE_OVAL; + pld.visible = 1; + acpigen_write_pld(&pld); + + /* Power online reference counter(_PWR) */ + acpigen_write_name("PWR"); + acpigen_write_zero(); + + /* Method (_DSM, 4, Serialized) */ + acpigen_write_method_serialized("_DSM", 0x4); + /* ToBuffer (Arg0, Local0) */ + acpigen_write_to_buffer(ARG0_OP, LOCAL0_OP); + acpigen_write_if(); /* If (UUID != INTEL_USB4_RETIMER_DSM_UUID) */ + acpigen_emit_byte(LNOT_OP); + acpigen_emit_byte(LEQUAL_OP); + acpigen_emit_byte(LOCAL0_OP); + acpigen_write_uuid(INTEL_USB4_RETIMER_DSM_UUID); + /* Return (Buffer (One) { 0x0 }) */ + acpigen_write_return_singleton_buffer(0x0); + acpigen_pop_len(); + usb4_retimer_write_dsm(port, INTEL_USB4_RETIMER_DSM_UUID, + usb4_retimer_callbacks, ARRAY_SIZE(usb4_retimer_callbacks), + (void *)&config->dfp[port].power_gpio); + /* Default case: Return (Buffer (One) { 0x0 }) */ + acpigen_write_return_singleton_buffer(0x0); + + acpigen_pop_len(); /* Method _DSM */ + acpigen_pop_len(); /* DFP */ + } + acpigen_pop_len(); /* Host Router */ acpigen_pop_len(); /* Scope */ printk(BIOS_INFO, "%s: %s at %s\n", acpi_device_path(dev), dev->chip_ops->name, @@ -164,6 +424,6 @@ __weak const char *ec_retimer_fw_update_path(void) return NULL; } -__weak void ec_retimer_fw_update(void *arg) +__weak void ec_retimer_fw_update(uint8_t data) { } diff --git a/src/drivers/intel/usb4/retimer/retimer.h b/src/drivers/intel/usb4/retimer/retimer.h index 4c4044a2fe..5a040a0d47 100644 --- a/src/drivers/intel/usb4/retimer/retimer.h +++ b/src/drivers/intel/usb4/retimer/retimer.h @@ -3,7 +3,32 @@ #ifndef _DRIVERS_INTEL_USB4_RETIMER_H_ #define _DRIVERS_INTEL_USB4_RETIMER_H_ +/* Flags representing mux state */ +#define USB_PD_MUX_NONE 0 /* Open switch */ +#define USB_PD_MUX_USB_ENABLED BIT(0) /* USB connected */ +#define USB_PD_MUX_SAFE_MODE BIT(5) /* DP is in safe mode */ +#define USB_PD_MUX_TBT_COMPAT_ENABLED BIT(6) /* TBT compat enabled */ +#define USB_PD_MUX_USB4_ENABLED BIT(7) /* USB4 enabled */ + +#define USB_RETIMER_FW_UPDATE_OP_SHIFT 4 +#define USB_RETIMER_FW_UPDATE_ERROR 0xfe +/* Retimer firmware update operations */ +#define USB_RETIMER_FW_UPDATE_SUSPEND_PD 1 /* Suspend PD port */ +#define USB_RETIMER_FW_UPDATE_RESUME_PD 2 /* Resume PD port */ +#define USB_RETIMER_FW_UPDATE_GET_MUX 3 /* Read current USB MUX */ +#define USB_RETIMER_FW_UPDATE_SET_USB 4 /* Set MUX to USB mode */ +#define USB_RETIMER_FW_UPDATE_SET_SAFE 5 /* Set MUX to Safe mode */ +#define USB_RETIMER_FW_UPDATE_SET_TBT 6 /* Set MUX to TBT mode */ +#define USB_RETIMER_FW_UPDATE_DISCONNECT 7 /* Set MUX to disconnect */ + +struct usb4_retimer_dsm_uuid { + const char *uuid; + void (**callbacks)(uint8_t port, void *); + size_t count; + void *arg; +}; + const char *ec_retimer_fw_update_path(void); -void ec_retimer_fw_update(void *arg); +void ec_retimer_fw_update(uint8_t data); #endif /* _DRIVERS_INTEL_USB4_RETIMER_H_ */ |