xref: /dpdk/drivers/net/ntnic/nim/i2c_nim.c (revision 3489b87b497ed477257f5ed5b112c27c1407a68d)
1eaf1ebdcSSerhii Iliushyk /*
2eaf1ebdcSSerhii Iliushyk  * SPDX-License-Identifier: BSD-3-Clause
3eaf1ebdcSSerhii Iliushyk  * Copyright(c) 2023 Napatech A/S
4eaf1ebdcSSerhii Iliushyk  */
5eaf1ebdcSSerhii Iliushyk 
6eaf1ebdcSSerhii Iliushyk #include <string.h>
7eaf1ebdcSSerhii Iliushyk 
8eaf1ebdcSSerhii Iliushyk #include "nthw_drv.h"
9eaf1ebdcSSerhii Iliushyk #include "i2c_nim.h"
10eaf1ebdcSSerhii Iliushyk #include "ntlog.h"
11eaf1ebdcSSerhii Iliushyk #include "nt_util.h"
12eaf1ebdcSSerhii Iliushyk #include "ntnic_mod_reg.h"
1327c15342SSerhii Iliushyk #include "qsfp_registers.h"
14eaf1ebdcSSerhii Iliushyk #include "nim_defines.h"
15eaf1ebdcSSerhii Iliushyk 
16eaf1ebdcSSerhii Iliushyk #define NIM_READ false
17eaf1ebdcSSerhii Iliushyk #define NIM_WRITE true
18eaf1ebdcSSerhii Iliushyk #define NIM_PAGE_SEL_REGISTER 127
19eaf1ebdcSSerhii Iliushyk #define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
20eaf1ebdcSSerhii Iliushyk 
2127c15342SSerhii Iliushyk 
2227c15342SSerhii Iliushyk static bool page_addressing(nt_nim_identifier_t id)
2327c15342SSerhii Iliushyk {
2427c15342SSerhii Iliushyk 	switch (id) {
2527c15342SSerhii Iliushyk 	case NT_NIM_QSFP:
2627c15342SSerhii Iliushyk 	case NT_NIM_QSFP_PLUS:
274783bd39SSerhii Iliushyk 	case NT_NIM_QSFP28:
2827c15342SSerhii Iliushyk 		return true;
2927c15342SSerhii Iliushyk 
3027c15342SSerhii Iliushyk 	default:
31*3489b87bSDanylo Vodopianov 		NT_LOG(DBG, NTNIC, "Unknown NIM identifier %d", id);
3227c15342SSerhii Iliushyk 		return false;
3327c15342SSerhii Iliushyk 	}
3427c15342SSerhii Iliushyk }
3527c15342SSerhii Iliushyk 
3627c15342SSerhii Iliushyk static nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx)
3727c15342SSerhii Iliushyk {
3827c15342SSerhii Iliushyk 	return (nt_nim_identifier_t)ctx->nim_id;
3927c15342SSerhii Iliushyk }
4027c15342SSerhii Iliushyk 
41eaf1ebdcSSerhii Iliushyk static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr,
42eaf1ebdcSSerhii Iliushyk 	uint8_t i2c_addr, uint8_t a_reg_addr, uint8_t seq_cnt,
43eaf1ebdcSSerhii Iliushyk 	uint8_t *p_data)
44eaf1ebdcSSerhii Iliushyk {
45eaf1ebdcSSerhii Iliushyk 	/* Divide i2c_addr by 2 because nthw_iic_read/writeData multiplies by 2 */
46eaf1ebdcSSerhii Iliushyk 	const uint8_t i2c_devaddr = i2c_addr / 2U;
47eaf1ebdcSSerhii Iliushyk 	(void)lin_addr;	/* Unused */
48eaf1ebdcSSerhii Iliushyk 
49eaf1ebdcSSerhii Iliushyk 	if (do_write) {
50eaf1ebdcSSerhii Iliushyk 		if (ctx->type == I2C_HWIIC) {
51eaf1ebdcSSerhii Iliushyk 			return nthw_iic_write_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt,
52eaf1ebdcSSerhii Iliushyk 					p_data);
53eaf1ebdcSSerhii Iliushyk 
54eaf1ebdcSSerhii Iliushyk 		} else {
55eaf1ebdcSSerhii Iliushyk 			return 0;
56eaf1ebdcSSerhii Iliushyk 		}
57eaf1ebdcSSerhii Iliushyk 
58eaf1ebdcSSerhii Iliushyk 	} else if (ctx->type == I2C_HWIIC) {
59eaf1ebdcSSerhii Iliushyk 		return nthw_iic_read_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt, p_data);
60eaf1ebdcSSerhii Iliushyk 
61eaf1ebdcSSerhii Iliushyk 	} else {
62eaf1ebdcSSerhii Iliushyk 		return 0;
63eaf1ebdcSSerhii Iliushyk 	}
64eaf1ebdcSSerhii Iliushyk }
65eaf1ebdcSSerhii Iliushyk 
66eaf1ebdcSSerhii Iliushyk /*
67eaf1ebdcSSerhii Iliushyk  * ------------------------------------------------------------------------------
68eaf1ebdcSSerhii Iliushyk  * Selects a new page for page addressing. This is only relevant if the NIM
69eaf1ebdcSSerhii Iliushyk  * supports this. Since page switching can take substantial time the current page
70eaf1ebdcSSerhii Iliushyk  * select is read and subsequently only changed if necessary.
71eaf1ebdcSSerhii Iliushyk  * Important:
72eaf1ebdcSSerhii Iliushyk  * XFP Standard 8077, Ver 4.5, Page 61 states that:
73eaf1ebdcSSerhii Iliushyk  * If the host attempts to write a table select value which is not supported in
74eaf1ebdcSSerhii Iliushyk  * a particular module, the table select byte will revert to 01h.
75eaf1ebdcSSerhii Iliushyk  * This can lead to some surprising result that some pages seems to be duplicated.
76eaf1ebdcSSerhii Iliushyk  * ------------------------------------------------------------------------------
77eaf1ebdcSSerhii Iliushyk  */
78eaf1ebdcSSerhii Iliushyk 
79eaf1ebdcSSerhii Iliushyk static int nim_setup_page(nim_i2c_ctx_p ctx, uint8_t page_sel)
80eaf1ebdcSSerhii Iliushyk {
81eaf1ebdcSSerhii Iliushyk 	uint8_t curr_page_sel;
82eaf1ebdcSSerhii Iliushyk 
83eaf1ebdcSSerhii Iliushyk 	/* Read the current page select value */
84eaf1ebdcSSerhii Iliushyk 	if (nim_read_write_i2c_data(ctx, NIM_READ, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0,
85eaf1ebdcSSerhii Iliushyk 			NIM_PAGE_SEL_REGISTER, sizeof(curr_page_sel),
86eaf1ebdcSSerhii Iliushyk 			&curr_page_sel) != 0) {
87eaf1ebdcSSerhii Iliushyk 		return -1;
88eaf1ebdcSSerhii Iliushyk 	}
89eaf1ebdcSSerhii Iliushyk 
90eaf1ebdcSSerhii Iliushyk 	/* Only write new page select value if necessary */
91eaf1ebdcSSerhii Iliushyk 	if (page_sel != curr_page_sel) {
92eaf1ebdcSSerhii Iliushyk 		if (nim_read_write_i2c_data(ctx, NIM_WRITE, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0,
93eaf1ebdcSSerhii Iliushyk 				NIM_PAGE_SEL_REGISTER, sizeof(page_sel),
94eaf1ebdcSSerhii Iliushyk 				&page_sel) != 0) {
95eaf1ebdcSSerhii Iliushyk 			return -1;
96eaf1ebdcSSerhii Iliushyk 		}
97eaf1ebdcSSerhii Iliushyk 	}
98eaf1ebdcSSerhii Iliushyk 
99eaf1ebdcSSerhii Iliushyk 	return 0;
100eaf1ebdcSSerhii Iliushyk }
101eaf1ebdcSSerhii Iliushyk 
102eaf1ebdcSSerhii Iliushyk static int nim_read_write_data_lin(nim_i2c_ctx_p ctx, bool m_page_addressing, uint16_t lin_addr,
103eaf1ebdcSSerhii Iliushyk 	uint16_t length, uint8_t *p_data, bool do_write)
104eaf1ebdcSSerhii Iliushyk {
105eaf1ebdcSSerhii Iliushyk 	uint16_t i;
106eaf1ebdcSSerhii Iliushyk 	uint8_t a_reg_addr;	/* The actual register address in I2C device */
107eaf1ebdcSSerhii Iliushyk 	uint8_t i2c_addr;
108eaf1ebdcSSerhii Iliushyk 	int block_size = 128;	/* Equal to size of MSA pages */
109eaf1ebdcSSerhii Iliushyk 	int seq_cnt;
110eaf1ebdcSSerhii Iliushyk 	int max_seq_cnt = 1;
111eaf1ebdcSSerhii Iliushyk 	int multi_byte = 1;	/* One byte per I2C register is default */
112eaf1ebdcSSerhii Iliushyk 
113eaf1ebdcSSerhii Iliushyk 	for (i = 0; i < length;) {
114eaf1ebdcSSerhii Iliushyk 		bool use_page_select = false;
115eaf1ebdcSSerhii Iliushyk 
116eaf1ebdcSSerhii Iliushyk 		/*
117eaf1ebdcSSerhii Iliushyk 		 * Find out how much can be read from the current block in case of
118eaf1ebdcSSerhii Iliushyk 		 * single byte access
119eaf1ebdcSSerhii Iliushyk 		 */
120eaf1ebdcSSerhii Iliushyk 		if (multi_byte == 1)
121eaf1ebdcSSerhii Iliushyk 			max_seq_cnt = block_size - (lin_addr % block_size);
122eaf1ebdcSSerhii Iliushyk 
123eaf1ebdcSSerhii Iliushyk 		if (m_page_addressing) {
124eaf1ebdcSSerhii Iliushyk 			if (lin_addr >= 128) {	/* Only page setup above this address */
125eaf1ebdcSSerhii Iliushyk 				use_page_select = true;
126eaf1ebdcSSerhii Iliushyk 
127eaf1ebdcSSerhii Iliushyk 				/* Map to [128..255] of 0xA0 device */
128eaf1ebdcSSerhii Iliushyk 				a_reg_addr = (uint8_t)(block_size + (lin_addr % block_size));
129eaf1ebdcSSerhii Iliushyk 
130eaf1ebdcSSerhii Iliushyk 			} else {
131eaf1ebdcSSerhii Iliushyk 				a_reg_addr = (uint8_t)lin_addr;
132eaf1ebdcSSerhii Iliushyk 			}
133eaf1ebdcSSerhii Iliushyk 
134eaf1ebdcSSerhii Iliushyk 			i2c_addr = NIM_I2C_0XA0;/* Base I2C address */
135eaf1ebdcSSerhii Iliushyk 
136eaf1ebdcSSerhii Iliushyk 		} else if (lin_addr >= 256) {
137eaf1ebdcSSerhii Iliushyk 			/* Map to address [0..255] of 0xA2 device */
138eaf1ebdcSSerhii Iliushyk 			a_reg_addr = (uint8_t)(lin_addr - 256);
139eaf1ebdcSSerhii Iliushyk 			i2c_addr = NIM_I2C_0XA2;
140eaf1ebdcSSerhii Iliushyk 
141eaf1ebdcSSerhii Iliushyk 		} else {
142eaf1ebdcSSerhii Iliushyk 			a_reg_addr = (uint8_t)lin_addr;
143eaf1ebdcSSerhii Iliushyk 			i2c_addr = NIM_I2C_0XA0;/* Base I2C address */
144eaf1ebdcSSerhii Iliushyk 		}
145eaf1ebdcSSerhii Iliushyk 
146eaf1ebdcSSerhii Iliushyk 		/* Now actually do the reading/writing */
147eaf1ebdcSSerhii Iliushyk 		seq_cnt = length - i;	/* Number of remaining bytes */
148eaf1ebdcSSerhii Iliushyk 
149eaf1ebdcSSerhii Iliushyk 		if (seq_cnt > max_seq_cnt)
150eaf1ebdcSSerhii Iliushyk 			seq_cnt = max_seq_cnt;
151eaf1ebdcSSerhii Iliushyk 
152eaf1ebdcSSerhii Iliushyk 		/*
153eaf1ebdcSSerhii Iliushyk 		 * Read a number of bytes without explicitly specifying a new address.
154eaf1ebdcSSerhii Iliushyk 		 * This can speed up I2C access since automatic incrementation of the
155eaf1ebdcSSerhii Iliushyk 		 * I2C device internal address counter can be used. It also allows
156eaf1ebdcSSerhii Iliushyk 		 * a HW implementation, that can deal with block access.
157eaf1ebdcSSerhii Iliushyk 		 * Furthermore it also allows for access to data that must be accessed
158eaf1ebdcSSerhii Iliushyk 		 * as 16bit words reading two bytes at each address eg PHYs.
159eaf1ebdcSSerhii Iliushyk 		 */
160eaf1ebdcSSerhii Iliushyk 		if (use_page_select) {
161eaf1ebdcSSerhii Iliushyk 			if (nim_setup_page(ctx, (uint8_t)((lin_addr / 128) - 1)) != 0) {
162eaf1ebdcSSerhii Iliushyk 				NT_LOG(ERR, NTNIC,
163*3489b87bSDanylo Vodopianov 					"Cannot set up page for linear address %u", lin_addr);
164eaf1ebdcSSerhii Iliushyk 				return -1;
165eaf1ebdcSSerhii Iliushyk 			}
166eaf1ebdcSSerhii Iliushyk 		}
167eaf1ebdcSSerhii Iliushyk 
168eaf1ebdcSSerhii Iliushyk 		if (nim_read_write_i2c_data(ctx, do_write, lin_addr, i2c_addr, a_reg_addr,
169eaf1ebdcSSerhii Iliushyk 				(uint8_t)seq_cnt, p_data) != 0) {
170*3489b87bSDanylo Vodopianov 			NT_LOG(ERR, NTNIC, " Call to nim_read_write_i2c_data failed");
171eaf1ebdcSSerhii Iliushyk 			return -1;
172eaf1ebdcSSerhii Iliushyk 		}
173eaf1ebdcSSerhii Iliushyk 
174eaf1ebdcSSerhii Iliushyk 		p_data += seq_cnt;
175eaf1ebdcSSerhii Iliushyk 		i = (uint16_t)(i + seq_cnt);
176eaf1ebdcSSerhii Iliushyk 		lin_addr = (uint16_t)(lin_addr + (seq_cnt / multi_byte));
177eaf1ebdcSSerhii Iliushyk 	}
178eaf1ebdcSSerhii Iliushyk 
179eaf1ebdcSSerhii Iliushyk 	return 0;
180eaf1ebdcSSerhii Iliushyk }
181eaf1ebdcSSerhii Iliushyk 
18227c15342SSerhii Iliushyk static int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data)
18327c15342SSerhii Iliushyk {
18427c15342SSerhii Iliushyk 	/* Wrapper for using Mutex for QSFP TODO */
18527c15342SSerhii Iliushyk 	return nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id), lin_addr, length, data,
18627c15342SSerhii Iliushyk 			NIM_READ);
18727c15342SSerhii Iliushyk }
18827c15342SSerhii Iliushyk 
1894783bd39SSerhii Iliushyk /* Read and return a single byte */
1904783bd39SSerhii Iliushyk static uint8_t read_byte(nim_i2c_ctx_p ctx, uint16_t addr)
1914783bd39SSerhii Iliushyk {
1924783bd39SSerhii Iliushyk 	uint8_t data;
1934783bd39SSerhii Iliushyk 	read_data_lin(ctx, addr, sizeof(data), &data);
1944783bd39SSerhii Iliushyk 	return data;
1954783bd39SSerhii Iliushyk }
1964783bd39SSerhii Iliushyk 
197eaf1ebdcSSerhii Iliushyk static int nim_read_id(nim_i2c_ctx_t *ctx)
198eaf1ebdcSSerhii Iliushyk {
199eaf1ebdcSSerhii Iliushyk 	/* We are only reading the first byte so we don't care about pages here. */
200eaf1ebdcSSerhii Iliushyk 	const bool USE_PAGE_ADDRESSING = false;
201eaf1ebdcSSerhii Iliushyk 
202eaf1ebdcSSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, USE_PAGE_ADDRESSING, NIM_IDENTIFIER_ADDR,
203eaf1ebdcSSerhii Iliushyk 			sizeof(ctx->nim_id), &ctx->nim_id, NIM_READ) != 0) {
204eaf1ebdcSSerhii Iliushyk 		return -1;
205eaf1ebdcSSerhii Iliushyk 	}
206eaf1ebdcSSerhii Iliushyk 
207eaf1ebdcSSerhii Iliushyk 	return 0;
208eaf1ebdcSSerhii Iliushyk }
209eaf1ebdcSSerhii Iliushyk 
210eaf1ebdcSSerhii Iliushyk static int i2c_nim_common_construct(nim_i2c_ctx_p ctx)
211eaf1ebdcSSerhii Iliushyk {
212eaf1ebdcSSerhii Iliushyk 	ctx->nim_id = 0;
213eaf1ebdcSSerhii Iliushyk 	int res;
214eaf1ebdcSSerhii Iliushyk 
215eaf1ebdcSSerhii Iliushyk 	if (ctx->type == I2C_HWIIC)
216eaf1ebdcSSerhii Iliushyk 		res = nim_read_id(ctx);
217eaf1ebdcSSerhii Iliushyk 
218eaf1ebdcSSerhii Iliushyk 	else
219eaf1ebdcSSerhii Iliushyk 		res = -1;
220eaf1ebdcSSerhii Iliushyk 
221eaf1ebdcSSerhii Iliushyk 	if (res) {
222*3489b87bSDanylo Vodopianov 		NT_LOG(ERR, NTNIC, "Can't read NIM id.");
223eaf1ebdcSSerhii Iliushyk 		return res;
224eaf1ebdcSSerhii Iliushyk 	}
225eaf1ebdcSSerhii Iliushyk 
226eaf1ebdcSSerhii Iliushyk 	memset(ctx->vendor_name, 0, sizeof(ctx->vendor_name));
227eaf1ebdcSSerhii Iliushyk 	memset(ctx->prod_no, 0, sizeof(ctx->prod_no));
228eaf1ebdcSSerhii Iliushyk 	memset(ctx->serial_no, 0, sizeof(ctx->serial_no));
229eaf1ebdcSSerhii Iliushyk 	memset(ctx->date, 0, sizeof(ctx->date));
230eaf1ebdcSSerhii Iliushyk 	memset(ctx->rev, 0, sizeof(ctx->rev));
231eaf1ebdcSSerhii Iliushyk 
232eaf1ebdcSSerhii Iliushyk 	ctx->content_valid = false;
233eaf1ebdcSSerhii Iliushyk 	memset(ctx->len_info, 0, sizeof(ctx->len_info));
234eaf1ebdcSSerhii Iliushyk 	ctx->pwr_level_req = 0;
235eaf1ebdcSSerhii Iliushyk 	ctx->pwr_level_cur = 0;
236eaf1ebdcSSerhii Iliushyk 	ctx->avg_pwr = false;
237eaf1ebdcSSerhii Iliushyk 	ctx->tx_disable = false;
238eaf1ebdcSSerhii Iliushyk 	ctx->lane_idx = -1;
239eaf1ebdcSSerhii Iliushyk 	ctx->lane_count = 1;
240eaf1ebdcSSerhii Iliushyk 	ctx->options = 0;
241eaf1ebdcSSerhii Iliushyk 	return 0;
242eaf1ebdcSSerhii Iliushyk }
243eaf1ebdcSSerhii Iliushyk 
24427c15342SSerhii Iliushyk /*
24527c15342SSerhii Iliushyk  * Read vendor information at a certain address. Any trailing whitespace is
24627c15342SSerhii Iliushyk  * removed and a missing string termination in the NIM data is handled.
24727c15342SSerhii Iliushyk  */
24827c15342SSerhii Iliushyk static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data)
24927c15342SSerhii Iliushyk {
25027c15342SSerhii Iliushyk 	const bool pg_addr = page_addressing(ctx->nim_id);
25127c15342SSerhii Iliushyk 	int i;
25227c15342SSerhii Iliushyk 	/* Subtract "1" from max_len that includes a terminating "0" */
25327c15342SSerhii Iliushyk 
25427c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, addr, (uint8_t)(max_len - 1), (uint8_t *)p_data,
25527c15342SSerhii Iliushyk 			NIM_READ) != 0) {
25627c15342SSerhii Iliushyk 		return -1;
25727c15342SSerhii Iliushyk 	}
25827c15342SSerhii Iliushyk 
25927c15342SSerhii Iliushyk 	/* Terminate at first found white space */
26027c15342SSerhii Iliushyk 	for (i = 0; i < max_len - 1; i++) {
26127c15342SSerhii Iliushyk 		if (*p_data == ' ' || *p_data == '\n' || *p_data == '\t' || *p_data == '\v' ||
26227c15342SSerhii Iliushyk 			*p_data == '\f' || *p_data == '\r') {
26327c15342SSerhii Iliushyk 			*p_data = '\0';
26427c15342SSerhii Iliushyk 			return 0;
26527c15342SSerhii Iliushyk 		}
26627c15342SSerhii Iliushyk 
26727c15342SSerhii Iliushyk 		p_data++;
26827c15342SSerhii Iliushyk 	}
26927c15342SSerhii Iliushyk 
27027c15342SSerhii Iliushyk 	/*
27127c15342SSerhii Iliushyk 	 * Add line termination as the very last character, if it was missing in the
27227c15342SSerhii Iliushyk 	 * NIM data
27327c15342SSerhii Iliushyk 	 */
27427c15342SSerhii Iliushyk 	*p_data = '\0';
27527c15342SSerhii Iliushyk 	return 0;
27627c15342SSerhii Iliushyk }
27727c15342SSerhii Iliushyk 
27827c15342SSerhii Iliushyk static void qsfp_read_vendor_info(nim_i2c_ctx_t *ctx)
27927c15342SSerhii Iliushyk {
28027c15342SSerhii Iliushyk 	nim_read_vendor_info(ctx, QSFP_VENDOR_NAME_LIN_ADDR, sizeof(ctx->vendor_name),
28127c15342SSerhii Iliushyk 		ctx->vendor_name);
28227c15342SSerhii Iliushyk 	nim_read_vendor_info(ctx, QSFP_VENDOR_PN_LIN_ADDR, sizeof(ctx->prod_no), ctx->prod_no);
28327c15342SSerhii Iliushyk 	nim_read_vendor_info(ctx, QSFP_VENDOR_SN_LIN_ADDR, sizeof(ctx->serial_no), ctx->serial_no);
28427c15342SSerhii Iliushyk 	nim_read_vendor_info(ctx, QSFP_VENDOR_DATE_LIN_ADDR, sizeof(ctx->date), ctx->date);
28527c15342SSerhii Iliushyk 	nim_read_vendor_info(ctx, QSFP_VENDOR_REV_LIN_ADDR, (uint8_t)(sizeof(ctx->rev) - 2),
28627c15342SSerhii Iliushyk 		ctx->rev);	/*OBS Only two bytes*/
28727c15342SSerhii Iliushyk }
28827c15342SSerhii Iliushyk static int qsfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
28927c15342SSerhii Iliushyk {
29027c15342SSerhii Iliushyk 	int res = 0;	/* unused due to no readings from HW */
29127c15342SSerhii Iliushyk 
29227c15342SSerhii Iliushyk 	assert(ctx && state);
29327c15342SSerhii Iliushyk 	assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized");
29427c15342SSerhii Iliushyk 
29527c15342SSerhii Iliushyk 	(void)memset(state, 0, sizeof(*state));
29627c15342SSerhii Iliushyk 
29727c15342SSerhii Iliushyk 	switch (ctx->nim_id) {
29827c15342SSerhii Iliushyk 	case 12U:
29927c15342SSerhii Iliushyk 		state->br = 10U;/* QSFP: 4 x 1G = 4G */
30027c15342SSerhii Iliushyk 		break;
30127c15342SSerhii Iliushyk 
30227c15342SSerhii Iliushyk 	case 13U:
30327c15342SSerhii Iliushyk 		state->br = 103U;	/* QSFP+: 4 x 10G = 40G */
30427c15342SSerhii Iliushyk 		break;
30527c15342SSerhii Iliushyk 
3064783bd39SSerhii Iliushyk 	case 17U:
3074783bd39SSerhii Iliushyk 		state->br = 255U;	/* QSFP28: 4 x 25G = 100G */
3084783bd39SSerhii Iliushyk 		break;
3094783bd39SSerhii Iliushyk 
31027c15342SSerhii Iliushyk 	default:
311*3489b87bSDanylo Vodopianov 		NT_LOG(INF, NTNIC, "nim_id = %u is not an QSFP/QSFP+/QSFP28 module", ctx->nim_id);
31227c15342SSerhii Iliushyk 		res = -1;
31327c15342SSerhii Iliushyk 	}
31427c15342SSerhii Iliushyk 
31527c15342SSerhii Iliushyk 	return res;
31627c15342SSerhii Iliushyk }
31727c15342SSerhii Iliushyk 
31827c15342SSerhii Iliushyk int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
31927c15342SSerhii Iliushyk {
32027c15342SSerhii Iliushyk 	return qsfp_nim_state_build(ctx, state);
32127c15342SSerhii Iliushyk }
32227c15342SSerhii Iliushyk 
323eaf1ebdcSSerhii Iliushyk const char *nim_id_to_text(uint8_t nim_id)
324eaf1ebdcSSerhii Iliushyk {
325eaf1ebdcSSerhii Iliushyk 	switch (nim_id) {
326eaf1ebdcSSerhii Iliushyk 	case 0x0:
327eaf1ebdcSSerhii Iliushyk 		return "UNKNOWN";
328eaf1ebdcSSerhii Iliushyk 
32927c15342SSerhii Iliushyk 	case 0x0C:
33027c15342SSerhii Iliushyk 		return "QSFP";
33127c15342SSerhii Iliushyk 
33227c15342SSerhii Iliushyk 	case 0x0D:
33327c15342SSerhii Iliushyk 		return "QSFP+";
33427c15342SSerhii Iliushyk 
3354783bd39SSerhii Iliushyk 	case 0x11:
3364783bd39SSerhii Iliushyk 		return "QSFP28";
3374783bd39SSerhii Iliushyk 
338eaf1ebdcSSerhii Iliushyk 	default:
339eaf1ebdcSSerhii Iliushyk 		return "ILLEGAL!";
340eaf1ebdcSSerhii Iliushyk 	}
341eaf1ebdcSSerhii Iliushyk }
342eaf1ebdcSSerhii Iliushyk 
34327c15342SSerhii Iliushyk /*
34427c15342SSerhii Iliushyk  * Disable laser for specific lane or all lanes
34527c15342SSerhii Iliushyk  */
34627c15342SSerhii Iliushyk int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable, int lane_idx)
34727c15342SSerhii Iliushyk {
34827c15342SSerhii Iliushyk 	uint8_t value;
34927c15342SSerhii Iliushyk 	uint8_t mask;
35027c15342SSerhii Iliushyk 	const bool pg_addr = page_addressing(ctx->nim_id);
35127c15342SSerhii Iliushyk 
35227c15342SSerhii Iliushyk 	if (lane_idx < 0)	/* If no lane is specified then all lanes */
35327c15342SSerhii Iliushyk 		mask = QSFP_SOFT_TX_ALL_DISABLE_BITS;
35427c15342SSerhii Iliushyk 
35527c15342SSerhii Iliushyk 	else
35627c15342SSerhii Iliushyk 		mask = (uint8_t)(1U << lane_idx);
35727c15342SSerhii Iliushyk 
35827c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
35927c15342SSerhii Iliushyk 			&value, NIM_READ) != 0) {
36027c15342SSerhii Iliushyk 		return -1;
36127c15342SSerhii Iliushyk 	}
36227c15342SSerhii Iliushyk 
36327c15342SSerhii Iliushyk 	if (disable)
36427c15342SSerhii Iliushyk 		value |= mask;
36527c15342SSerhii Iliushyk 
36627c15342SSerhii Iliushyk 	else
36727c15342SSerhii Iliushyk 		value &= (uint8_t)(~mask);
36827c15342SSerhii Iliushyk 
36927c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
37027c15342SSerhii Iliushyk 			&value, NIM_WRITE) != 0) {
37127c15342SSerhii Iliushyk 		return -1;
37227c15342SSerhii Iliushyk 	}
37327c15342SSerhii Iliushyk 
37427c15342SSerhii Iliushyk 	return 0;
37527c15342SSerhii Iliushyk }
37627c15342SSerhii Iliushyk 
37727c15342SSerhii Iliushyk /*
37827c15342SSerhii Iliushyk  * Import length info in various units from NIM module data and convert to meters
37927c15342SSerhii Iliushyk  */
38027c15342SSerhii Iliushyk static void nim_import_len_info(nim_i2c_ctx_p ctx, uint8_t *p_nim_len_info, uint16_t *p_nim_units)
38127c15342SSerhii Iliushyk {
38227c15342SSerhii Iliushyk 	size_t i;
38327c15342SSerhii Iliushyk 
38427c15342SSerhii Iliushyk 	for (i = 0; i < ARRAY_SIZE(ctx->len_info); i++)
38527c15342SSerhii Iliushyk 		if (*(p_nim_len_info + i) == 255) {
38627c15342SSerhii Iliushyk 			ctx->len_info[i] = 65535;
38727c15342SSerhii Iliushyk 
38827c15342SSerhii Iliushyk 		} else {
38927c15342SSerhii Iliushyk 			uint32_t len = *(p_nim_len_info + i) * *(p_nim_units + i);
39027c15342SSerhii Iliushyk 
39127c15342SSerhii Iliushyk 			if (len > 65535)
39227c15342SSerhii Iliushyk 				ctx->len_info[i] = 65535;
39327c15342SSerhii Iliushyk 
39427c15342SSerhii Iliushyk 			else
39527c15342SSerhii Iliushyk 				ctx->len_info[i] = (uint16_t)len;
39627c15342SSerhii Iliushyk 		}
39727c15342SSerhii Iliushyk }
39827c15342SSerhii Iliushyk 
39927c15342SSerhii Iliushyk static int qsfpplus_read_basic_data(nim_i2c_ctx_t *ctx)
40027c15342SSerhii Iliushyk {
40127c15342SSerhii Iliushyk 	const bool pg_addr = page_addressing(ctx->nim_id);
40227c15342SSerhii Iliushyk 	uint8_t options;
40327c15342SSerhii Iliushyk 	uint8_t value;
40427c15342SSerhii Iliushyk 	uint8_t nim_len_info[5];
40527c15342SSerhii Iliushyk 	uint16_t nim_units[5] = { 1000, 2, 1, 1, 1 };	/* QSFP MSA units in meters */
40627c15342SSerhii Iliushyk 	const char *yes_no[2] = { "No", "Yes" };
40727c15342SSerhii Iliushyk 	(void)yes_no;
408*3489b87bSDanylo Vodopianov 	NT_LOG(DBG, NTNIC, "Instance %d: NIM id: %s (%d)", ctx->instance,
40927c15342SSerhii Iliushyk 		nim_id_to_text(ctx->nim_id), ctx->nim_id);
41027c15342SSerhii Iliushyk 
41127c15342SSerhii Iliushyk 	/* Read DMI options */
41227c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_DMI_OPTION_LIN_ADDR, sizeof(options),
41327c15342SSerhii Iliushyk 			&options, NIM_READ) != 0) {
41427c15342SSerhii Iliushyk 		return -1;
41527c15342SSerhii Iliushyk 	}
41627c15342SSerhii Iliushyk 
41727c15342SSerhii Iliushyk 	ctx->avg_pwr = options & QSFP_DMI_AVG_PWR_BIT;
418*3489b87bSDanylo Vodopianov 	NT_LOG(DBG, NTNIC, "Instance %d: NIM options: (DMI: Yes, AvgPwr: %s)", ctx->instance,
41927c15342SSerhii Iliushyk 		yes_no[ctx->avg_pwr]);
42027c15342SSerhii Iliushyk 
42127c15342SSerhii Iliushyk 	qsfp_read_vendor_info(ctx);
422*3489b87bSDanylo Vodopianov 	NT_LOG(DBG, NTNIC,
423*3489b87bSDanylo Vodopianov 		"Instance %d: NIM info: (Vendor: %s, PN: %s, SN: %s, Date: %s, Rev: %s)",
42427c15342SSerhii Iliushyk 		ctx->instance, ctx->vendor_name, ctx->prod_no, ctx->serial_no, ctx->date, ctx->rev);
42527c15342SSerhii Iliushyk 
42627c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info),
42727c15342SSerhii Iliushyk 			nim_len_info, NIM_READ) != 0) {
42827c15342SSerhii Iliushyk 		return -1;
42927c15342SSerhii Iliushyk 	}
43027c15342SSerhii Iliushyk 
43127c15342SSerhii Iliushyk 	/*
43227c15342SSerhii Iliushyk 	 * Returns supported length information in meters for various fibers as 5 indivi-
43327c15342SSerhii Iliushyk 	 * dual values: [SM(9um), EBW(50um), MM(50um), MM(62.5um), Copper]
43427c15342SSerhii Iliushyk 	 * If no length information is available for a certain entry, the returned value
43527c15342SSerhii Iliushyk 	 * will be zero. This will be the case for SFP modules - EBW entry.
43627c15342SSerhii Iliushyk 	 * If the MSBit is set the returned value in the lower 31 bits indicates that the
43727c15342SSerhii Iliushyk 	 * supported length is greater than this.
43827c15342SSerhii Iliushyk 	 */
43927c15342SSerhii Iliushyk 
44027c15342SSerhii Iliushyk 	nim_import_len_info(ctx, nim_len_info, nim_units);
44127c15342SSerhii Iliushyk 
44227c15342SSerhii Iliushyk 	/* Read required power level */
44327c15342SSerhii Iliushyk 	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_EXTENDED_IDENTIFIER, sizeof(value), &value,
44427c15342SSerhii Iliushyk 			NIM_READ) != 0) {
44527c15342SSerhii Iliushyk 		return -1;
44627c15342SSerhii Iliushyk 	}
44727c15342SSerhii Iliushyk 
44827c15342SSerhii Iliushyk 	/*
44927c15342SSerhii Iliushyk 	 * Get power class according to SFF-8636 Rev 2.7, Table 6-16, Page 43:
45027c15342SSerhii Iliushyk 	 * If power class >= 5 setHighPower must be called for the module to be fully
45127c15342SSerhii Iliushyk 	 * functional
45227c15342SSerhii Iliushyk 	 */
45327c15342SSerhii Iliushyk 	if ((value & QSFP_POWER_CLASS_BITS_5_7) == 0) {
45427c15342SSerhii Iliushyk 		/* NIM in power class 1 - 4 */
45527c15342SSerhii Iliushyk 		ctx->pwr_level_req = (uint8_t)(((value & QSFP_POWER_CLASS_BITS_1_4) >> 6) + 1);
45627c15342SSerhii Iliushyk 
45727c15342SSerhii Iliushyk 	} else {
45827c15342SSerhii Iliushyk 		/* NIM in power class 5 - 7 */
45927c15342SSerhii Iliushyk 		ctx->pwr_level_req = (uint8_t)((value & QSFP_POWER_CLASS_BITS_5_7) + 4);
46027c15342SSerhii Iliushyk 	}
46127c15342SSerhii Iliushyk 
46227c15342SSerhii Iliushyk 	return 0;
46327c15342SSerhii Iliushyk }
46427c15342SSerhii Iliushyk 
4654783bd39SSerhii Iliushyk static void qsfp28_find_port_params(nim_i2c_ctx_p ctx)
4664783bd39SSerhii Iliushyk {
4674783bd39SSerhii Iliushyk 	uint8_t fiber_chan_speed;
4684783bd39SSerhii Iliushyk 
4694783bd39SSerhii Iliushyk 	/* Table 6-17 SFF-8636 */
4704783bd39SSerhii Iliushyk 	read_data_lin(ctx, QSFP_SPEC_COMPLIANCE_CODES_ADDR, 1, &fiber_chan_speed);
4714783bd39SSerhii Iliushyk 
4724783bd39SSerhii Iliushyk 	if (fiber_chan_speed & (1 << 7)) {
4734783bd39SSerhii Iliushyk 		/* SFF-8024, Rev 4.7, Table 4-4 */
4744783bd39SSerhii Iliushyk 		uint8_t extended_specification_compliance_code = 0;
4754783bd39SSerhii Iliushyk 		read_data_lin(ctx, QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR, 1,
4764783bd39SSerhii Iliushyk 			&extended_specification_compliance_code);
4774783bd39SSerhii Iliushyk 
4784783bd39SSerhii Iliushyk 		switch (extended_specification_compliance_code) {
4794783bd39SSerhii Iliushyk 		case 0x02:
4804783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_SR4;
4814783bd39SSerhii Iliushyk 			break;
4824783bd39SSerhii Iliushyk 
4834783bd39SSerhii Iliushyk 		case 0x03:
4844783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_LR4;
4854783bd39SSerhii Iliushyk 			break;
4864783bd39SSerhii Iliushyk 
4874783bd39SSerhii Iliushyk 		case 0x0B:
4884783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_L;
4894783bd39SSerhii Iliushyk 			break;
4904783bd39SSerhii Iliushyk 
4914783bd39SSerhii Iliushyk 		case 0x0C:
4924783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_S;
4934783bd39SSerhii Iliushyk 			break;
4944783bd39SSerhii Iliushyk 
4954783bd39SSerhii Iliushyk 		case 0x0D:
4964783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_N;
4974783bd39SSerhii Iliushyk 			break;
4984783bd39SSerhii Iliushyk 
4994783bd39SSerhii Iliushyk 		case 0x25:
5004783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_DR;
5014783bd39SSerhii Iliushyk 			break;
5024783bd39SSerhii Iliushyk 
5034783bd39SSerhii Iliushyk 		case 0x26:
5044783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_FR;
5054783bd39SSerhii Iliushyk 			break;
5064783bd39SSerhii Iliushyk 
5074783bd39SSerhii Iliushyk 		case 0x27:
5084783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28_LR;
5094783bd39SSerhii Iliushyk 			break;
5104783bd39SSerhii Iliushyk 
5114783bd39SSerhii Iliushyk 		default:
5124783bd39SSerhii Iliushyk 			ctx->port_type = NT_PORT_TYPE_QSFP28;
5134783bd39SSerhii Iliushyk 		}
5144783bd39SSerhii Iliushyk 
5154783bd39SSerhii Iliushyk 	} else {
5164783bd39SSerhii Iliushyk 		ctx->port_type = NT_PORT_TYPE_QSFP28;
5174783bd39SSerhii Iliushyk 	}
5184783bd39SSerhii Iliushyk }
5194783bd39SSerhii Iliushyk 
5204783bd39SSerhii Iliushyk /*
5214783bd39SSerhii Iliushyk  * If true the user must actively select the desired rate. If false the module
5224783bd39SSerhii Iliushyk  * however can still support several rates without the user is required to select
5234783bd39SSerhii Iliushyk  * one of them. Supported rates must then be deduced from the product number.
5244783bd39SSerhii Iliushyk  * SFF-8636, Rev 2.10a:
5254783bd39SSerhii Iliushyk  * p40: 6.2.7 Rate Select
5264783bd39SSerhii Iliushyk  * p85: A.2 Rate Select
5274783bd39SSerhii Iliushyk  */
5284783bd39SSerhii Iliushyk static bool qsfp28_is_rate_selection_enabled(nim_i2c_ctx_p ctx)
5294783bd39SSerhii Iliushyk {
5304783bd39SSerhii Iliushyk 	const uint8_t ext_rate_select_compl_reg_addr = 141;
5314783bd39SSerhii Iliushyk 	const uint8_t options_reg_addr = 195;
5324783bd39SSerhii Iliushyk 	const uint8_t enh_options_reg_addr = 221;
5334783bd39SSerhii Iliushyk 
5344783bd39SSerhii Iliushyk 	uint8_t rate_select_ena = (read_byte(ctx, options_reg_addr) >> 5) & 0x01;	/* bit: 5 */
5354783bd39SSerhii Iliushyk 
5364783bd39SSerhii Iliushyk 	if (rate_select_ena == 0)
5374783bd39SSerhii Iliushyk 		return false;
5384783bd39SSerhii Iliushyk 
5394783bd39SSerhii Iliushyk 	uint8_t rate_select_type =
5404783bd39SSerhii Iliushyk 		(read_byte(ctx, enh_options_reg_addr) >> 2) & 0x03;	/* bit 3..2 */
5414783bd39SSerhii Iliushyk 
5424783bd39SSerhii Iliushyk 	if (rate_select_type != 2) {
543*3489b87bSDanylo Vodopianov 		NT_LOG(DBG, NTNIC, "NIM has unhandled rate select type (%d)", rate_select_type);
5444783bd39SSerhii Iliushyk 		return false;
5454783bd39SSerhii Iliushyk 	}
5464783bd39SSerhii Iliushyk 
5474783bd39SSerhii Iliushyk 	uint8_t ext_rate_select_ver =
5484783bd39SSerhii Iliushyk 		read_byte(ctx, ext_rate_select_compl_reg_addr) & 0x03;	/* bit 1..0 */
5494783bd39SSerhii Iliushyk 
5504783bd39SSerhii Iliushyk 	if (ext_rate_select_ver != 0x02) {
551*3489b87bSDanylo Vodopianov 		NT_LOG(DBG, NTNIC, "NIM has unhandled extended rate select version (%d)",
5524783bd39SSerhii Iliushyk 			ext_rate_select_ver);
5534783bd39SSerhii Iliushyk 		return false;
5544783bd39SSerhii Iliushyk 	}
5554783bd39SSerhii Iliushyk 
5564783bd39SSerhii Iliushyk 	return true;	/* When true selectRate() can be used */
5574783bd39SSerhii Iliushyk }
5584783bd39SSerhii Iliushyk 
5594783bd39SSerhii Iliushyk static void qsfp28_set_speed_mask(nim_i2c_ctx_p ctx)
5604783bd39SSerhii Iliushyk {
5614783bd39SSerhii Iliushyk 	if (ctx->port_type == NT_PORT_TYPE_QSFP28_FR || ctx->port_type == NT_PORT_TYPE_QSFP28_DR ||
5624783bd39SSerhii Iliushyk 		ctx->port_type == NT_PORT_TYPE_QSFP28_LR) {
5634783bd39SSerhii Iliushyk 		if (ctx->lane_idx < 0)
5644783bd39SSerhii Iliushyk 			ctx->speed_mask = NT_LINK_SPEED_100G;
5654783bd39SSerhii Iliushyk 
5664783bd39SSerhii Iliushyk 		else
5674783bd39SSerhii Iliushyk 			/* PAM-4 modules can only run on all lanes together */
5684783bd39SSerhii Iliushyk 			ctx->speed_mask = 0;
5694783bd39SSerhii Iliushyk 
5704783bd39SSerhii Iliushyk 	} else {
5714783bd39SSerhii Iliushyk 		if (ctx->lane_idx < 0)
5724783bd39SSerhii Iliushyk 			ctx->speed_mask = NT_LINK_SPEED_100G;
5734783bd39SSerhii Iliushyk 
5744783bd39SSerhii Iliushyk 		else
5754783bd39SSerhii Iliushyk 			ctx->speed_mask = NT_LINK_SPEED_25G;
5764783bd39SSerhii Iliushyk 
5774783bd39SSerhii Iliushyk 		if (qsfp28_is_rate_selection_enabled(ctx)) {
5784783bd39SSerhii Iliushyk 			/*
5794783bd39SSerhii Iliushyk 			 * It is assumed that if the module supports dual rates then the other rate
5804783bd39SSerhii Iliushyk 			 * is 10G per lane or 40G for all lanes.
5814783bd39SSerhii Iliushyk 			 */
5824783bd39SSerhii Iliushyk 			if (ctx->lane_idx < 0)
5834783bd39SSerhii Iliushyk 				ctx->speed_mask |= NT_LINK_SPEED_40G;
5844783bd39SSerhii Iliushyk 
5854783bd39SSerhii Iliushyk 			else
5864783bd39SSerhii Iliushyk 				ctx->speed_mask = NT_LINK_SPEED_10G;
5874783bd39SSerhii Iliushyk 		}
5884783bd39SSerhii Iliushyk 	}
5894783bd39SSerhii Iliushyk }
5904783bd39SSerhii Iliushyk 
59127c15342SSerhii Iliushyk static void qsfpplus_find_port_params(nim_i2c_ctx_p ctx)
59227c15342SSerhii Iliushyk {
59327c15342SSerhii Iliushyk 	uint8_t device_tech;
59427c15342SSerhii Iliushyk 	read_data_lin(ctx, QSFP_TRANSMITTER_TYPE_LIN_ADDR, sizeof(device_tech), &device_tech);
59527c15342SSerhii Iliushyk 
59627c15342SSerhii Iliushyk 	switch (device_tech & 0xF0) {
59727c15342SSerhii Iliushyk 	case 0xA0:	/* Copper cable unequalized */
59827c15342SSerhii Iliushyk 		break;
59927c15342SSerhii Iliushyk 
60027c15342SSerhii Iliushyk 	case 0xC0:	/* Copper cable, near and far end limiting active equalizers */
60127c15342SSerhii Iliushyk 	case 0xD0:	/* Copper cable, far end limiting active equalizers */
60227c15342SSerhii Iliushyk 	case 0xE0:	/* Copper cable, near end limiting active equalizers */
60327c15342SSerhii Iliushyk 		break;
60427c15342SSerhii Iliushyk 
60527c15342SSerhii Iliushyk 	default:/* Optical */
60627c15342SSerhii Iliushyk 		ctx->port_type = NT_PORT_TYPE_QSFP_PLUS;
60727c15342SSerhii Iliushyk 		break;
60827c15342SSerhii Iliushyk 	}
60927c15342SSerhii Iliushyk }
61027c15342SSerhii Iliushyk 
61127c15342SSerhii Iliushyk static void qsfpplus_set_speed_mask(nim_i2c_ctx_p ctx)
61227c15342SSerhii Iliushyk {
61327c15342SSerhii Iliushyk 	ctx->speed_mask = (ctx->lane_idx < 0) ? NT_LINK_SPEED_40G : (NT_LINK_SPEED_10G);
61427c15342SSerhii Iliushyk }
61527c15342SSerhii Iliushyk 
61627c15342SSerhii Iliushyk static void qsfpplus_construct(nim_i2c_ctx_p ctx, int8_t lane_idx)
61727c15342SSerhii Iliushyk {
61827c15342SSerhii Iliushyk 	assert(lane_idx < 4);
6194783bd39SSerhii Iliushyk 	ctx->specific_u.qsfp.qsfp28 = false;
62027c15342SSerhii Iliushyk 	ctx->lane_idx = lane_idx;
62127c15342SSerhii Iliushyk 	ctx->lane_count = 4;
62227c15342SSerhii Iliushyk }
62327c15342SSerhii Iliushyk 
62427c15342SSerhii Iliushyk static int qsfpplus_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
62527c15342SSerhii Iliushyk {
62627c15342SSerhii Iliushyk 	qsfpplus_construct(ctx, lane_idx);
62727c15342SSerhii Iliushyk 	int res = qsfpplus_read_basic_data(ctx);
62827c15342SSerhii Iliushyk 
62927c15342SSerhii Iliushyk 	if (!res) {
63027c15342SSerhii Iliushyk 		qsfpplus_find_port_params(ctx);
63127c15342SSerhii Iliushyk 
63227c15342SSerhii Iliushyk 		/*
63327c15342SSerhii Iliushyk 		 * Read if TX_DISABLE has been implemented
63427c15342SSerhii Iliushyk 		 * For passive optical modules this is required while it for copper and active
63527c15342SSerhii Iliushyk 		 * optical modules is optional. Under all circumstances register 195.4 will
63627c15342SSerhii Iliushyk 		 * indicate, if TX_DISABLE has been implemented in register 86.0-3
63727c15342SSerhii Iliushyk 		 */
63827c15342SSerhii Iliushyk 		uint8_t value;
63927c15342SSerhii Iliushyk 		read_data_lin(ctx, QSFP_OPTION3_LIN_ADDR, sizeof(value), &value);
64027c15342SSerhii Iliushyk 
64127c15342SSerhii Iliushyk 		ctx->tx_disable = (value & QSFP_OPTION3_TX_DISABLE_BIT) != 0;
64227c15342SSerhii Iliushyk 
64327c15342SSerhii Iliushyk 		if (ctx->tx_disable)
64427c15342SSerhii Iliushyk 			ctx->options |= (1 << NIM_OPTION_TX_DISABLE);
64527c15342SSerhii Iliushyk 
64627c15342SSerhii Iliushyk 		/*
64727c15342SSerhii Iliushyk 		 * Previously - considering AFBR-89BRDZ - code tried to establish if a module was
64827c15342SSerhii Iliushyk 		 * RxOnly by testing the state of the lasers after reset. Lasers were for this
64927c15342SSerhii Iliushyk 		 * module default disabled.
65027c15342SSerhii Iliushyk 		 * However that code did not work for GigaLight, GQS-MPO400-SR4C so it was
65127c15342SSerhii Iliushyk 		 * decided that this option should not be detected automatically but from PN
65227c15342SSerhii Iliushyk 		 */
65327c15342SSerhii Iliushyk 		ctx->specific_u.qsfp.rx_only = (ctx->options & (1 << NIM_OPTION_RX_ONLY)) != 0;
65427c15342SSerhii Iliushyk 		qsfpplus_set_speed_mask(ctx);
65527c15342SSerhii Iliushyk 	}
65627c15342SSerhii Iliushyk 
65727c15342SSerhii Iliushyk 	return res;
65827c15342SSerhii Iliushyk }
65927c15342SSerhii Iliushyk 
6604783bd39SSerhii Iliushyk static void qsfp28_wait_for_ready_after_reset(nim_i2c_ctx_p ctx)
6614783bd39SSerhii Iliushyk {
6624783bd39SSerhii Iliushyk 	uint8_t data;
6634783bd39SSerhii Iliushyk 	bool init_complete_flag_present = false;
6644783bd39SSerhii Iliushyk 
6654783bd39SSerhii Iliushyk 	/*
6664783bd39SSerhii Iliushyk 	 * Revision compliance
6674783bd39SSerhii Iliushyk 	 * 7: SFF-8636 Rev 2.5, 2.6 and 2.7
6684783bd39SSerhii Iliushyk 	 * 8: SFF-8636 Rev 2.8, 2.9 and 2.10
6694783bd39SSerhii Iliushyk 	 */
6704783bd39SSerhii Iliushyk 	read_data_lin(ctx, 1, sizeof(ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance),
6714783bd39SSerhii Iliushyk 		&ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance);
6724783bd39SSerhii Iliushyk 	NT_LOG(DBG, NTHW, "NIM RevCompliance = %d",
6734783bd39SSerhii Iliushyk 		ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance);
6744783bd39SSerhii Iliushyk 
6754783bd39SSerhii Iliushyk 	/* Wait if lane_idx == -1 (all lanes are used) or lane_idx == 0 (the first lane) */
6764783bd39SSerhii Iliushyk 	if (ctx->lane_idx > 0)
6774783bd39SSerhii Iliushyk 		return;
6784783bd39SSerhii Iliushyk 
6794783bd39SSerhii Iliushyk 	if (ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance >= 7) {
6804783bd39SSerhii Iliushyk 		/* Check if init complete flag is implemented */
6814783bd39SSerhii Iliushyk 		read_data_lin(ctx, 221, sizeof(data), &data);
6824783bd39SSerhii Iliushyk 		init_complete_flag_present = (data & (1 << 4)) != 0;
6834783bd39SSerhii Iliushyk 	}
6844783bd39SSerhii Iliushyk 
6854783bd39SSerhii Iliushyk 	NT_LOG(DBG, NTHW, "NIM InitCompleteFlagPresent = %d", init_complete_flag_present);
6864783bd39SSerhii Iliushyk 
6874783bd39SSerhii Iliushyk 	/*
6884783bd39SSerhii Iliushyk 	 * If the init complete flag is not present then wait 500ms that together with 500ms
6894783bd39SSerhii Iliushyk 	 * after reset (in the adapter code) should be enough to read data from upper pages
6904783bd39SSerhii Iliushyk 	 * that otherwise would not be ready. Especially BiDi modules AFBR-89BDDZ have been
6914783bd39SSerhii Iliushyk 	 * prone to this when trying to read sensor options using getQsfpOptionsFromData()
6924783bd39SSerhii Iliushyk 	 * Probably because access to the paged address space is required.
6934783bd39SSerhii Iliushyk 	 */
6944783bd39SSerhii Iliushyk 	if (!init_complete_flag_present) {
6954783bd39SSerhii Iliushyk 		nt_os_wait_usec(500000);
6964783bd39SSerhii Iliushyk 		return;
6974783bd39SSerhii Iliushyk 	}
6984783bd39SSerhii Iliushyk 
6994783bd39SSerhii Iliushyk 	/* Otherwise wait for the init complete flag to be set */
7004783bd39SSerhii Iliushyk 	int count = 0;
7014783bd39SSerhii Iliushyk 
7024783bd39SSerhii Iliushyk 	while (true) {
7034783bd39SSerhii Iliushyk 		if (count > 10) {	/* 1 s timeout */
7044783bd39SSerhii Iliushyk 			NT_LOG(WRN, NTHW, "Timeout waiting for module ready");
7054783bd39SSerhii Iliushyk 			break;
7064783bd39SSerhii Iliushyk 		}
7074783bd39SSerhii Iliushyk 
7084783bd39SSerhii Iliushyk 		read_data_lin(ctx, 6, sizeof(data), &data);
7094783bd39SSerhii Iliushyk 
7104783bd39SSerhii Iliushyk 		if (data & 0x01) {
7114783bd39SSerhii Iliushyk 			NT_LOG(DBG, NTHW, "Module ready after %dms", count * 100);
7124783bd39SSerhii Iliushyk 			break;
7134783bd39SSerhii Iliushyk 		}
7144783bd39SSerhii Iliushyk 
7154783bd39SSerhii Iliushyk 		nt_os_wait_usec(100000);/* 100 ms */
7164783bd39SSerhii Iliushyk 		count++;
7174783bd39SSerhii Iliushyk 	}
7184783bd39SSerhii Iliushyk }
7194783bd39SSerhii Iliushyk 
7204783bd39SSerhii Iliushyk static void qsfp28_get_fec_options(nim_i2c_ctx_p ctx)
7214783bd39SSerhii Iliushyk {
7224783bd39SSerhii Iliushyk 	const char *const nim_list[] = {
7234783bd39SSerhii Iliushyk 		"AFBR-89BDDZ",	/* Avago BiDi */
7244783bd39SSerhii Iliushyk 		"AFBR-89BRDZ",	/* Avago BiDi, RxOnly */
7254783bd39SSerhii Iliushyk 		"FTLC4352RKPL",	/* Finisar QSFP28-LR */
7264783bd39SSerhii Iliushyk 		"FTLC4352RHPL",	/* Finisar QSFP28-DR */
7274783bd39SSerhii Iliushyk 		"FTLC4352RJPL",	/* Finisar QSFP28-FR */
7284783bd39SSerhii Iliushyk 		"SFBR-89BDDZ-CS4",	/* Foxconn, QSFP28 100G/40G BiDi */
7294783bd39SSerhii Iliushyk 	};
7304783bd39SSerhii Iliushyk 
7314783bd39SSerhii Iliushyk 	for (size_t i = 0; i < ARRAY_SIZE(nim_list); i++) {
7324783bd39SSerhii Iliushyk 		if (ctx->prod_no == nim_list[i]) {
7334783bd39SSerhii Iliushyk 			ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC);
7344783bd39SSerhii Iliushyk 			ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ena = true;
7354783bd39SSerhii Iliushyk 			NT_LOG(DBG, NTHW, "Found FEC info via PN list");
7364783bd39SSerhii Iliushyk 			return;
7374783bd39SSerhii Iliushyk 		}
7384783bd39SSerhii Iliushyk 	}
7394783bd39SSerhii Iliushyk 
7404783bd39SSerhii Iliushyk 	/*
7414783bd39SSerhii Iliushyk 	 * For modules not in the list find FEC info via registers
7424783bd39SSerhii Iliushyk 	 * Read if the module has controllable FEC
7434783bd39SSerhii Iliushyk 	 * SFF-8636, Rev 2.10a TABLE 6-28 Equalizer, Emphasis, Amplitude and Timing)
7444783bd39SSerhii Iliushyk 	 * (Page 03h, Bytes 224-229)
7454783bd39SSerhii Iliushyk 	 */
7464783bd39SSerhii Iliushyk 	uint8_t data;
7474783bd39SSerhii Iliushyk 	uint16_t addr = 227 + 3 * 128;
7484783bd39SSerhii Iliushyk 	read_data_lin(ctx, addr, sizeof(data), &data);
7494783bd39SSerhii Iliushyk 
7504783bd39SSerhii Iliushyk 	/* Check if the module has FEC support that can be controlled */
7514783bd39SSerhii Iliushyk 	ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl = (data & (1 << 6)) != 0;
7524783bd39SSerhii Iliushyk 	ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl = (data & (1 << 7)) != 0;
7534783bd39SSerhii Iliushyk 
7544783bd39SSerhii Iliushyk 	if (ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl)
7554783bd39SSerhii Iliushyk 		ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC);
7564783bd39SSerhii Iliushyk 
7574783bd39SSerhii Iliushyk 	if (ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl)
7584783bd39SSerhii Iliushyk 		ctx->options |= (1 << NIM_OPTION_HOST_SIDE_FEC);
7594783bd39SSerhii Iliushyk }
7604783bd39SSerhii Iliushyk 
7614783bd39SSerhii Iliushyk static int qsfp28_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
7624783bd39SSerhii Iliushyk {
7634783bd39SSerhii Iliushyk 	int res = qsfpplus_preinit(ctx, lane_idx);
7644783bd39SSerhii Iliushyk 
7654783bd39SSerhii Iliushyk 	if (!res) {
7664783bd39SSerhii Iliushyk 		qsfp28_wait_for_ready_after_reset(ctx);
7674783bd39SSerhii Iliushyk 		memset(&ctx->specific_u.qsfp.specific_u.qsfp28, 0,
7684783bd39SSerhii Iliushyk 			sizeof(ctx->specific_u.qsfp.specific_u.qsfp28));
7694783bd39SSerhii Iliushyk 		ctx->specific_u.qsfp.qsfp28 = true;
7704783bd39SSerhii Iliushyk 		qsfp28_find_port_params(ctx);
7714783bd39SSerhii Iliushyk 		qsfp28_get_fec_options(ctx);
7724783bd39SSerhii Iliushyk 		qsfp28_set_speed_mask(ctx);
7734783bd39SSerhii Iliushyk 	}
7744783bd39SSerhii Iliushyk 
7754783bd39SSerhii Iliushyk 	return res;
7764783bd39SSerhii Iliushyk }
7774783bd39SSerhii Iliushyk 
77827c15342SSerhii Iliushyk int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra)
779eaf1ebdcSSerhii Iliushyk {
780eaf1ebdcSSerhii Iliushyk 	int res = i2c_nim_common_construct(ctx);
781eaf1ebdcSSerhii Iliushyk 
78227c15342SSerhii Iliushyk 	switch (translate_nimid(ctx)) {
78327c15342SSerhii Iliushyk 	case NT_NIM_QSFP_PLUS:
78427c15342SSerhii Iliushyk 		qsfpplus_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
78527c15342SSerhii Iliushyk 		break;
78627c15342SSerhii Iliushyk 
7874783bd39SSerhii Iliushyk 	case NT_NIM_QSFP28:
7884783bd39SSerhii Iliushyk 		qsfp28_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
7894783bd39SSerhii Iliushyk 		break;
7904783bd39SSerhii Iliushyk 
79127c15342SSerhii Iliushyk 	default:
79227c15342SSerhii Iliushyk 		res = 1;
793*3489b87bSDanylo Vodopianov 		NT_LOG(ERR, NTHW, "NIM type %s is not supported.", nim_id_to_text(ctx->nim_id));
79427c15342SSerhii Iliushyk 	}
79527c15342SSerhii Iliushyk 
796eaf1ebdcSSerhii Iliushyk 	return res;
797eaf1ebdcSSerhii Iliushyk }
798