diff options
Diffstat (limited to 'drivers/thunderbolt/cap.c')
-rw-r--r-- | drivers/thunderbolt/cap.c | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c new file mode 100644 index 000000000..c2277b8ee --- /dev/null +++ b/drivers/thunderbolt/cap.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thunderbolt Cactus Ridge driver - capabilities lookup + * + * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> + */ + +#include <linux/slab.h> +#include <linux/errno.h> + +#include "tb.h" + +#define CAP_OFFSET_MAX 0xff +#define VSE_CAP_OFFSET_MAX 0xffff + +struct tb_cap_any { + union { + struct tb_cap_basic basic; + struct tb_cap_extended_short extended_short; + struct tb_cap_extended_long extended_long; + }; +} __packed; + +/** + * 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) +{ + u32 offset; + + /* + * 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 + 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 int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) +{ + 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; + } + + return -ENOENT; +} + +/** + * tb_switch_find_vse_cap() - Find switch vendor specific capability + * @sw: Switch to find the capability for + * @vsec: Vendor specific capability to look + * + * 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_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) +{ + struct tb_cap_any header; + 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; + 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; + } + } + + return -ENOENT; +} |