[v10,18/21] net/ntnic: add QSFP support

Message ID 20240717133313.3104239-18-sil-plv@napatech.com (mailing list archive)
State Accepted, archived
Delegated to: Ferruh Yigit
Headers
Series [v10,01/21] net/ntnic: add ethdev and makes PMD available |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Serhii Iliushyk July 17, 2024, 1:33 p.m. UTC
Includes support for QSFP and QSFP+.

Signed-off-by: Serhii Iliushyk <sil-plv@napatech.com>
---
 drivers/net/ntnic/include/ntnic_nim.h         |  10 +
 .../link_mgmt/link_100g/nt4ga_link_100g.c     |  12 +-
 drivers/net/ntnic/nim/i2c_nim.c               | 310 +++++++++++++++++-
 drivers/net/ntnic/nim/i2c_nim.h               |  14 +-
 drivers/net/ntnic/nim/nim_defines.h           |   3 +
 drivers/net/ntnic/nim/qsfp_registers.h        |  43 +++
 6 files changed, 389 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/ntnic/nim/qsfp_registers.h
  

Patch

diff --git a/drivers/net/ntnic/include/ntnic_nim.h b/drivers/net/ntnic/include/ntnic_nim.h
index fd4a915811..216930af76 100644
--- a/drivers/net/ntnic/include/ntnic_nim.h
+++ b/drivers/net/ntnic/include/ntnic_nim.h
@@ -15,6 +15,8 @@  typedef enum i2c_type {
 enum nt_port_type_e {
 	NT_PORT_TYPE_NOT_AVAILABLE = 0,	/* The NIM/port type is not available (unknown) */
 	NT_PORT_TYPE_NOT_RECOGNISED,	/* The NIM/port type not recognized */
+	NT_PORT_TYPE_QSFP_PLUS_NOT_PRESENT,	/* QSFP type but slot is empty */
+	NT_PORT_TYPE_QSFP_PLUS,	/* QSFP type */
 };
 
 typedef enum nt_port_type_e nt_port_type_t, *nt_port_type_p;
@@ -51,6 +53,14 @@  typedef struct nim_i2c_ctx {
 	bool tx_disable;
 	bool dmi_supp;
 
+	union {
+		struct {
+			bool rx_only;
+			union {
+			} specific_u;
+		} qsfp;
+
+	} specific_u;
 } nim_i2c_ctx_t, *nim_i2c_ctx_p;
 
 struct nim_sensor_group {
diff --git a/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
index 4a8d28af9c..69d0a5d24a 100644
--- a/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
+++ b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
@@ -18,6 +18,7 @@  static int _create_nim(adapter_info_t *drv, int port)
 	int res = 0;
 	const uint8_t valid_nim_id = 17U;
 	nim_i2c_ctx_t *nim_ctx;
+	sfp_nim_state_t nim;
 	nt4ga_link_t *link_info = &drv->nt4ga_link;
 
 	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
@@ -31,11 +32,20 @@  static int _create_nim(adapter_info_t *drv, int port)
 	 */
 	nt_os_wait_usec(1000000);	/* pause 1.0s */
 
-	res = construct_and_preinit_nim(nim_ctx);
+	res = construct_and_preinit_nim(nim_ctx, NULL);
 
 	if (res)
 		return res;
 
+	res = nim_state_build(nim_ctx, &nim);
+
+	if (res)
+		return res;
+
+	NT_LOG(DBG, NTHW, "%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+		drv->mp_port_id_str[port], nim_ctx->nim_id, nim_id_to_text(nim_ctx->nim_id), nim.br,
+		nim_ctx->vendor_name, nim_ctx->prod_no, nim_ctx->serial_no);
+
 	/*
 	 * Does the driver support the NIM module type?
 	 */
diff --git a/drivers/net/ntnic/nim/i2c_nim.c b/drivers/net/ntnic/nim/i2c_nim.c
index 3281058822..0071d452bb 100644
--- a/drivers/net/ntnic/nim/i2c_nim.c
+++ b/drivers/net/ntnic/nim/i2c_nim.c
@@ -10,6 +10,7 @@ 
 #include "ntlog.h"
 #include "nt_util.h"
 #include "ntnic_mod_reg.h"
+#include "qsfp_registers.h"
 #include "nim_defines.h"
 
 #define NIM_READ false
@@ -17,6 +18,25 @@ 
 #define NIM_PAGE_SEL_REGISTER 127
 #define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
 
+
+static bool page_addressing(nt_nim_identifier_t id)
+{
+	switch (id) {
+	case NT_NIM_QSFP:
+	case NT_NIM_QSFP_PLUS:
+		return true;
+
+	default:
+		NT_LOG(DBG, NTNIC, "Unknown NIM identifier %d\n", id);
+		return false;
+	}
+}
+
+static nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx)
+{
+	return (nt_nim_identifier_t)ctx->nim_id;
+}
+
 static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr,
 	uint8_t i2c_addr, uint8_t a_reg_addr, uint8_t seq_cnt,
 	uint8_t *p_data)
