Includes support for QSFP and QSFP+.

Signed-off-by: Serhii Iliushyk <sil-...@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

diff --git a/drivers/net/ntnic/include/ntnic_nim.h 
b/drivers/net/ntnic/include/ntnic_nim.h
index 263875a857..300842b570 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 */
-- 
2.45.0

Reply via email to