1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Thunderbolt Cactus Ridge driver - capabilities lookup 4 * 5 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 6 */ 7 8 #include <linux/slab.h> 9 #include <linux/errno.h> 10 11 #include "tb.h" 12 13 #define CAP_OFFSET_MAX 0xff 14 #define VSE_CAP_OFFSET_MAX 0xffff 15 16 struct tb_cap_any { 17 union { 18 struct tb_cap_basic basic; 19 struct tb_cap_extended_short extended_short; 20 struct tb_cap_extended_long extended_long; 21 }; 22 } __packed; 23 24 /** 25 * tb_port_find_cap() - Find port capability 26 * @port: Port to find the capability for 27 * @cap: Capability to look 28 * 29 * Returns offset to start of capability or %-ENOENT if no such 30 * capability was found. Negative errno is returned if there was an 31 * error. 32 */ 33 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 34 { 35 u32 offset; 36 37 /* 38 * DP out adapters claim to implement TMU capability but in 39 * reality they do not so we hard code the adapter specific 40 * capability offset here. 41 */ 42 if (port->config.type == TB_TYPE_DP_HDMI_OUT) 43 offset = 0x39; 44 else 45 offset = 0x1; 46 47 do { 48 struct tb_cap_any header; 49 int ret; 50 51 ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); 52 if (ret) 53 return ret; 54 55 if (header.basic.cap == cap) 56 return offset; 57 58 offset = header.basic.next; 59 } while (offset); 60 61 return -ENOENT; 62 } 63 64 static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) 65 { 66 int offset = sw->config.first_cap_offset; 67 68 while (offset > 0 && offset < CAP_OFFSET_MAX) { 69 struct tb_cap_any header; 70 int ret; 71 72 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); 73 if (ret) 74 return ret; 75 76 if (header.basic.cap == cap) 77 return offset; 78 79 offset = header.basic.next; 80 } 81 82 return -ENOENT; 83 } 84 85 /** 86 * tb_switch_find_vse_cap() - Find switch vendor specific capability 87 * @sw: Switch to find the capability for 88 * @vsec: Vendor specific capability to look 89 * 90 * Functions enumerates vendor specific capabilities (VSEC) of a switch 91 * and returns offset when capability matching @vsec is found. If no 92 * such capability is found returns %-ENOENT. In case of error returns 93 * negative errno. 94 */ 95 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) 96 { 97 struct tb_cap_any header; 98 int offset; 99 100 offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); 101 if (offset < 0) 102 return offset; 103 104 while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { 105 int ret; 106 107 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); 108 if (ret) 109 return ret; 110 111 /* 112 * Extended vendor specific capabilities come in two 113 * flavors: short and long. The latter is used when 114 * offset is over 0xff. 115 */ 116 if (offset >= CAP_OFFSET_MAX) { 117 if (header.extended_long.vsec_id == vsec) 118 return offset; 119 offset = header.extended_long.next; 120 } else { 121 if (header.extended_short.vsec_id == vsec) 122 return offset; 123 if (!header.extended_short.length) 124 return -ENOENT; 125 offset = header.extended_short.next; 126 } 127 } 128 129 return -ENOENT; 130 } 131