@@ -158,6 +178,13 @@  static int nim_read_write_data_lin(nim_i2c_ctx_p ctx, bool m_page_addressing, ui
 	return 0;
 }
 
+static int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data)
+{
+	/* Wrapper for using Mutex for QSFP TODO */
+	return nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id), lin_addr, length, data,
+			NIM_READ);
+}
+
 static int nim_read_id(nim_i2c_ctx_t *ctx)
 {
 	/* We are only reading the first byte so we don't care about pages here. */
@@ -205,20 +232,301 @@  static int i2c_nim_common_construct(nim_i2c_ctx_p ctx)
 	return 0;
 }
 
+/*
+ * Read vendor information at a certain address. Any trailing whitespace is
+ * removed and a missing string termination in the NIM data is handled.
+ */
+static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data)
+{
+	const bool pg_addr = page_addressing(ctx->nim_id);
+	int i;
+	/* Subtract "1" from max_len that includes a terminating "0" */
+
+	if (nim_read_write_data_lin(ctx, pg_addr, addr, (uint8_t)(max_len - 1), (uint8_t *)p_data,
+			NIM_READ) != 0) {
+		return -1;
+	}
+
+	/* Terminate at first found white space */
+	for (i = 0; i < max_len - 1; i++) {
+		if (*p_data == ' ' || *p_data == '\n' || *p_data == '\t' || *p_data == '\v' ||
+			*p_data == '\f' || *p_data == '\r') {
+			*p_data = '\0';
+			return 0;
+		}
+
+		p_data++;
+	}
+
+	/*
+	 * Add line termination as the very last character, if it was missing in the
+	 * NIM data
+	 */
+	*p_data = '\0';
+	return 0;
+}
+
+static void qsfp_read_vendor_info(nim_i2c_ctx_t *ctx)
+{
+	nim_read_vendor_info(ctx, QSFP_VENDOR_NAME_LIN_ADDR, sizeof(ctx->vendor_name),
+		ctx->vendor_name);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_PN_LIN_ADDR, sizeof(ctx->prod_no), ctx->prod_no);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_SN_LIN_ADDR, sizeof(ctx->serial_no), ctx->serial_no);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_DATE_LIN_ADDR, sizeof(ctx->date), ctx->date);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_REV_LIN_ADDR, (uint8_t)(sizeof(ctx->rev) - 2),
+		ctx->rev);	/*OBS Only two bytes*/
+}
+static int qsfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+	int res = 0;	/* unused due to no readings from HW */
+
+	assert(ctx && state);
+	assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized");
+
+	(void)memset(state, 0, sizeof(*state));
+
+	switch (ctx->nim_id) {
+	case 12U:
+		state->br = 10U;/* QSFP: 4 x 1G = 4G */
+		break;
+
+	case 13U:
+		state->br = 103U;	/* QSFP+: 4 x 10G = 40G */
+		break;
+
+	default:
+		NT_LOG(INF, NIM, "nim_id = %u is not an QSFP/QSFP+ module\n", ctx->nim_id);
+		res = -1;
+	}
+
+	return res;
+}
+
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+	return qsfp_nim_state_build(ctx, state);
+}
+
 const char *nim_id_to_text(uint8_t nim_id)
 {
 	switch (nim_id) {
 	case 0x0:
 		return "UNKNOWN";
 
+	case 0x0C:
+		return "QSFP";
+
+	case 0x0D:
+		return "QSFP+";
+
 	default:
 		return "ILLEGAL!";
 	}
 }
 
