diff options
Diffstat (limited to 'drivers/thunderbolt/cap.c')
-rw-r--r-- | drivers/thunderbolt/cap.c | 169 |
1 files changed, 91 insertions, 78 deletions
diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index a7b47e7cddbd..38bc27a5ce4f 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -9,6 +9,8 @@ #include "tb.h" +#define CAP_OFFSET_MAX 0xff +#define VSE_CAP_OFFSET_MAX 0xffff struct tb_cap_any { union { @@ -18,99 +20,110 @@ struct tb_cap_any { }; } __packed; -static bool tb_cap_is_basic(struct tb_cap_any *cap) -{ - /* basic.cap is u8. This checks only the lower 8 bit of cap. */ - return cap->basic.cap != 5; -} - -static bool tb_cap_is_long(struct tb_cap_any *cap) +/** + * tb_port_find_cap() - Find port capability + * @port: Port to find the capability for + * @cap: Capability to look + * + * Returns offset to start of capability or %-ENOENT if no such + * capability was found. Negative errno is returned if there was an + * error. + */ +int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) { - return !tb_cap_is_basic(cap) - && cap->extended_short.next == 0 - && cap->extended_short.length == 0; -} + u32 offset; -static enum tb_cap tb_cap(struct tb_cap_any *cap) -{ - if (tb_cap_is_basic(cap)) - return cap->basic.cap; + /* + * DP out adapters claim to implement TMU capability but in + * reality they do not so we hard code the adapter specific + * capability offset here. + */ + if (port->config.type == TB_TYPE_DP_HDMI_OUT) + offset = 0x39; else - /* extended_short/long have cap at the same offset. */ - return cap->extended_short.cap; + offset = 0x1; + + do { + struct tb_cap_any header; + int ret; + + ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); + if (ret) + return ret; + + if (header.basic.cap == cap) + return offset; + + offset = header.basic.next; + } while (offset); + + return -ENOENT; } -static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset) +static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) { - int next; - if (offset == 1) { - /* - * The first pointer is part of the switch header and always - * a simple pointer. - */ - next = cap->basic.next; - } else { - /* - * Somehow Intel decided to use 3 different types of capability - * headers. It is not like anyone could have predicted that - * single byte offsets are not enough... - */ - if (tb_cap_is_basic(cap)) - next = cap->basic.next; - else if (!tb_cap_is_long(cap)) - next = cap->extended_short.next; - else - next = cap->extended_long.next; + int offset = sw->config.first_cap_offset; + + while (offset > 0 && offset < CAP_OFFSET_MAX) { + struct tb_cap_any header; + int ret; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); + if (ret) + return ret; + + if (header.basic.cap == cap) + return offset; + + offset = header.basic.next; } - /* - * "Hey, we could terminate some capability lists with a null offset - * and others with a pointer to the last element." - "Great idea!" - */ - if (next == offset) - return 0; - return next; + + return -ENOENT; } /** - * tb_find_cap() - find a capability + * tb_switch_find_vse_cap() - Find switch vendor specific capability + * @sw: Switch to find the capability for + * @vsec: Vendor specific capability to look * - * Return: Returns a positive offset if the capability was found and 0 if not. - * Returns an error code on failure. + * Functions enumerates vendor specific capabilities (VSEC) of a switch + * and returns offset when capability matching @vsec is found. If no + * such capability is found returns %-ENOENT. In case of error returns + * negative errno. */ -int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap) +int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) { - u32 offset = 1; struct tb_cap_any header; - int res; - int retries = 10; - while (retries--) { - res = tb_port_read(port, &header, space, offset, 1); - if (res) { - /* Intel needs some help with linked lists. */ - if (space == TB_CFG_PORT && offset == 0xa - && port->config.type == TB_TYPE_DP_HDMI_OUT) { - offset = 0x39; - continue; - } - return res; - } - if (offset != 1) { - if (tb_cap(&header) == cap) + int offset; + + offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); + if (offset < 0) + return offset; + + while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { + int ret; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); + if (ret) + return ret; + + /* + * Extended vendor specific capabilities come in two + * flavors: short and long. The latter is used when + * offset is over 0xff. + */ + if (offset >= CAP_OFFSET_MAX) { + if (header.extended_long.vsec_id == vsec) return offset; - if (tb_cap_is_long(&header)) { - /* tb_cap_extended_long is 2 dwords */ - res = tb_port_read(port, &header, space, - offset, 2); - if (res) - return res; - } + offset = header.extended_long.next; + } else { + if (header.extended_short.vsec_id == vsec) + return offset; + if (!header.extended_short.length) + return -ENOENT; + offset = header.extended_short.next; } - offset = tb_cap_next(&header, offset); - if (!offset) - return 0; } - tb_port_WARN(port, - "run out of retries while looking for cap %#x in config space %d, last offset: %#x\n", - cap, space, offset); - return -EIO; + + return -ENOENT; } |