1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2022 Intel Corporation 3 * Implements SFF-8472 optics diagnostics. 4 */ 5 6 #include <stdio.h> 7 8 #include "sff_common.h" 9 10 /* Offsets in decimal, for direct comparison with the SFF specs */ 11 12 /* A0-based EEPROM offsets for DOM support checks */ 13 #define SFF_A0_DOM 92 14 #define SFF_A0_OPTIONS 93 15 #define SFF_A0_COMP 94 16 17 /* EEPROM bit values for various registers */ 18 #define SFF_A0_DOM_EXTCAL RTE_BIT32(4) 19 #define SFF_A0_DOM_INTCAL RTE_BIT32(5) 20 #define SFF_A0_DOM_IMPL RTE_BIT32(6) 21 #define SFF_A0_DOM_PWRT RTE_BIT32(3) 22 23 #define SFF_A0_OPTIONS_AW RTE_BIT32(7) 24 25 /* 26 * This is the offset at which the A2 page is in the EEPROM 27 * blob returned by the kernel. 28 */ 29 #define SFF_A2_BASE 0x100 30 31 /* A2-based offsets for DOM */ 32 #define SFF_A2_TEMP 96 33 #define SFF_A2_TEMP_HALRM 0 34 #define SFF_A2_TEMP_LALRM 2 35 #define SFF_A2_TEMP_HWARN 4 36 #define SFF_A2_TEMP_LWARN 6 37 38 #define SFF_A2_VCC 98 39 #define SFF_A2_VCC_HALRM 8 40 #define SFF_A2_VCC_LALRM 10 41 #define SFF_A2_VCC_HWARN 12 42 #define SFF_A2_VCC_LWARN 14 43 44 #define SFF_A2_BIAS 100 45 #define SFF_A2_BIAS_HALRM 16 46 #define SFF_A2_BIAS_LALRM 18 47 #define SFF_A2_BIAS_HWARN 20 48 #define SFF_A2_BIAS_LWARN 22 49 50 #define SFF_A2_TX_PWR 102 51 #define SFF_A2_TX_PWR_HALRM 24 52 #define SFF_A2_TX_PWR_LALRM 26 53 #define SFF_A2_TX_PWR_HWARN 28 54 #define SFF_A2_TX_PWR_LWARN 30 55 56 #define SFF_A2_RX_PWR 104 57 #define SFF_A2_RX_PWR_HALRM 32 58 #define SFF_A2_RX_PWR_LALRM 34 59 #define SFF_A2_RX_PWR_HWARN 36 60 #define SFF_A2_RX_PWR_LWARN 38 61 62 #define SFF_A2_ALRM_FLG 112 63 #define SFF_A2_WARN_FLG 116 64 65 /* 32-bit little-endian calibration constants */ 66 #define SFF_A2_CAL_RXPWR4 56 67 #define SFF_A2_CAL_RXPWR3 60 68 #define SFF_A2_CAL_RXPWR2 64 69 #define SFF_A2_CAL_RXPWR1 68 70 #define SFF_A2_CAL_RXPWR0 72 71 72 /* 16-bit little endian calibration constants */ 73 #define SFF_A2_CAL_TXI_SLP 76 74 #define SFF_A2_CAL_TXI_OFF 78 75 #define SFF_A2_CAL_TXPWR_SLP 80 76 #define SFF_A2_CAL_TXPWR_OFF 82 77 #define SFF_A2_CAL_T_SLP 84 78 #define SFF_A2_CAL_T_OFF 86 79 #define SFF_A2_CAL_V_SLP 88 80 #define SFF_A2_CAL_V_OFF 90 81 82 static struct sff_8472_aw_flags { 83 const char *str; /* Human-readable string, null at the end */ 84 int offset; /* A2-relative address offset */ 85 uint8_t value; /* Alarm is on if (offset & value) != 0. */ 86 } sff_8472_aw_flags[] = { 87 { "Laser bias current high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(3) }, 88 { "Laser bias current low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(2) }, 89 { "Laser bias current high warning", SFF_A2_WARN_FLG, RTE_BIT32(3) }, 90 { "Laser bias current low warning", SFF_A2_WARN_FLG, RTE_BIT32(2) }, 91 92 { "Laser output power high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(1) }, 93 { "Laser output power low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(0) }, 94 { "Laser output power high warning", SFF_A2_WARN_FLG, RTE_BIT32(1) }, 95 { "Laser output power low warning", SFF_A2_WARN_FLG, RTE_BIT32(0) }, 96 97 { "Module temperature high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(7) }, 98 { "Module temperature low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(6) }, 99 { "Module temperature high warning", SFF_A2_WARN_FLG, RTE_BIT32(7) }, 100 { "Module temperature low warning", SFF_A2_WARN_FLG, RTE_BIT32(6) }, 101 102 { "Module voltage high alarm", SFF_A2_ALRM_FLG, RTE_BIT32(5) }, 103 { "Module voltage low alarm", SFF_A2_ALRM_FLG, RTE_BIT32(4) }, 104 { "Module voltage high warning", SFF_A2_WARN_FLG, RTE_BIT32(5) }, 105 { "Module voltage low warning", SFF_A2_WARN_FLG, RTE_BIT32(4) }, 106 107 { "Laser rx power high alarm", SFF_A2_ALRM_FLG + 1, RTE_BIT32(7) }, 108 { "Laser rx power low alarm", SFF_A2_ALRM_FLG + 1, RTE_BIT32(6) }, 109 { "Laser rx power high warning", SFF_A2_WARN_FLG + 1, RTE_BIT32(7) }, 110 { "Laser rx power low warning", SFF_A2_WARN_FLG + 1, RTE_BIT32(6) }, 111 112 { NULL, 0, 0 }, 113 }; 114 115 /* Most common case: 16-bit unsigned integer in a certain unit */ 116 #define A2_OFFSET_TO_U16(offset) \ 117 (data[SFF_A2_BASE + (offset)] << 8 | data[SFF_A2_BASE + (offset) + 1]) 118 119 /* Calibration slope is a number between 0.0 included and 256.0 excluded. */ 120 #define A2_OFFSET_TO_SLP(offset) \ 121 (data[SFF_A2_BASE + (offset)] + data[SFF_A2_BASE + (offset) + 1] / 256.) 122 123 /* Calibration offset is an integer from -32768 to 32767 */ 124 #define A2_OFFSET_TO_OFF(offset) \ 125 ((int16_t)A2_OFFSET_TO_U16(offset)) 126 127 /* RXPWR(x) are IEEE-754 floating point numbers in big-endian format */ 128 #define A2_OFFSET_TO_RXPWRx(offset) \ 129 (befloattoh((const uint32_t *)(data + SFF_A2_BASE + (offset)))) 130 131 /* 132 * 2-byte internal temperature conversions: 133 * First byte is a signed 8-bit integer, which is the temp decimal part 134 * Second byte are 1/256th of degree, which are added to the dec part. 135 */ 136 #define A2_OFFSET_TO_TEMP(offset) ((int16_t)A2_OFFSET_TO_U16(offset)) 137 138 static void sff_8472_dom_parse(const uint8_t *data, struct sff_diags *sd) 139 { 140 sd->bias_cur[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_BIAS); 141 sd->bias_cur[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HALRM); 142 sd->bias_cur[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LALRM); 143 sd->bias_cur[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_HWARN); 144 sd->bias_cur[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_BIAS_LWARN); 145 146 sd->sfp_voltage[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_VCC); 147 sd->sfp_voltage[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_HALRM); 148 sd->sfp_voltage[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_VCC_LALRM); 149 sd->sfp_voltage[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_HWARN); 150 sd->sfp_voltage[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_VCC_LWARN); 151 152 sd->tx_power[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR); 153 sd->tx_power[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HALRM); 154 sd->tx_power[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LALRM); 155 sd->tx_power[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_HWARN); 156 sd->tx_power[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_TX_PWR_LWARN); 157 158 sd->rx_power[SFF_MCURR] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR); 159 sd->rx_power[SFF_HALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HALRM); 160 sd->rx_power[SFF_LALRM] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LALRM); 161 sd->rx_power[SFF_HWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_HWARN); 162 sd->rx_power[SFF_LWARN] = A2_OFFSET_TO_U16(SFF_A2_RX_PWR_LWARN); 163 164 sd->sfp_temp[SFF_MCURR] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP); 165 sd->sfp_temp[SFF_HALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HALRM); 166 sd->sfp_temp[SFF_LALRM] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LALRM); 167 sd->sfp_temp[SFF_HWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_HWARN); 168 sd->sfp_temp[SFF_LWARN] = A2_OFFSET_TO_TEMP(SFF_A2_TEMP_LWARN); 169 } 170 171 /* Converts to a float from a big-endian 4-byte source buffer. */ 172 static float befloattoh(const uint32_t *source) 173 { 174 union { 175 uint32_t src; 176 float dst; 177 } converter; 178 179 converter.src = ntohl(*source); 180 return converter.dst; 181 } 182 183 static void sff_8472_calibration(const uint8_t *data, struct sff_diags *sd) 184 { 185 unsigned long i; 186 uint16_t rx_reading; 187 188 /* Calibration should occur for all values (threshold and current) */ 189 for (i = 0; i < RTE_DIM(sd->bias_cur); ++i) { 190 /* 191 * Apply calibration formula 1 (Temp., Voltage, Bias, Tx Power) 192 */ 193 sd->bias_cur[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXI_SLP); 194 sd->tx_power[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_TXPWR_SLP); 195 sd->sfp_voltage[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_V_SLP); 196 sd->sfp_temp[i] *= A2_OFFSET_TO_SLP(SFF_A2_CAL_T_SLP); 197 198 sd->bias_cur[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXI_OFF); 199 sd->tx_power[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_TXPWR_OFF); 200 sd->sfp_voltage[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_V_OFF); 201 sd->sfp_temp[i] += A2_OFFSET_TO_OFF(SFF_A2_CAL_T_OFF); 202 203 /* 204 * Apply calibration formula 2 (Rx Power only) 205 */ 206 rx_reading = sd->rx_power[i]; 207 sd->rx_power[i] = A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR0); 208 sd->rx_power[i] += rx_reading * 209 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR1); 210 sd->rx_power[i] += rx_reading * 211 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR2); 212 sd->rx_power[i] += rx_reading * 213 A2_OFFSET_TO_RXPWRx(SFF_A2_CAL_RXPWR3); 214 } 215 } 216 217 static void sff_8472_parse_eeprom(const uint8_t *data, struct sff_diags *sd) 218 { 219 sd->supports_dom = data[SFF_A0_DOM] & SFF_A0_DOM_IMPL; 220 sd->supports_alarms = data[SFF_A0_OPTIONS] & SFF_A0_OPTIONS_AW; 221 sd->calibrated_ext = data[SFF_A0_DOM] & SFF_A0_DOM_EXTCAL; 222 sd->rx_power_type = data[SFF_A0_DOM] & SFF_A0_DOM_PWRT; 223 224 sff_8472_dom_parse(data, sd); 225 226 /* 227 * If the SFP is externally calibrated, we need to read calibration data 228 * and compensate the already stored readings. 229 */ 230 if (sd->calibrated_ext) 231 sff_8472_calibration(data, sd); 232 } 233 234 void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d) 235 { 236 struct sff_diags sd = {0}; 237 const char *rx_power_string = NULL; 238 char val_string[SFF_ITEM_VAL_COMPOSE_SIZE]; 239 int i; 240 241 sff_8472_parse_eeprom(data, &sd); 242 243 if (!sd.supports_dom) { 244 ssf_add_dict_string(d, "Optical diagnostics support", "No"); 245 return; 246 } 247 ssf_add_dict_string(d, "Optical diagnostics support", "Yes"); 248 249 SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_MCURR]); 250 ssf_add_dict_string(d, "Laser bias current", val_string); 251 252 SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_MCURR]); 253 ssf_add_dict_string(d, "Laser output power", val_string); 254 255 if (!sd.rx_power_type) 256 rx_power_string = "Receiver signal OMA"; 257 else 258 rx_power_string = "Receiver signal average optical power"; 259 260 SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_MCURR]); 261 ssf_add_dict_string(d, rx_power_string, val_string); 262 263 SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_MCURR]); 264 ssf_add_dict_string(d, "Module temperature", val_string); 265 266 SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_MCURR]); 267 ssf_add_dict_string(d, "Module voltage", val_string); 268 269 ssf_add_dict_string(d, "Alarm/warning flags implemented", 270 (sd.supports_alarms ? "Yes" : "No")); 271 272 if (sd.supports_alarms) { 273 for (i = 0; sff_8472_aw_flags[i].str; ++i) { 274 ssf_add_dict_string(d, sff_8472_aw_flags[i].str, 275 data[SFF_A2_BASE + sff_8472_aw_flags[i].offset] 276 & sff_8472_aw_flags[i].value ? "On" : "Off"); 277 } 278 sff_show_thresholds(sd, d); 279 } 280 } 281