-int construct_and_preinit_nim(nim_i2c_ctx_p ctx)
+/*
+ * Disable laser for specific lane or all lanes
+ */
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable, int lane_idx)
+{
+	uint8_t value;
+	uint8_t mask;
+	const bool pg_addr = page_addressing(ctx->nim_id);
+
+	if (lane_idx < 0)	/* If no lane is specified then all lanes */
+		mask = QSFP_SOFT_TX_ALL_DISABLE_BITS;
+
+	else
+		mask = (uint8_t)(1U << lane_idx);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+			&value, NIM_READ) != 0) {
+		return -1;
+	}
+
+	if (disable)
+		value |= mask;
+
+	else
+		value &= (uint8_t)(~mask);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+			&value, NIM_WRITE) != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Import length info in various units from NIM module data and convert to meters
+ */
+static void nim_import_len_info(nim_i2c_ctx_p ctx, uint8_t *p_nim_len_info, uint16_t *p_nim_units)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->len_info); i++)
+		if (*(p_nim_len_info + i) == 255) {
+			ctx->len_info[i] = 65535;
+
+		} else {
+			uint32_t len = *(p_nim_len_info + i) * *(p_nim_units + i);
+
+			if (len > 65535)
+				ctx->len_info[i] = 65535;
+
+			else
+				ctx->len_info[i] = (uint16_t)len;
+		}
+}
+
+static int qsfpplus_read_basic_data(nim_i2c_ctx_t *ctx)
+{
+	const bool pg_addr = page_addressing(ctx->nim_id);
+	uint8_t options;
+	uint8_t value;
+	uint8_t nim_len_info[5];
+	uint16_t nim_units[5] = { 1000, 2, 1, 1, 1 };	/* QSFP MSA units in meters */
+	const char *yes_no[2] = { "No", "Yes" };
+	(void)yes_no;
+	NT_LOG(DBG, NTNIC, "Instance %d: NIM id: %s (%d)\n", ctx->instance,
+		nim_id_to_text(ctx->nim_id), ctx->nim_id);
+
+	/* Read DMI options */
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_DMI_OPTION_LIN_ADDR, sizeof(options),
+			&options, NIM_READ) != 0) {
+		return -1;
+	}
+
+	ctx->avg_pwr = options & QSFP_DMI_AVG_PWR_BIT;
+	NT_LOG(DBG, NTNIC, "Instance %d: NIM options: (DMI: Yes, AvgPwr: %s)\n", ctx->instance,
+		yes_no[ctx->avg_pwr]);
+
+	qsfp_read_vendor_info(ctx);
+	NT_LOG(DBG, PMD,
+		"Instance %d: NIM info: (Vendor: %s, PN: %s, SN: %s, Date: %s, Rev: %s)\n",
+		ctx->instance, ctx->vendor_name, ctx->prod_no, ctx->serial_no, ctx->date, ctx->rev);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info),
+			nim_len_info, NIM_READ) != 0) {
+		return -1;
+	}
+
+	/*
+	 * Returns supported length information in meters for various fibers as 5 indivi-
+	 * dual values: [SM(9um), EBW(50um), MM(50um), MM(62.5um), Copper]
+	 * If no length information is available for a certain entry, the returned value
+	 * will be zero. This will be the case for SFP modules - EBW entry.
+	 * If the MSBit is set the returned value in the lower 31 bits indicates that the
+	 * supported length is greater than this.
+	 */
+
+	nim_import_len_info(ctx, nim_len_info, nim_units);
+
+	/* Read required power level */
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_EXTENDED_IDENTIFIER, sizeof(value), &value,
+			NIM_READ) != 0) {
+		return -1;
+	}
+
+	/*
+	 * Get power class according to SFF-8636 Rev 2.7, Table 6-16, Page 43:
+	 * If power class >= 5 setHighPower must be called for the module to be fully
+	 * functional
+	 */
+	if ((value & QSFP_POWER_CLASS_BITS_5_7) == 0) {
+		/* NIM in power class 1 - 4 */
+		ctx->pwr_level_req = (uint8_t)(((value & QSFP_POWER_CLASS_BITS_1_4) >> 6) + 1);
+
+	} else {
+		/* NIM in power class 5 - 7 */
+		ctx->pwr_level_req = (uint8_t)((value & QSFP_POWER_CLASS_BITS_5_7) + 4);
+	}
+
+	return 0;
+}
+
+static void qsfpplus_find_port_params(nim_i2c_ctx_p ctx)
+{
+	uint8_t device_tech;
+	read_data_lin(ctx, QSFP_TRANSMITTER_TYPE_LIN_ADDR, sizeof(device_tech), &device_tech);
+
+	switch (device_tech & 0xF0) {
+	case 0xA0:	/* Copper cable unequalized */
+		break;
+
+	case 0xC0:	/* Copper cable, near and far end limiting active equalizers */
+	case 0xD0:	/* Copper cable, far end limiting active equalizers */
+	case 0xE0:	/* Copper cable, near end limiting active equalizers */
+		break;
+
+	default:/* Optical */
+		ctx->port_type = NT_PORT_TYPE_QSFP_PLUS;
+		break;
+	}
+}
+
+static void qsfpplus_set_speed_mask(nim_i2c_ctx_p ctx)
+{
+	ctx->speed_mask = (ctx->lane_idx < 0) ? NT_LINK_SPEED_40G : (NT_LINK_SPEED_10G);
+}
+
+static void qsfpplus_construct(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+	assert(lane_idx < 4);
+	ctx->lane_idx = lane_idx;
+	ctx->lane_count = 4;
+}
+
+static int qsfpplus_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+	qsfpplus_construct(ctx, lane_idx);
+	int res = qsfpplus_read_basic_data(ctx);
+
+	if (!res) {
+		qsfpplus_find_port_params(ctx);
+
+		/*
+		 * Read if TX_DISABLE has been implemented
+		 * For passive optical modules this is required while it for copper and active
+		 * optical modules is optional. Under all circumstances register 195.4 will
+		 * indicate, if TX_DISABLE has been implemented in register 86.0-3
+		 */
+		uint8_t value;
+		read_data_lin(ctx, QSFP_OPTION3_LIN_ADDR, sizeof(value), &value);
+
+		ctx->tx_disable = (value & QSFP_OPTION3_TX_DISABLE_BIT) != 0;
+
+		if (ctx->tx_disable)
+			ctx->options |= (1 << NIM_OPTION_TX_DISABLE);
+
+		/*
+		 * Previously - considering AFBR-89BRDZ - code tried to establish if a module was
+		 * RxOnly by testing the state of the lasers after reset. Lasers were for this
+		 * module default disabled.
+		 * However that code did not work for GigaLight, GQS-MPO400-SR4C so it was
+		 * decided that this option should not be detected automatically but from PN
+		 */
+		ctx->specific_u.qsfp.rx_only = (ctx->options & (1 << NIM_OPTION_RX_ONLY)) != 0;
+		qsfpplus_set_speed_mask(ctx);
+	}
+
+	return res;
+}
+
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra)
 {
 	int res = i2c_nim_common_construct(ctx);
 
+	switch (translate_nimid(ctx)) {
+	case NT_NIM_QSFP_PLUS:
+		qsfpplus_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
+		break;
+
+	default:
+		res = 1;
+		NT_LOG(ERR, NTHW, "NIM type %s is not supported.\n", nim_id_to_text(ctx->nim_id));
+	}
+
 	return res;
 }
