Commit | Line | Data |
---|---|---|
d93c19a1 VP |
1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ | |
3 | ||
4 | #include <linux/kernel.h> | |
5 | #include <linux/err.h> | |
f1436c80 | 6 | #include <linux/sfp.h> |
d93c19a1 VP |
7 | |
8 | #include "core.h" | |
9 | #include "core_env.h" | |
10 | #include "item.h" | |
11 | #include "reg.h" | |
12 | ||
13 | static int mlxsw_env_validate_cable_ident(struct mlxsw_core *core, int id, | |
14 | bool *qsfp) | |
15 | { | |
16 | char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; | |
17 | char mcia_pl[MLXSW_REG_MCIA_LEN]; | |
18 | u8 ident; | |
19 | int err; | |
20 | ||
21 | mlxsw_reg_mcia_pack(mcia_pl, id, 0, MLXSW_REG_MCIA_PAGE0_LO_OFF, 0, 1, | |
22 | MLXSW_REG_MCIA_I2C_ADDR_LOW); | |
23 | err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl); | |
24 | if (err) | |
25 | return err; | |
26 | mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); | |
27 | ident = eeprom_tmp[0]; | |
28 | switch (ident) { | |
29 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: | |
30 | *qsfp = false; | |
31 | break; | |
32 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: /* fall-through */ | |
33 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: /* fall-through */ | |
34 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: /* fall-through */ | |
35 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD: | |
36 | *qsfp = true; | |
37 | break; | |
38 | default: | |
39 | return -EINVAL; | |
40 | } | |
41 | ||
42 | return 0; | |
43 | } | |
44 | ||
1b1c6c1a VP |
45 | static int |
46 | mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, int module, | |
47 | u16 offset, u16 size, void *data, | |
48 | unsigned int *p_read_size) | |
49 | { | |
50 | char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; | |
51 | char mcia_pl[MLXSW_REG_MCIA_LEN]; | |
52 | u16 i2c_addr; | |
a45bfb5a | 53 | u8 page = 0; |
1b1c6c1a VP |
54 | int status; |
55 | int err; | |
56 | ||
57 | size = min_t(u16, size, MLXSW_REG_MCIA_EEPROM_SIZE); | |
58 | ||
59 | if (offset < MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH && | |
60 | offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) | |
61 | /* Cross pages read, read until offset 256 in low page */ | |
62 | size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset; | |
63 | ||
64 | i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_LOW; | |
65 | if (offset >= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) { | |
a45bfb5a VP |
66 | page = MLXSW_REG_MCIA_PAGE_GET(offset); |
67 | offset -= MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH * page; | |
68 | /* When reading upper pages 1, 2 and 3 the offset starts at | |
69 | * 128. Please refer to "QSFP+ Memory Map" figure in SFF-8436 | |
70 | * specification for graphical depiction. | |
71 | * MCIA register accepts buffer size <= 48. Page of size 128 | |
72 | * should be read by chunks of size 48, 48, 32. Align the size | |
73 | * of the last chunk to avoid reading after the end of the | |
74 | * page. | |
75 | */ | |
76 | if (offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) | |
77 | size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset; | |
1b1c6c1a VP |
78 | } |
79 | ||
a45bfb5a | 80 | mlxsw_reg_mcia_pack(mcia_pl, module, 0, page, offset, size, i2c_addr); |
1b1c6c1a VP |
81 | |
82 | err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), mcia_pl); | |
83 | if (err) | |
84 | return err; | |
85 | ||
86 | status = mlxsw_reg_mcia_status_get(mcia_pl); | |
87 | if (status) | |
88 | return -EIO; | |
89 | ||
90 | mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); | |
91 | memcpy(data, eeprom_tmp, size); | |
92 | *p_read_size = size; | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
d93c19a1 VP |
97 | int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module, |
98 | int off, int *temp) | |
99 | { | |
100 | char eeprom_tmp[MLXSW_REG_MCIA_EEPROM_SIZE]; | |
101 | union { | |
102 | u8 buf[MLXSW_REG_MCIA_TH_ITEM_SIZE]; | |
103 | u16 temp; | |
104 | } temp_thresh; | |
105 | char mcia_pl[MLXSW_REG_MCIA_LEN] = {0}; | |
e4e93d6d VP |
106 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; |
107 | unsigned int module_temp; | |
d93c19a1 VP |
108 | bool qsfp; |
109 | int err; | |
110 | ||
e4e93d6d VP |
111 | mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, |
112 | false, false); | |
113 | err = mlxsw_reg_query(core, MLXSW_REG(mtmp), mtmp_pl); | |
d93c19a1 VP |
114 | if (err) |
115 | return err; | |
e4e93d6d VP |
116 | mlxsw_reg_mtmp_unpack(mtmp_pl, &module_temp, NULL, NULL); |
117 | if (!module_temp) { | |
d93c19a1 VP |
118 | *temp = 0; |
119 | return 0; | |
d93c19a1 VP |
120 | } |
121 | ||
122 | /* Read Free Side Device Temperature Thresholds from page 03h | |
123 | * (MSB at lower byte address). | |
124 | * Bytes: | |
125 | * 128-129 - Temp High Alarm (SFP_TEMP_HIGH_ALARM); | |
126 | * 130-131 - Temp Low Alarm (SFP_TEMP_LOW_ALARM); | |
127 | * 132-133 - Temp High Warning (SFP_TEMP_HIGH_WARN); | |
128 | * 134-135 - Temp Low Warning (SFP_TEMP_LOW_WARN); | |
129 | */ | |
130 | ||
131 | /* Validate module identifier value. */ | |
132 | err = mlxsw_env_validate_cable_ident(core, module, &qsfp); | |
133 | if (err) | |
134 | return err; | |
135 | ||
136 | if (qsfp) | |
137 | mlxsw_reg_mcia_pack(mcia_pl, module, 0, | |
138 | MLXSW_REG_MCIA_TH_PAGE_NUM, | |
139 | MLXSW_REG_MCIA_TH_PAGE_OFF + off, | |
140 | MLXSW_REG_MCIA_TH_ITEM_SIZE, | |
141 | MLXSW_REG_MCIA_I2C_ADDR_LOW); | |
142 | else | |
143 | mlxsw_reg_mcia_pack(mcia_pl, module, 0, | |
144 | MLXSW_REG_MCIA_PAGE0_LO, | |
145 | off, MLXSW_REG_MCIA_TH_ITEM_SIZE, | |
146 | MLXSW_REG_MCIA_I2C_ADDR_HIGH); | |
147 | ||
148 | err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl); | |
149 | if (err) | |
150 | return err; | |
151 | ||
152 | mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); | |
153 | memcpy(temp_thresh.buf, eeprom_tmp, MLXSW_REG_MCIA_TH_ITEM_SIZE); | |
154 | *temp = temp_thresh.temp * 1000; | |
155 | ||
156 | return 0; | |
157 | } | |
1b1c6c1a VP |
158 | |
159 | int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module, | |
160 | struct ethtool_modinfo *modinfo) | |
161 | { | |
162 | u8 module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE]; | |
163 | u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE; | |
f1436c80 | 164 | u8 module_rev_id, module_id, diag_mon; |
1b1c6c1a VP |
165 | unsigned int read_size; |
166 | int err; | |
167 | ||
168 | err = mlxsw_env_query_module_eeprom(mlxsw_core, module, 0, offset, | |
169 | module_info, &read_size); | |
170 | if (err) | |
171 | return err; | |
172 | ||
173 | if (read_size < offset) | |
174 | return -EIO; | |
175 | ||
176 | module_rev_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID]; | |
177 | module_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID]; | |
178 | ||
179 | switch (module_id) { | |
180 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP: | |
181 | modinfo->type = ETH_MODULE_SFF_8436; | |
a45bfb5a | 182 | modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; |
1b1c6c1a VP |
183 | break; |
184 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS: /* fall-through */ | |
185 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28: | |
186 | if (module_id == MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28 || | |
187 | module_rev_id >= | |
188 | MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8636) { | |
189 | modinfo->type = ETH_MODULE_SFF_8636; | |
a45bfb5a | 190 | modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN; |
1b1c6c1a VP |
191 | } else { |
192 | modinfo->type = ETH_MODULE_SFF_8436; | |
a45bfb5a | 193 | modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN; |
1b1c6c1a VP |
194 | } |
195 | break; | |
196 | case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP: | |
f1436c80 VP |
197 | /* Verify if transceiver provides diagnostic monitoring page */ |
198 | err = mlxsw_env_query_module_eeprom(mlxsw_core, module, | |
199 | SFP_DIAGMON, 1, &diag_mon, | |
200 | &read_size); | |
201 | if (err) | |
202 | return err; | |
203 | ||
204 | if (read_size < 1) | |
205 | return -EIO; | |
206 | ||
1b1c6c1a | 207 | modinfo->type = ETH_MODULE_SFF_8472; |
f1436c80 VP |
208 | if (diag_mon) |
209 | modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; | |
210 | else | |
211 | modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2; | |
1b1c6c1a VP |
212 | break; |
213 | default: | |
214 | return -EINVAL; | |
215 | } | |
216 | ||
217 | return 0; | |
218 | } | |
219 | EXPORT_SYMBOL(mlxsw_env_get_module_info); | |
220 | ||
221 | int mlxsw_env_get_module_eeprom(struct net_device *netdev, | |
222 | struct mlxsw_core *mlxsw_core, int module, | |
223 | struct ethtool_eeprom *ee, u8 *data) | |
224 | { | |
225 | int offset = ee->offset; | |
226 | unsigned int read_size; | |
227 | int i = 0; | |
228 | int err; | |
229 | ||
230 | if (!ee->len) | |
231 | return -EINVAL; | |
232 | ||
233 | memset(data, 0, ee->len); | |
234 | ||
235 | while (i < ee->len) { | |
236 | err = mlxsw_env_query_module_eeprom(mlxsw_core, module, offset, | |
237 | ee->len - i, data + i, | |
238 | &read_size); | |
239 | if (err) { | |
240 | netdev_err(netdev, "Eeprom query failed\n"); | |
241 | return err; | |
242 | } | |
243 | ||
244 | i += read_size; | |
245 | offset += read_size; | |
246 | } | |
247 | ||
248 | return 0; | |
249 | } | |
250 | EXPORT_SYMBOL(mlxsw_env_get_module_eeprom); |