| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * USB4 specific functionality | |
| * | |
| * Copyright (C) 2019, Intel Corporation | |
| * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> | |
| * Rajmohan Mani <rajmohan.mani@intel.com> | |
| */ | |
| #include <linux/delay.h> | |
| #include <linux/ktime.h> | |
| #include <linux/string_choices.h> | |
| #include <linux/units.h> | |
| #include "sb_regs.h" | |
| #include "tb.h" | |
| #define USB4_DATA_RETRIES 3 | |
| #define USB4_DATA_DWORDS 16 | |
| #define USB4_NVM_READ_OFFSET_MASK GENMASK(23, 2) | |
| #define USB4_NVM_READ_OFFSET_SHIFT 2 | |
| #define USB4_NVM_READ_LENGTH_MASK GENMASK(27, 24) | |
| #define USB4_NVM_READ_LENGTH_SHIFT 24 | |
| #define USB4_NVM_SET_OFFSET_MASK USB4_NVM_READ_OFFSET_MASK | |
| #define USB4_NVM_SET_OFFSET_SHIFT USB4_NVM_READ_OFFSET_SHIFT | |
| #define USB4_DROM_ADDRESS_MASK GENMASK(14, 2) | |
| #define USB4_DROM_ADDRESS_SHIFT 2 | |
| #define USB4_DROM_SIZE_MASK GENMASK(19, 15) | |
| #define USB4_DROM_SIZE_SHIFT 15 | |
| #define USB4_NVM_SECTOR_SIZE_MASK GENMASK(23, 0) | |
| #define USB4_BA_LENGTH_MASK GENMASK(7, 0) | |
| #define USB4_BA_INDEX_MASK GENMASK(15, 0) | |
| enum usb4_ba_index { | |
| USB4_BA_MAX_USB3 = 0x1, | |
| USB4_BA_MIN_DP_AUX = 0x2, | |
| USB4_BA_MIN_DP_MAIN = 0x3, | |
| USB4_BA_MAX_PCIE = 0x4, | |
| USB4_BA_MAX_HI = 0x5, | |
| }; | |
| #define USB4_BA_VALUE_MASK GENMASK(31, 16) | |
| #define USB4_BA_VALUE_SHIFT 16 | |
| /* Delays in us used with usb4_port_wait_for_bit() */ | |
| #define USB4_PORT_DELAY 50 | |
| #define USB4_PORT_SB_DELAY 1000 | |
| static int usb4_native_switch_op(struct tb_switch *sw, u16 opcode, | |
| u32 *metadata, u8 *status, | |
| const void *tx_data, size_t tx_dwords, | |
| void *rx_data, size_t rx_dwords) | |
| { | |
| u32 val; | |
| int ret; | |
| if (metadata) { | |
| ret = tb_sw_write(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); | |
| if (ret) | |
| return ret; | |
| } | |
| if (tx_dwords) { | |
| ret = tb_sw_write(sw, tx_data, TB_CFG_SWITCH, ROUTER_CS_9, | |
| tx_dwords); | |
| if (ret) | |
| return ret; | |
| } | |
| val = opcode | ROUTER_CS_26_OV; | |
| ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); | |
| if (ret) | |
| return ret; | |
| ret = tb_switch_wait_for_bit(sw, ROUTER_CS_26, ROUTER_CS_26_OV, 0, 500); | |
| if (ret) | |
| return ret; | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); | |
| if (ret) | |
| return ret; | |
| if (val & ROUTER_CS_26_ONS) | |
| return -EOPNOTSUPP; | |
| if (status) | |
| *status = (val & ROUTER_CS_26_STATUS_MASK) >> | |
| ROUTER_CS_26_STATUS_SHIFT; | |
| if (metadata) { | |
| ret = tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); | |
| if (ret) | |
| return ret; | |
| } | |
| if (rx_dwords) { | |
| ret = tb_sw_read(sw, rx_data, TB_CFG_SWITCH, ROUTER_CS_9, | |
| rx_dwords); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, | |
| u8 *status, const void *tx_data, size_t tx_dwords, | |
| void *rx_data, size_t rx_dwords) | |
| { | |
| const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; | |
| if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) | |
| return -EINVAL; | |
| /* | |
| * If the connection manager implementation provides USB4 router | |
| * operation proxy callback, call it here instead of running the | |
| * operation natively. | |
| */ | |
| if (cm_ops->usb4_switch_op) { | |
| int ret; | |
| ret = cm_ops->usb4_switch_op(sw, opcode, metadata, status, | |
| tx_data, tx_dwords, rx_data, | |
| rx_dwords); | |
| if (ret != -EOPNOTSUPP) | |
| return ret; | |
| /* | |
| * If the proxy was not supported then run the native | |
| * router operation instead. | |
| */ | |
| } | |
| return usb4_native_switch_op(sw, opcode, metadata, status, tx_data, | |
| tx_dwords, rx_data, rx_dwords); | |
| } | |
| static inline int usb4_switch_op(struct tb_switch *sw, u16 opcode, | |
| u32 *metadata, u8 *status) | |
| { | |
| return __usb4_switch_op(sw, opcode, metadata, status, NULL, 0, NULL, 0); | |
| } | |
| static inline int usb4_switch_op_data(struct tb_switch *sw, u16 opcode, | |
| u32 *metadata, u8 *status, | |
| const void *tx_data, size_t tx_dwords, | |
| void *rx_data, size_t rx_dwords) | |
| { | |
| return __usb4_switch_op(sw, opcode, metadata, status, tx_data, | |
| tx_dwords, rx_data, rx_dwords); | |
| } | |
| /** | |
| * usb4_switch_check_wakes() - Check for wakes and notify PM core about them | |
| * @sw: Router whose wakes to check | |
| * | |
| * Checks wakes occurred during suspend and notify the PM core about them. | |
| */ | |
| void usb4_switch_check_wakes(struct tb_switch *sw) | |
| { | |
| bool wakeup_usb4 = false; | |
| struct usb4_port *usb4; | |
| struct tb_port *port; | |
| bool wakeup = false; | |
| u32 val; | |
| if (tb_route(sw)) { | |
| if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1)) | |
| return; | |
| tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n", | |
| str_yes_no(val & ROUTER_CS_6_WOPS), | |
| str_yes_no(val & ROUTER_CS_6_WOUS)); | |
| wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS); | |
| } | |
| /* | |
| * Check for any downstream ports for USB4 wake, | |
| * connection wake and disconnection wake. | |
| */ | |
| tb_switch_for_each_port(sw, port) { | |
| if (!port->cap_usb4) | |
| continue; | |
| if (tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_18, 1)) | |
| break; | |
| tb_port_dbg(port, "USB4 wake: %s, connection wake: %s, disconnection wake: %s\n", | |
| str_yes_no(val & PORT_CS_18_WOU4S), | |
| str_yes_no(val & PORT_CS_18_WOCS), | |
| str_yes_no(val & PORT_CS_18_WODS)); | |
| wakeup_usb4 = val & (PORT_CS_18_WOU4S | PORT_CS_18_WOCS | | |
| PORT_CS_18_WODS); | |
| usb4 = port->usb4; | |
| if (device_may_wakeup(&usb4->dev) && wakeup_usb4) | |
| pm_wakeup_event(&usb4->dev, 0); | |
| wakeup |= wakeup_usb4; | |
| } | |
| if (wakeup) | |
| pm_wakeup_event(&sw->dev, 0); | |
| } | |
| static bool link_is_usb4(struct tb_port *port) | |
| { | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return false; | |
| if (tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_18, 1)) | |
| return false; | |
| return !(val & PORT_CS_18_TCM); | |
| } | |
| /** | |
| * usb4_switch_setup() - Additional setup for USB4 device | |
| * @sw: USB4 router to setup | |
| * | |
| * USB4 routers need additional settings in order to enable all the | |
| * tunneling. This function enables USB and PCIe tunneling if it can be | |
| * enabled (e.g the parent switch also supports them). If USB tunneling | |
| * is not available for some reason (like that there is Thunderbolt 3 | |
| * switch upstream) then the internal xHCI controller is enabled | |
| * instead. | |
| * | |
| * This does not set the configuration valid bit of the router. To do | |
| * that call usb4_switch_configuration_valid(). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_setup(struct tb_switch *sw) | |
| { | |
| struct tb_switch *parent = tb_switch_parent(sw); | |
| struct tb_port *down; | |
| bool tbt3, xhci; | |
| u32 val = 0; | |
| int ret; | |
| if (!tb_route(sw)) | |
| return 0; | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1); | |
| if (ret) | |
| return ret; | |
| down = tb_switch_downstream_port(sw); | |
| sw->link_usb4 = link_is_usb4(down); | |
| tb_sw_dbg(sw, "link: %s\n", sw->link_usb4 ? "USB4" : "TBT"); | |
| xhci = val & ROUTER_CS_6_HCI; | |
| tbt3 = !(val & ROUTER_CS_6_TNS); | |
| tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n", | |
| str_yes_no(tbt3), str_yes_no(xhci)); | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| if (tb_acpi_may_tunnel_usb3() && sw->link_usb4 && | |
| tb_switch_find_port(parent, TB_TYPE_USB3_DOWN)) { | |
| val |= ROUTER_CS_5_UTO; | |
| xhci = false; | |
| } | |
| /* | |
| * Only enable PCIe tunneling if the parent router supports it | |
| * and it is not disabled. | |
| */ | |
| if (tb_acpi_may_tunnel_pcie() && | |
| tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) { | |
| val |= ROUTER_CS_5_PTO; | |
| /* | |
| * xHCI can be enabled if PCIe tunneling is supported | |
| * and the parent does not have any USB3 downstream | |
| * adapters (so we cannot do USB 3.x tunneling). | |
| */ | |
| if (xhci) | |
| val |= ROUTER_CS_5_HCO; | |
| } | |
| /* TBT3 supported by the CM */ | |
| val &= ~ROUTER_CS_5_CNS; | |
| ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_RR, | |
| ROUTER_CS_6_RR, 500); | |
| } | |
| /** | |
| * usb4_switch_configuration_valid() - Set tunneling configuration to be valid | |
| * @sw: USB4 router | |
| * | |
| * Sets configuration valid bit for the router. Must be called before | |
| * any tunnels can be set through the router and after | |
| * usb4_switch_setup() has been called. Can be called to host and device | |
| * routers (does nothing for the former). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_configuration_valid(struct tb_switch *sw) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!tb_route(sw)) | |
| return 0; | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| val |= ROUTER_CS_5_CV; | |
| ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR, | |
| ROUTER_CS_6_CR, 500); | |
| } | |
| /** | |
| * usb4_switch_read_uid() - Read UID from USB4 router | |
| * @sw: USB4 router | |
| * @uid: UID is stored here | |
| * | |
| * Reads 64-bit UID from USB4 router config space. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid) | |
| { | |
| return tb_sw_read(sw, uid, TB_CFG_SWITCH, ROUTER_CS_7, 2); | |
| } | |
| static int usb4_switch_drom_read_block(void *data, | |
| unsigned int dwaddress, void *buf, | |
| size_t dwords) | |
| { | |
| struct tb_switch *sw = data; | |
| u8 status = 0; | |
| u32 metadata; | |
| int ret; | |
| metadata = (dwords << USB4_DROM_SIZE_SHIFT) & USB4_DROM_SIZE_MASK; | |
| metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) & | |
| USB4_DROM_ADDRESS_MASK; | |
| ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_DROM_READ, &metadata, | |
| &status, NULL, 0, buf, dwords); | |
| if (ret) | |
| return ret; | |
| return status ? -EIO : 0; | |
| } | |
| /** | |
| * usb4_switch_drom_read() - Read arbitrary bytes from USB4 router DROM | |
| * @sw: USB4 router | |
| * @address: Byte address inside DROM to start reading | |
| * @buf: Buffer where the DROM content is stored | |
| * @size: Number of bytes to read from DROM | |
| * | |
| * Uses USB4 router operations to read router DROM. For devices this | |
| * should always work but for hosts it may return %-EOPNOTSUPP in which | |
| * case the host router does not have DROM. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, | |
| size_t size) | |
| { | |
| return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, | |
| usb4_switch_drom_read_block, sw); | |
| } | |
| /** | |
| * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding | |
| * @sw: USB4 router | |
| * | |
| * Checks whether conditions are met so that lane bonding can be | |
| * established with the upstream router. Call only for device routers. | |
| * | |
| * Return: %true if lane bonding is possible, %false otherwise. | |
| */ | |
| bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) | |
| { | |
| struct tb_port *up; | |
| int ret; | |
| u32 val; | |
| up = tb_upstream_port(sw); | |
| ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1); | |
| if (ret) | |
| return false; | |
| return !!(val & PORT_CS_18_BE); | |
| } | |
| /** | |
| * usb4_switch_set_wake() - Enabled/disable wake | |
| * @sw: USB4 router | |
| * @flags: Wakeup flags (%0 to disable) | |
| * @runtime: Wake is being programmed during system runtime | |
| * | |
| * Enables/disables router to wake up from sleep. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime) | |
| { | |
| struct tb_port *port; | |
| u64 route = tb_route(sw); | |
| u32 val; | |
| int ret; | |
| /* | |
| * Enable wakes coming from all USB4 downstream ports (from | |
| * child routers). For device routers do this also for the | |
| * upstream USB4 port. | |
| */ | |
| tb_switch_for_each_port(sw, port) { | |
| if (!tb_port_is_null(port)) | |
| continue; | |
| if (!route && tb_is_upstream_port(port)) | |
| continue; | |
| if (!port->cap_usb4) | |
| continue; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4); | |
| if (tb_is_upstream_port(port)) { | |
| val |= PORT_CS_19_WOU4; | |
| } else { | |
| bool configured = val & PORT_CS_19_PC; | |
| bool wakeup = runtime || device_may_wakeup(&port->usb4->dev); | |
| if ((flags & TB_WAKE_ON_CONNECT) && wakeup && !configured) | |
| val |= PORT_CS_19_WOC; | |
| if ((flags & TB_WAKE_ON_DISCONNECT) && wakeup && configured) | |
| val |= PORT_CS_19_WOD; | |
| if ((flags & TB_WAKE_ON_USB4) && configured) | |
| val |= PORT_CS_19_WOU4; | |
| } | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| } | |
| /* | |
| * Enable wakes from PCIe, USB 3.x and DP on this router. Only | |
| * needed for device routers. | |
| */ | |
| if (route) { | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU | ROUTER_CS_5_WOD); | |
| if (flags & TB_WAKE_ON_USB3) | |
| val |= ROUTER_CS_5_WOU; | |
| if (flags & TB_WAKE_ON_PCIE) | |
| val |= ROUTER_CS_5_WOP; | |
| if (flags & TB_WAKE_ON_DP) | |
| val |= ROUTER_CS_5_WOD; | |
| ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * usb4_switch_set_sleep() - Prepare the router to enter sleep | |
| * @sw: USB4 router | |
| * | |
| * Sets sleep bit for the router and waits until router sleep ready | |
| * bit has been asserted. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_set_sleep(struct tb_switch *sw) | |
| { | |
| int ret; | |
| u32 val; | |
| /* Set sleep bit and wait for sleep ready to be asserted */ | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| val |= ROUTER_CS_5_SLP; | |
| ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_SLPR, | |
| ROUTER_CS_6_SLPR, 500); | |
| } | |
| /** | |
| * usb4_switch_nvm_sector_size() - Return router NVM sector size | |
| * @sw: USB4 router | |
| * | |
| * Return: | |
| * * NVM sector size in bytes if router supports NVM operations. | |
| * * %-EOPNOTSUPP - If router does not support NVM operations. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_switch_nvm_sector_size(struct tb_switch *sw) | |
| { | |
| u32 metadata; | |
| u8 status; | |
| int ret; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &metadata, | |
| &status); | |
| if (ret) | |
| return ret; | |
| if (status) | |
| return status == 0x2 ? -EOPNOTSUPP : -EIO; | |
| return metadata & USB4_NVM_SECTOR_SIZE_MASK; | |
| } | |
| static int usb4_switch_nvm_read_block(void *data, | |
| unsigned int dwaddress, void *buf, size_t dwords) | |
| { | |
| struct tb_switch *sw = data; | |
| u8 status = 0; | |
| u32 metadata; | |
| int ret; | |
| metadata = (dwords << USB4_NVM_READ_LENGTH_SHIFT) & | |
| USB4_NVM_READ_LENGTH_MASK; | |
| metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) & | |
| USB4_NVM_READ_OFFSET_MASK; | |
| ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_READ, &metadata, | |
| &status, NULL, 0, buf, dwords); | |
| if (ret) | |
| return ret; | |
| return status ? -EIO : 0; | |
| } | |
| /** | |
| * usb4_switch_nvm_read() - Read arbitrary bytes from router NVM | |
| * @sw: USB4 router | |
| * @address: Starting address in bytes | |
| * @buf: Read data is placed here | |
| * @size: How many bytes to read | |
| * | |
| * Reads NVM contents of the router. | |
| * | |
| * Return: | |
| * * %0 - Read completed successfully. | |
| * * %-EOPNOTSUPP - NVM not supported. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, | |
| size_t size) | |
| { | |
| return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, | |
| usb4_switch_nvm_read_block, sw); | |
| } | |
| /** | |
| * usb4_switch_nvm_set_offset() - Set NVM write offset | |
| * @sw: USB4 router | |
| * @address: Start offset | |
| * | |
| * Explicitly sets NVM write offset. Normally when writing to NVM this | |
| * is done automatically by usb4_switch_nvm_write(). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address) | |
| { | |
| u32 metadata, dwaddress; | |
| u8 status = 0; | |
| int ret; | |
| dwaddress = address / 4; | |
| metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) & | |
| USB4_NVM_SET_OFFSET_MASK; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &metadata, | |
| &status); | |
| if (ret) | |
| return ret; | |
| return status ? -EIO : 0; | |
| } | |
| static int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress, | |
| const void *buf, size_t dwords) | |
| { | |
| struct tb_switch *sw = data; | |
| u8 status; | |
| int ret; | |
| ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_WRITE, NULL, &status, | |
| buf, dwords, NULL, 0); | |
| if (ret) | |
| return ret; | |
| return status ? -EIO : 0; | |
| } | |
| /** | |
| * usb4_switch_nvm_write() - Write to the router NVM | |
| * @sw: USB4 router | |
| * @address: Start address where to write in bytes | |
| * @buf: Pointer to the data to write | |
| * @size: Size of @buf in bytes | |
| * | |
| * Writes @buf to the router NVM using USB4 router operations. | |
| * | |
| * Return: | |
| * * %0 - Write completed successfully. | |
| * * %-EOPNOTSUPP - NVM write not supported. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, | |
| const void *buf, size_t size) | |
| { | |
| int ret; | |
| ret = usb4_switch_nvm_set_offset(sw, address); | |
| if (ret) | |
| return ret; | |
| return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES, | |
| usb4_switch_nvm_write_next_block, sw); | |
| } | |
| /** | |
| * usb4_switch_nvm_authenticate() - Authenticate new NVM | |
| * @sw: USB4 router | |
| * | |
| * After the new NVM has been written via usb4_switch_nvm_write(), this | |
| * function triggers NVM authentication process. The router gets power | |
| * cycled and if the authentication is successful the new NVM starts | |
| * running. | |
| * | |
| * The caller should call usb4_switch_nvm_authenticate_status() to read | |
| * the status of the authentication after power cycle. It should be the | |
| * first router operation to avoid the status being lost. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_nvm_authenticate(struct tb_switch *sw) | |
| { | |
| int ret; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL, NULL); | |
| switch (ret) { | |
| /* | |
| * The router is power cycled once NVM_AUTH is started so it is | |
| * expected to get any of the following errors back. | |
| */ | |
| case -EACCES: | |
| case -ENOTCONN: | |
| case -ETIMEDOUT: | |
| return 0; | |
| default: | |
| return ret; | |
| } | |
| } | |
| /** | |
| * usb4_switch_nvm_authenticate_status() - Read status of last NVM authenticate | |
| * @sw: USB4 router | |
| * @status: Status code of the operation | |
| * | |
| * The function checks if there is status available from the last NVM | |
| * authenticate router operation. | |
| * | |
| * Must be called before any other router operation. | |
| * | |
| * Return: | |
| * * %0 - If there is status. Status code is placed in @status. | |
| * * Negative errno - Failure occurred. | |
| */ | |
| int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) | |
| { | |
| const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; | |
| u16 opcode; | |
| u32 val; | |
| int ret; | |
| if (cm_ops->usb4_switch_nvm_authenticate_status) { | |
| ret = cm_ops->usb4_switch_nvm_authenticate_status(sw, status); | |
| if (ret != -EOPNOTSUPP) | |
| return ret; | |
| } | |
| ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); | |
| if (ret) | |
| return ret; | |
| /* Check that the opcode is correct */ | |
| opcode = val & ROUTER_CS_26_OPCODE_MASK; | |
| if (opcode == USB4_SWITCH_OP_NVM_AUTH) { | |
| if (val & ROUTER_CS_26_OV) | |
| return -EBUSY; | |
| if (val & ROUTER_CS_26_ONS) | |
| return -EOPNOTSUPP; | |
| *status = (val & ROUTER_CS_26_STATUS_MASK) >> | |
| ROUTER_CS_26_STATUS_SHIFT; | |
| } else { | |
| *status = 0; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * usb4_switch_credits_init() - Read buffer allocation parameters | |
| * @sw: USB4 router | |
| * | |
| * Reads @sw buffer allocation parameters and initializes @sw buffer | |
| * allocation fields accordingly. Specifically @sw->credits_allocation | |
| * is set to %true if these parameters can be used in tunneling. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_credits_init(struct tb_switch *sw) | |
| { | |
| int max_usb3, min_dp_aux, min_dp_main, max_pcie, max_dma; | |
| int ret, length, i, nports; | |
| const struct tb_port *port; | |
| u32 data[USB4_DATA_DWORDS]; | |
| u32 metadata = 0; | |
| u8 status = 0; | |
| memset(data, 0, sizeof(data)); | |
| ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_BUFFER_ALLOC, &metadata, | |
| &status, NULL, 0, data, ARRAY_SIZE(data)); | |
| if (ret) | |
| return ret; | |
| if (status) | |
| return -EIO; | |
| length = metadata & USB4_BA_LENGTH_MASK; | |
| if (WARN_ON(length > ARRAY_SIZE(data))) | |
| return -EMSGSIZE; | |
| max_usb3 = -1; | |
| min_dp_aux = -1; | |
| min_dp_main = -1; | |
| max_pcie = -1; | |
| max_dma = -1; | |
| tb_sw_dbg(sw, "credit allocation parameters:\n"); | |
| for (i = 0; i < length; i++) { | |
| u16 index, value; | |
| index = data[i] & USB4_BA_INDEX_MASK; | |
| value = (data[i] & USB4_BA_VALUE_MASK) >> USB4_BA_VALUE_SHIFT; | |
| switch (index) { | |
| case USB4_BA_MAX_USB3: | |
| tb_sw_dbg(sw, " USB3: %u\n", value); | |
| max_usb3 = value; | |
| break; | |
| case USB4_BA_MIN_DP_AUX: | |
| tb_sw_dbg(sw, " DP AUX: %u\n", value); | |
| min_dp_aux = value; | |
| break; | |
| case USB4_BA_MIN_DP_MAIN: | |
| tb_sw_dbg(sw, " DP main: %u\n", value); | |
| min_dp_main = value; | |
| break; | |
| case USB4_BA_MAX_PCIE: | |
| tb_sw_dbg(sw, " PCIe: %u\n", value); | |
| max_pcie = value; | |
| break; | |
| case USB4_BA_MAX_HI: | |
| tb_sw_dbg(sw, " DMA: %u\n", value); | |
| max_dma = value; | |
| break; | |
| default: | |
| tb_sw_dbg(sw, " unknown credit allocation index %#x, skipping\n", | |
| index); | |
| break; | |
| } | |
| } | |
| /* | |
| * Validate the buffer allocation preferences. If we find | |
| * issues, log a warning and fall back using the hard-coded | |
| * values. | |
| */ | |
| /* Host router must report baMaxHI */ | |
| if (!tb_route(sw) && max_dma < 0) { | |
| tb_sw_warn(sw, "host router is missing baMaxHI\n"); | |
| goto err_invalid; | |
| } | |
| nports = 0; | |
| tb_switch_for_each_port(sw, port) { | |
| if (tb_port_is_null(port)) | |
| nports++; | |
| } | |
| /* Must have DP buffer allocation (multiple USB4 ports) */ | |
| if (nports > 2 && (min_dp_aux < 0 || min_dp_main < 0)) { | |
| tb_sw_warn(sw, "multiple USB4 ports require baMinDPaux/baMinDPmain\n"); | |
| goto err_invalid; | |
| } | |
| tb_switch_for_each_port(sw, port) { | |
| if (tb_port_is_dpout(port) && min_dp_main < 0) { | |
| tb_sw_warn(sw, "missing baMinDPmain"); | |
| goto err_invalid; | |
| } | |
| if ((tb_port_is_dpin(port) || tb_port_is_dpout(port)) && | |
| min_dp_aux < 0) { | |
| tb_sw_warn(sw, "missing baMinDPaux"); | |
| goto err_invalid; | |
| } | |
| if ((tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) && | |
| max_usb3 < 0) { | |
| tb_sw_warn(sw, "missing baMaxUSB3"); | |
| goto err_invalid; | |
| } | |
| if ((tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) && | |
| max_pcie < 0) { | |
| tb_sw_warn(sw, "missing baMaxPCIe"); | |
| goto err_invalid; | |
| } | |
| } | |
| /* | |
| * Buffer allocation passed the validation so we can use it in | |
| * path creation. | |
| */ | |
| sw->credit_allocation = true; | |
| if (max_usb3 > 0) | |
| sw->max_usb3_credits = max_usb3; | |
| if (min_dp_aux > 0) | |
| sw->min_dp_aux_credits = min_dp_aux; | |
| if (min_dp_main > 0) | |
| sw->min_dp_main_credits = min_dp_main; | |
| if (max_pcie > 0) | |
| sw->max_pcie_credits = max_pcie; | |
| if (max_dma > 0) | |
| sw->max_dma_credits = max_dma; | |
| return 0; | |
| err_invalid: | |
| return -EINVAL; | |
| } | |
| /** | |
| * usb4_switch_query_dp_resource() - Query availability of DP IN resource | |
| * @sw: USB4 router | |
| * @in: DP IN adapter | |
| * | |
| * For DP tunneling this function can be used to query availability of | |
| * DP IN resource. | |
| * | |
| * Return: %true if the resource is available for DP tunneling, %false | |
| * otherwise. | |
| */ | |
| bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) | |
| { | |
| u32 metadata = in->port; | |
| u8 status; | |
| int ret; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &metadata, | |
| &status); | |
| /* | |
| * If DP resource allocation is not supported assume it is | |
| * always available. | |
| */ | |
| if (ret == -EOPNOTSUPP) | |
| return true; | |
| if (ret) | |
| return false; | |
| return !status; | |
| } | |
| /** | |
| * usb4_switch_alloc_dp_resource() - Allocate DP IN resource | |
| * @sw: USB4 router | |
| * @in: DP IN adapter | |
| * | |
| * Allocates DP IN resource for DP tunneling using USB4 router | |
| * operations. | |
| * | |
| * Return: | |
| * * %0 - Resource allocated successfully. | |
| * * %-EBUSY - Resource is already allocated. | |
| * * Negative errno - Other failure occurred. | |
| */ | |
| int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) | |
| { | |
| u32 metadata = in->port; | |
| u8 status; | |
| int ret; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &metadata, | |
| &status); | |
| if (ret == -EOPNOTSUPP) | |
| return 0; | |
| if (ret) | |
| return ret; | |
| return status ? -EBUSY : 0; | |
| } | |
| /** | |
| * usb4_switch_dealloc_dp_resource() - Releases allocated DP IN resource | |
| * @sw: USB4 router | |
| * @in: DP IN adapter | |
| * | |
| * Releases the previously allocated DP IN resource. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) | |
| { | |
| u32 metadata = in->port; | |
| u8 status; | |
| int ret; | |
| ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &metadata, | |
| &status); | |
| if (ret == -EOPNOTSUPP) | |
| return 0; | |
| if (ret) | |
| return ret; | |
| return status ? -EIO : 0; | |
| } | |
| /** | |
| * usb4_port_index() - Finds matching USB4 port index | |
| * @sw: USB4 router | |
| * @port: USB4 protocol or lane adapter | |
| * | |
| * Finds matching USB4 port index (starting from %0) that given @port goes | |
| * through. | |
| */ | |
| int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port) | |
| { | |
| struct tb_port *p; | |
| int usb4_idx = 0; | |
| /* Assume port is primary */ | |
| tb_switch_for_each_port(sw, p) { | |
| if (!tb_port_is_null(p)) | |
| continue; | |
| if (tb_is_upstream_port(p)) | |
| continue; | |
| if (!p->link_nr) { | |
| if (p == port) | |
| break; | |
| usb4_idx++; | |
| } | |
| } | |
| return usb4_idx; | |
| } | |
| /** | |
| * usb4_switch_map_pcie_down() - Map USB4 port to a PCIe downstream adapter | |
| * @sw: USB4 router | |
| * @port: USB4 port | |
| * | |
| * USB4 routers have direct mapping between USB4 ports and PCIe | |
| * downstream adapters where the PCIe topology is extended. This | |
| * function returns the corresponding downstream PCIe adapter or %NULL | |
| * if no such mapping was possible. | |
| * | |
| * Return: Pointer to &struct tb_port or %NULL if not found. | |
| */ | |
| struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, | |
| const struct tb_port *port) | |
| { | |
| int usb4_idx = usb4_port_index(sw, port); | |
| struct tb_port *p; | |
| int pcie_idx = 0; | |
| /* Find PCIe down port matching usb4_port */ | |
| tb_switch_for_each_port(sw, p) { | |
| if (!tb_port_is_pcie_down(p)) | |
| continue; | |
| if (pcie_idx == usb4_idx) | |
| return p; | |
| pcie_idx++; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * usb4_switch_map_usb3_down() - Map USB4 port to a USB3 downstream adapter | |
| * @sw: USB4 router | |
| * @port: USB4 port | |
| * | |
| * USB4 routers have direct mapping between USB4 ports and USB 3.x | |
| * downstream adapters where the USB 3.x topology is extended. This | |
| * function returns the corresponding downstream USB 3.x adapter or | |
| * %NULL if no such mapping was possible. | |
| * | |
| * Return: Pointer to &struct tb_port or %NULL if not found. | |
| */ | |
| struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, | |
| const struct tb_port *port) | |
| { | |
| int usb4_idx = usb4_port_index(sw, port); | |
| struct tb_port *p; | |
| int usb_idx = 0; | |
| /* Find USB3 down port matching usb4_port */ | |
| tb_switch_for_each_port(sw, p) { | |
| if (!tb_port_is_usb3_down(p)) | |
| continue; | |
| if (usb_idx == usb4_idx) | |
| return p; | |
| usb_idx++; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * usb4_switch_add_ports() - Add USB4 ports for this router | |
| * @sw: USB4 router | |
| * | |
| * For USB4 router finds all USB4 ports and registers devices for each. | |
| * Can be called to any router. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_switch_add_ports(struct tb_switch *sw) | |
| { | |
| struct tb_port *port; | |
| if (tb_switch_is_icm(sw) || !tb_switch_is_usb4(sw)) | |
| return 0; | |
| tb_switch_for_each_port(sw, port) { | |
| struct usb4_port *usb4; | |
| if (!tb_port_is_null(port)) | |
| continue; | |
| if (!port->cap_usb4) | |
| continue; | |
| usb4 = usb4_port_device_add(port); | |
| if (IS_ERR(usb4)) { | |
| usb4_switch_remove_ports(sw); | |
| return PTR_ERR(usb4); | |
| } | |
| port->usb4 = usb4; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * usb4_switch_remove_ports() - Removes USB4 ports from this router | |
| * @sw: USB4 router | |
| * | |
| * Unregisters previously registered USB4 ports. | |
| */ | |
| void usb4_switch_remove_ports(struct tb_switch *sw) | |
| { | |
| struct tb_port *port; | |
| tb_switch_for_each_port(sw, port) { | |
| if (port->usb4) { | |
| usb4_port_device_remove(port->usb4); | |
| port->usb4 = NULL; | |
| } | |
| } | |
| } | |
| /** | |
| * usb4_port_unlock() - Unlock USB4 downstream port | |
| * @port: USB4 port to unlock | |
| * | |
| * Unlocks USB4 downstream port so that the connection manager can | |
| * access the router below this port. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_unlock(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_4, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_CS_4_LCK; | |
| return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1); | |
| } | |
| /** | |
| * usb4_port_hotplug_enable() - Enables hotplug for a port | |
| * @port: USB4 port to operate on | |
| * | |
| * Enables hot plug events on a given port. This is only intended | |
| * to be used on lane, DP-IN, and DP-OUT adapters. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_hotplug_enable(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_5, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_CS_5_DHP; | |
| return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_5, 1); | |
| } | |
| /** | |
| * usb4_port_reset() - Issue downstream port reset | |
| * @port: USB4 port to reset | |
| * | |
| * Issues downstream port reset to @port. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_reset(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| val |= PORT_CS_19_DPR; | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| fsleep(10000); | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~PORT_CS_19_DPR; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| } | |
| static int usb4_port_set_configured(struct tb_port *port, bool configured) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| if (configured) | |
| val |= PORT_CS_19_PC; | |
| else | |
| val &= ~PORT_CS_19_PC; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| } | |
| /** | |
| * usb4_port_configure() - Set USB4 port configured | |
| * @port: USB4 router | |
| * | |
| * Sets the USB4 link to be configured for power management purposes. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_configure(struct tb_port *port) | |
| { | |
| return usb4_port_set_configured(port, true); | |
| } | |
| /** | |
| * usb4_port_unconfigure() - Set USB4 port unconfigured | |
| * @port: USB4 router | |
| * | |
| * Sets the USB4 link to be unconfigured for power management purposes. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| void usb4_port_unconfigure(struct tb_port *port) | |
| { | |
| usb4_port_set_configured(port, false); | |
| } | |
| static int usb4_set_xdomain_configured(struct tb_port *port, bool configured) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| if (configured) | |
| val |= PORT_CS_19_PID; | |
| else | |
| val &= ~PORT_CS_19_PID; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| } | |
| /** | |
| * usb4_port_configure_xdomain() - Configure port for XDomain | |
| * @port: USB4 port connected to another host | |
| * @xd: XDomain that is connected to the port | |
| * | |
| * Marks the USB4 port as being connected to another host and updates | |
| * the link type. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd) | |
| { | |
| xd->link_usb4 = link_is_usb4(port); | |
| return usb4_set_xdomain_configured(port, true); | |
| } | |
| /** | |
| * usb4_port_unconfigure_xdomain() - Unconfigure port for XDomain | |
| * @port: USB4 port that was connected to another host | |
| * | |
| * Clears USB4 port from being marked as XDomain. | |
| */ | |
| void usb4_port_unconfigure_xdomain(struct tb_port *port) | |
| { | |
| usb4_set_xdomain_configured(port, false); | |
| } | |
| static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit, | |
| u32 value, int timeout_msec, unsigned long delay_usec) | |
| { | |
| ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec); | |
| do { | |
| u32 val; | |
| int ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, offset, 1); | |
| if (ret) | |
| return ret; | |
| if ((val & bit) == value) | |
| return 0; | |
| fsleep(delay_usec); | |
| } while (ktime_before(ktime_get(), timeout)); | |
| return -ETIMEDOUT; | |
| } | |
| static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) | |
| { | |
| if (dwords > USB4_DATA_DWORDS) | |
| return -EINVAL; | |
| return tb_port_read(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, | |
| dwords); | |
| } | |
| static int usb4_port_write_data(struct tb_port *port, const void *data, | |
| size_t dwords) | |
| { | |
| if (dwords > USB4_DATA_DWORDS) | |
| return -EINVAL; | |
| return tb_port_write(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, | |
| dwords); | |
| } | |
| /** | |
| * usb4_port_sb_read() - Read from sideband register | |
| * @port: USB4 port to read | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @reg: Sideband register index | |
| * @buf: Buffer where the sideband data is copied | |
| * @size: Size of @buf | |
| * | |
| * Reads data from sideband register @reg and copies it into @buf. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index, | |
| u8 reg, void *buf, u8 size) | |
| { | |
| size_t dwords = DIV_ROUND_UP(size, 4); | |
| int ret; | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return -EINVAL; | |
| val = reg; | |
| val |= size << PORT_CS_1_LENGTH_SHIFT; | |
| val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK; | |
| if (target == USB4_SB_TARGET_RETIMER) | |
| val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT); | |
| val |= PORT_CS_1_PND; | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1, | |
| PORT_CS_1_PND, 0, 500, USB4_PORT_SB_DELAY); | |
| if (ret) | |
| return ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| if (val & PORT_CS_1_NR) | |
| return -ENODEV; | |
| if (val & PORT_CS_1_RC) | |
| return -EIO; | |
| return buf ? usb4_port_read_data(port, buf, dwords) : 0; | |
| } | |
| /** | |
| * usb4_port_sb_write() - Write to sideband register | |
| * @port: USB4 port to write | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @reg: Sideband register index | |
| * @buf: Data to write | |
| * @size: Size of @buf | |
| * | |
| * Writes @buf to sideband register @reg. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, u8 reg, const void *buf, u8 size) | |
| { | |
| size_t dwords = DIV_ROUND_UP(size, 4); | |
| int ret; | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return -EINVAL; | |
| if (buf) { | |
| ret = usb4_port_write_data(port, buf, dwords); | |
| if (ret) | |
| return ret; | |
| } | |
| val = reg; | |
| val |= size << PORT_CS_1_LENGTH_SHIFT; | |
| val |= PORT_CS_1_WNR_WRITE; | |
| val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK; | |
| if (target == USB4_SB_TARGET_RETIMER) | |
| val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT); | |
| val |= PORT_CS_1_PND; | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1, | |
| PORT_CS_1_PND, 0, 500, USB4_PORT_SB_DELAY); | |
| if (ret) | |
| return ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| if (val & PORT_CS_1_NR) | |
| return -ENODEV; | |
| if (val & PORT_CS_1_RC) | |
| return -EIO; | |
| return 0; | |
| } | |
| static int usb4_port_sb_opcode_err_to_errno(u32 val) | |
| { | |
| switch (val) { | |
| case 0: | |
| return 0; | |
| case USB4_SB_OPCODE_ERR: | |
| return -EAGAIN; | |
| case USB4_SB_OPCODE_ONS: | |
| return -EOPNOTSUPP; | |
| default: | |
| return -EIO; | |
| } | |
| } | |
| static int usb4_port_sb_op(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, enum usb4_sb_opcode opcode, int timeout_msec) | |
| { | |
| ktime_t timeout; | |
| u32 val; | |
| int ret; | |
| val = opcode; | |
| ret = usb4_port_sb_write(port, target, index, USB4_SB_OPCODE, &val, | |
| sizeof(val)); | |
| if (ret) | |
| return ret; | |
| timeout = ktime_add_ms(ktime_get(), timeout_msec); | |
| do { | |
| /* Check results */ | |
| ret = usb4_port_sb_read(port, target, index, USB4_SB_OPCODE, | |
| &val, sizeof(val)); | |
| if (ret) | |
| return ret; | |
| if (val != opcode) | |
| return usb4_port_sb_opcode_err_to_errno(val); | |
| fsleep(USB4_PORT_SB_DELAY); | |
| } while (ktime_before(ktime_get(), timeout)); | |
| return -ETIMEDOUT; | |
| } | |
| static int usb4_port_set_router_offline(struct tb_port *port, bool offline) | |
| { | |
| u32 val = !offline; | |
| int ret; | |
| ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, | |
| USB4_SB_METADATA, &val, sizeof(val)); | |
| if (ret) | |
| return ret; | |
| val = USB4_SB_OPCODE_ROUTER_OFFLINE; | |
| return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, | |
| USB4_SB_OPCODE, &val, sizeof(val)); | |
| } | |
| /** | |
| * usb4_port_router_offline() - Put the USB4 port to offline mode | |
| * @port: USB4 port | |
| * | |
| * This function puts the USB4 port into offline mode. In this mode the | |
| * port does not react on hotplug events anymore. This needs to be | |
| * called before retimer access is done when the USB4 links is not up. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_router_offline(struct tb_port *port) | |
| { | |
| return usb4_port_set_router_offline(port, true); | |
| } | |
| /** | |
| * usb4_port_router_online() - Put the USB4 port back online | |
| * @port: USB4 port | |
| * | |
| * Makes the USB4 port functional again. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_router_online(struct tb_port *port) | |
| { | |
| return usb4_port_set_router_offline(port, false); | |
| } | |
| /** | |
| * usb4_port_enumerate_retimers() - Send RT broadcast transaction | |
| * @port: USB4 port | |
| * | |
| * This forces the USB4 port to send broadcast RT transaction which | |
| * makes the retimers on the link assign index to themselves. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_enumerate_retimers(struct tb_port *port) | |
| { | |
| u32 val; | |
| val = USB4_SB_OPCODE_ENUMERATE_RETIMERS; | |
| return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, | |
| USB4_SB_OPCODE, &val, sizeof(val)); | |
| } | |
| /** | |
| * usb4_port_clx_supported() - Check if CLx is supported by the link | |
| * @port: Port to check for CLx support for | |
| * | |
| * PORT_CS_18_CPS bit reflects if the link supports CLx including | |
| * active cables (if connected on the link). | |
| * | |
| * Return: %true if Clx is supported, %false otherwise. | |
| */ | |
| bool usb4_port_clx_supported(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_18, 1); | |
| if (ret) | |
| return false; | |
| return !!(val & PORT_CS_18_CPS); | |
| } | |
| /** | |
| * usb4_port_asym_supported() - If the port supports asymmetric link | |
| * @port: USB4 port | |
| * | |
| * Checks if the port and the cable support asymmetric link. | |
| * | |
| * Return: %true if asymmetric link is supported, %false otherwise. | |
| */ | |
| bool usb4_port_asym_supported(struct tb_port *port) | |
| { | |
| u32 val; | |
| if (!port->cap_usb4) | |
| return false; | |
| if (tb_port_read(port, &val, TB_CFG_PORT, port->cap_usb4 + PORT_CS_18, 1)) | |
| return false; | |
| return !!(val & PORT_CS_18_CSA); | |
| } | |
| /** | |
| * usb4_port_asym_set_link_width() - Set link width to asymmetric or symmetric | |
| * @port: USB4 port | |
| * @width: Asymmetric width to configure | |
| * | |
| * Sets USB4 port link width to @width. Can be called for widths where | |
| * usb4_port_asym_width_supported() returned @true. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!port->cap_phy) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_phy + LANE_ADP_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK; | |
| switch (width) { | |
| case TB_LINK_WIDTH_DUAL: | |
| val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK, | |
| LANE_ADP_CS_1_TARGET_WIDTH_ASYM_DUAL); | |
| break; | |
| case TB_LINK_WIDTH_ASYM_TX: | |
| val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK, | |
| LANE_ADP_CS_1_TARGET_WIDTH_ASYM_TX); | |
| break; | |
| case TB_LINK_WIDTH_ASYM_RX: | |
| val |= FIELD_PREP(LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK, | |
| LANE_ADP_CS_1_TARGET_WIDTH_ASYM_RX); | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_phy + LANE_ADP_CS_1, 1); | |
| } | |
| /** | |
| * usb4_port_asym_start() - Start symmetry change and wait for completion | |
| * @port: USB4 port | |
| * | |
| * Start symmetry change of the link to asymmetric or symmetric | |
| * (according to what was previously set in tb_port_set_link_width(). | |
| * Wait for completion of the change. | |
| * | |
| * Return: | |
| * * %0 - Symmetry change was successful. | |
| * * %-ETIMEDOUT - Timeout occurred. | |
| * * Negative errno - Other failure occurred. | |
| */ | |
| int usb4_port_asym_start(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~PORT_CS_19_START_ASYM; | |
| val |= FIELD_PREP(PORT_CS_19_START_ASYM, 1); | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_usb4 + PORT_CS_19, 1); | |
| if (ret) | |
| return ret; | |
| /* | |
| * Wait for PORT_CS_19_START_ASYM to be 0. This means the USB4 | |
| * port started the symmetry transition. | |
| */ | |
| ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_19, | |
| PORT_CS_19_START_ASYM, 0, 1000, | |
| USB4_PORT_DELAY); | |
| if (ret) | |
| return ret; | |
| /* Then wait for the transtion to be completed */ | |
| return usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_18, | |
| PORT_CS_18_TIP, 0, 5000, USB4_PORT_DELAY); | |
| } | |
| /** | |
| * usb4_port_margining_caps() - Read USB4 port margining capabilities | |
| * @port: USB4 port | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @caps: Array with at least two elements to hold the results | |
| * @ncaps: Number of elements in the caps array | |
| * | |
| * Reads the USB4 port lane margining capabilities into @caps. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, u32 *caps, size_t ncaps) | |
| { | |
| int ret; | |
| ret = usb4_port_sb_op(port, target, index, | |
| USB4_SB_OPCODE_READ_LANE_MARGINING_CAP, 500); | |
| if (ret) | |
| return ret; | |
| return usb4_port_sb_read(port, target, index, USB4_SB_DATA, caps, | |
| sizeof(*caps) * ncaps); | |
| } | |
| /** | |
| * usb4_port_hw_margin() - Run hardware lane margining on port | |
| * @port: USB4 port | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @params: Parameters for USB4 hardware margining | |
| * @results: Array to hold the results | |
| * @nresults: Number of elements in the results array | |
| * | |
| * Runs hardware lane margining on USB4 port and returns the result in | |
| * @results. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, const struct usb4_port_margining_params *params, | |
| u32 *results, size_t nresults) | |
| { | |
| u32 val; | |
| int ret; | |
| if (WARN_ON_ONCE(!params)) | |
| return -EINVAL; | |
| val = params->lanes; | |
| if (params->time) | |
| val |= USB4_MARGIN_HW_TIME; | |
| if (params->right_high || params->upper_eye) | |
| val |= USB4_MARGIN_HW_RHU; | |
| if (params->ber_level) | |
| val |= FIELD_PREP(USB4_MARGIN_HW_BER_MASK, params->ber_level); | |
| if (params->optional_voltage_offset_range) | |
| val |= USB4_MARGIN_HW_OPT_VOLTAGE; | |
| ret = usb4_port_sb_write(port, target, index, USB4_SB_METADATA, &val, | |
| sizeof(val)); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_op(port, target, index, | |
| USB4_SB_OPCODE_RUN_HW_LANE_MARGINING, 2500); | |
| if (ret) | |
| return ret; | |
| return usb4_port_sb_read(port, target, index, USB4_SB_DATA, results, | |
| sizeof(*results) * nresults); | |
| } | |
| /** | |
| * usb4_port_sw_margin() - Run software lane margining on port | |
| * @port: USB4 port | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @params: Parameters for USB4 software margining | |
| * @results: Data word for the operation completion data | |
| * | |
| * Runs software lane margining on USB4 port. Read back the error | |
| * counters by calling usb4_port_sw_margin_errors(). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, const struct usb4_port_margining_params *params, | |
| u32 *results) | |
| { | |
| u32 val; | |
| int ret; | |
| if (WARN_ON_ONCE(!params)) | |
| return -EINVAL; | |
| val = params->lanes; | |
| if (params->time) | |
| val |= USB4_MARGIN_SW_TIME; | |
| if (params->optional_voltage_offset_range) | |
| val |= USB4_MARGIN_SW_OPT_VOLTAGE; | |
| if (params->right_high) | |
| val |= USB4_MARGIN_SW_RH; | |
| if (params->upper_eye) | |
| val |= USB4_MARGIN_SW_UPPER_EYE; | |
| val |= FIELD_PREP(USB4_MARGIN_SW_COUNTER_MASK, params->error_counter); | |
| val |= FIELD_PREP(USB4_MARGIN_SW_VT_MASK, params->voltage_time_offset); | |
| ret = usb4_port_sb_write(port, target, index, USB4_SB_METADATA, &val, | |
| sizeof(val)); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_op(port, target, index, | |
| USB4_SB_OPCODE_RUN_SW_LANE_MARGINING, 2500); | |
| if (ret) | |
| return ret; | |
| return usb4_port_sb_read(port, target, index, USB4_SB_DATA, results, | |
| sizeof(*results)); | |
| } | |
| /** | |
| * usb4_port_sw_margin_errors() - Read the software margining error counters | |
| * @port: USB4 port | |
| * @target: Sideband target | |
| * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER | |
| * @errors: Error metadata is copied here. | |
| * | |
| * This reads back the software margining error counters from the port. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, | |
| u8 index, u32 *errors) | |
| { | |
| int ret; | |
| ret = usb4_port_sb_op(port, target, index, | |
| USB4_SB_OPCODE_READ_SW_MARGIN_ERR, 150); | |
| if (ret) | |
| return ret; | |
| return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors, | |
| sizeof(*errors)); | |
| } | |
| static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, | |
| enum usb4_sb_opcode opcode, | |
| int timeout_msec) | |
| { | |
| return usb4_port_sb_op(port, USB4_SB_TARGET_RETIMER, index, opcode, | |
| timeout_msec); | |
| } | |
| /** | |
| * usb4_port_retimer_set_inbound_sbtx() - Enable sideband channel transactions | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * Enables sideband channel transactions on SBTX. Can be used when USB4 | |
| * link does not go up, for example if there is no device connected. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) | |
| { | |
| int ret; | |
| ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX, | |
| 500); | |
| if (ret != -ENODEV) | |
| return ret; | |
| /* | |
| * Per the USB4 retimer spec, the retimer is not required to | |
| * send an RT (Retimer Transaction) response for the first | |
| * SET_INBOUND_SBTX command | |
| */ | |
| return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX, | |
| 500); | |
| } | |
| /** | |
| * usb4_port_retimer_unset_inbound_sbtx() - Disable sideband channel transactions | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * Disables sideband channel transactions on SBTX. The reverse of | |
| * usb4_port_retimer_set_inbound_sbtx(). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index) | |
| { | |
| return usb4_port_retimer_op(port, index, | |
| USB4_SB_OPCODE_UNSET_INBOUND_SBTX, 500); | |
| } | |
| /** | |
| * usb4_port_retimer_is_last() - Is the retimer last on-board retimer | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * Return: | |
| * * %1 - Retimer at @index is the last one (connected directly to the | |
| * Type-C port). | |
| * * %0 - Retimer at @index is not the last one. | |
| * * %-ENODEV - Retimer is not present. | |
| * * Negative errno - Other failure occurred. | |
| */ | |
| int usb4_port_retimer_is_last(struct tb_port *port, u8 index) | |
| { | |
| u32 metadata; | |
| int ret; | |
| ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_LAST_RETIMER, | |
| 500); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, sizeof(metadata)); | |
| return ret ? ret : metadata & 1; | |
| } | |
| /** | |
| * usb4_port_retimer_is_cable() - Is the retimer cable retimer | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * Return: | |
| * * %1 - Retimer at @index is the last cable retimer. | |
| * * %0 - Retimer at @index is on-board retimer. | |
| * * %-ENODEV - Retimer is not present. | |
| * * Negative errno - Other failure occurred. | |
| */ | |
| int usb4_port_retimer_is_cable(struct tb_port *port, u8 index) | |
| { | |
| u32 metadata; | |
| int ret; | |
| ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_CABLE_RETIMER, | |
| 500); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, sizeof(metadata)); | |
| return ret ? ret : metadata & 1; | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_sector_size() - Read retimer NVM sector size | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * Reads NVM sector size (in bytes) of a retimer at @index. This | |
| * operation can be used to determine whether the retimer supports NVM | |
| * upgrade for example. | |
| * | |
| * Return: | |
| * * Sector size in bytes. | |
| * * %-ENODEV - If there is no retimer at @index. | |
| * * Negative errno - In case of an error. | |
| */ | |
| int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) | |
| { | |
| u32 metadata; | |
| int ret; | |
| ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE, | |
| 500); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, sizeof(metadata)); | |
| return ret ? ret : metadata & USB4_NVM_SECTOR_SIZE_MASK; | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_set_offset() - Set NVM write offset | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * @address: Start offset | |
| * | |
| * Explicitly sets NVM write offset. Normally when writing to NVM this is | |
| * done automatically by usb4_port_retimer_nvm_write(). | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, | |
| unsigned int address) | |
| { | |
| u32 metadata, dwaddress; | |
| int ret; | |
| dwaddress = address / 4; | |
| metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) & | |
| USB4_NVM_SET_OFFSET_MASK; | |
| ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, sizeof(metadata)); | |
| if (ret) | |
| return ret; | |
| return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_SET_OFFSET, | |
| 500); | |
| } | |
| struct retimer_info { | |
| struct tb_port *port; | |
| u8 index; | |
| }; | |
| static int usb4_port_retimer_nvm_write_next_block(void *data, | |
| unsigned int dwaddress, const void *buf, size_t dwords) | |
| { | |
| const struct retimer_info *info = data; | |
| struct tb_port *port = info->port; | |
| u8 index = info->index; | |
| int ret; | |
| ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_DATA, buf, dwords * 4); | |
| if (ret) | |
| return ret; | |
| return usb4_port_retimer_op(port, index, | |
| USB4_SB_OPCODE_NVM_BLOCK_WRITE, 1000); | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_write() - Write to retimer NVM | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * @address: Byte address where to start the write | |
| * @buf: Data to write | |
| * @size: Size in bytes how much to write | |
| * | |
| * Writes @size bytes from @buf to the retimer NVM. Used for NVM | |
| * upgrade. | |
| * | |
| * Return: | |
| * * %0 - If the data was written successfully. | |
| * * %-ENODEV - If there is no retimer at @index. | |
| * * Negative errno - In case of an error. | |
| */ | |
| int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, | |
| const void *buf, size_t size) | |
| { | |
| struct retimer_info info = { .port = port, .index = index }; | |
| int ret; | |
| ret = usb4_port_retimer_nvm_set_offset(port, index, address); | |
| if (ret) | |
| return ret; | |
| return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES, | |
| usb4_port_retimer_nvm_write_next_block, &info); | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_authenticate() - Start retimer NVM upgrade | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * | |
| * After the new NVM image has been written via usb4_port_retimer_nvm_write() | |
| * this function can be used to trigger the NVM upgrade process. If | |
| * successful the retimer restarts with the new NVM and may not have the | |
| * index set so one needs to call usb4_port_enumerate_retimers() to | |
| * force index to be assigned. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) | |
| { | |
| u32 val; | |
| /* | |
| * We need to use the raw operation here because once the | |
| * authentication completes the retimer index is not set anymore | |
| * so we do not get back the status now. | |
| */ | |
| val = USB4_SB_OPCODE_NVM_AUTH_WRITE; | |
| return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_OPCODE, &val, sizeof(val)); | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_authenticate_status() - Read status of NVM upgrade | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * @status: Raw status code read from metadata | |
| * | |
| * This can be called after usb4_port_retimer_nvm_authenticate() and | |
| * usb4_port_enumerate_retimers() to fetch status of the NVM upgrade. | |
| * | |
| * Return: %0 if the authentication status was successfully read. The | |
| * completion metadata (the result) is then stored into @status. If | |
| * status read fails, returns negative errno. | |
| */ | |
| int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index, | |
| u32 *status) | |
| { | |
| u32 metadata, val; | |
| int ret; | |
| ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_OPCODE, &val, sizeof(val)); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_sb_opcode_err_to_errno(val); | |
| switch (ret) { | |
| case 0: | |
| *status = 0; | |
| return 0; | |
| case -EAGAIN: | |
| ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, | |
| sizeof(metadata)); | |
| if (ret) | |
| return ret; | |
| *status = metadata & USB4_SB_METADATA_NVM_AUTH_WRITE_MASK; | |
| return 0; | |
| default: | |
| return ret; | |
| } | |
| } | |
| static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, | |
| void *buf, size_t dwords) | |
| { | |
| const struct retimer_info *info = data; | |
| struct tb_port *port = info->port; | |
| u8 index = info->index; | |
| u32 metadata; | |
| int ret; | |
| metadata = dwaddress << USB4_NVM_READ_OFFSET_SHIFT; | |
| if (dwords < USB4_DATA_DWORDS) | |
| metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT; | |
| ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_METADATA, &metadata, sizeof(metadata)); | |
| if (ret) | |
| return ret; | |
| ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_READ, 500); | |
| if (ret) | |
| return ret; | |
| return usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, | |
| USB4_SB_DATA, buf, dwords * 4); | |
| } | |
| /** | |
| * usb4_port_retimer_nvm_read() - Read contents of retimer NVM | |
| * @port: USB4 port | |
| * @index: Retimer index | |
| * @address: NVM address (in bytes) to start reading | |
| * @buf: Data read from NVM is stored here | |
| * @size: Number of bytes to read | |
| * | |
| * Reads retimer NVM and copies the contents to @buf. | |
| * | |
| * Return: | |
| * * %0 - If the read was successful. | |
| * * %-ENODEV - If there is no retimer at @index. | |
| * * Negative errno - In case of an error. | |
| */ | |
| int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, | |
| unsigned int address, void *buf, size_t size) | |
| { | |
| struct retimer_info info = { .port = port, .index = index }; | |
| return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, | |
| usb4_port_retimer_nvm_read_block, &info); | |
| } | |
| static inline unsigned int | |
| usb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw) | |
| { | |
| /* Take the possible bandwidth limitation into account */ | |
| if (port->max_bw) | |
| return min(bw, port->max_bw); | |
| return bw; | |
| } | |
| /** | |
| * usb4_usb3_port_max_link_rate() - Maximum supported USB3 link rate | |
| * @port: USB3 adapter port | |
| * | |
| * Return: Maximum supported link rate of a USB3 adapter in Mb/s. | |
| * Negative errno in case of an error. | |
| */ | |
| int usb4_usb3_port_max_link_rate(struct tb_port *port) | |
| { | |
| int ret, lr; | |
| u32 val; | |
| if (!tb_port_is_usb3_down(port) && !tb_port_is_usb3_up(port)) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_4, 1); | |
| if (ret) | |
| return ret; | |
| lr = (val & ADP_USB3_CS_4_MSLR_MASK) >> ADP_USB3_CS_4_MSLR_SHIFT; | |
| ret = lr == ADP_USB3_CS_4_MSLR_20G ? 20000 : 10000; | |
| return usb4_usb3_port_max_bandwidth(port, ret); | |
| } | |
| static int usb4_usb3_port_cm_request(struct tb_port *port, bool request) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!tb_port_is_usb3_down(port)) | |
| return -EINVAL; | |
| if (tb_route(port->sw)) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| if (request) | |
| val |= ADP_USB3_CS_2_CMR; | |
| else | |
| val &= ~ADP_USB3_CS_2_CMR; | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| /* | |
| * We can use val here directly as the CMR bit is in the same place | |
| * as HCA. Just mask out others. | |
| */ | |
| val &= ADP_USB3_CS_2_CMR; | |
| return usb4_port_wait_for_bit(port, port->cap_adap + ADP_USB3_CS_1, | |
| ADP_USB3_CS_1_HCA, val, 1500, | |
| USB4_PORT_DELAY); | |
| } | |
| static inline int usb4_usb3_port_set_cm_request(struct tb_port *port) | |
| { | |
| return usb4_usb3_port_cm_request(port, true); | |
| } | |
| static inline int usb4_usb3_port_clear_cm_request(struct tb_port *port) | |
| { | |
| return usb4_usb3_port_cm_request(port, false); | |
| } | |
| static unsigned int usb3_bw_to_mbps(u32 bw, u8 scale) | |
| { | |
| unsigned long uframes; | |
| uframes = bw * 512UL << scale; | |
| return DIV_ROUND_CLOSEST(uframes * 8000, MEGA); | |
| } | |
| static u32 mbps_to_usb3_bw(unsigned int mbps, u8 scale) | |
| { | |
| unsigned long uframes; | |
| /* 1 uframe is 1/8 ms (125 us) -> 1 / 8000 s */ | |
| uframes = ((unsigned long)mbps * MEGA) / 8000; | |
| return DIV_ROUND_UP(uframes, 512UL << scale); | |
| } | |
| static int usb4_usb3_port_read_allocated_bandwidth(struct tb_port *port, | |
| int *upstream_bw, | |
| int *downstream_bw) | |
| { | |
| u32 val, bw, scale; | |
| int ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| ret = tb_port_read(port, &scale, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_3, 1); | |
| if (ret) | |
| return ret; | |
| scale &= ADP_USB3_CS_3_SCALE_MASK; | |
| bw = val & ADP_USB3_CS_2_AUBW_MASK; | |
| *upstream_bw = usb3_bw_to_mbps(bw, scale); | |
| bw = (val & ADP_USB3_CS_2_ADBW_MASK) >> ADP_USB3_CS_2_ADBW_SHIFT; | |
| *downstream_bw = usb3_bw_to_mbps(bw, scale); | |
| return 0; | |
| } | |
| /** | |
| * usb4_usb3_port_allocated_bandwidth() - Bandwidth allocated for USB3 | |
| * @port: USB3 adapter port | |
| * @upstream_bw: Allocated upstream bandwidth is stored here | |
| * @downstream_bw: Allocated downstream bandwidth is stored here | |
| * | |
| * Stores currently allocated USB3 bandwidth into @upstream_bw and | |
| * @downstream_bw in Mb/s. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw, | |
| int *downstream_bw) | |
| { | |
| int ret; | |
| ret = usb4_usb3_port_set_cm_request(port); | |
| if (ret) | |
| return ret; | |
| ret = usb4_usb3_port_read_allocated_bandwidth(port, upstream_bw, | |
| downstream_bw); | |
| usb4_usb3_port_clear_cm_request(port); | |
| return ret; | |
| } | |
| static int usb4_usb3_port_read_consumed_bandwidth(struct tb_port *port, | |
| int *upstream_bw, | |
| int *downstream_bw) | |
| { | |
| u32 val, bw, scale; | |
| int ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| ret = tb_port_read(port, &scale, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_3, 1); | |
| if (ret) | |
| return ret; | |
| scale &= ADP_USB3_CS_3_SCALE_MASK; | |
| bw = val & ADP_USB3_CS_1_CUBW_MASK; | |
| *upstream_bw = usb3_bw_to_mbps(bw, scale); | |
| bw = (val & ADP_USB3_CS_1_CDBW_MASK) >> ADP_USB3_CS_1_CDBW_SHIFT; | |
| *downstream_bw = usb3_bw_to_mbps(bw, scale); | |
| return 0; | |
| } | |
| static int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port, | |
| int upstream_bw, | |
| int downstream_bw) | |
| { | |
| u32 val, ubw, dbw, scale; | |
| int ret, max_bw; | |
| /* Figure out suitable scale */ | |
| scale = 0; | |
| max_bw = max(upstream_bw, downstream_bw); | |
| while (scale < 64) { | |
| if (mbps_to_usb3_bw(max_bw, scale) < 4096) | |
| break; | |
| scale++; | |
| } | |
| if (WARN_ON(scale >= 64)) | |
| return -EINVAL; | |
| ret = tb_port_write(port, &scale, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_3, 1); | |
| if (ret) | |
| return ret; | |
| ubw = mbps_to_usb3_bw(upstream_bw, scale); | |
| dbw = mbps_to_usb3_bw(downstream_bw, scale); | |
| tb_port_dbg(port, "scaled bandwidth %u/%u, scale %u\n", ubw, dbw, scale); | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~(ADP_USB3_CS_2_AUBW_MASK | ADP_USB3_CS_2_ADBW_MASK); | |
| val |= dbw << ADP_USB3_CS_2_ADBW_SHIFT; | |
| val |= ubw; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_USB3_CS_2, 1); | |
| } | |
| /** | |
| * usb4_usb3_port_allocate_bandwidth() - Allocate bandwidth for USB3 | |
| * @port: USB3 adapter port | |
| * @upstream_bw: New upstream bandwidth | |
| * @downstream_bw: New downstream bandwidth | |
| * | |
| * This can be used to set how much bandwidth is allocated for the USB3 | |
| * tunneled isochronous traffic. @upstream_bw and @downstream_bw are the | |
| * new values programmed to the USB3 adapter allocation registers. If | |
| * the values are lower than what is currently consumed the allocation | |
| * is set to what is currently consumed instead (consumed bandwidth | |
| * cannot be taken away by CM). The actual new values are returned in | |
| * @upstream_bw and @downstream_bw. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, | |
| int *downstream_bw) | |
| { | |
| int ret, consumed_up, consumed_down, allocate_up, allocate_down; | |
| ret = usb4_usb3_port_set_cm_request(port); | |
| if (ret) | |
| return ret; | |
| ret = usb4_usb3_port_read_consumed_bandwidth(port, &consumed_up, | |
| &consumed_down); | |
| if (ret) | |
| goto err_request; | |
| /* Don't allow it go lower than what is consumed */ | |
| allocate_up = max(*upstream_bw, consumed_up); | |
| allocate_down = max(*downstream_bw, consumed_down); | |
| ret = usb4_usb3_port_write_allocated_bandwidth(port, allocate_up, | |
| allocate_down); | |
| if (ret) | |
| goto err_request; | |
| *upstream_bw = allocate_up; | |
| *downstream_bw = allocate_down; | |
| err_request: | |
| usb4_usb3_port_clear_cm_request(port); | |
| return ret; | |
| } | |
| /** | |
| * usb4_usb3_port_release_bandwidth() - Release allocated USB3 bandwidth | |
| * @port: USB3 adapter port | |
| * @upstream_bw: New allocated upstream bandwidth | |
| * @downstream_bw: New allocated downstream bandwidth | |
| * | |
| * Releases USB3 allocated bandwidth down to what is actually consumed. | |
| * The new bandwidth is returned in @upstream_bw and @downstream_bw. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, | |
| int *downstream_bw) | |
| { | |
| int ret, consumed_up, consumed_down; | |
| ret = usb4_usb3_port_set_cm_request(port); | |
| if (ret) | |
| return ret; | |
| ret = usb4_usb3_port_read_consumed_bandwidth(port, &consumed_up, | |
| &consumed_down); | |
| if (ret) | |
| goto err_request; | |
| /* | |
| * Always keep 900 Mb/s to make sure xHCI has at least some | |
| * bandwidth available for isochronous traffic. | |
| */ | |
| if (consumed_up < 900) | |
| consumed_up = 900; | |
| if (consumed_down < 900) | |
| consumed_down = 900; | |
| ret = usb4_usb3_port_write_allocated_bandwidth(port, consumed_up, | |
| consumed_down); | |
| if (ret) | |
| goto err_request; | |
| *upstream_bw = consumed_up; | |
| *downstream_bw = consumed_down; | |
| err_request: | |
| usb4_usb3_port_clear_cm_request(port); | |
| return ret; | |
| } | |
| static bool is_usb4_dpin(const struct tb_port *port) | |
| { | |
| if (!tb_port_is_dpin(port)) | |
| return false; | |
| if (!tb_switch_is_usb4(port->sw)) | |
| return false; | |
| return true; | |
| } | |
| /** | |
| * usb4_dp_port_set_cm_id() - Assign CM ID to the DP IN adapter | |
| * @port: DP IN adapter | |
| * @cm_id: CM ID to assign | |
| * | |
| * Sets CM ID for the @port. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the @port does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_CM_ID_MASK; | |
| val |= cm_id << ADP_DP_CS_2_CM_ID_SHIFT; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_bandwidth_mode_supported() - Is the bandwidth allocation mode | |
| * supported | |
| * @port: DP IN adapter to check | |
| * | |
| * Can be called to any DP IN adapter. | |
| * | |
| * Return: %true if the adapter supports USB4 bandwidth allocation mode, | |
| * %false otherwise. | |
| */ | |
| bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!is_usb4_dpin(port)) | |
| return false; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + DP_LOCAL_CAP, 1); | |
| if (ret) | |
| return false; | |
| return !!(val & DP_COMMON_CAP_BW_MODE); | |
| } | |
| /** | |
| * usb4_dp_port_bandwidth_mode_enabled() - Is the bandwidth allocation mode | |
| * enabled | |
| * @port: DP IN adapter to check | |
| * | |
| * Can be called to any DP IN adapter. | |
| * | |
| * Return: %true if the bandwidth allocation mode has been enabled, | |
| * %false otherwise. | |
| */ | |
| bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) | |
| { | |
| int ret; | |
| u32 val; | |
| if (!is_usb4_dpin(port)) | |
| return false; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_8, 1); | |
| if (ret) | |
| return false; | |
| return !!(val & ADP_DP_CS_8_DPME); | |
| } | |
| /** | |
| * usb4_dp_port_set_cm_bandwidth_mode_supported() - Set/clear CM support for | |
| * bandwidth allocation mode | |
| * @port: DP IN adapter | |
| * @supported: Does the CM support bandwidth allocation mode | |
| * | |
| * Can be called to any DP IN adapter. Sets or clears the CM support bit | |
| * of the DP IN adapter. | |
| * | |
| * * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the passed IN adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, | |
| bool supported) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| if (supported) | |
| val |= ADP_DP_CS_2_CMMS; | |
| else | |
| val &= ~ADP_DP_CS_2_CMMS; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_group_id() - Return Group ID assigned for the adapter | |
| * @port: DP IN adapter | |
| * | |
| * Reads bandwidth allocation Group ID from the DP IN adapter and | |
| * returns it. | |
| * | |
| * Return: | |
| * * Group ID assigned to adapter @port. | |
| * * %-EOPNOTSUPP - If adapter does not support setting GROUP_ID. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_group_id(struct tb_port *port) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| return (val & ADP_DP_CS_2_GROUP_ID_MASK) >> ADP_DP_CS_2_GROUP_ID_SHIFT; | |
| } | |
| /** | |
| * usb4_dp_port_set_group_id() - Set adapter Group ID | |
| * @port: DP IN adapter | |
| * @group_id: Group ID for the adapter | |
| * | |
| * Sets bandwidth allocation mode Group ID for the DP IN adapter. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_GROUP_ID_MASK; | |
| val |= group_id << ADP_DP_CS_2_GROUP_ID_SHIFT; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_nrd() - Read non-reduced rate and lanes | |
| * @port: DP IN adapter | |
| * @rate: Non-reduced rate in Mb/s is placed here | |
| * @lanes: Non-reduced lanes are placed here | |
| * | |
| * Reads the non-reduced rate and lanes from the DP IN adapter. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) | |
| { | |
| u32 val, tmp; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| tmp = (val & ADP_DP_CS_2_NRD_MLR_MASK) >> ADP_DP_CS_2_NRD_MLR_SHIFT; | |
| switch (tmp) { | |
| case DP_COMMON_CAP_RATE_RBR: | |
| *rate = 1620; | |
| break; | |
| case DP_COMMON_CAP_RATE_HBR: | |
| *rate = 2700; | |
| break; | |
| case DP_COMMON_CAP_RATE_HBR2: | |
| *rate = 5400; | |
| break; | |
| case DP_COMMON_CAP_RATE_HBR3: | |
| *rate = 8100; | |
| break; | |
| } | |
| tmp = val & ADP_DP_CS_2_NRD_MLC_MASK; | |
| switch (tmp) { | |
| case DP_COMMON_CAP_1_LANE: | |
| *lanes = 1; | |
| break; | |
| case DP_COMMON_CAP_2_LANES: | |
| *lanes = 2; | |
| break; | |
| case DP_COMMON_CAP_4_LANES: | |
| *lanes = 4; | |
| break; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * usb4_dp_port_set_nrd() - Set non-reduced rate and lanes | |
| * @port: DP IN adapter | |
| * @rate: Non-reduced rate in Mb/s | |
| * @lanes: Non-reduced lanes | |
| * | |
| * Before the capabilities reduction, this function can be used to set | |
| * the non-reduced values for the DP IN adapter. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_NRD_MLR_MASK; | |
| switch (rate) { | |
| case 1620: | |
| break; | |
| case 2700: | |
| val |= (DP_COMMON_CAP_RATE_HBR << ADP_DP_CS_2_NRD_MLR_SHIFT) | |
| & ADP_DP_CS_2_NRD_MLR_MASK; | |
| break; | |
| case 5400: | |
| val |= (DP_COMMON_CAP_RATE_HBR2 << ADP_DP_CS_2_NRD_MLR_SHIFT) | |
| & ADP_DP_CS_2_NRD_MLR_MASK; | |
| break; | |
| case 8100: | |
| val |= (DP_COMMON_CAP_RATE_HBR3 << ADP_DP_CS_2_NRD_MLR_SHIFT) | |
| & ADP_DP_CS_2_NRD_MLR_MASK; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| val &= ~ADP_DP_CS_2_NRD_MLC_MASK; | |
| switch (lanes) { | |
| case 1: | |
| break; | |
| case 2: | |
| val |= DP_COMMON_CAP_2_LANES; | |
| break; | |
| case 4: | |
| val |= DP_COMMON_CAP_4_LANES; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_granularity() - Return granularity for the bandwidth values | |
| * @port: DP IN adapter | |
| * | |
| * Reads the programmed granularity from @port. | |
| * | |
| * Return: | |
| * * Granularity value of a @port. | |
| * * %-EOPNOTSUPP - If the DP IN adapter does not support bandwidth | |
| * allocation mode. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_granularity(struct tb_port *port) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ADP_DP_CS_2_GR_MASK; | |
| val >>= ADP_DP_CS_2_GR_SHIFT; | |
| switch (val) { | |
| case ADP_DP_CS_2_GR_0_25G: | |
| return 250; | |
| case ADP_DP_CS_2_GR_0_5G: | |
| return 500; | |
| case ADP_DP_CS_2_GR_1G: | |
| return 1000; | |
| } | |
| return -EINVAL; | |
| } | |
| /** | |
| * usb4_dp_port_set_granularity() - Set granularity for the bandwidth values | |
| * @port: DP IN adapter | |
| * @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250. | |
| * | |
| * Sets the granularity used with the estimated, allocated and requested | |
| * bandwidth. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_GR_MASK; | |
| switch (granularity) { | |
| case 250: | |
| val |= ADP_DP_CS_2_GR_0_25G << ADP_DP_CS_2_GR_SHIFT; | |
| break; | |
| case 500: | |
| val |= ADP_DP_CS_2_GR_0_5G << ADP_DP_CS_2_GR_SHIFT; | |
| break; | |
| case 1000: | |
| val |= ADP_DP_CS_2_GR_1G << ADP_DP_CS_2_GR_SHIFT; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_set_estimated_bandwidth() - Set estimated bandwidth | |
| * @port: DP IN adapter | |
| * @bw: Estimated bandwidth in Mb/s. | |
| * | |
| * Sets the estimated bandwidth to @bw. Set the granularity by calling | |
| * usb4_dp_port_set_granularity() before calling this. The @bw is rounded | |
| * down to the closest granularity multiplier. | |
| * | |
| * Return: | |
| * * %0 - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support this. | |
| * * Negative errno - Another error occurred. | |
| */ | |
| int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) | |
| { | |
| u32 val, granularity; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = usb4_dp_port_granularity(port); | |
| if (ret < 0) | |
| return ret; | |
| granularity = ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_ESTIMATED_BW_MASK; | |
| val |= (bw / granularity) << ADP_DP_CS_2_ESTIMATED_BW_SHIFT; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth | |
| * @port: DP IN adapter | |
| * | |
| * Reads the allocated bandwidth for @port in Mb/s (taking into account | |
| * the programmed granularity). | |
| * | |
| * Return: Allocated bandwidth in Mb/s or negative errno in case of an error. | |
| */ | |
| int usb4_dp_port_allocated_bandwidth(struct tb_port *port) | |
| { | |
| u32 val, granularity; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = usb4_dp_port_granularity(port); | |
| if (ret < 0) | |
| return ret; | |
| granularity = ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + DP_STATUS, 1); | |
| if (ret) | |
| return ret; | |
| val &= DP_STATUS_ALLOCATED_BW_MASK; | |
| val >>= DP_STATUS_ALLOCATED_BW_SHIFT; | |
| return val * granularity; | |
| } | |
| static int __usb4_dp_port_set_cm_ack(struct tb_port *port, bool ack) | |
| { | |
| u32 val; | |
| int ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| if (ack) | |
| val |= ADP_DP_CS_2_CA; | |
| else | |
| val &= ~ADP_DP_CS_2_CA; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| static inline int usb4_dp_port_set_cm_ack(struct tb_port *port) | |
| { | |
| return __usb4_dp_port_set_cm_ack(port, true); | |
| } | |
| static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, | |
| int timeout_msec) | |
| { | |
| ktime_t end; | |
| u32 val; | |
| int ret; | |
| ret = __usb4_dp_port_set_cm_ack(port, false); | |
| if (ret) | |
| return ret; | |
| end = ktime_add_ms(ktime_get(), timeout_msec); | |
| do { | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_8, 1); | |
| if (ret) | |
| return ret; | |
| if (!(val & ADP_DP_CS_8_DR)) | |
| break; | |
| usleep_range(50, 100); | |
| } while (ktime_before(ktime_get(), end)); | |
| if (val & ADP_DP_CS_8_DR) { | |
| tb_port_warn(port, "timeout waiting for DPTX request to clear\n"); | |
| return -ETIMEDOUT; | |
| } | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~ADP_DP_CS_2_CA; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_2, 1); | |
| } | |
| /** | |
| * usb4_dp_port_allocate_bandwidth() - Set allocated bandwidth | |
| * @port: DP IN adapter | |
| * @bw: New allocated bandwidth in Mb/s | |
| * | |
| * Communicates the new allocated bandwidth with the DPCD (graphics | |
| * driver). Takes into account the programmed granularity. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) | |
| { | |
| u32 val, granularity; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = usb4_dp_port_granularity(port); | |
| if (ret < 0) | |
| return ret; | |
| granularity = ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + DP_STATUS, 1); | |
| if (ret) | |
| return ret; | |
| val &= ~DP_STATUS_ALLOCATED_BW_MASK; | |
| val |= (bw / granularity) << DP_STATUS_ALLOCATED_BW_SHIFT; | |
| ret = tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + DP_STATUS, 1); | |
| if (ret) | |
| return ret; | |
| ret = usb4_dp_port_set_cm_ack(port); | |
| if (ret) | |
| return ret; | |
| return usb4_dp_port_wait_and_clear_cm_ack(port, 500); | |
| } | |
| /** | |
| * usb4_dp_port_requested_bandwidth() - Read requested bandwidth | |
| * @port: DP IN adapter | |
| * | |
| * Reads the DPCD (graphics driver) requested bandwidth and returns it | |
| * in Mb/s. Takes the programmed granularity into account. | |
| * | |
| * Return: | |
| * * Requested bandwidth in Mb/s - On success. | |
| * * %-EOPNOTSUPP - If the adapter does not support bandwidth allocation | |
| * mode. | |
| * * %ENODATA - If there is no active bandwidth request from the graphics | |
| * driver. | |
| * * Negative errno - On failure. | |
| */ | |
| int usb4_dp_port_requested_bandwidth(struct tb_port *port) | |
| { | |
| u32 val, granularity; | |
| int ret; | |
| if (!is_usb4_dpin(port)) | |
| return -EOPNOTSUPP; | |
| ret = usb4_dp_port_granularity(port); | |
| if (ret < 0) | |
| return ret; | |
| granularity = ret; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_DP_CS_8, 1); | |
| if (ret) | |
| return ret; | |
| if (!(val & ADP_DP_CS_8_DR)) | |
| return -ENODATA; | |
| return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity; | |
| } | |
| /** | |
| * usb4_pci_port_set_ext_encapsulation() - Enable/disable extended encapsulation | |
| * @port: PCIe adapter | |
| * @enable: Enable/disable extended encapsulation | |
| * | |
| * Enables or disables extended encapsulation used in PCIe tunneling. Caller | |
| * needs to make sure both adapters support this before enabling. | |
| * | |
| * Return: %0 on success, negative errno otherwise. | |
| */ | |
| int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!tb_port_is_pcie_up(port) && !tb_port_is_pcie_down(port)) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_PCIE_CS_1, 1); | |
| if (ret) | |
| return ret; | |
| if (enable) | |
| val |= ADP_PCIE_CS_1_EE; | |
| else | |
| val &= ~ADP_PCIE_CS_1_EE; | |
| return tb_port_write(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_PCIE_CS_1, 1); | |
| } | |
| /** | |
| * usb4_pci_port_ltssm_state() - Read PCIe adapter LTSSM state | |
| * @port: PCIe adapter | |
| * | |
| * Return: | |
| * * LTSSM state of @port. | |
| * * Negative errno - On failure. | |
| */ | |
| int usb4_pci_port_ltssm_state(struct tb_port *port) | |
| { | |
| u32 val; | |
| int ret; | |
| if (!tb_port_is_pcie_down(port) && !tb_port_is_pcie_up(port)) | |
| return -EINVAL; | |
| ret = tb_port_read(port, &val, TB_CFG_PORT, | |
| port->cap_adap + ADP_PCIE_CS_0, 1); | |
| if (ret) | |
| return ret; | |
| return FIELD_GET(ADP_PCIE_CS_0_LTSSM_MASK, val); | |
| } |