diff --git a/drivers/net/ntnic/nim/i2c_nim.h b/drivers/net/ntnic/nim/i2c_nim.h
index e89ae47835..edb6dcf1b6 100644
--- a/drivers/net/ntnic/nim/i2c_nim.h
+++ b/drivers/net/ntnic/nim/i2c_nim.h
@@ -8,17 +8,29 @@ 
 
 #include "ntnic_nim.h"
 
+typedef struct sfp_nim_state {
+	uint8_t br;	/* bit rate, units of 100 MBits/sec */
+} sfp_nim_state_t, *sfp_nim_state_p;
+
+/*
+ * Builds an nim state for the port implied by `ctx`, returns zero
+ * if successful, and non-zero otherwise. SFP and QSFP nims are supported
+ */
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state);
+
 /*
  * Returns a type name such as "SFP/SFP+" for a given NIM type identifier,
  * or the string "ILLEGAL!".
  */
 const char *nim_id_to_text(uint8_t nim_id);
 
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_t *ctx, bool disable, int lane_idx);
+
 /*
  * This function tries to classify NIM based on it's ID and some register reads
  * and collects information into ctx structure. The @extra parameter could contain
  * the initialization argument for specific type of NIMS.
  */
-int construct_and_preinit_nim(nim_i2c_ctx_p ctx);
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra);
 
 #endif	/* I2C_NIM_H_ */
diff --git a/drivers/net/ntnic/nim/nim_defines.h b/drivers/net/ntnic/nim/nim_defines.h
index 9ba861bb4f..e5a033a3d4 100644
--- a/drivers/net/ntnic/nim/nim_defines.h
+++ b/drivers/net/ntnic/nim/nim_defines.h
@@ -7,6 +7,7 @@ 
 #define NIM_DEFINES_H_
 
 #define NIM_IDENTIFIER_ADDR 0	/* 1 byte */
+#define QSFP_EXTENDED_IDENTIFIER 129
 
 /* I2C addresses */
 #define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
@@ -23,6 +24,8 @@  typedef enum {
 
 enum nt_nim_identifier_e {
 	NT_NIM_UNKNOWN = 0x00,	/* Nim type is unknown */
+	NT_NIM_QSFP = 0x0C,	/* Nim type = QSFP */
+	NT_NIM_QSFP_PLUS = 0x0D,/* Nim type = QSFP+ */
 };
 typedef enum nt_nim_identifier_e nt_nim_identifier_t;
 
diff --git a/drivers/net/ntnic/nim/qsfp_registers.h b/drivers/net/ntnic/nim/qsfp_registers.h
new file mode 100644
index 0000000000..13172ce30b
--- /dev/null
+++ b/drivers/net/ntnic/nim/qsfp_registers.h
@@ -0,0 +1,43 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _QSFP_REGISTERS_H
+#define _QSFP_REGISTERS_H
+
+/*
+ * QSFP Registers
+ */
+#define QSFP_INT_STATUS_RX_LOS_ADDR 3
+#define QSFP_TEMP_LIN_ADDR 22
+#define QSFP_VOLT_LIN_ADDR 26
+#define QSFP_RX_PWR_LIN_ADDR 34	/* uint16_t [0..3] */
+#define QSFP_TX_BIAS_LIN_ADDR 42/* uint16_t [0..3] */
+#define QSFP_TX_PWR_LIN_ADDR 50	/* uint16_t [0..3] */
+
+#define QSFP_CONTROL_STATUS_LIN_ADDR 86
+#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F
+
+#define QSFP_POWER_CLASS_BITS_1_4 0xC0
+#define QSFP_POWER_CLASS_BITS_5_7 0x03
+
+#define QSFP_SUP_LEN_INFO_LIN_ADDR 142	/* 5bytes */
+#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147	/* 1byte */
+#define QSFP_VENDOR_NAME_LIN_ADDR 148	/* 16bytes */
+#define QSFP_VENDOR_PN_LIN_ADDR 168	/* 16bytes */
+#define QSFP_VENDOR_SN_LIN_ADDR 196	/* 16bytes */
+#define QSFP_VENDOR_DATE_LIN_ADDR 212	/* 8bytes */
+#define QSFP_VENDOR_REV_LIN_ADDR 184	/* 2bytes */
+
+#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131	/* 8 bytes */
+#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192	/* 1 byte */
+
+#define QSFP_OPTION3_LIN_ADDR 195
+#define QSFP_OPTION3_TX_DISABLE_BIT (1 << 4)
+
+#define QSFP_DMI_OPTION_LIN_ADDR 220
+#define QSFP_DMI_AVG_PWR_BIT (1 << 3)
+
+
+#endif	/* _QSFP_REGISTERS_H */