[PATCH 13/23] net: hpe: sl: add media and cable handling
flat view
HOTtoday
From: Doug Ledford <hidden>
Date: 2026-10-30 21:01:17
Also in:
linux-kbuild, lkml
Subsystem:
networking drivers, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Add cable and media handling: EEPROM parsing, the cable database,
per-jack data, and the emulated jack used when no real media is
present.
checkpatch --strict (100 columns) still reports the following in this
patch. They need coordination with the internal repositories this code
is maintained in and will be fixed in V2:
- WARNING: LONG_LINE (164), LONG_LINE_STRING (21), LONG_LINE_COMMENT
(2), BRACES (1)
- CHECK: UNNECESSARY_PARENTHESES (27), ALLOC_SIZEOF_STRUCT (3), BRACES
(2), COMPARISON_TO_NULL (1)
Co-developed-by: Mamila Santosh <redacted>
Signed-off-by: Mamila Santosh <redacted>
Signed-off-by: Doug Ledford <redacted>
---
.../ethernet/hpe/sl/media/base/sl_media_eeprom.c | 611 +++++++++++
.../hpe/sl/media/data/nic/sl_media_data_jack.c | 496 +++++++++
.../hpe/sl/media/data/nic/sl_media_data_jack_emu.c | 38 +
.../hpe/sl/media/data/nic/sl_media_data_ldev.c | 30 +
.../hpe/sl/media/data/sl_media_data_cable_db_ops.c | 111 ++
.../hpe/sl/media/data/sl_media_data_jack.c | 344 +++++++
.../hpe/sl/media/data/sl_media_data_ldev.c | 115 +++
.../hpe/sl/media/data/sl_media_data_lgrp.c | 127 +++
.../hpe/sl/media/data/sl_media_data_lgrp_trace.c | 31 +
.../net/ethernet/hpe/sl/media/nic/sl_media_io.c | 63 ++
.../net/ethernet/hpe/sl/media/nic/sl_media_jack.c | 315 ++++++
.../net/ethernet/hpe/sl/media/nic/sl_media_ldev.c | 13 +
drivers/net/ethernet/hpe/sl/media/sl_media_jack.c | 1067 ++++++++++++++++++++
drivers/net/ethernet/hpe/sl/media/sl_media_ldev.c | 22 +
drivers/net/ethernet/hpe/sl/media/sl_media_lgrp.c | 498 +++++++++
15 files changed, 3881 insertions(+)
diff --git a/drivers/net/ethernet/hpe/sl/media/base/sl_media_eeprom.c b/drivers/net/ethernet/hpe/sl/media/base/sl_media_eeprom.c
new file mode 100644
index 000000000000..80113ec4860d
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/base/sl_media_eeprom.c@@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2023-2026 Hewlett Packard Enterprise Development LP */ + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "sl_media_eeprom.h" +#include "sl_media_lgrp.h" +#include "sl_media_jack.h" +#include "base/sl_media_log.h" +#include "data/sl_media_data_cable_db.h" + +#define LOG_NAME SL_MEDIA_EEPROM_LOG_NAME + +static void sl_media_eeprom_appsel_info_store(struct sl_media_jack *media_jack, u8 host_interface, + unsigned long *speeds_map, u8 appsel_num, u8 lane_count) +{ + switch (host_interface) { + case SL_MEDIA_SS1_HOST_INTERFACE_50GAUI_1_C2M: + case SL_MEDIA_SS1_HOST_INTERFACE_50GBASE_CR: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CD_50G; + break; + case SL_MEDIA_SS1_HOST_INTERFACE_100GAUI_2_C2M: + case SL_MEDIA_SS1_HOST_INTERFACE_100GBASE_CR2: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CD_100G; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_100GBASE_CR1: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + fallthrough; + case SL_MEDIA_SS2_HOST_INTERFACE_100GAUI_1_L_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_100G; + break; + case SL_MEDIA_SS1_HOST_INTERFACE_200GBASE_CR4: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BS_200G; + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + break; + case SL_MEDIA_SS1_HOST_INTERFACE_200GAUI_4_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BS_200G; + media_jack->appsel_num_200_gaui = appsel_num; + media_jack->lane_count_200_gaui = lane_count; + media_jack->host_interface_200_gaui = host_interface; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_200GBASE_CR2: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + fallthrough; + case SL_MEDIA_SS2_HOST_INTERFACE_200GAUI_2_S_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_200G; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_400GBASE_CR4: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_400GAUI_4_S_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + if (media_jack->appsel_num_400_gaui) /* we prefer 4_l_c2m over 4_s_c2m for default ck400G speed */ + break; + media_jack->appsel_num_400_gaui = appsel_num; + media_jack->lane_count_400_gaui = lane_count; + media_jack->host_interface_400_gaui = host_interface; + break; + case SL_MEDIA_SS1_HOST_INTERFACE_400GAUI_4_L_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + media_jack->appsel_num_400_gaui = appsel_num; + media_jack->lane_count_400_gaui = lane_count; + media_jack->host_interface_400_gaui = host_interface; + fallthrough; + case SL_MEDIA_SS1_HOST_INTERFACE_400GBASE_CR8: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BS_200G; + fallthrough; + case SL_MEDIA_SS1_HOST_INTERFACE_CAUI_4_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_800GBASE_CR8: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_800G; + break; + case SL_MEDIA_SS2_HOST_INTERFACE_800GAUI_8_S_C2M: + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + *speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CK_800G; + if (!media_jack->host_interface_400_gaui) { + media_jack->appsel_num_400_gaui = appsel_num; + media_jack->host_interface_400_gaui = host_interface; + } + break; + default: + sl_media_log_dbg(media_jack, LOG_NAME, + "invalid host interface (0x%x)", host_interface); + } +} + +#define APPSEL_PAGE0_START_OFFSET 86 +#define APPSEL_PAGE1_START_OFFSET 223 +#define APPSEL_STRIDE 4 +#define APPSEL_END 0xFF +#define APPSEL_LAST_PAGE0 114 +#define APPSEL_LAST_PAGE1 247 +#define APPSEL_LANE_COUNT_OFFSET 2 +static int sl_media_eeprom_appsel_info_get(struct sl_media_jack *media_jack, unsigned long *speeds_map) +{ + u8 host_interface; + u8 appsel_curr; + u8 appsel_num; + + appsel_curr = APPSEL_PAGE0_START_OFFSET; + appsel_num = 1; + while (appsel_curr <= APPSEL_LAST_PAGE0) { + host_interface = media_jack->eeprom_page0[appsel_curr]; + if (host_interface == APPSEL_END) + return 0; + sl_media_eeprom_appsel_info_store(media_jack, host_interface, speeds_map, appsel_num, + media_jack->eeprom_page0[appsel_curr + APPSEL_LANE_COUNT_OFFSET]); + appsel_curr += APPSEL_STRIDE; + appsel_num++; + } + + appsel_curr = APPSEL_PAGE1_START_OFFSET; + while (appsel_curr <= APPSEL_LAST_PAGE1) { + host_interface = media_jack->eeprom_page1[appsel_curr]; + if (host_interface == APPSEL_END) + return 0; + sl_media_eeprom_appsel_info_store(media_jack, host_interface, speeds_map, appsel_num, + media_jack->eeprom_page1[appsel_curr + APPSEL_LANE_COUNT_OFFSET]); + appsel_curr += APPSEL_STRIDE; + appsel_num++; + } + + return 0; +} + +static int sl_media_eeprom_furcation_get(struct sl_media_jack *media_jack, u32 *furcation) +{ + *furcation = SL_MEDIA_FURCATION_X1; + return 0; +} + +#define CMIS_CONNECTOR_TYPE_OFFSET 203 +#define CMIS_CONNECTOR_POC_SR8 0x28 +#define CMIS_CONNECTOR_POC_SR4 0x0C +#define SFF8436_CONNECTOR_TYPE_OFFSET 130 +#define SFF_CONNECTOR_AOC 0x23 +static bool sl_media_eeprom_is_type_poc(struct sl_media_jack *media_jack, u8 format) +{ + if (format == SL_MEDIA_MGMT_IF_CMIS) { + switch (media_jack->eeprom_page0[CMIS_CONNECTOR_TYPE_OFFSET]) { + case CMIS_CONNECTOR_POC_SR8: + case CMIS_CONNECTOR_POC_SR4: + return true; + default: + return false; + } + } + + /* SFF */ + switch (media_jack->eeprom_page0[SFF8436_CONNECTOR_TYPE_OFFSET]) { + case SFF_CONNECTOR_AOC: + return false; + default: + return true; + } +} + +#define CMIS_TYPE_OFFSET 212 +#define CMIS_MEDIA_AOC 0x00 +#define CMIS_MEDIA_PEC 0x0A +#define CMIS_MEDIA_AEC 0x0C +#define CMIS_MEDIA_ACC 0x0F +#define SFF8436_TYPE_OFFSET 147 +#define SFF_MEDIA_AOC 0x00 +#define SFF_MEDIA_PEC 0xA0 +#define SFF_MEDIA_AEC 0xC0 +static int sl_media_eeprom_type_get(struct sl_media_jack *media_jack, u8 format, u32 vendor, u32 *type) +{ + u8 media; + + if (format == SL_MEDIA_MGMT_IF_CMIS) { + media = media_jack->eeprom_page0[CMIS_TYPE_OFFSET]; + switch (media) { + case CMIS_MEDIA_AOC: + if (sl_media_eeprom_is_type_poc(media_jack, format)) + *type = SL_MEDIA_TYPE_POC; + else + *type = SL_MEDIA_TYPE_AOC; + break; + case CMIS_MEDIA_PEC: + *type = SL_MEDIA_TYPE_PEC; + break; + case CMIS_MEDIA_AEC: + *type = SL_MEDIA_TYPE_AEC; + break; + case CMIS_MEDIA_ACC: + /* compensate for Molex type programming issue */ + if (vendor == SL_MEDIA_VENDOR_MOLEX) + *type = SL_MEDIA_TYPE_AEC; + else + *type = SL_MEDIA_TYPE_ACC; + break; + default: + *type = SL_MEDIA_TYPE_UNSUPPORTED; + } + } else { + media = media_jack->eeprom_page0[SFF8436_TYPE_OFFSET]; + switch (media) { + case SFF_MEDIA_AOC: + if (sl_media_eeprom_is_type_poc(media_jack, format)) + *type = SL_MEDIA_TYPE_POC; + else + *type = SL_MEDIA_TYPE_AOC; + break; + case SFF_MEDIA_PEC: + *type = SL_MEDIA_TYPE_PEC; + break; + case SFF_MEDIA_AEC: + *type = SL_MEDIA_TYPE_AEC; + break; + default: + *type = SL_MEDIA_TYPE_UNSUPPORTED; + } + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "type get (media = 0x%X, type = 0x%X %s)", + media, *type, sl_media_type_str(*type)); + + return 0; +} + +#define CMIS_VENDOR_OFFSET 129 +#define SFF8436_VENDOR_OFFSET 148 +#define VENDOR_NAME_MAX_LEN 32 +struct vendor { + char name[VENDOR_NAME_MAX_LEN]; + u32 type; +}; + +static const struct vendor vendor_list[] = { + { .name = "TE Connectivity", + .type = SL_MEDIA_VENDOR_TE }, + { .name = "Hisense", + .type = SL_MEDIA_VENDOR_HISENSE }, + { .name = "FINISAR CORP", + .type = SL_MEDIA_VENDOR_FINISAR }, + { .name = "Bizlink", + .type = SL_MEDIA_VENDOR_BIZLINK }, + { .name = "Cloud Light", + .type = SL_MEDIA_VENDOR_CLOUD_LIGHT }, + { .name = "Molex", + .type = SL_MEDIA_VENDOR_MOLEX }, + { .name = "MULTILANE", + .type = SL_MEDIA_VENDOR_MULTILANE }, + { .name = "Amphenol", + .type = SL_MEDIA_VENDOR_AMPHENOL }, +}; + +static int sl_media_eeprom_vendor_get(struct sl_media_jack *media_jack, u8 format, u32 *vendor) +{ + const char *vendor_ptr; + int x; + + if (format == SL_MEDIA_MGMT_IF_CMIS) + vendor_ptr = &(media_jack->eeprom_page0[CMIS_VENDOR_OFFSET]); + else + vendor_ptr = &(media_jack->eeprom_page0[SFF8436_VENDOR_OFFSET]); + + for (x = 0; x < ARRAY_SIZE(vendor_list); ++x) { + if (strnstr(vendor_ptr, vendor_list[x].name, VENDOR_NAME_MAX_LEN) == NULL) + continue; + *vendor = vendor_list[x].type; + sl_media_log_dbg(media_jack, LOG_NAME, + "vendor get (vendor = %u %s)", *vendor, sl_media_vendor_str(*vendor)); + return 0; + } + + *vendor = 0; + + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_EEPROM_VENDOR_UNSUPPORTED); + sl_media_log_err_trace(media_jack, LOG_NAME, "eeprom vendor get unsupported"); + + return 0; +} + +static bool is_valid_char(char c) +{ + return c >= 33 && c <= 126; +} + +#define CMIS_VENDOR_PN_OFFSET 148 +#define SFF_VENDOR_PN_OFFSET 168 +static int sl_media_eeprom_vendor_pn_str_get(struct sl_media_jack *media_jack, u8 format, char *vendor_pn_str) +{ + int x; + int pos; + u8 vendor_pn_offset; + + if (format == SL_MEDIA_MGMT_IF_CMIS) + vendor_pn_offset = CMIS_VENDOR_PN_OFFSET; + else + vendor_pn_offset = SFF_VENDOR_PN_OFFSET; + + pos = 0; + memset(vendor_pn_str, 0, SL_MEDIA_VENDOR_PN_SIZE); + for (x = 0; x < SL_MEDIA_VENDOR_PN_SIZE - 1; ++x) { + if (is_valid_char(media_jack->eeprom_page0[vendor_pn_offset + x])) { + vendor_pn_str[pos] = media_jack->eeprom_page0[vendor_pn_offset + x]; + pos++; + } + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "vendor part num get (par_num = %s)", vendor_pn_str); + + return 0; +} + +#define HPE_PN_OFFSET 229 +static int sl_media_eeprom_hpe_pn_get(struct sl_media_jack *media_jack, u32 *hpe_pn, char *hpe_pn_str) +{ + const char *pn_ptr; + int i; + int counter; + + counter = 0; + for (i = 0; i < SL_MEDIA_HPE_PN_SIZE - 1; ++i) { + if (is_valid_char(media_jack->eeprom_page0[HPE_PN_OFFSET + i])) { + hpe_pn_str[counter] = media_jack->eeprom_page0[HPE_PN_OFFSET + i]; + counter++; + } + } + hpe_pn_str[counter] = '\0'; + + pn_ptr = &(media_jack->eeprom_page0[HPE_PN_OFFSET]); + for (i = 0; i < SL_MEDIA_HPE_PN_SIZE - 1; ++i) { + if (pn_ptr[i] < 48 || pn_ptr[i] > 57) /* if the PN character is not numeric, ignore it */ + continue; + *hpe_pn = *hpe_pn * 10 + (pn_ptr[i] - '0'); + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "hpe part num get (hpe_pn = %s)", hpe_pn_str); + + return 0; +} + +#define CMIS_SERIAL_NUM_OFFSET 166 +#define SFF_SERIAL_NUM_OFFSET 196 +static int sl_media_eeprom_serial_num_get(struct sl_media_jack *media_jack, u8 format, char *serial_num_str) +{ + int i; + int counter; + u8 serial_num_offset; + + if (format == SL_MEDIA_MGMT_IF_CMIS) + serial_num_offset = CMIS_SERIAL_NUM_OFFSET; + else + serial_num_offset = SFF_SERIAL_NUM_OFFSET; + + counter = 0; + memset(serial_num_str, '\0', SL_MEDIA_SERIAL_NUM_SIZE); + for (i = 0; i < (SL_MEDIA_SERIAL_NUM_SIZE - 1); ++i) { + if (media_jack->eeprom_page0[serial_num_offset + i] == '-') + break; + + if (is_valid_char(media_jack->eeprom_page0[serial_num_offset + i])) { + serial_num_str[counter] = media_jack->eeprom_page0[serial_num_offset + i]; + counter++; + } + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "serial num get (serial_num = %s)", serial_num_str); + + return 0; +} + +#define CMIS_DATE_CODE_OFFSET 182 +#define SFF_DATE_CODE_OFFSET 212 +static int sl_media_eeprom_date_code_get(struct sl_media_jack *media_jack, u8 format, char *date_code_str) +{ + u8 date_code_offset; + + if (format == SL_MEDIA_MGMT_IF_CMIS) + date_code_offset = CMIS_DATE_CODE_OFFSET; + else + date_code_offset = SFF_DATE_CODE_OFFSET; + + date_code_str[0] = media_jack->eeprom_page0[date_code_offset]; + date_code_str[1] = media_jack->eeprom_page0[date_code_offset + 1]; + date_code_str[2] = '-'; + date_code_str[3] = media_jack->eeprom_page0[date_code_offset + 2]; + date_code_str[4] = media_jack->eeprom_page0[date_code_offset + 3]; + date_code_str[5] = '-'; + date_code_str[6] = media_jack->eeprom_page0[date_code_offset + 4]; + date_code_str[7] = media_jack->eeprom_page0[date_code_offset + 5]; + date_code_str[8] = '\0'; + + sl_media_log_dbg(media_jack, LOG_NAME, + "date code get (date = %s)", date_code_str); + + return 0; +} + +#define FIRMWARE_VERSION_OFFSET 39 +static int sl_media_eeprom_fw_ver_get(struct sl_media_jack *media_jack, u8 *fw_ver) +{ + fw_ver[0] = media_jack->eeprom_page0[FIRMWARE_VERSION_OFFSET]; + fw_ver[1] = media_jack->eeprom_page0[FIRMWARE_VERSION_OFFSET + 1]; + + return 0; +} + +#define CABLE_END_OFFSET 240 +static int sl_media_eeprom_cable_end_get(struct sl_media_jack *media_jack) +{ + media_jack->cable_end = media_jack->eeprom_page0[CABLE_END_OFFSET]; + + return 0; +} + +#define CMIS_LENGTH_OFFSET 202 +#define SFF8436_LENGTH_OFFSET 146 +static int sl_media_eeprom_length_get(struct sl_media_jack *media_jack, u8 format, u32 *length) +{ + if (format == SL_MEDIA_MGMT_IF_CMIS) { + *length = (media_jack->eeprom_page0[CMIS_LENGTH_OFFSET] & 0x3F); + switch (media_jack->eeprom_page0[CMIS_LENGTH_OFFSET] & 0xC0) { + case 0: + *length *= 10; + break; + case 0x40: + *length *= 100; + break; + case 0x80: + *length *= 1000; + break; + case 0xC0: + *length *= 10000; + break; + default: + *length = 0; + break; + } + } else { + *length = media_jack->eeprom_page0[SFF8436_LENGTH_OFFSET] * 100; + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "length get (length = %ucm)", *length); + + return 0; +} + +#define SUPPORTED_FLAGS_ADVERTISEMENT_OFFSET 157 +static void sl_media_eeprom_supported_flags_advertised_get(struct sl_media_jack *media_jack, + u8 *supported_flags_advertised) +{ + spin_lock(&media_jack->data_lock); + memcpy(supported_flags_advertised, &media_jack->eeprom_page1[SUPPORTED_FLAGS_ADVERTISEMENT_OFFSET], + SL_MEDIA_SUPPORTED_FLAGS_ADVERTISED_SIZE); + spin_unlock(&media_jack->data_lock); +} + +bool sl_media_eeprom_is_fw_version_supported(struct sl_media_jack *media_jack, struct sl_media_attr *media_attr) +{ + sl_media_log_dbg(media_jack, LOG_NAME, + "is fw version supported (type = 0x%X %s, shape = %u %s, end = %u %s, idx = %d)", + media_attr->type, sl_media_type_str(media_attr->type), + media_attr->shape, sl_media_shape_str(media_attr->shape), + media_jack->cable_end, sl_media_cable_end_str(media_jack->cable_end), + media_jack->cable_db_idx); + sl_media_log_dbg(media_jack, LOG_NAME, + "is fw version supported (fw_ver = %02X.%02X, db_fw_ver = %02X.%02X, db_split_fw_ver = %02X.%02X)", + media_attr->fw_ver[0], media_attr->fw_ver[1], + cable_db[media_jack->cable_db_idx].fw_ver.major, + cable_db[media_jack->cable_db_idx].fw_ver.minor, + cable_db[media_jack->cable_db_idx].fw_ver.split_major, + cable_db[media_jack->cable_db_idx].fw_ver.split_minor); + + if (media_attr->shape == SL_MEDIA_SHAPE_SPLITTER && media_jack->cable_end != SL_MEDIA_CABLE_END_DD) + return ((media_attr->fw_ver[0] > cable_db[media_jack->cable_db_idx].fw_ver.split_major) || + ((media_attr->fw_ver[0] == cable_db[media_jack->cable_db_idx].fw_ver.split_major) && + (media_attr->fw_ver[1] >= cable_db[media_jack->cable_db_idx].fw_ver.split_minor))); + + return ((media_attr->fw_ver[0] > cable_db[media_jack->cable_db_idx].fw_ver.major) || + ((media_attr->fw_ver[0] == cable_db[media_jack->cable_db_idx].fw_ver.major) && + (media_attr->fw_ver[1] >= cable_db[media_jack->cable_db_idx].fw_ver.minor))); +} + +int sl_media_eeprom_target_fw_ver_str_get(struct sl_media_jack *media_jack, char *target_fw_str, size_t target_fw_size) +{ + sl_media_log_dbg(media_jack, LOG_NAME, + "target fw ver get (idx = %u, type = 0x%X %s, shape = %u %s, end = %u %s, supported = %s)", + media_jack->cable_db_idx, + media_jack->cable_info[0].media_attr.type, + sl_media_type_str(media_jack->cable_info[0].media_attr.type), + media_jack->cable_info[0].media_attr.shape, + sl_media_shape_str(media_jack->cable_info[0].media_attr.shape), + media_jack->cable_end, + sl_media_cable_end_str(media_jack->cable_end), + media_jack->is_cable_unsupported ? "no" : "yes"); + + if (!SL_MEDIA_LGRP_MEDIA_TYPE_IS_ACTIVE(media_jack->cable_info[0].media_attr.type)) { + snprintf(target_fw_str, target_fw_size, "invalid-type\n"); + return 0; + } + + if (media_jack->is_cable_unsupported) { + snprintf(target_fw_str, target_fw_size, "unsupported\n"); + return 0; + } + + if (media_jack->is_supported_ss200_cable) { + snprintf(target_fw_str, target_fw_size, "ss200-cable\n"); + return 0; + } + + if (media_jack->cable_info[0].media_attr.shape == SL_MEDIA_SHAPE_SPLITTER && + media_jack->cable_end != SL_MEDIA_CABLE_END_DD) { + snprintf(target_fw_str, target_fw_size, "%02X.%02X\n", + cable_db[media_jack->cable_db_idx].fw_ver.split_major, + cable_db[media_jack->cable_db_idx].fw_ver.split_minor); + return 0; + } + + snprintf(target_fw_str, target_fw_size, "%02X.%02X\n", + cable_db[media_jack->cable_db_idx].fw_ver.major, + cable_db[media_jack->cable_db_idx].fw_ver.minor); + + return 0; +} + +void sl_media_eeprom_parse(struct sl_media_jack *media_jack, struct sl_media_attr *media_attr) +{ + sl_media_log_dbg(media_jack, LOG_NAME, "parse"); + + /* vendor get MUST be first */ + sl_media_eeprom_vendor_get(media_jack, media_attr->format, &(media_attr->vendor)); + sl_media_eeprom_vendor_pn_str_get(media_jack, media_attr->format, media_attr->vendor_pn_str); + sl_media_eeprom_type_get(media_jack, media_attr->format, media_attr->vendor, &(media_attr->type)); + sl_media_eeprom_hpe_pn_get(media_jack, &(media_attr->hpe_pn), media_attr->hpe_pn_str); + sl_media_eeprom_serial_num_get(media_jack, media_attr->format, media_attr->serial_num_str); + sl_media_eeprom_date_code_get(media_jack, media_attr->format, media_attr->date_code_str); + sl_media_eeprom_fw_ver_get(media_jack, media_attr->fw_ver); + sl_media_eeprom_cable_end_get(media_jack); + sl_media_eeprom_length_get(media_jack, media_attr->format, &(media_attr->length_cm)); + sl_media_eeprom_appsel_info_get(media_jack, &(media_attr->speeds_map)); + sl_media_eeprom_furcation_get(media_jack, &(media_attr->furcation)); + sl_media_eeprom_supported_flags_advertised_get(media_jack, media_attr->supported_flags_advertised); + + sl_media_log_dbg(media_jack, LOG_NAME, + "parse (format = %u, vendor = %u %s, vendor_pn_str = %s, fw = %02X.%02X)", + media_attr->format, media_attr->vendor, sl_media_vendor_str(media_attr->vendor), + media_attr->vendor_pn_str, media_attr->fw_ver[0], media_attr->fw_ver[1]); + sl_media_log_dbg(media_jack, LOG_NAME, + "parse (type = 0x%X %s, length = %ucm, speeds_map = 0x%lX)", + media_attr->type, sl_media_type_str(media_attr->type), media_attr->length_cm, + media_attr->speeds_map); + sl_media_log_dbg(media_jack, LOG_NAME, + "parse (hpe_pn = %u, serial_num = %s, errors = 0x%X, info = 0x%X)", + media_attr->hpe_pn, media_attr->serial_num_str, media_attr->errors, media_attr->info); +} + +#define IDENTIFIER_OFFSET 0 +#define REV_CMPL_OFFSET 1 +int sl_media_eeprom_format_get(struct sl_media_jack *media_jack, u8 *format) +{ + u8 identifier; + u8 revision; + bool is_sff8636; + bool is_cmis; + + sl_media_log_dbg(media_jack, LOG_NAME, "format get"); + + identifier = media_jack->eeprom_page0[IDENTIFIER_OFFSET]; + revision = media_jack->eeprom_page0[REV_CMPL_OFFSET]; + + is_sff8636 = (identifier == 0x0D || identifier == 0x11) && (revision < 0x27); + is_cmis = ((identifier == 0x18) || (identifier == 0x19) || (identifier >= 0x1E && identifier <= 0x25)) && + ((revision & 0xF0) >= 0x30 && (revision & 0xF0) <= 0x50); + + if (is_sff8636) { + *format = SL_MEDIA_MGMT_IF_SFF8636; + sl_media_log_dbg(media_jack, LOG_NAME, "format SFF8636 (id = 0x%X, rev = 0x%X)", identifier, revision); + return 0; + } + + if (is_cmis) { + *format = SL_MEDIA_MGMT_IF_CMIS; + sl_media_log_dbg(media_jack, LOG_NAME, "format CMIS (id = 0x%X, rev = 0x%X)", identifier, revision); + return 0; + } + + media_jack->is_cable_format_unsupported = true; + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_EEPROM_FORMAT_UNSUPPORTED); + sl_media_log_err_trace(media_jack, LOG_NAME, "unsupported format (id = 0x%X, rev = 0x%X)", + identifier, revision); + + return -EMEDIUMTYPE; +} + +#define MEDIA_INTERFACE_CODE_OFFSET 87 +int sl_media_eeprom_media_interface_get(struct sl_media_jack *media_jack, u8 *media_interface) +{ + spin_lock(&media_jack->data_lock); + *media_interface = media_jack->eeprom_page0[MEDIA_INTERFACE_CODE_OFFSET]; + spin_unlock(&media_jack->data_lock); + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack.c b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack.c
new file mode 100644
index 000000000000..9d46b3f13de2
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack.c@@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024-2026 Hewlett Packard Enterprise Development LP */ + +#include <linux/delay.h> + +#include "base/sl_media_log.h" + +#include "sl_media_io.h" +#include "sl_media_jack.h" +#include "data/sl_media_data_jack.h" +#include "sl_core_link.h" + +#define LOG_NAME SL_MEDIA_DATA_JACK_LOG_NAME + +enum sl_media_led_states { + LED_OFF = 0, + LED_ON_GRN = 1, + LED_SLOW_GRN = 2, + LED_FAST_GRN = 3, + LED_ON_YEL = 4, + LED_SLOW_YEL = 5, + LED_FAST_YEL = 6, + LED_SLOW_GRN_YEL = 7, + LED_FAST_GRN_YEL = 8, +}; + +int sl_media_data_jack_scan(u8 ldev_num) +{ + struct sl_media_jack *media_jack; + + sl_media_log_dbg(NULL, LOG_NAME, "jack scan"); + + media_jack = sl_media_data_jack_get(ldev_num, 0); + media_jack->cable_info[0].ldev_num = ldev_num; + media_jack->cable_info[0].lgrp_num = 0; /* hardcoding 0 since only one lgrp*/ + + return 0; +} + +int sl_media_data_jack_fake_media_attr_set(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info, struct sl_media_attr *fake_media_attr) +{ + return 0; +} + +void sl_media_data_jack_fake_media_attr_clr(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info) +{ + +} + +#define DATA_PATH_STATE_DEACTIVATED 0x1 +#define DATA_PATH_EXPLICIT_CONTROL_ENABLE 0x00 +#define DATA_PATH_ID 0x08 +#define DATA_PATH_LOWER_LANE_CONFIG (DATA_PATH_EXPLICIT_CONTROL_ENABLE) +#define DATA_PATH_LANES_DEACTIVATED (DATA_PATH_STATE_DEACTIVATED << 4 | DATA_PATH_STATE_DEACTIVATED) +int sl_media_data_jack_cable_downshift(struct sl_media_jack *media_jack) +{ + int rtn; + int i; + + sl_media_log_dbg(media_jack, LOG_NAME, "data jack cable downshift"); + + /* + * Deinit all lanes (DataPathDeinit @ page 0x10 byte 128) + */ + rtn = sl_media_io_write8(media_jack, 0x10, 128, 0xFF); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "data path deinit = 0xFF - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * enable low power mode + */ + rtn = sl_media_data_jack_cable_low_power_set(media_jack); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO_LOW_POWER_SET); + sl_media_log_err_trace(media_jack, LOG_NAME, "low power mode - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * Staged Control Set 0, Data Path Configuration bytes @ page 0x10 bytes 145-148 + * Config lanes 1 to 4 + */ + for (i = 0; i < 4; ++i) { + rtn = sl_media_io_write8(media_jack, 0x10, 145 + i, + (media_jack->appsel_num_200_gaui << 4) | + DATA_PATH_LOWER_LANE_CONFIG); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, + "SCS0 configuration - config lanes 1-4 - write failed [%d]", rtn); + return rtn; + } + } + msleep(100); + + /* + * ApplyDPInitLane8-1 + */ + rtn = sl_media_io_write8(media_jack, 0x10, 143, 0xFF); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "apply dpinit = 0xFF - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * enable high power mode + */ + rtn = sl_media_data_jack_cable_high_power_set(media_jack); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO_HIGH_POWER_SET); + sl_media_log_err_trace(media_jack, LOG_NAME, "high power mode - write failed [%d]", rtn); + return rtn; + } + msleep(8000); + + /* + * (Re)Init all lanes (DataPathDeinit @ page 0x10 byte 128) + */ + rtn = sl_media_io_write8(media_jack, 0x10, 128, 0x00); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "data path deinit = 0x00 - write failed [%d]", rtn); + return rtn; + } + + /* + * waiting for firmware reload + */ + msleep(3000); + + return 0; +} + +int sl_media_data_jack_cable_hw_shift_state_get(struct sl_media_jack *media_jack) +{ + int rtn; + int i; + u8 downshift_lower_lane_config; + u8 upshift_lower_lane_config; + u8 read_data[4]; + + sl_media_log_dbg(media_jack, LOG_NAME, "data jack cable hw shift state get"); + + downshift_lower_lane_config = (media_jack->appsel_num_200_gaui << 4) | DATA_PATH_LOWER_LANE_CONFIG; + upshift_lower_lane_config = (media_jack->appsel_num_400_gaui << 4) | DATA_PATH_LOWER_LANE_CONFIG; + + for (i = 0; i < 4; ++i) { + rtn = sl_media_io_read8(media_jack, 0x10, 145 + i, &read_data[i]); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, + "SCS0 configuration - config lanes 1-4 - read failed [%d]", rtn); + return SL_MEDIA_JACK_CABLE_HW_SHIFT_IO_ERROR; + } + } + + if ((read_data[0] == downshift_lower_lane_config) && + (read_data[1] == downshift_lower_lane_config) && + (read_data[2] == downshift_lower_lane_config) && + (read_data[3] == downshift_lower_lane_config)) { + sl_media_log_dbg(media_jack, LOG_NAME, "cable is downshifted"); + return SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_DOWNSHIFTED; + } else if ((read_data[0] == upshift_lower_lane_config) && + (read_data[1] == upshift_lower_lane_config) && + (read_data[2] == upshift_lower_lane_config) && + (read_data[3] == upshift_lower_lane_config)) { + sl_media_log_dbg(media_jack, LOG_NAME, "cable is upshifted"); + return SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_UPSHIFTED; + } + + sl_media_log_dbg(media_jack, LOG_NAME, "cable is in unknown shift state"); + return SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_UNKNOWN; +} + +int sl_media_data_jack_cable_upshift(struct sl_media_jack *media_jack) +{ + int rtn; + int i; + + sl_media_log_dbg(media_jack, LOG_NAME, "data jack cable upshift"); + + /* + * Deinit all lanes (DataPathDeinit @ page 0x10 byte 128) + */ + rtn = sl_media_io_write8(media_jack, 0x10, 128, 0xFF); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "data path deinit = 0xFF - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * enable low power mode + */ + rtn = sl_media_data_jack_cable_low_power_set(media_jack); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO_LOW_POWER_SET); + sl_media_log_err_trace(media_jack, LOG_NAME, "low power mode - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * Staged Control Set 0, Data Path Configuration bytes @ page 0x10 bytes 145-148 + * Config lanes 1 to 4 + */ + for (i = 0; i < 4; ++i) { + rtn = sl_media_io_write8(media_jack, 0x10, 145 + i, + (media_jack->appsel_num_400_gaui << 4) | DATA_PATH_LOWER_LANE_CONFIG); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, + "SCS0 configuration - config lanes 1-4 - write failed [%d]", rtn); + return rtn; + } + } + msleep(100); + + /* + * ApplyDPInitLane8-1 + */ + rtn = sl_media_io_write8(media_jack, 0x10, 143, 0xFF); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "apply dpinit = 0xFF - write failed [%d]", rtn); + return rtn; + } + msleep(500); + + /* + * enable high power mode + */ + rtn = sl_media_data_jack_cable_high_power_set(media_jack); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO_HIGH_POWER_SET); + sl_media_log_err_trace(media_jack, LOG_NAME, "high power mode - write failed [%d]", rtn); + return rtn; + } + msleep(8000); + + /* + * (Re)Init all lanes (DataPathDeinit @ page 0x10 byte 128) + */ + rtn = sl_media_io_write8(media_jack, 0x10, 128, 0x00); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO); + sl_media_log_err_trace(media_jack, LOG_NAME, "data path deinit = 0x00 - write failed [%d]", rtn); + return rtn; + } + + /* + * waiting for firmware reload + */ + msleep(3000); + + return 0; +} + +int sl_media_data_jack_cable_soft_reset(struct sl_media_jack *media_jack) +{ + int rtn; + + sl_media_log_dbg(media_jack, LOG_NAME, "data jack cable soft reset"); + + rtn = sl_media_io_write8(media_jack, 0x00, 0x1a, 0x08); + if (rtn) + return rtn; + + msleep(300); + + rtn = sl_media_io_write8(media_jack, 0x00, 0x1a, 0x00); + if (rtn) + return rtn; + + /* + * waiting for firmware reload + */ + msleep(3000); + + return 0; +} + +#define SL_MEDIA_POWER_UP_PAGE 0x00 +#define SL_MEDIA_CMIS_POWER_UP_ADDR 0x1a +#define SL_MEDIA_CMIS_POWER_UP_DATA 0x00 +#define SL_MEDIA_SFF_POWER_UP_ADDR 0x5D +#define SL_MEDIA_SFF_POWER_UP_DATA 0x0D +int sl_media_data_jack_cable_high_power_set(struct sl_media_jack *media_jack) +{ + int rtn; + u8 addr; + u8 data; + + if (sl_media_data_jack_media_is_format_cmis(media_jack)) { + addr = SL_MEDIA_CMIS_POWER_UP_ADDR; + data = SL_MEDIA_CMIS_POWER_UP_DATA; + } else { + addr = SL_MEDIA_SFF_POWER_UP_ADDR; + data = SL_MEDIA_SFF_POWER_UP_DATA; + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "high power set (addr = 0x%X, data = 0x%X)", addr, data); + + rtn = sl_media_io_write8(media_jack, SL_MEDIA_POWER_UP_PAGE, addr, data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "write8 failed [%d]", rtn); + return -EIO; + } + + spin_lock(&media_jack->data_lock); + media_jack->is_high_powered = true; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +#define SL_MEDIA_POWER_DOWN_PAGE 0x00 +#define SL_MEDIA_CMIS_POWER_DOWN_ADDR 0x1a +#define SL_MEDIA_CMIS_POWER_DOWN_DATA 0x10 +#define SL_MEDIA_SFF_POWER_DOWN_ADDR 0x5D +#define SL_MEDIA_SFF_POWER_DOWN_DATA 0x00 +int sl_media_data_jack_cable_low_power_set(struct sl_media_jack *media_jack) +{ + int rtn; + u8 addr; + u8 data; + + if (sl_media_data_jack_media_is_format_cmis(media_jack)) { + addr = SL_MEDIA_CMIS_POWER_DOWN_ADDR; + data = SL_MEDIA_CMIS_POWER_DOWN_DATA; + } else { + addr = SL_MEDIA_SFF_POWER_DOWN_ADDR; + data = SL_MEDIA_SFF_POWER_DOWN_DATA; + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "low power set (addr = 0x%X, data = 0x%X)", addr, data); + + rtn = sl_media_io_write8(media_jack, SL_MEDIA_POWER_DOWN_PAGE, addr, data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "write8 failed [%d]", rtn); + return -EIO; + } + + spin_lock(&media_jack->data_lock); + media_jack->is_high_powered = false; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_data_jack_cable_temp_get(struct sl_media_jack *media_jack, u8 *temp) +{ + int rtn; + u8 data; + + sl_media_log_dbg(media_jack, LOG_NAME, "data jack cable temp get"); + + if (!sl_media_lgrp_media_type_is_active(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num)) { + sl_media_log_dbg(media_jack, LOG_NAME, "not active cable"); + return -EBADRQC; + } + + rtn = sl_media_io_read8(media_jack, 0, 14, &data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "temp get read failed [%d]", rtn); + return -EIO; + } + + *temp = data; + return 0; +} + +int sl_media_data_jack_cable_temp_warn_limit_get(struct sl_media_jack *media_jack, u8 *temp_warn_limit_c) +{ + int rtn; + u8 data; + + sl_media_log_dbg(media_jack, LOG_NAME, "cable temp warn limit get"); + + if (!sl_media_lgrp_media_type_is_active(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num)) { + sl_media_log_dbg(media_jack, LOG_NAME, "not active cable"); + return -EBADRQC; + } + + rtn = sl_media_io_read8(media_jack, 2, 132, &data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "cable temp warn limit read failed [%d]", rtn); + return -EIO; + } + + *temp_warn_limit_c = data; + return 0; +} + +int sl_media_data_jack_cable_temp_down_limit_get(struct sl_media_jack *media_jack, u8 *temp_down_limit_c) +{ + int rtn; + u8 data; + + sl_media_log_dbg(media_jack, LOG_NAME, "cable temp down limit get"); + + if (!sl_media_lgrp_media_type_is_active(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num)) { + sl_media_log_dbg(media_jack, LOG_NAME, "not active cable"); + return -EBADRQC; + } + + rtn = sl_media_io_read8(media_jack, 2, 128, &data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "cable temp down limit read failed [%d]", rtn); + return -EIO; + } + + *temp_down_limit_c = data; + return 0; +} + +void sl_media_data_jack_led_set(struct sl_media_jack *media_jack) +{ + int rtn; + struct sl_core_link *core_link; + u32 link_state; + u8 state; + + sl_media_log_dbg(media_jack, LOG_NAME, "led set"); + + core_link = sl_core_link_get_pinned(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num, 0); + if (!core_link) + return; + + /* hardcoding 0 since only one link*/ + rtn = sl_core_link_state_get(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num, 0, &link_state); + sl_core_link_put_pinned(core_link); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "core link state get failed [%d]", rtn); + return; + } + + rtn = sl_media_jack_state_get(media_jack, &state); + if (rtn) + sl_media_log_err_trace(media_jack, LOG_NAME, "media_jack_state_get failed [%d]", rtn); + + switch (state) { + case SL_MEDIA_JACK_CABLE_REMOVED: + sl_media_io_led_set(media_jack, LED_OFF); + return; + case SL_MEDIA_JACK_CABLE_ERROR: + sl_media_io_led_set(media_jack, LED_FAST_YEL); + return; + } + + switch (link_state) { + case SL_CORE_LINK_STATE_UNCONFIGURED: + case SL_CORE_LINK_STATE_CANCELING: + case SL_CORE_LINK_STATE_DOWN: + case SL_CORE_LINK_STATE_GOING_DOWN: + case SL_CORE_LINK_STATE_TIMEOUT: + sl_media_io_led_set(media_jack, LED_OFF); + break; + case SL_CORE_LINK_STATE_GOING_UP: + case SL_CORE_LINK_STATE_AN: + sl_media_io_led_set(media_jack, LED_FAST_GRN); + break; + case SL_CORE_LINK_STATE_UP: + sl_media_io_led_set(media_jack, LED_ON_GRN); + break; + } +} + +static inline u8 sl_media_data_jack_num_update(u8 physical_jack_num) +{ + return physical_jack_num - 1; +} + +void sl_media_data_jack_cable_temp_monitor_start(struct sl_media_ldev *media_ldev) +{ +} + +void sl_media_data_jack_cable_temp_monitor_stop(struct sl_media_ldev *media_ldev) +{ +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack_emu.c b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack_emu.c
new file mode 100644
index 000000000000..0aed275e8c5c
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_jack_emu.c@@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> + +#include "sl_asic.h" + +#include "base/sl_media_log.h" + +#include "sl_media_lgrp.h" +#include "sl_media_jack.h" +#include "data/sl_media_data_jack.h" + +#define LOG_NAME SL_MEDIA_DATA_JACK_LOG_NAME + +int sl_media_data_jack_lgrp_connect(struct sl_media_lgrp *media_lgrp) +{ + struct sl_media_jack *media_jack; + u8 i; + + sl_media_log_dbg(media_lgrp, LOG_NAME, "jack lgrp connect"); + + media_jack = sl_media_data_jack_get(media_lgrp->media_ldev->num, + media_lgrp->num >> (SL_MEDIA_LGRPS_DIVIDE_PER_PORT)); + for (i = 0; i < SL_MEDIA_LGRPS_PER_PORT; ++i) { + if (media_jack->cable_info[i].lgrp_num == media_lgrp->num) { + media_lgrp->media_jack = media_jack; + media_lgrp->cable_info = &media_jack->cable_info[i]; + return 0; + } + } + + return -EFAULT; +} + +void sl_media_data_jack_unregister_event_notifier(void) +{ +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_ldev.c b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_ldev.c
new file mode 100644
index 000000000000..a2cd33257fc5
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/nic/sl_media_data_ldev.c@@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/errno.h> + +#include "base/sl_media_log.h" +#include "sl_media_ldev.h" +#include "data/sl_media_data_ldev.h" + +#define LOG_NAME SL_LOG_LDEV_LOG_NAME + +int sl_media_data_ldev_uc_ops_set(u8 ldev_num, struct sl_uc_ops *uc_ops, + struct sl_uc_accessor *uc_accessor) +{ + struct sl_media_ldev *media_ldev; + + media_ldev = sl_media_ldev_get(ldev_num); + if (!media_ldev) { + sl_media_log_err(NULL, LOG_NAME, "ldev not found (ldev_num = %u)", ldev_num); + return -EINVAL; + } + + sl_media_log_dbg(media_ldev, LOG_NAME, "media_data_ldev_uc_ops_set"); + + media_ldev->uc_ops = uc_ops; + media_ldev->uc_accessor = uc_accessor; + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_cable_db_ops.c b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_cable_db_ops.c
new file mode 100644
index 000000000000..8099343084d8
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_cable_db_ops.c@@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/slab.h> +#include <linux/kernel.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "sl_media_jack.h" +#include "data/sl_media_data_cable_db.h" +#include "data/sl_media_data_cable_db_ops.h" +#include "base/sl_media_log.h" + +#define LOG_NAME SL_MEDIA_CABLE_LOG_NAME + +#define SL_MEDIA_TYPE_SERDES 1 + +int sl_media_data_cable_db_ops_cable_validate(struct sl_media_attr *media_attr, struct sl_media_jack *media_jack) +{ + int indexer; + + sl_media_log_dbg(NULL, LOG_NAME, "cable validate"); + + /* + * Check for loopback module + */ + if (media_attr->vendor == SL_MEDIA_VENDOR_MULTILANE) { + media_attr->hpe_pn = 1; + media_attr->type = SL_MEDIA_TYPE_PEC; + media_attr->length_cm = 100; + media_attr->furcation = SL_MEDIA_FURCATION_X1; + media_attr->max_speed = SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + media_attr->speeds_map = SL_MEDIA_SPEEDS_SUPPORT_CK_400G | + SL_MEDIA_SPEEDS_SUPPORT_BS_200G | + SL_MEDIA_SPEEDS_SUPPORT_BJ_100G | + SL_MEDIA_SPEEDS_SUPPORT_CD_50G; + media_jack->cable_db_idx = -1; + return 0; + } + + /* + * Linear search through the cable_db array + */ + for (indexer = 0; indexer < ARRAY_SIZE(cable_db); ++indexer) { + if (cable_db[indexer].hpe_pn == media_attr->hpe_pn && + cable_db[indexer].vendor == media_attr->vendor && + cable_db[indexer].type == media_attr->type) { + media_attr->shape = cable_db[indexer].shape; + media_attr->max_speed = cable_db[indexer].max_speed; + media_jack->is_supported_ss200_cable = cable_db[indexer].is_supported_ss200_cable; + media_jack->cable_db_idx = indexer; + return 0; + } + } + + return -ENOENT; +} + +int sl_media_data_cable_db_ops_serdes_settings_get(struct sl_media_jack *media_jack, u32 media_type, u32 flags) +{ + sl_media_log_dbg(media_jack, LOG_NAME, + "serdes settings get (media_type = 0x%X %s, flags = 0x%X)", + media_type, sl_media_type_str(media_type), flags); + + if (flags & SL_MEDIA_TYPE_UNSUPPORTED) { + sl_media_log_warn_trace(media_jack, LOG_NAME, "serdes setting get unsuppported cable"); + if (media_type == SL_MEDIA_TYPE_AEC || + media_type == SL_MEDIA_TYPE_ACC || + media_type == SL_MEDIA_TYPE_POC || + media_type == SL_MEDIA_TYPE_AOC) { + media_jack->serdes_settings.pre1 = -12; + media_jack->serdes_settings.pre2 = 0; + media_jack->serdes_settings.pre3 = 0; + media_jack->serdes_settings.cursor = 98; + media_jack->serdes_settings.post1 = -4; + media_jack->serdes_settings.post2 = 0; + } else { + media_jack->serdes_settings.pre1 = 0; + media_jack->serdes_settings.pre2 = 0; + media_jack->serdes_settings.pre3 = 0; + media_jack->serdes_settings.cursor = 100; + media_jack->serdes_settings.post1 = 0; + media_jack->serdes_settings.post2 = 0; + } + } else if (flags & SL_MEDIA_TYPE_BACKPLANE) { + media_jack->serdes_settings.pre1 = 0; + media_jack->serdes_settings.pre2 = 0; + media_jack->serdes_settings.pre3 = 0; + media_jack->serdes_settings.cursor = 100; + media_jack->serdes_settings.post1 = 0; + media_jack->serdes_settings.post2 = 0; + } else if (flags & SL_MEDIA_TYPE_LOOPBACK) { + media_jack->serdes_settings.pre1 = 0; + media_jack->serdes_settings.pre2 = 0; + media_jack->serdes_settings.pre3 = 0; + media_jack->serdes_settings.cursor = 100; + media_jack->serdes_settings.post1 = 0; + media_jack->serdes_settings.post2 = 0; + } else { + if (media_jack->cable_db_idx < 0 || media_jack->cable_db_idx >= ARRAY_SIZE(cable_db)) { + sl_media_log_err_trace(media_jack, LOG_NAME, + "serdes settings get invalid (idx = %d)", + media_jack->cable_db_idx); + return -ENOENT; + } + media_jack->serdes_settings = cable_db[media_jack->cable_db_idx].serdes_settings; + } + media_jack->serdes_settings.media = SL_MEDIA_TYPE_SERDES; + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_jack.c b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_jack.c
new file mode 100644
index 000000000000..17631509afa2
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_jack.c@@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2022,2023,2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/atomic.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "base/sl_media_log.h" +#include "sl_media_ldev.h" +#include "sl_media_lgrp.h" +#include "sl_media_jack.h" +#include "data/sl_media_data_jack.h" +#include "sl_asic.h" +#include "sl_ctrl_lgrp_notif.h" +#include "data/sl_media_data_lgrp.h" +#include "sl_ctrl_lgrp.h" +#include "sl_core_link.h" +#include "sl_ctrl_media_counters.h" + +static struct sl_media_jack *media_jacks[SL_ASIC_MAX_LDEVS][SL_MEDIA_MAX_JACK_NUM]; +static DEFINE_SPINLOCK(media_jacks_lock); + +#define LOG_NAME SL_MEDIA_DATA_JACK_LOG_NAME + +void sl_media_data_jack_del(u8 ldev_num, u8 jack_num) +{ + struct sl_media_jack *media_jack; + + media_jack = sl_media_data_jack_get(ldev_num, jack_num); + if (!media_jack) { + sl_media_log_dbg(NULL, LOG_NAME, "not found (jack_num = %u)", jack_num); + return; + } + + sl_ctrl_media_cause_counters_del(media_jack); + + spin_lock(&media_jacks_lock); + media_jacks[ldev_num][jack_num] = NULL; + spin_unlock(&media_jacks_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "del (jack = 0x%p)", media_jack); + + kfree(media_jack); +} + +int sl_media_data_jack_new(struct sl_media_ldev *media_ldev, u8 jack_num) +{ + struct sl_media_jack *media_jack; + int rtn; + + media_jack = sl_media_data_jack_get(media_ldev->num, jack_num); + if (media_jack) { + sl_media_log_err(NULL, LOG_NAME, "jack already exists"); + return -EEXIST; + } + + media_jack = kzalloc(sizeof(struct sl_media_jack), GFP_KERNEL); + if (!media_jack) + return -ENOMEM; + + media_jack->magic = SL_MEDIA_JACK_MAGIC; + media_jack->num = jack_num; + media_jack->physical_num = 1; + media_jack->cable_db_idx = -1; + media_jack->fault_cause = SL_MEDIA_FAULT_CAUSE_NONE; + + spin_lock_init(&media_jack->log_lock); + + rtn = sl_ctrl_media_cause_counters_init(media_jack); + if (rtn) { + sl_media_log_err(media_jack, LOG_NAME, "ctrl_media_cause_counters_init failed [%d]", rtn); + goto out; + } + + spin_lock_init(&(media_jack->data_lock)); + + media_jack->media_ldev = media_ldev; + + media_jack->state = SL_MEDIA_JACK_CABLE_REMOVED; + media_jack->lane_data.cache.cached = false; + media_jack->lane_data.read_state = SL_MEDIA_JACK_LANE_DATA_READ_STATE_IDLE; + media_jack->lane_data.read_generation = 0; + init_waitqueue_head(&media_jack->lane_data.read_wait); + + media_jack->temperature_value_c = -1; + media_jack->temperature_down_limit_c = -1; + media_jack->temperature_warn_limit_c = -1; + + sl_media_log_dbg(media_jack, LOG_NAME, "new (jack = 0x%p)", media_jack); + + spin_lock(&media_jacks_lock); + media_jacks[media_ldev->num][jack_num] = media_jack; + spin_unlock(&media_jacks_lock); + + return 0; + +out: + kfree(media_jack); + return -ENOMEM; +} + +struct sl_media_jack *sl_media_data_jack_get(u8 ldev_num, u8 jack_num) +{ + struct sl_media_jack *media_jack; + + spin_lock(&media_jacks_lock); + media_jack = media_jacks[ldev_num][jack_num]; + spin_unlock(&media_jacks_lock); + + sl_media_log_dbg(NULL, LOG_NAME, + "get (ldev_num = %u, jack_num = %u, jack = 0x%p)", + ldev_num, jack_num, media_jack); + + return media_jack; +} + +void sl_media_data_cable_serdes_settings_clr(struct sl_media_jack *media_jack) +{ + sl_media_log_dbg(media_jack, LOG_NAME, "serdes settings clr"); + + memset(&(media_jack->serdes_settings), 0, sizeof(struct sl_media_serdes_settings)); +} + +void sl_media_data_jack_eeprom_clr(struct sl_media_jack *media_jack) +{ + sl_media_log_dbg(media_jack, LOG_NAME, "eeprom clr"); + + memset(media_jack->eeprom_page0, 0, SL_MEDIA_EEPROM_PAGE_SIZE); + memset(media_jack->eeprom_page1, 0, SL_MEDIA_EEPROM_PAGE_SIZE); +} + +/* + * This state describes the cable that is currently in the jack, so it must be + * cleared whenever the cable changes or the next cable inherits it. Client + * notification registrations, fake-cable debug state and counters belong to + * the jack and are kept. A stash that is still live behind a fake cable is + * kept too. + */ +void sl_media_data_jack_cable_state_clr(struct sl_media_jack *media_jack) +{ + unsigned int i; + + sl_media_log_dbg(media_jack, LOG_NAME, "cable state clr"); + + spin_lock(&media_jack->data_lock); + media_jack->is_cable_unsupported = false; + media_jack->is_cable_format_unsupported = false; + media_jack->is_supported_ss200_cable = false; + media_jack->cable_db_idx = -1; + + media_jack->is_high_powered = false; + media_jack->cable_high_power_wait_time_end = 0; + + media_jack->cable_shift_state = 0; + media_jack->appsel_num_200_gaui = 0; + media_jack->lane_count_200_gaui = 0; + media_jack->host_interface_200_gaui = 0; + media_jack->appsel_num_400_gaui = 0; + media_jack->lane_count_400_gaui = 0; + media_jack->host_interface_400_gaui = 0; + + memset(&media_jack->lane_data.cache, 0, sizeof(media_jack->lane_data.cache)); + + media_jack->temperature_prev_state = SL_MEDIA_JACK_TEMP_STATE_COLD; + media_jack->temperature_state = SL_MEDIA_JACK_TEMP_STATE_COLD; + media_jack->temperature_value_c = -1; + media_jack->temperature_warn_limit_c = -1; + media_jack->temperature_down_limit_c = -1; + + for (i = 0; i < ARRAY_SIZE(media_jack->cable_info); ++i) { + struct sl_media_lgrp_cable_info *cable_info = &media_jack->cable_info[i]; + + if (cable_info->real_cable_status != CABLE_MEDIA_ATTR_STASHED) + memset(&cable_info->stashed_media_attr, 0, + sizeof(cable_info->stashed_media_attr)); + cable_info->hot_notif_sent = false; + cable_info->warm_notif_sent = false; + cable_info->cold_notif_sent = false; + } + spin_unlock(&media_jack->data_lock); +} + +int sl_media_data_jack_media_attr_set(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info, struct sl_media_attr *media_attr) +{ + sl_media_log_dbg(media_jack, LOG_NAME, + "media attr set (format = %d, vendor = %d %s, type = 0x%X %s, length_cm = %d, speeds_map = 0x%lX)", + media_attr->format, media_attr->vendor, sl_media_vendor_str(media_attr->vendor), + media_attr->type, sl_media_type_str(media_attr->type), + media_attr->length_cm, media_attr->speeds_map); + + spin_lock(&media_jack->data_lock); + + if (cable_info->real_cable_status == CABLE_MEDIA_ATTR_ADDED) { + sl_media_log_err_trace(media_jack, LOG_NAME, "media attr already set"); + spin_unlock(&media_jack->data_lock); + return -EEXIST; + } + + if (cable_info->fake_cable_status == CABLE_MEDIA_ATTR_ADDED) { + cable_info->stashed_media_attr = *media_attr; + cable_info->real_cable_status = CABLE_MEDIA_ATTR_STASHED; + } else { + cable_info->media_attr = *media_attr; + cable_info->real_cable_status = CABLE_MEDIA_ATTR_ADDED; + } + + spin_unlock(&media_jack->data_lock); + + return 0; +} + +void sl_media_data_jack_media_attr_clr(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info) +{ + struct sl_media_lgrp *media_lgrp; + + sl_media_log_dbg(media_jack, LOG_NAME, "media attr clr"); + + media_lgrp = sl_media_data_lgrp_get(cable_info->ldev_num, cable_info->lgrp_num); + spin_lock(&media_jack->data_lock); + if (cable_info->real_cable_status == CABLE_MEDIA_ATTR_REMOVED) { + spin_unlock(&media_jack->data_lock); + sl_media_log_dbg(media_jack, LOG_NAME, "no media attr to clear"); + return; + } + + if (cable_info->real_cable_status == CABLE_MEDIA_ATTR_ADDED) + memset(&(cable_info->media_attr), 0, sizeof(struct sl_media_attr)); + + cable_info->real_cable_status = CABLE_MEDIA_ATTR_REMOVED; + spin_unlock(&media_jack->data_lock); + + if (media_lgrp) { + /* Only send notification if both real and fake cables are removed */ + if (cable_info->fake_cable_status == CABLE_MEDIA_ATTR_REMOVED) + sl_media_data_jack_cable_if_not_present_send(media_lgrp); + } +} + +bool sl_media_data_jack_media_is_format_cmis(struct sl_media_jack *media_jack) +{ + bool is_cmis; + + spin_lock(&media_jack->data_lock); + is_cmis = (media_jack->cable_info[0].media_attr.format == SL_MEDIA_MGMT_IF_CMIS); + spin_unlock(&media_jack->data_lock); + + return is_cmis; +} + +void sl_media_data_jack_cable_if_present_send(struct sl_media_lgrp *media_lgrp) +{ + int rtn; + union sl_lgrp_notif_info info; + + sl_media_log_dbg(media_lgrp, LOG_NAME, "present send"); + + spin_lock(&media_lgrp->media_jack->data_lock); + if ((media_lgrp->cable_info->real_cable_status == CABLE_MEDIA_ATTR_ADDED) || + (media_lgrp->cable_info->fake_cable_status == CABLE_MEDIA_ATTR_ADDED)) { + info.media_attr = media_lgrp->cable_info->media_attr; + rtn = sl_ctrl_lgrp_notif_enqueue(sl_ctrl_lgrp_get(media_lgrp->media_ldev->num, media_lgrp->num), + SL_LGRP_NOTIF_NO_LINK, SL_LGRP_NOTIF_MEDIA_PRESENT, &info, 0); + if (rtn) + sl_media_log_warn_trace(media_lgrp, LOG_NAME, + "present_send ctrl_lgrp_notif_enqueue failed [%d]", rtn); + } + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +void sl_media_data_jack_cable_if_not_present_send(struct sl_media_lgrp *media_lgrp) +{ + int rtn; + + sl_media_log_dbg(media_lgrp, LOG_NAME, "not present send"); + + spin_lock(&media_lgrp->media_jack->data_lock); + if (media_lgrp->cable_info->real_cable_status == CABLE_MEDIA_ATTR_REMOVED) { + rtn = sl_ctrl_lgrp_notif_enqueue(sl_ctrl_lgrp_get(media_lgrp->media_ldev->num, media_lgrp->num), + SL_LGRP_NOTIF_NO_LINK, SL_LGRP_NOTIF_MEDIA_NOT_PRESENT, NULL, 0); + if (rtn) + sl_media_log_warn_trace(media_lgrp, LOG_NAME, + "not_present_send ctrl_lgrp_notif_enqueue failed [%d]", rtn); + } + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +void sl_media_data_jack_cable_temp_state_init(struct sl_media_jack *media_jack) +{ + spin_lock(&media_jack->data_lock); + media_jack->temperature_prev_state = SL_MEDIA_JACK_TEMP_STATE_COLD; + media_jack->temperature_state = SL_MEDIA_JACK_TEMP_STATE_COLD; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "temperature state init (prev_state = %u, state = %u)", + media_jack->temperature_prev_state, media_jack->temperature_state); +} + +void sl_media_data_jack_cable_temp_state_set(struct sl_media_jack *media_jack, u8 temperature_state) +{ + spin_lock(&media_jack->data_lock); + media_jack->temperature_prev_state = media_jack->temperature_state; + media_jack->temperature_state = temperature_state; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "temperature state set (state = %u)", media_jack->temperature_state); +} + +int sl_media_data_jack_cable_temp_state_get(struct sl_media_jack *media_jack, u8 *temperature_state) +{ + spin_lock(&media_jack->data_lock); + *temperature_state = media_jack->temperature_state; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "temperature state get (state = %u %s)", + *temperature_state, sl_media_temp_state_str(*temperature_state)); + + return 0; +} + +int sl_media_data_jack_cable_temp_prev_state_get(struct sl_media_jack *media_jack, u8 *temperature_prev_state) +{ + spin_lock(&media_jack->data_lock); + *temperature_prev_state = media_jack->temperature_prev_state; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "temperature prev state get (state = %u %s)", + *temperature_prev_state, sl_media_temp_state_str(*temperature_prev_state)); + + return 0; +} + +void sl_media_data_jack_headshell_busy_set(struct sl_media_jack *media_jack, int value) +{ + atomic_set(&media_jack->is_headshell_busy, value); +} + +bool sl_media_data_jack_is_headshell_busy(struct sl_media_jack *media_jack) +{ + return (atomic_read(&media_jack->is_headshell_busy) == SL_MEDIA_JACK_HEADSHELL_BUSY); +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_ldev.c b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_ldev.c
new file mode 100644
index 000000000000..fddedcd46776
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_ldev.c@@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2023,2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/spinlock.h> +#include <linux/slab.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "sl_asic.h" +#include "base/sl_media_log.h" +#include "sl_media_ldev.h" +#include "data/sl_media_data_ldev.h" +#include "data/sl_media_data_lgrp.h" +#include "data/sl_media_data_jack.h" + +static struct sl_media_ldev *media_ldevs[SL_ASIC_MAX_LDEVS]; +static DEFINE_SPINLOCK(media_ldevs_lock); + +#define LOG_NAME SL_MEDIA_DATA_LDEV_LOG_NAME + +int sl_media_data_ldev_new(u8 ldev_num, struct workqueue_struct *workqueue) +{ + struct sl_media_ldev *media_ldev; + int rtn; + u8 jack_num; + + media_ldev = sl_media_data_ldev_get(ldev_num); + if (media_ldev) { + sl_media_log_err(NULL, LOG_NAME, "exists (ldev = 0x%p)", media_ldev); + return -EBADRQC; + } + + media_ldev = kzalloc(sizeof(struct sl_media_ldev), GFP_KERNEL); + if (!media_ldev) + return -ENOMEM; + + media_ldev->magic = SL_MEDIA_LDEV_MAGIC; + media_ldev->num = ldev_num; + media_ldev->workqueue = workqueue; + + for (jack_num = 0; jack_num < SL_MEDIA_MAX_JACK_NUM; ++jack_num) { + rtn = sl_media_data_jack_new(media_ldev, jack_num); + if (rtn) { + sl_media_log_err(media_ldev, LOG_NAME, "jack new failed [%d]", rtn); + goto out_jacks; + } + } + + rtn = sl_media_data_jack_scan(ldev_num); + if (rtn) { + sl_media_log_err(media_ldev, LOG_NAME, "jack scan failed [%d]", rtn); + goto out_jacks; + } + + sl_media_log_dbg(media_ldev, LOG_NAME, "new (ldev = 0x%p)", media_ldev); + + spin_lock(&media_ldevs_lock); + media_ldevs[ldev_num] = media_ldev; + spin_unlock(&media_ldevs_lock); + + sl_media_data_jack_cable_temp_monitor_start(media_ldev); + + return 0; + +out_jacks: + while (jack_num) + sl_media_data_jack_del(ldev_num, --jack_num); + kfree(media_ldev); + return -EFAULT; +} + +void sl_media_data_ldev_del(u8 ldev_num) +{ + struct sl_media_ldev *media_ldev; + u8 lgrp_num; + u8 jack_num; + + media_ldev = sl_media_data_ldev_get(ldev_num); + if (!media_ldev) { + sl_media_log_dbg(NULL, LOG_NAME, "not found (ldev_num = %u)", ldev_num); + return; + } + + sl_media_data_jack_cable_temp_monitor_stop(media_ldev); + + spin_lock(&media_ldevs_lock); + media_ldevs[ldev_num] = NULL; + spin_unlock(&media_ldevs_lock); + + sl_media_log_dbg(media_ldev, LOG_NAME, "del (ldev = 0x%p)", media_ldev); + + for (lgrp_num = 0; lgrp_num < SL_ASIC_MAX_LGRPS; ++lgrp_num) + sl_media_data_lgrp_del(ldev_num, lgrp_num); + + for (jack_num = 0; jack_num < SL_MEDIA_MAX_JACK_NUM; ++jack_num) + sl_media_data_jack_del(ldev_num, jack_num); + + sl_media_data_jack_unregister_event_notifier(); + + kfree(media_ldev); +} + +struct sl_media_ldev *sl_media_data_ldev_get(u8 ldev_num) +{ + struct sl_media_ldev *media_ldev; + + spin_lock(&media_ldevs_lock); + media_ldev = media_ldevs[ldev_num]; + spin_unlock(&media_ldevs_lock); + + sl_media_log_dbg(NULL, LOG_NAME, "get (ldev_num = %u, ldev = 0x%p)", + ldev_num, media_ldev); + + return media_ldev; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp.c b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp.c
new file mode 100644
index 000000000000..27c30bdf3378
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp.c@@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2023,2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/spinlock.h> +#include <linux/slab.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "sl_media_lgrp.h" +#include "data/sl_media_data_lgrp.h" +#include "data/sl_media_data_ldev.h" +#include "data/sl_media_data_jack.h" +#include "sl_asic.h" +#include "base/sl_media_log.h" + +static struct sl_media_lgrp *media_lgrps[SL_ASIC_MAX_LDEVS][SL_ASIC_MAX_LGRPS]; +static DEFINE_SPINLOCK(media_lgrps_lock); + +#define LOG_NAME SL_MEDIA_DATA_LGRP_LOG_NAME + +int sl_media_data_lgrp_new(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + int rtn; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + if (media_lgrp) { + sl_media_log_err(NULL, LOG_NAME, "exists (lgrp = 0x%p)", media_lgrp); + return -EBADRQC; + } + + media_lgrp = kzalloc(sizeof(struct sl_media_lgrp), GFP_KERNEL); + if (!media_lgrp) + return -ENOMEM; + + media_lgrp->magic = SL_MEDIA_LGRP_MAGIC; + media_lgrp->num = lgrp_num; + media_lgrp->media_ldev = sl_media_data_ldev_get(ldev_num); + + media_lgrp->speeds_kobj_init = false; + spin_lock_init(&media_lgrp->log_lock); + spin_lock_init(&media_lgrp->data_lock); + snprintf(media_lgrp->connect_id, sizeof(media_lgrp->connect_id), "lgrp%02u", lgrp_num); + + rtn = sl_media_data_jack_lgrp_connect(media_lgrp); + if (rtn) { + sl_media_log_err(media_lgrp, LOG_NAME, "jack lgrp connect failed [%d]", rtn); + kfree(media_lgrp); + return -EFAULT; + } + + sl_media_log_dbg(media_lgrp, LOG_NAME, "new (lgrp = 0x%p)", media_lgrp); + + spin_lock(&media_lgrps_lock); + media_lgrps[ldev_num][lgrp_num] = media_lgrp; + spin_unlock(&media_lgrps_lock); + + return 0; +} + +void sl_media_data_lgrp_del(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + if (!media_lgrp) { + sl_media_log_dbg(NULL, LOG_NAME, "not found (lgrp_num = %u)", lgrp_num); + return; + } + + spin_lock(&media_lgrps_lock); + media_lgrps[ldev_num][lgrp_num] = NULL; + spin_unlock(&media_lgrps_lock); + + sl_media_log_dbg(media_lgrp, LOG_NAME, "del (lgrp = 0x%p)", media_lgrp); + + kfree(media_lgrp); +} + +struct sl_media_lgrp *sl_media_data_lgrp_get(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + spin_lock(&media_lgrps_lock); + media_lgrp = media_lgrps[ldev_num][lgrp_num]; + spin_unlock(&media_lgrps_lock); + + sl_media_log_dbg(NULL, LOG_NAME, + "get (ldev_num = %u, lgrp_num = %u, lgrp = 0x%p)", + ldev_num, lgrp_num, media_lgrp); + + return media_lgrp; +} + +void sl_media_data_lgrp_connect_id_set(struct sl_media_lgrp *media_lgrp, const char *connect_id) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&(media_lgrp->log_lock), irq_flags); + strscpy_pad(media_lgrp->connect_id, connect_id, + sizeof(media_lgrp->connect_id)); + spin_unlock_irqrestore(&(media_lgrp->log_lock), irq_flags); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_DATA_LGRP_LOG_NAME, "connect_id = %s", connect_id); +} + +int sl_media_data_lgrp_err_trace_enable_set(struct sl_media_lgrp *media_lgrp, bool err_trace_enable) +{ + spin_lock(&media_lgrp->data_lock); + media_lgrp->err_trace_enable = err_trace_enable; + spin_unlock(&media_lgrp->data_lock); + + sl_media_log_dbg(media_lgrp, LOG_NAME, "set (err_trace_enable = %s)", err_trace_enable ? "true" : "false"); + + return 0; +} + +int sl_media_data_lgrp_warn_trace_enable_set(struct sl_media_lgrp *media_lgrp, bool warn_trace_enable) +{ + spin_lock(&media_lgrp->data_lock); + media_lgrp->warn_trace_enable = warn_trace_enable; + spin_unlock(&media_lgrp->data_lock); + + sl_media_log_dbg(media_lgrp, LOG_NAME, "set (warn_trace_enable = %s)", warn_trace_enable ? "true" : "false"); + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp_trace.c b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp_trace.c
new file mode 100644
index 000000000000..aec8a52d1cc0
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/data/sl_media_data_lgrp_trace.c@@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/spinlock.h> + +#include "data/sl_media_data_lgrp_trace.h" + +#define LOG_NAME SL_MEDIA_DATA_LGRP_LOG_NAME + +int sl_media_data_lgrp_is_err_trace_enabled(struct sl_media_lgrp *media_lgrp, bool *err_trace_enable) +{ + spin_lock(&media_lgrp->data_lock); + *err_trace_enable = media_lgrp->err_trace_enable; + spin_unlock(&media_lgrp->data_lock); + + sl_media_log_dbg(media_lgrp, LOG_NAME, "get (err_trace_enable = %s)", *err_trace_enable ? "true" : "false"); + + return 0; +} + +int sl_media_data_lgrp_is_warn_trace_enabled(struct sl_media_lgrp *media_lgrp, bool *warn_trace_enable) +{ + spin_lock(&media_lgrp->data_lock); + *warn_trace_enable = media_lgrp->warn_trace_enable; + spin_unlock(&media_lgrp->data_lock); + + sl_media_log_dbg(media_lgrp, LOG_NAME, "get (warn_trace_enable = %s)", *warn_trace_enable ? "true" : "false"); + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/nic/sl_media_io.c b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_io.c
new file mode 100644
index 000000000000..e031d82b104b
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_io.c@@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/hpe/sl/sl_ldev.h> + +#include "sl_media_ldev.h" +#include "sl_media_io.h" +#include "sl_media_jack.h" +#include "base/sl_media_log.h" + +#define LOG_NAME SL_MEDIA_IO_LOG_NAME + +int sl_media_io_write8(struct sl_media_jack *media_jack, u8 page, u8 offset, u8 data) +{ + struct sl_media_ldev *media_ldev; + + media_ldev = media_jack->media_ldev; + + sl_media_log_dbg(media_jack, LOG_NAME, "media_io_write8 0x%x <- 0x%x", offset, data); + + return media_ldev->uc_ops->uc_write8(media_ldev->uc_accessor->uc, page, offset, data); +} + +int sl_media_io_read(struct sl_media_jack *media_jack, u8 page, u8 offset, u8 *data, size_t len) +{ + int rtn_len; + struct sl_media_ldev *media_ldev; + + media_ldev = media_jack->media_ldev; + + rtn_len = media_ldev->uc_ops->uc_read(media_ldev->uc_accessor->uc, (u32)offset, (u32)page, data, len); + if (rtn_len < 0) { + sl_media_log_err_trace(media_jack, LOG_NAME, "media_io_read failed [%d]", rtn_len); + return rtn_len; + } + + if (rtn_len != len) { + sl_media_log_err_trace(media_jack, LOG_NAME, + "media_io_read length mismatch (expected = %zu, returned = %d)", len, rtn_len); + return -EIO; + } + + sl_media_log_dbg(media_jack, LOG_NAME, + "media_io_read (page = 0x%X, offset = 0x%X, rtn_len = %d)", page, offset, rtn_len); + + return 0; +} + +int sl_media_io_read8(struct sl_media_jack *media_jack, u8 page, u8 offset, u8 *data) +{ + return sl_media_io_read(media_jack, page, offset, data, 1); +} + +void sl_media_io_led_set(struct sl_media_jack *media_jack, u8 led_pattern) +{ + struct sl_media_ldev *media_ldev; + + sl_media_log_dbg(media_jack, LOG_NAME, "media_io_led_set (pattern = %u)", led_pattern); + + media_ldev = media_jack->media_ldev; + + media_ldev->uc_ops->uc_led_set(media_ldev->uc_accessor->uc, led_pattern); +}
diff --git a/drivers/net/ethernet/hpe/sl/media/nic/sl_media_jack.c b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_jack.c
new file mode 100644
index 000000000000..33f1a2a20ab7
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_jack.c@@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024,2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/string.h> + +#include <linux/hpe/sl/sl_media.h> + +#include "sl_asic.h" +#include "sl_sysfs.h" +#include "sl_media_jack.h" +#include "sl_media_lgrp.h" +#include "data/sl_media_data_jack.h" +#include "data/sl_media_data_lgrp.h" +#include "data/sl_media_data_cable_db_ops.h" +#include "base/sl_media_log.h" +#include "base/sl_media_eeprom.h" + +#define LOG_NAME SL_MEDIA_JACK_LOG_NAME + +#define FLAT_MEM_OFFSET 2 +#define FLAT_MEM_BIT 7 +static int sl_media_data_jack_eeprom_page1_get(struct sl_media_jack *media_jack, u8 *eeprom_page1) +{ + sl_media_log_dbg(media_jack, LOG_NAME, "eeprom page1 get"); + + if ((media_jack->eeprom_page0[FLAT_MEM_OFFSET] & BIT(FLAT_MEM_BIT)) != 0) { + sl_media_log_dbg(media_jack, LOG_NAME, "no page1 in eeprom"); + return 0; + } + + memcpy(media_jack->eeprom_page1, eeprom_page1, SL_MEDIA_EEPROM_PAGE_SIZE); + + return 0; +} + +static int sl_media_jack_cable_attr_set(struct sl_media_jack *media_jack, u8 ldev_num, u8 lgrp_num, + struct sl_media_attr *media_attr) +{ + int rtn; + + sl_media_log_dbg(media_jack, LOG_NAME, "cable attr set"); + + /* + * only first element is valid in cable_info since single lgrp on nic + */ + media_jack->cable_info[0].ldev_num = ldev_num; + media_jack->cable_info[0].lgrp_num = lgrp_num; + + rtn = sl_media_data_jack_media_attr_set(media_jack, &media_jack->cable_info[0], media_attr); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "media attr set failed [%d]", rtn); + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_MEDIA_ATTR_SET); + return rtn; + } + + return 0; +} + +static void sl_media_jack_cable_attr_errors_update(struct sl_media_jack *media_jack, u32 errors) +{ + sl_media_log_dbg(media_jack, LOG_NAME, "cable attr errors update"); + + /* + * only first element is valid in cable_info since single lgrp on nic + */ + media_jack->cable_info[0].media_attr.errors |= errors; +} + +static void sl_media_data_jack_cable_attr_send(struct sl_media_jack *media_jack) +{ + struct sl_media_lgrp *media_lgrp; + + sl_media_log_dbg(media_jack, LOG_NAME, "cable attr send"); + + /* + * only first element is valid in cable_info since single lgrp on nic + */ + media_lgrp = sl_media_data_lgrp_get(media_jack->cable_info[0].ldev_num, + media_jack->cable_info[0].lgrp_num); + if (media_lgrp) + sl_media_data_jack_cable_if_present_send(media_lgrp); +} + +int sl_media_jack_cable_insert(u8 ldev_num, u8 lgrp_num, u8 jack_num, + u8 *eeprom_page0, u8 *eeprom_page1, u32 flags) +{ + int rtn; + struct sl_media_attr media_attr; + struct sl_media_jack *media_jack; + + media_jack = sl_media_data_jack_get(ldev_num, jack_num); + if (!media_jack) { + sl_media_log_err(NULL, LOG_NAME, "media_data_jack_get failed"); + return -EFAULT; + } + + sl_media_log_dbg(media_jack, LOG_NAME, "cable insert"); + + /* Start clean so nothing from the previous cable is inherited */ + sl_media_data_jack_cable_state_clr(media_jack); + + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_NONE); + + memset(&media_attr, 0, sizeof(struct sl_media_attr)); + + media_attr.magic = SL_MEDIA_ATTR_MAGIC; + media_attr.ver = SL_MEDIA_ATTR_VER; + media_attr.size = sizeof(struct sl_media_attr); + media_attr.errors = 0; + media_attr.info = 0; + + if (flags & SL_MEDIA_TYPE_BACKPLANE) { + media_attr.vendor = SL_MEDIA_VENDOR_HPE; + media_attr.type = SL_MEDIA_TYPE_BKP; + media_attr.info = SL_MEDIA_INFO_AUTONEG; + media_attr.length_cm = 25; + media_attr.hpe_pn = 60821555; + media_attr.furcation = SL_MEDIA_FURCATION_X1; + media_attr.speeds_map = SL_MEDIA_SPEEDS_SUPPORT_CK_400G | + SL_MEDIA_SPEEDS_SUPPORT_CK_200G | + SL_MEDIA_SPEEDS_SUPPORT_BS_200G | + SL_MEDIA_SPEEDS_SUPPORT_CK_100G | + SL_MEDIA_SPEEDS_SUPPORT_CD_100G | + SL_MEDIA_SPEEDS_SUPPORT_BJ_100G | + SL_MEDIA_SPEEDS_SUPPORT_CD_50G; + media_attr.max_speed = SL_MEDIA_SPEEDS_SUPPORT_CK_400G; + media_attr.jack_type = SL_MEDIA_JACK_TYPE_BACKPLANE; + strscpy_pad(media_attr.serial_num_str, "AK20212120", sizeof(media_attr.serial_num_str)); + strscpy_pad(media_attr.hpe_pn_str, "PK60821-555", sizeof(media_attr.hpe_pn_str)); + strscpy_pad(media_attr.date_code_str, "08-19-21", sizeof(media_attr.date_code_str)); + memset(media_attr.fw_ver, 0, sizeof(media_attr.fw_ver)); + } else { + memcpy(media_jack->eeprom_page0, eeprom_page0, SL_MEDIA_EEPROM_PAGE_SIZE); + + sl_media_data_jack_eeprom_page1_get(media_jack, eeprom_page1); + + rtn = sl_media_eeprom_format_get(media_jack, &(media_attr.format)); + if (rtn) { + sl_media_log_warn_trace(media_jack, LOG_NAME, "eeprom format unsupported"); + memset(&media_attr, 0, sizeof(struct sl_media_attr)); + media_attr.errors |= SL_MEDIA_ERROR_CABLE_FORMAT_UNSUPPORTED; + media_attr.errors |= SL_MEDIA_ERROR_TRYABLE; + rtn = sl_media_jack_cable_attr_set(media_jack, ldev_num, lgrp_num, &media_attr); + if (rtn) + sl_media_log_err_trace(media_jack, LOG_NAME, "cable attr set failed [%d]", rtn); + sl_media_data_jack_cable_attr_send(media_jack); + return 0; + } + + sl_media_eeprom_parse(media_jack, &media_attr); + + if (media_attr.type == SL_MEDIA_TYPE_PEC) + media_attr.info |= SL_MEDIA_INFO_AUTONEG; + + rtn = sl_media_data_cable_db_ops_cable_validate(&media_attr, media_jack); + if (rtn) { + sl_media_log_warn_trace(media_jack, LOG_NAME, + "cable validate failed [%d] (vendor = %d %s, type = 0x%X %s, length_cm = %d, speeds = 0x%lX)", + rtn, media_attr.vendor, sl_media_vendor_str(media_attr.vendor), + media_attr.type, sl_media_type_str(media_attr.type), + media_attr.length_cm, media_attr.speeds_map); + + media_jack->is_cable_unsupported = true; + media_attr.errors |= SL_MEDIA_ERROR_CABLE_UNSUPPORTED; + media_attr.errors |= SL_MEDIA_ERROR_TRYABLE; + } + + if (SL_MEDIA_LGRP_MEDIA_TYPE_IS_ACTIVE(media_attr.type) && + !media_jack->is_cable_unsupported && !media_jack->is_supported_ss200_cable) { + if (!sl_media_eeprom_is_fw_version_supported(media_jack, &media_attr)) { + sl_media_log_warn_trace(media_jack, LOG_NAME, "eeprom fw version unsupported"); + media_attr.errors |= SL_MEDIA_ERROR_CABLE_FW_UNSUPPORTED; + media_attr.errors |= SL_MEDIA_ERROR_TRYABLE; + } + /* + * disallow BJ100 speed on active cables + */ + media_attr.speeds_map &= ~SL_MEDIA_SPEEDS_SUPPORT_BJ_100G; + } + + if (media_jack->is_supported_ss200_cable) { + media_attr.speeds_map = 0; + media_attr.speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_BS_200G; + media_attr.speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CD_100G; + media_attr.speeds_map |= SL_MEDIA_SPEEDS_SUPPORT_CD_50G; + media_attr.info |= SL_MEDIA_INFO_SUPPORTED_SS200_CABLE; + } + + media_attr.jack_type = SL_MEDIA_JACK_TYPE_QSFP; + media_attr.jack_type_info.qsfp.density = SL_MEDIA_QSFP_DENSITY_SINGLE; + } + + rtn = sl_media_jack_cable_attr_set(media_jack, ldev_num, lgrp_num, &media_attr); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "cable attr set failed [%d]", rtn); + return 0; + } + + if (media_jack->is_cable_unsupported) + flags |= SL_MEDIA_TYPE_UNSUPPORTED; + if (media_attr.vendor == SL_MEDIA_VENDOR_MULTILANE) + flags |= SL_MEDIA_TYPE_LOOPBACK; + rtn = sl_media_data_cable_db_ops_serdes_settings_get(media_jack, media_attr.type, flags); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "serdes settings get failed [%d]", rtn); + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_SERDES_SETTINGS_GET); + sl_media_data_jack_cable_attr_send(media_jack); + return 0; + } + + if (SL_MEDIA_LGRP_MEDIA_TYPE_IS_ACTIVE(media_attr.type)) { + rtn = sl_media_data_jack_cable_soft_reset(media_jack); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "cable soft reset failed [%d]", rtn); + sl_media_jack_state_set(media_jack, SL_MEDIA_JACK_CABLE_ERROR); + sl_media_jack_cable_attr_errors_update(media_jack, SL_MEDIA_ERROR_CABLE_HEADSHELL_FAULT); + sl_media_data_jack_cable_attr_send(media_jack); + return rtn; + } + + /* must be last in the sequence because it needs time to load firmware */ + rtn = sl_media_jack_cable_high_power_set(ldev_num, jack_num); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "cable high power set failed [%d]", rtn); + sl_media_jack_state_set(media_jack, SL_MEDIA_JACK_CABLE_ERROR); + sl_media_jack_cable_attr_errors_update(media_jack, SL_MEDIA_ERROR_CABLE_HEADSHELL_FAULT); + sl_media_data_jack_cable_attr_send(media_jack); + return 0; + } + } + + rtn = sl_sysfs_media_speeds_create(ldev_num, lgrp_num); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "media speeds create failed [%d]", rtn); + sl_media_data_jack_cable_attr_send(media_jack); + return rtn; + } + + if (SL_MEDIA_LGRP_MEDIA_TYPE_IS_ACTIVE(media_attr.type)) { + if (sl_media_data_jack_cable_hw_shift_state_get(media_jack) == + SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_DOWNSHIFTED) + sl_media_jack_cable_shift_state_set(media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_DOWNSHIFTED); + else if (sl_media_data_jack_cable_hw_shift_state_get(media_jack) == + SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_UPSHIFTED) + sl_media_jack_cable_shift_state_set(media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_UPSHIFTED); + else + sl_media_jack_cable_shift_state_set(media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_NOTSHIFTED); + } + + sl_media_jack_state_set(media_jack, SL_MEDIA_JACK_CABLE_ONLINE); + + sl_media_data_jack_cable_attr_send(media_jack); + + return 0; +} + +int sl_media_jack_cable_remove(u8 ldev_num, u8 lgrp_num, u8 jack_num) +{ + struct sl_media_jack *media_jack; + + media_jack = sl_media_data_jack_get(ldev_num, jack_num); + if (!media_jack) { + sl_media_log_err(NULL, LOG_NAME, "media_data_jack_get failed"); + return -EFAULT; + } + + sl_media_log_dbg(media_jack, LOG_NAME, "cable remove"); + + sl_sysfs_media_speeds_delete(ldev_num, lgrp_num); + + /* + * only first element is valid in cable_info since single lgrp on nic + */ + sl_media_data_jack_media_attr_clr(media_jack, &media_jack->cable_info[0]); + sl_media_data_cable_serdes_settings_clr(media_jack); + sl_media_data_jack_eeprom_clr(media_jack); + sl_media_data_jack_cable_state_clr(media_jack); + + sl_media_jack_state_set(media_jack, SL_MEDIA_JACK_CABLE_REMOVED); + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_NONE); + + return 0; +} + +int sl_media_jack_cable_temp_hw_check(struct sl_media_jack *media_jack) +{ + return SL_MEDIA_JACK_TEMP_STATE_COLD; +} + +void sl_media_jack_cable_hot_notif_send(struct sl_media_jack *media_jack) +{ +} + +void sl_media_jack_cable_warm_notif_send(struct sl_media_jack *media_jack) +{ +} + +void sl_media_jack_cable_cold_notif_send(struct sl_media_jack *media_jack) +{ +} + +void sl_media_jack_cable_hot_notif_sent_set(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info, bool value) +{ +} + +void sl_media_jack_cable_warm_notif_sent_set(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info, bool value) +{ +} + +void sl_media_jack_cable_cold_notif_sent_set(struct sl_media_jack *media_jack, + struct sl_media_lgrp_cable_info *cable_info, bool value) +{ +}
diff --git a/drivers/net/ethernet/hpe/sl/media/nic/sl_media_ldev.c b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_ldev.c
new file mode 100644
index 000000000000..6902223eb76a
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/nic/sl_media_ldev.c@@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> + +#include "sl_media_ldev.h" +#include "data/sl_media_data_ldev.h" + +int sl_media_ldev_uc_ops_set(u8 ldev_num, struct sl_uc_ops *uc_ops, + struct sl_uc_accessor *uc_accessor) +{ + return sl_media_data_ldev_uc_ops_set(ldev_num, uc_ops, uc_accessor); +}
diff --git a/drivers/net/ethernet/hpe/sl/media/sl_media_jack.c b/drivers/net/ethernet/hpe/sl/media/sl_media_jack.c
new file mode 100644
index 000000000000..14be1d19c097
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/sl_media_jack.c@@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024-2026 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/delay.h> + +#include "sl_asic.h" +#include "sl_core_link.h" +#include "sl_media_jack.h" +#include "sl_media_lgrp.h" +#include "sl_media_io.h" +#include "data/sl_media_data_jack.h" +#include "base/sl_media_log.h" + +#define LOG_NAME SL_MEDIA_JACK_LOG_NAME + +#define SL_MEDIA_JACK_SIGNAL_CACHE_LIFETIME_MS 1200 +#define SL_MEDIA_JACK_SIGNAL_READ_TIMEOUT_MS (2 * SL_MEDIA_JACK_SIGNAL_CACHE_LIFETIME_MS) +#define SL_MEDIA_JACK_MAX_NUM_LANE_DATA_READS 4 +#define SL_MEDIA_JACK_LANE_DATA_POLL_TIME_MS (SL_MEDIA_JACK_SIGNAL_CACHE_LIFETIME_MS / \ + SL_MEDIA_JACK_MAX_NUM_LANE_DATA_READS) + +#define SWAP_BYTE_NIBBLE(_x) ({ \ + typeof(_x) __x = (_x); \ + ((__x & 0x0F) << 4) | ((__x & 0xF0) >> 4); \ +}) + +#define MEDIA_LANE_DP_STATE_GET(_lane_data, _lane_num) ({ \ + typeof(_lane_data) __lane_data = (_lane_data); \ + typeof(_lane_num) __lane_num = (_lane_num); \ + (__lane_data)->dp_states[__lane_num / 2] >> \ + (4 * (__lane_num & 1)) & 0x0F; \ +}) + +#define SL_MEDIA_JACK_SIGNAL_INFO_ALLOWED(_dp_state) ({ \ + typeof(_dp_state) __dp_state = (_dp_state); \ + ((__dp_state) == SL_MEDIA_JACK_DP_STATE_ACTIVATED) || \ + ((__dp_state) == SL_MEDIA_JACK_DP_STATE_TX_TURN_ON) || \ + ((__dp_state) == SL_MEDIA_JACK_DP_STATE_TX_TURN_OFF) || \ + ((__dp_state) == SL_MEDIA_JACK_DP_STATE_INITIALIZED); \ +}) + +enum sl_media_cable_shift { + SL_MEDIA_JACK_CABLE_DOWNSHIFT, + SL_MEDIA_JACK_CABLE_UPSHIFT, +}; + +int sl_media_jack_new(struct sl_media_ldev *media_ldev, u8 jack_num) +{ + return sl_media_data_jack_new(media_ldev, jack_num); +} + +void sl_media_jack_del(u8 ldev_num, u8 jack_num) +{ + sl_media_data_jack_del(ldev_num, jack_num); +} + +struct sl_media_jack *sl_media_jack_get(u8 ldev_num, u8 jack_num) +{ + return sl_media_data_jack_get(ldev_num, jack_num); +} + +void sl_media_jack_state_set(struct sl_media_jack *media_jack, u8 jack_state) +{ + spin_lock(&media_jack->data_lock); + media_jack->state = jack_state; + spin_unlock(&media_jack->data_lock); + + sl_media_data_jack_led_set(media_jack); +} + +int sl_media_jack_state_get(struct sl_media_jack *media_jack, u8 *state) +{ + spin_lock(&media_jack->data_lock); + *state = media_jack->state; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_cable_end_get(struct sl_media_jack *media_jack, u8 *cable_end) +{ + spin_lock(&media_jack->data_lock); + *cable_end = media_jack->cable_end; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +bool sl_media_jack_is_high_powered(struct sl_media_jack *media_jack) +{ + bool is_high_powered; + + spin_lock(&media_jack->data_lock); + is_high_powered = media_jack->is_high_powered; + spin_unlock(&media_jack->data_lock); + + return is_high_powered; +} + +void sl_media_jack_cable_shift_state_set(struct sl_media_jack *media_jack, u8 state) +{ + spin_lock(&media_jack->data_lock); + media_jack->cable_shift_state = state; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "cable shift state set = %u", media_jack->cable_shift_state); +} + +int sl_media_jack_cable_shift_state_get(struct sl_media_jack *media_jack, u8 *cable_shift_state) +{ + spin_lock(&media_jack->data_lock); + *cable_shift_state = media_jack->cable_shift_state; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "cable shift state get = %u", *cable_shift_state); + + return 0; +} + +bool sl_media_jack_is_cable_online(struct sl_media_jack *media_jack) +{ + u8 state; + + spin_lock(&media_jack->data_lock); + state = media_jack->state; + spin_unlock(&media_jack->data_lock); + + return (state == SL_MEDIA_JACK_CABLE_ONLINE); +} + +bool sl_media_jack_is_cable_format_unsupported(struct sl_media_jack *media_jack) +{ + bool is_format_unsupported; + + spin_lock(&media_jack->data_lock); + is_format_unsupported = media_jack->is_cable_format_unsupported; + spin_unlock(&media_jack->data_lock); + + return is_format_unsupported; +} + +int sl_media_jack_active_cable_200g_host_interface_get(struct sl_media_jack *media_jack, u8 *host_interface_200_gaui) +{ + spin_lock(&media_jack->data_lock); + *host_interface_200_gaui = media_jack->host_interface_200_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_active_cable_200g_appsel_num_get(struct sl_media_jack *media_jack, u8 *appsel_num_200_gaui) +{ + spin_lock(&media_jack->data_lock); + *appsel_num_200_gaui = media_jack->appsel_num_200_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_active_cable_200g_lane_count_get(struct sl_media_jack *media_jack, u8 *lane_count_200_gaui) +{ + spin_lock(&media_jack->data_lock); + *lane_count_200_gaui = media_jack->lane_count_200_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_active_cable_400g_host_interface_get(struct sl_media_jack *media_jack, u8 *host_interface_400_gaui) +{ + spin_lock(&media_jack->data_lock); + *host_interface_400_gaui = media_jack->host_interface_400_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_active_cable_400g_appsel_num_get(struct sl_media_jack *media_jack, u8 *appsel_num_400_gaui) +{ + spin_lock(&media_jack->data_lock); + *appsel_num_400_gaui = media_jack->appsel_num_400_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +int sl_media_jack_active_cable_400g_lane_count_get(struct sl_media_jack *media_jack, u8 *lane_count_400_gaui) +{ + spin_lock(&media_jack->data_lock); + *lane_count_400_gaui = media_jack->lane_count_400_gaui; + spin_unlock(&media_jack->data_lock); + + return 0; +} + +#define SL_MEDIA_HIGH_POWER_WAIT_TIME 10000 +int sl_media_jack_cable_high_power_set(u8 ldev_num, u8 jack_num) +{ + int rtn; + struct sl_media_jack *media_jack; + + media_jack = sl_media_jack_get(ldev_num, jack_num); + + sl_media_log_dbg(media_jack, LOG_NAME, "high power set"); + + rtn = sl_media_data_jack_cable_high_power_set(media_jack); + if (rtn) { + sl_media_jack_fault_cause_set(media_jack, SL_MEDIA_FAULT_CAUSE_POWER_SET); + sl_media_log_err_trace(media_jack, LOG_NAME, "high power set failed [%d]", rtn); + return -EIO; + } + + media_jack->cable_high_power_wait_time_end = jiffies + + msecs_to_jiffies(SL_MEDIA_HIGH_POWER_WAIT_TIME); + + return 0; +} + +static int sl_media_jack_cable_shift_checks(struct sl_media_lgrp *media_lgrp, u8 shift_state) +{ + u8 i; + + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "cable shift checks"); + + spin_lock(&media_lgrp->media_jack->data_lock); + if (media_lgrp->media_jack->state != SL_MEDIA_JACK_CABLE_ONLINE) { + media_lgrp->media_jack->cable_shift_state = SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_NO_CABLE; + spin_unlock(&media_lgrp->media_jack->data_lock); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "shift check failed - no online cable"); + return -EFAULT; + } + + for (i = 0; i < SL_MEDIA_MAX_LGRPS_PER_JACK; ++i) { + if (media_lgrp->media_jack->cable_info[i].real_cable_status == CABLE_MEDIA_ATTR_STASHED) { + media_lgrp->media_jack->cable_shift_state = SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_FAKE_CABLE; + spin_unlock(&media_lgrp->media_jack->data_lock); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, + "shift check failed - fake cable (lgrp_num = %u)", + media_lgrp->media_jack->cable_info[i].lgrp_num); + return -EFAULT; + } + } + if (!(media_lgrp->media_jack->cable_info[0].media_attr.speeds_map & + (SL_MEDIA_SPEEDS_SUPPORT_CK_400G | SL_MEDIA_SPEEDS_SUPPORT_BS_200G))) { + media_lgrp->media_jack->cable_shift_state = SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_NO_SUPPORT; + spin_unlock(&media_lgrp->media_jack->data_lock); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, + "shift check failed - no shift support in cable"); + return -EINVAL; + } + if (shift_state == SL_MEDIA_JACK_CABLE_DOWNSHIFT) { + if ((media_lgrp->media_jack->host_interface_200_gaui != SL_MEDIA_SS1_HOST_INTERFACE_200GAUI_4_C2M) || + (media_lgrp->media_jack->lane_count_200_gaui != 0x44) || + (media_lgrp->media_jack->host_interface_400_gaui == 0)) { + media_lgrp->media_jack->cable_shift_state = SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_CHECK; + spin_unlock(&media_lgrp->media_jack->data_lock); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, + "downshift check failed - invalid host interface and/or lane count"); + return -EINVAL; + } + } else { + if (((media_lgrp->media_jack->host_interface_400_gaui != SL_MEDIA_SS2_HOST_INTERFACE_400GAUI_4_S_C2M) && + (media_lgrp->media_jack->host_interface_400_gaui != SL_MEDIA_SS1_HOST_INTERFACE_400GAUI_4_L_C2M)) || + (media_lgrp->media_jack->lane_count_400_gaui != 0x44)) { + media_lgrp->media_jack->cable_shift_state = SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_CHECK; + spin_unlock(&media_lgrp->media_jack->data_lock); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, + "upshift check failed - invalid host interface and/or lane count"); + return -EINVAL; + } + } + + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_jack_cable_downshift(u8 ldev_num, u8 lgrp_num, u8 link_num) +{ + int rtn; + struct sl_media_lgrp *media_lgrp; + struct sl_core_link *core_link __free(sl_core_link_put) = NULL; + struct sl_media_attr media_attr; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + core_link = sl_core_link_get(ldev_num, lgrp_num, link_num); + if (!core_link) + return -ENODEV; + + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "cable downshift"); + + if (media_lgrp->media_jack->is_cable_unsupported) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "cable unsupported - shift not required"); + return 0; + } + + if (media_lgrp->media_jack->is_supported_ss200_cable) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "ss200 cable - shift not required"); + return 0; + } + + if (!sl_media_lgrp_media_type_is_active(media_lgrp->media_ldev->num, media_lgrp->num)) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "non-active cable - shift not required"); + return 0; + } + + if (sl_media_data_jack_cable_hw_shift_state_get(media_lgrp->media_jack) == SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_DOWNSHIFTED) { + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_DOWNSHIFTED); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "already downshifted"); + return 0; + } + + rtn = sl_media_jack_cable_shift_checks(media_lgrp, SL_MEDIA_JACK_CABLE_DOWNSHIFT); + if (rtn) { + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, "cable shift checks failed [%d]", rtn); + return 0; + } + + if (!sl_core_link_policy_is_ignore_media_errors_set(core_link)) { + sl_media_lgrp_media_attr_get(ldev_num, lgrp_num, &media_attr); + if (media_attr.errors & SL_MEDIA_ERROR_CABLE_FW_UNSUPPORTED) { + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, + "can't downshift - cable firmware unsupported"); + return 0; + } + } + + rtn = sl_media_data_jack_cable_downshift(media_lgrp->media_jack); + if (rtn) { + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, + SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_DOWNSHIFT); + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, "data jack cable downshift failed [%d]", rtn); + return rtn; + } + + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_DOWNSHIFTED); + + return 0; +} + +int sl_media_jack_cable_upshift(u8 ldev_num, u8 lgrp_num, u8 link_num) +{ + int rtn; + struct sl_media_lgrp *media_lgrp; + struct sl_core_link *core_link __free(sl_core_link_put) = NULL; + struct sl_media_attr media_attr; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + core_link = sl_core_link_get(ldev_num, lgrp_num, link_num); + if (!core_link) + return -ENODEV; + + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "cable upshift"); + + if (media_lgrp->media_jack->is_cable_unsupported) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "cable unsupported - shift not required"); + return 0; + } + + if (media_lgrp->media_jack->is_supported_ss200_cable) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "ss200 cable - shift not required"); + return 0; + } + + if (!sl_media_lgrp_media_type_is_active(media_lgrp->media_ldev->num, media_lgrp->num)) { + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "non-active cable - shift not required"); + return 0; + } + + if (sl_media_data_jack_cable_hw_shift_state_get(media_lgrp->media_jack) == SL_MEDIA_JACK_CABLE_HW_SHIFT_STATE_UPSHIFTED) { + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_UPSHIFTED); + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "already upshifted"); + return 0; + } + + rtn = sl_media_jack_cable_shift_checks(media_lgrp, SL_MEDIA_JACK_CABLE_UPSHIFT); + if (rtn) { + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, "cable shift checks failed [%d]", rtn); + return 0; + } + + if (!sl_core_link_policy_is_ignore_media_errors_set(core_link)) { + sl_media_lgrp_media_attr_get(ldev_num, lgrp_num, &media_attr); + if (media_attr.errors & SL_MEDIA_ERROR_CABLE_FW_UNSUPPORTED) { + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, + "can't upshift - cable firmware unsupported"); + return 0; + } + } + + rtn = sl_media_data_jack_cable_upshift(media_lgrp->media_jack); + if (rtn) { + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, + SL_MEDIA_JACK_CABLE_SHIFT_STATE_FAILED_UPSHIFT); + sl_media_log_err_trace(media_lgrp->media_jack, LOG_NAME, "data jack cable upshift failed [%d]", rtn); + return rtn; + } + + sl_media_jack_cable_shift_state_set(media_lgrp->media_jack, SL_MEDIA_JACK_CABLE_SHIFT_STATE_UPSHIFTED); + + return 0; +} + +void sl_media_jack_fault_cause_set(struct sl_media_jack *media_jack, u32 fault_cause) +{ + spin_lock(&media_jack->data_lock); + media_jack->fault_cause = fault_cause; + media_jack->fault_time = ktime_get_real_seconds(); + spin_unlock(&media_jack->data_lock); + + if (fault_cause == SL_MEDIA_FAULT_CAUSE_NONE) + return; + + if (fault_cause == SL_MEDIA_FAULT_CAUSE_HOT) + sl_media_data_jack_led_set(media_jack); + + sl_ctrl_media_cause_counter_inc(media_jack, fault_cause); + + sl_media_log_dbg(media_jack, LOG_NAME, "fault cause set (cause = %u %s)", fault_cause, + sl_media_fault_cause_str(fault_cause)); +} + +int sl_media_jack_fault_cause_get(struct sl_media_jack *media_jack, u32 *fault_cause, + time64_t *fault_time) +{ + spin_lock(&media_jack->data_lock); + *fault_cause = media_jack->fault_cause; + *fault_time = media_jack->fault_time; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "cable fault cause get (cause = %u %s)", *fault_cause, + sl_media_fault_cause_str(*fault_cause)); + + return 0; +} + +const char *sl_media_fault_cause_str(u32 fault_cause) +{ + switch (fault_cause) { + case SL_MEDIA_FAULT_CAUSE_EEPROM_FORMAT_UNSUPPORTED: + return "eeprom-format-unsupported"; + case SL_MEDIA_FAULT_CAUSE_EEPROM_VENDOR_UNSUPPORTED: + return "eeprom-vendor-unsupported"; + case SL_MEDIA_FAULT_CAUSE_EEPROM_JACK_IO: + return "eeprom-jack-io"; + case SL_MEDIA_FAULT_CAUSE_ONLINE_STATUS_GET: + return "online-status-get"; + case SL_MEDIA_FAULT_CAUSE_ONLINE_TIMEDOUT: + return "online-timedout"; + case SL_MEDIA_FAULT_CAUSE_ONLINE_JACK_IO: + return "online-jack-io"; + case SL_MEDIA_FAULT_CAUSE_ONLINE_JACK_GET: + return "online-jack-get"; + case SL_MEDIA_FAULT_CAUSE_SERDES_SETTINGS_GET: + return "serdes-settings-get"; + case SL_MEDIA_FAULT_CAUSE_SCAN_STATUS_GET: + return "scan-status-get"; + case SL_MEDIA_FAULT_CAUSE_SCAN_HDL_GET: + return "scan-hdl-get"; + case SL_MEDIA_FAULT_CAUSE_SCAN_JACK_GET: + return "scan-jack-get"; + case SL_MEDIA_FAULT_CAUSE_MEDIA_ATTR_SET: + return "media-attr-set"; + case SL_MEDIA_FAULT_CAUSE_INTR_EVENT_JACK_IO: + return "intr-event-jack-io"; + case SL_MEDIA_FAULT_CAUSE_POWER_SET: + return "power-set"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO: + return "shift-down-jack-io"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO_LOW_POWER_SET: + return "shift-down-jack-io-low-power-set"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_DOWN_JACK_IO_HIGH_POWER_SET: + return "shift-down-jack-io-high-power-set"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO: + return "shift-up-jack-io"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO_LOW_POWER_SET: + return "shift-up-jack-io-low-power-set"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_UP_JACK_IO_HIGH_POWER_SET: + return "shift-up-jack-io-high-power-set"; + case SL_MEDIA_FAULT_CAUSE_SHIFT_STATE_JACK_IO: + return "shift-state-jack-io"; + case SL_MEDIA_FAULT_CAUSE_OFFLINE: + return "offline"; + case SL_MEDIA_FAULT_CAUSE_HOT: + return "hot"; + case SL_MEDIA_FAULT_CAUSE_WARM: + return "warm"; + default: + return "unknown"; + } +} + +const char *sl_media_temp_state_str(u8 temperature_state) +{ + switch (temperature_state) { + case SL_MEDIA_JACK_TEMP_STATE_COLD: + return "cold"; + case SL_MEDIA_JACK_TEMP_STATE_WARM: + return "warm"; + case SL_MEDIA_JACK_TEMP_STATE_HOT: + return "hot"; + default: + return "unknown"; + } +} + +int sl_media_jack_cable_temp_state_get(struct sl_media_jack *media_jack, u8 *temperature_state) +{ + return sl_media_data_jack_cable_temp_state_get(media_jack, temperature_state); +} + +int sl_media_jack_cable_temp_prev_state_get(struct sl_media_jack *media_jack, u8 *temperature_prev_state) +{ + return sl_media_data_jack_cable_temp_prev_state_get(media_jack, temperature_prev_state); +} + +int sl_media_jack_cable_temp_get(u8 ldev_num, u8 lgrp_num, u8 *temp) +{ + struct sl_media_lgrp *group = sl_media_lgrp_get(ldev_num, lgrp_num); + + if (!group || !group->media_jack) + return -EBADRQC; + return sl_media_data_jack_cable_temp_get(group->media_jack, temp); +} + +int sl_media_jack_cable_temp_warn_limit_get(u8 ldev_num, u8 lgrp_num, u8 *temp_warn_limit_c) +{ + struct sl_media_lgrp *group = sl_media_lgrp_get(ldev_num, lgrp_num); + + if (!group || !group->media_jack) + return -EBADRQC; + return sl_media_data_jack_cable_temp_warn_limit_get(group->media_jack, + temp_warn_limit_c); +} + +int sl_media_jack_cable_temp_down_limit_get(u8 ldev_num, u8 lgrp_num, u8 *temp_down_limit_c) +{ + struct sl_media_lgrp *group = sl_media_lgrp_get(ldev_num, lgrp_num); + + if (!group || !group->media_jack) + return -EBADRQC; + return sl_media_data_jack_cable_temp_down_limit_get(group->media_jack, + temp_down_limit_c); +} + +int sl_media_jack_cable_low_power_set(struct sl_media_jack *media_jack) +{ + return sl_media_data_jack_cable_low_power_set(media_jack); +} + +void sl_media_jack_led_set(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp->media_jack, LOG_NAME, "led set"); + + sl_media_data_jack_led_set(media_lgrp->media_jack); +} + +static const char *sl_media_jack_lane_dp_state_str(u8 dp_state) +{ + switch (dp_state) { + case SL_MEDIA_JACK_DP_STATE_DEACTIVATED: + return "dp-deactivated"; + case SL_MEDIA_JACK_DP_STATE_INIT: + return "dp-init"; + case SL_MEDIA_JACK_DP_STATE_DEINIT: + return "dp-deinit"; + case SL_MEDIA_JACK_DP_STATE_ACTIVATED: + return "dp-activated"; + case SL_MEDIA_JACK_DP_STATE_TX_TURN_ON: + return "dp-tx-turn-on"; + case SL_MEDIA_JACK_DP_STATE_TX_TURN_OFF: + return "dp-tx-turn-off"; + case SL_MEDIA_JACK_DP_STATE_INITIALIZED: + return "dp-initialized"; + default: + return "unknown"; + } +} + +/* Swap the lanes in the lane data structure. See sl_core_lgrp_media_lane_data_swap + * for more details. + */ +static void sl_media_jack_lane_data_swap(struct sl_media_jack_lane_data *lane_data) +{ + u8 dp_states; + + lane_data->signal.rx.los_map = SWAP_BYTE_NIBBLE(lane_data->signal.rx.los_map); + lane_data->signal.tx.los_map = SWAP_BYTE_NIBBLE(lane_data->signal.tx.los_map); + lane_data->signal.rx.lol_map = SWAP_BYTE_NIBBLE(lane_data->signal.rx.lol_map); + lane_data->signal.tx.lol_map = SWAP_BYTE_NIBBLE(lane_data->signal.tx.lol_map); + lane_data->dp_states_changed = SWAP_BYTE_NIBBLE(lane_data->dp_states_changed); + + /* Swap lanes 0 & 1 for 4 & 5 and visa versa */ + dp_states = lane_data->dp_states[2]; + lane_data->dp_states[2] = lane_data->dp_states[0]; + lane_data->dp_states[0] = dp_states; + + /* Swap lanes 2 & 3 for 6 & 7 and visa versa */ + dp_states = lane_data->dp_states[3]; + lane_data->dp_states[3] = lane_data->dp_states[1]; + lane_data->dp_states[1] = dp_states; +} + +#define SL_MEDIA_JACK_DP_STATE_OFFSET 128 +#define SL_MEDIA_JACK_TX_LOS_OFFSET 136 +#define SL_MEDIA_JACK_RX_LOS_OFFSET 147 +#define SL_MEDIA_JACK_MAX_COHERENT_BYTES 8 +static int sl_media_jack_lane_data_read(u8 ldev_num, u8 lgrp_num, struct sl_media_jack_lane_data *lane_data) +{ + int rtn; + u8 read_bytes[SL_MEDIA_JACK_MAX_COHERENT_BYTES]; + bool swap; + struct sl_media_jack_lane_data tmp_lane_data; + struct sl_media_jack *media_jack; + + media_jack = sl_media_lgrp_get(ldev_num, lgrp_num)->media_jack; + + sl_media_log_dbg(media_jack, LOG_NAME, "media_io_read_lane_data (ldev_num = %u, lgrp_num = %u)", + ldev_num, lgrp_num); + + rtn = sl_media_io_read(media_jack, 0x11, SL_MEDIA_JACK_TX_LOS_OFFSET, (u8 *)&tmp_lane_data.signal.tx, 2); + if (rtn) { + sl_media_log_err(media_jack, LOG_NAME, "media_io_read_data failed [%d]", rtn); + return -EIO; + } + + rtn = sl_media_io_read(media_jack, 0x11, SL_MEDIA_JACK_RX_LOS_OFFSET, (u8 *)&tmp_lane_data.signal.rx, 2); + if (rtn) { + sl_media_log_err(media_jack, LOG_NAME, "media_io_read_data failed [%d]", rtn); + return -EIO; + } + + rtn = sl_media_io_read(media_jack, 0x11, SL_MEDIA_JACK_DP_STATE_OFFSET, read_bytes, + SL_MEDIA_JACK_MAX_COHERENT_BYTES); + if (rtn) { + sl_media_log_err(media_jack, LOG_NAME, "media_io_read_data failed [%d]", rtn); + return -EIO; + } + + memcpy(tmp_lane_data.dp_states, read_bytes, 4); + tmp_lane_data.dp_states_changed = read_bytes[6]; + + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data read (dp_states_changed = 0x%X)", tmp_lane_data.dp_states_changed); + + swap = sl_core_lgrp_media_lane_data_swap(ldev_num, lgrp_num); + if (swap) + sl_media_jack_lane_data_swap(&tmp_lane_data); + + sl_media_log_dbg(media_jack, LOG_NAME, "lane data swap = %s", swap ? "yes" : "no"); + + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data read map (rx_lol = 0x%X, tx_lol = 0x%X, rx_los = 0x%X, tx_los = 0x%X)", + tmp_lane_data.signal.rx.lol_map, tmp_lane_data.signal.tx.lol_map, + tmp_lane_data.signal.rx.los_map, tmp_lane_data.signal.tx.los_map); + + sl_media_log_dbg(media_jack, LOG_NAME, + "lanes dp_state (0 = %s, 1 = %s, 2 = %s, 3 = %s, 4 = %s, 5 = %s, 6 = %s, 7 = %s)", + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 0)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 1)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 2)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 3)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 4)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 5)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 6)), + sl_media_jack_lane_dp_state_str(MEDIA_LANE_DP_STATE_GET(&tmp_lane_data, 7))); + + spin_lock(&media_jack->data_lock); + memcpy(lane_data, &tmp_lane_data, sizeof(*lane_data)); + spin_unlock(&media_jack->data_lock); + + return 0; +} + +static bool sl_media_jack_is_lane_data_valid(struct sl_media_jack *media_jack, unsigned long serdes_lane_map, + struct sl_media_jack_lane_data *lane_data) +{ + u8 serdes_lane_num; + u8 lane_dp_state; + + sl_media_log_dbg(media_jack, LOG_NAME, "lane data valid (serdes_lane_map = 0x%lx)", serdes_lane_map); + + if (lane_data->dp_states_changed & serdes_lane_map) { + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data valid DP state changed (dp_states_changed = 0x%X, serdes_lane_map = 0x%lx)", + lane_data->dp_states_changed, serdes_lane_map); + return false; + } + + for_each_set_bit(serdes_lane_num, &serdes_lane_map, SL_ASIC_MAX_LANES) { + lane_dp_state = MEDIA_LANE_DP_STATE_GET(lane_data, serdes_lane_num); + + if (!SL_MEDIA_JACK_SIGNAL_INFO_ALLOWED(lane_dp_state)) { + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data valid DP state not allowed (lane_num = %u, dp_state = %u %s)", + serdes_lane_num, lane_dp_state, + sl_media_jack_lane_dp_state_str(lane_dp_state)); + return false; + } + } + + return true; +} + +static void sl_media_jack_lane_data_cache_set(struct sl_media_jack *media_jack, + struct sl_media_jack_lane_data *lane_data) +{ + spin_lock(&media_jack->data_lock); + media_jack->lane_data.cache.data = *lane_data; + media_jack->lane_data.cache.cached = true; + media_jack->lane_data.cache.timestamp_s = ktime_get_real_seconds(); + spin_unlock(&media_jack->data_lock); +} + +static bool sl_media_jack_lane_data_cache_get(struct sl_media_jack *media_jack, + struct sl_media_jack_lane_data *lane_data) +{ + bool cached; + + spin_lock(&media_jack->data_lock); + cached = media_jack->lane_data.cache.cached; + *lane_data = media_jack->lane_data.cache.data; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data cache get map (rx_los = 0x%X, tx_los = 0x%X, rx_lol = 0x%X, tx_lol = 0x%X)", + lane_data->signal.rx.los_map, lane_data->signal.tx.los_map, + lane_data->signal.rx.lol_map, lane_data->signal.tx.lol_map); + + return cached; +} + +static bool sl_media_jack_signal_equal(struct sl_media_jack_signal *a, struct sl_media_jack_signal *b) +{ + return (a->rx.los_map == b->rx.los_map) && + (a->tx.los_map == b->tx.los_map) && + (a->rx.lol_map == b->rx.lol_map) && + (a->tx.lol_map == b->tx.lol_map); +} + +static void sl_media_jack_signal_or(struct sl_media_jack_signal *a, struct sl_media_jack_signal *b) +{ + a->rx.los_map |= b->rx.los_map; + a->tx.los_map |= b->tx.los_map; + a->rx.lol_map |= b->rx.lol_map; + a->tx.lol_map |= b->tx.lol_map; +} + +static int sl_media_jack_lane_data_get(u8 ldev_num, u8 lgrp_num, u8 serdes_lane_map, + struct sl_media_jack_lane_data *lane_data) +{ + int rtn; + u8 read_count; + bool is_stable; + struct sl_media_jack_lane_data current_lane_data; + struct sl_media_jack_signal prev_signal; + struct sl_media_jack *media_jack; + + media_jack = sl_media_lgrp_get(ldev_num, lgrp_num)->media_jack; + + sl_media_log_dbg(media_jack, LOG_NAME, "lane data get (ldev_num = %u, lgrp_num = %u, serdes_lane_map = 0x%X)", + ldev_num, lgrp_num, serdes_lane_map); + + rtn = sl_media_jack_lane_data_read(ldev_num, lgrp_num, lane_data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read failed [%d]", rtn); + return rtn; + } + + msleep(SL_MEDIA_JACK_LANE_DATA_POLL_TIME_MS); + + read_count = 0; + is_stable = false; + + rtn = sl_media_jack_lane_data_read(ldev_num, lgrp_num, lane_data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read failed [%d]", rtn); + return rtn; + } + + if (!sl_media_jack_is_lane_data_valid(media_jack, serdes_lane_map, lane_data)) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data not valid"); + return -EIO; + } + + /* Short circuit, no new bits can be set */ + if (lane_data->signal.rx.los_map == 0xFF && + lane_data->signal.tx.los_map == 0xFF && + lane_data->signal.rx.lol_map == 0xFF && + lane_data->signal.tx.lol_map == 0xFF) { + read_count++; + is_stable = true; + goto out; + } + + prev_signal = lane_data->signal; + + msleep(SL_MEDIA_JACK_LANE_DATA_POLL_TIME_MS); + + for (read_count = 1; read_count < SL_MEDIA_JACK_MAX_NUM_LANE_DATA_READS; ++read_count) { + rtn = sl_media_jack_lane_data_read(ldev_num, lgrp_num, ¤t_lane_data); + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read failed [%d]", rtn); + return rtn; + } + + if (!sl_media_jack_is_lane_data_valid(media_jack, serdes_lane_map, ¤t_lane_data)) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data not valid"); + return -EIO; + } + + prev_signal = lane_data->signal; + sl_media_jack_signal_or(&lane_data->signal, ¤t_lane_data.signal); + + is_stable = sl_media_jack_signal_equal(&lane_data->signal, &prev_signal); + if (is_stable) { + ++read_count; + break; + } + + msleep(SL_MEDIA_JACK_LANE_DATA_POLL_TIME_MS); + } + +out: + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data get (read_count = %u)", read_count); + + if (!is_stable) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data unstable"); + return -EIO; + } + + sl_media_jack_lane_data_cache_set(media_jack, lane_data); + + sl_media_log_dbg(media_jack, LOG_NAME, + "lane data get map (rx_los = 0x%X, tx_los = 0x%X, rx_lol = 0x%X, tx_lol = 0x%X)", + lane_data->signal.rx.los_map, lane_data->signal.tx.los_map, + lane_data->signal.rx.lol_map, lane_data->signal.tx.lol_map); + + return 0; +} + +int sl_media_jack_signal_get(u8 ldev_num, u8 lgrp_num, u8 serdes_lane_map, struct sl_media_jack_signal *media_signal) +{ + int rtn; + struct sl_media_jack_lane_data lane_data; + struct sl_media_jack *media_jack; + bool cached; + unsigned long time_left; + u32 generation; + + media_jack = sl_media_lgrp_get(ldev_num, lgrp_num)->media_jack; + + sl_media_log_dbg(media_jack, LOG_NAME, "signal get (ldev_num = %u, lgrp_num = %u, serdes_lane_map = 0x%X)", + ldev_num, lgrp_num, serdes_lane_map); + + if (!sl_media_lgrp_media_type_is_active(ldev_num, lgrp_num)) { + sl_media_log_warn_trace(media_jack, LOG_NAME, "cable not active"); + return -EBADRQC; + } + + if (!sl_media_lgrp_is_signal_status_supported(ldev_num, lgrp_num)) { + sl_media_log_dbg(media_jack, LOG_NAME, "signal status unsupported"); + return -EBADRQC; + } + + spin_lock(&media_jack->data_lock); + switch (media_jack->lane_data.read_state) { + case SL_MEDIA_JACK_LANE_DATA_READ_STATE_IDLE: + media_jack->lane_data.read_state = SL_MEDIA_JACK_LANE_DATA_READ_STATE_BUSY; + sl_media_log_dbg(media_jack, LOG_NAME, "reading lane data - state set to BUSY"); + spin_unlock(&media_jack->data_lock); + + rtn = sl_media_jack_lane_data_get(ldev_num, lgrp_num, serdes_lane_map, &lane_data); + + spin_lock(&media_jack->data_lock); + media_jack->lane_data.read_state = SL_MEDIA_JACK_LANE_DATA_READ_STATE_IDLE; + media_jack->lane_data.last_read_rtn = rtn; + WRITE_ONCE(media_jack->lane_data.read_generation, + media_jack->lane_data.read_generation + 1); + spin_unlock(&media_jack->data_lock); + + wake_up_all(&media_jack->lane_data.read_wait); + + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read failed [%d]", rtn); + return rtn; + } + + goto out; + + case SL_MEDIA_JACK_LANE_DATA_READ_STATE_BUSY: + generation = media_jack->lane_data.read_generation; + sl_media_log_dbg(media_jack, LOG_NAME, "waiting for lane data read to complete"); + spin_unlock(&media_jack->data_lock); + + time_left = msecs_to_jiffies(SL_MEDIA_JACK_SIGNAL_READ_TIMEOUT_MS); + time_left = wait_event_timeout(media_jack->lane_data.read_wait, + READ_ONCE(media_jack->lane_data.read_generation) != + generation, time_left); + + if (time_left == 0) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read timed out"); + return -ETIMEDOUT; + } + + spin_lock(&media_jack->data_lock); + rtn = media_jack->lane_data.last_read_rtn; + if (rtn) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data read failed [%d]", rtn); + spin_unlock(&media_jack->data_lock); + return rtn; + } + + cached = media_jack->lane_data.cache.cached; + lane_data = media_jack->lane_data.cache.data; + spin_unlock(&media_jack->data_lock); + + if (!cached) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data not cached"); + return -EIO; + } + + goto out; + + default: + sl_media_log_err(media_jack, LOG_NAME, "invalid lane data read state (%u)", + media_jack->lane_data.read_state); + spin_unlock(&media_jack->data_lock); + return -EIO; + } + +out: + *media_signal = lane_data.signal; + + sl_media_log_dbg(media_jack, LOG_NAME, + "signal get (rx_los = 0x%X, rx_lol = 0x%X, tx_los = 0x%X, tx_lol = 0x%X)", + media_signal->rx.los_map, media_signal->rx.lol_map, + media_signal->tx.los_map, media_signal->tx.lol_map); + + return 0; +} + +int sl_media_jack_signal_cache_get(u8 ldev_num, u8 lgrp_num, u8 serdes_lane_map, + struct sl_media_jack_signal *media_signal) +{ + struct sl_media_jack_lane_data lane_data; + struct sl_media_jack *media_jack; + + media_jack = sl_media_lgrp_get(ldev_num, lgrp_num)->media_jack; + + sl_media_log_dbg(media_jack, LOG_NAME, "signal cache get (ldev_num =%u, lgrp_num = %u, serdes_lane_map = 0x%X)", + ldev_num, lgrp_num, serdes_lane_map); + + if (!sl_media_jack_lane_data_cache_get(media_jack, &lane_data)) { + sl_media_log_err_trace(media_jack, LOG_NAME, "lane data not cached"); + return -ENOENT; + } + + *media_signal = lane_data.signal; + + return 0; +} + +int sl_media_jack_signal_cache_time_s_get(u8 ldev_num, u8 lgrp_num, time64_t *cache_time) +{ + bool cached; + struct sl_media_jack *media_jack; + + media_jack = sl_media_lgrp_get(ldev_num, lgrp_num)->media_jack; + + sl_media_log_dbg(media_jack, LOG_NAME, "signal cache time get (ldev_num =%u, lgrp_num = %u)", + ldev_num, lgrp_num); + + spin_lock(&media_jack->data_lock); + cached = media_jack->lane_data.cache.cached; + *cache_time = media_jack->lane_data.cache.timestamp_s; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, + "signal cache time get (cache_time = %lld, cached = %s)", + *cache_time, cached ? "yes" : "no"); + + return cached ? 0 : -ENOENT; +} + +int sl_media_jack_attr_error_map_str(unsigned long error_map, char *error_str, unsigned int error_str_size) +{ + int rtn; + int str_pos; + int which; + + if (!error_str) + return -EINVAL; + + if (error_str_size < SL_MEDIA_JACK_ATTR_ERR_STR_SIZE) + return -EINVAL; + + if (!error_map) { + str_pos = snprintf(error_str, error_str_size, "none "); + goto out; + } + + str_pos = 0; + + for_each_set_bit(which, &error_map, sizeof(error_map) * BITS_PER_BYTE) { + switch (BIT(which)) { + case SL_MEDIA_ERROR_CABLE_UNSUPPORTED: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "cable-unsupported "); + break; + case SL_MEDIA_ERROR_CABLE_FORMAT_UNSUPPORTED: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "cable-format-unsupported "); + break; + case SL_MEDIA_ERROR_CABLE_FW_UNSUPPORTED: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "cable-fw-unsupported "); + break; + case SL_MEDIA_ERROR_CABLE_HEADSHELL_FAULT: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "cable-headshell-fault "); + break; + case SL_MEDIA_ERROR_TEMP_DOWN_LIMIT_DEFAULT: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "temp-down-limit-default "); + break; + case SL_MEDIA_ERROR_TEMP_WARN_LIMIT_DEFAULT: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "temp-warn-limit-default "); + break; + case SL_MEDIA_ERROR_TRYABLE: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "tryable "); + break; + default: + rtn = snprintf(error_str + str_pos, error_str_size - str_pos, "unknown "); + break; + } + + if (rtn < 0) { + str_pos = snprintf(error_str, error_str_size, "error "); + goto out; + } + if (str_pos + rtn >= error_str_size) { + error_str[str_pos - 2] = '.'; + error_str[str_pos - 3] = '.'; + error_str[str_pos - 4] = '.'; + break; + } + str_pos += rtn; + } + +out: + error_str[str_pos - 1] = '\0'; + + return 0; +} + +int sl_media_jack_attr_error_map_get(struct sl_media_jack *media_jack, u32 *error_map) +{ + spin_lock(&media_jack->data_lock); + *error_map = media_jack->cable_info->media_attr.errors; + spin_unlock(&media_jack->data_lock); + + sl_media_log_dbg(media_jack, LOG_NAME, "attr error map get (error_map = %u)", *error_map); + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sl/media/sl_media_ldev.c b/drivers/net/ethernet/hpe/sl/media/sl_media_ldev.c
new file mode 100644
index 000000000000..a78d70494b5c
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/sl_media_ldev.c@@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2024 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> + +#include "sl_media_ldev.h" +#include "data/sl_media_data_ldev.h" + +int sl_media_ldev_new(u8 ldev_num, struct workqueue_struct *workqueue) +{ + return sl_media_data_ldev_new(ldev_num, workqueue); +} + +void sl_media_ldev_del(u8 ldev_num) +{ + sl_media_data_ldev_del(ldev_num); +} + +struct sl_media_ldev *sl_media_ldev_get(u8 ldev_num) +{ + return sl_media_data_ldev_get(ldev_num); +}
diff --git a/drivers/net/ethernet/hpe/sl/media/sl_media_lgrp.c b/drivers/net/ethernet/hpe/sl/media/sl_media_lgrp.c
new file mode 100644
index 000000000000..b15805a82270
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sl/media/sl_media_lgrp.c@@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2023-2026 Hewlett Packard Enterprise Development LP */ + +#include <linux/slab.h> +#include <linux/types.h> + +#include "base/sl_media_log.h" + +#include "sl_media_lgrp.h" +#include "sl_media_jack.h" +#include "data/sl_media_data_lgrp.h" +#include "data/sl_media_data_jack.h" + +#define LOG_NAME SL_MEDIA_LGRP_LOG_NAME + +int sl_media_lgrp_new(u8 ldev_num, u8 lgrp_num) +{ + return sl_media_data_lgrp_new(ldev_num, lgrp_num); +} + +void sl_media_lgrp_del(u8 ldev_num, u8 lgrp_num) +{ + sl_media_data_lgrp_del(ldev_num, lgrp_num); +} + +struct sl_media_lgrp *sl_media_lgrp_get(u8 ldev_num, u8 lgrp_num) +{ + return sl_media_data_lgrp_get(ldev_num, lgrp_num); +} + +void sl_media_lgrp_media_attr_get(u8 ldev_num, u8 lgrp_num, struct sl_media_attr *media_attr) +{ + struct sl_media_lgrp *media_lgrp; + + memset(media_attr, 0, sizeof(struct sl_media_attr)); + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + if (!media_lgrp) { + sl_media_log_err(NULL, SL_MEDIA_LGRP_LOG_NAME, "media_data_lgrp_get failed"); + return; + } + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "media_attr_get"); + + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *media_attr = media_lgrp->cable_info->media_attr; + break; + case CABLE_MEDIA_ATTR_STASHED: + *media_attr = media_lgrp->cable_info->stashed_media_attr; + break; + } + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +bool sl_media_lgrp_media_has_error(struct sl_media_lgrp *media_lgrp) +{ + struct sl_media_attr media_attr; + + sl_media_lgrp_media_attr_get(media_lgrp->media_ldev->num, media_lgrp->num, &media_attr); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, + "media has error (media_attr.errors = 0x%X)", media_attr.errors); + + return ((media_attr.errors & (SL_MEDIA_ERROR_CABLE_FORMAT_UNSUPPORTED | + SL_MEDIA_ERROR_CABLE_FW_UNSUPPORTED)) != 0); +} + +void sl_media_lgrp_media_serdes_settings_get(u8 ldev_num, u8 lgrp_num, + struct sl_media_serdes_settings *media_serdes_settings) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "media serdes settings get"); + + *media_serdes_settings = media_lgrp->media_jack->serdes_settings; +} + +bool sl_media_lgrp_media_type_is_active(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_attr media_attr; + + sl_media_lgrp_media_attr_get(ldev_num, lgrp_num, &media_attr); + + return SL_MEDIA_LGRP_MEDIA_TYPE_IS_ACTIVE(media_attr.type); +} + +void sl_media_lgrp_connect_id_set(u8 ldev_num, u8 lgrp_num, const char *connect_id) +{ + sl_media_data_lgrp_connect_id_set(sl_media_data_lgrp_get(ldev_num, lgrp_num), connect_id); +} + +void sl_media_lgrp_real_cable_if_present_send(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "real cable if present send"); + + sl_media_data_jack_cable_if_present_send(media_lgrp); +} + +void sl_media_lgrp_real_cable_if_not_present_send(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "real cable if not present send"); + + sl_media_data_jack_cable_if_not_present_send(media_lgrp); +} + +void sl_media_lgrp_cable_hot_send(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "cable hot send"); + + if (sl_media_lgrp_media_type_is_active(ldev_num, lgrp_num)) { + sl_media_lgrp_hot_client_ready_set(ldev_num, lgrp_num, true); + if (sl_media_jack_cable_temp_hw_check(media_lgrp->media_jack) == SL_MEDIA_JACK_TEMP_STATE_HOT) { + sl_media_jack_fault_cause_set(media_lgrp->media_jack, SL_MEDIA_FAULT_CAUSE_HOT); + sl_media_jack_cable_hot_notif_send(media_lgrp->media_jack); + } + } +} + +void sl_media_lgrp_cable_warm_send(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "cable warm send"); + + if (sl_media_lgrp_media_type_is_active(ldev_num, lgrp_num)) { + sl_media_lgrp_warm_client_ready_set(ldev_num, lgrp_num, true); + if (sl_media_jack_cable_temp_hw_check(media_lgrp->media_jack) == SL_MEDIA_JACK_TEMP_STATE_WARM) { + sl_media_jack_cable_warm_notif_send(media_lgrp->media_jack); + } + } +} + +void sl_media_lgrp_cable_cold_send(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "cable cold send"); + + if (sl_media_lgrp_media_type_is_active(ldev_num, lgrp_num)) { + sl_media_lgrp_cold_client_ready_set(ldev_num, lgrp_num, true); + if (sl_media_jack_cable_temp_hw_check(media_lgrp->media_jack) == SL_MEDIA_JACK_TEMP_STATE_COLD) + sl_media_jack_cable_cold_notif_send(media_lgrp->media_jack); + } +} + +void sl_media_lgrp_cable_temp_flags_clr(u8 ldev_num, u8 lgrp_num) +{ + u8 i; + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "cable temp flags clr"); + + for (i = 0; i < media_lgrp->media_jack->port_count; ++i) { + if (sl_media_lgrp_media_type_is_active(ldev_num, lgrp_num)) { + sl_media_lgrp_hot_client_ready_set(ldev_num, lgrp_num, false); + sl_media_jack_cable_hot_notif_sent_set(media_lgrp->media_jack, + &media_lgrp->media_jack->cable_info[i], false); + sl_media_lgrp_warm_client_ready_set(ldev_num, lgrp_num, false); + sl_media_jack_cable_warm_notif_sent_set(media_lgrp->media_jack, + &media_lgrp->media_jack->cable_info[i], false); + sl_media_lgrp_cold_client_ready_set(ldev_num, lgrp_num, false); + sl_media_jack_cable_cold_notif_sent_set(media_lgrp->media_jack, + &media_lgrp->media_jack->cable_info[i], false); + } + } +} + +void sl_media_lgrp_hot_client_ready_set(u8 ldev_num, u8 lgrp_num, bool value) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "hot client ready set (value = %s)", + value ? "true" : "false"); + + spin_lock(&media_lgrp->media_jack->data_lock); + media_lgrp->cable_info->hot_client_ready = value; + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +void sl_media_lgrp_warm_client_ready_set(u8 ldev_num, u8 lgrp_num, bool value) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "warm client ready set (value = %s)", + value ? "true" : "false"); + + spin_lock(&media_lgrp->media_jack->data_lock); + media_lgrp->cable_info->warm_client_ready = value; + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +void sl_media_lgrp_cold_client_ready_set(u8 ldev_num, u8 lgrp_num, bool value) +{ + struct sl_media_lgrp *media_lgrp; + + media_lgrp = sl_media_data_lgrp_get(ldev_num, lgrp_num); + + sl_media_log_dbg(media_lgrp, SL_MEDIA_LGRP_LOG_NAME, "cold client ready set (value = %s)", + value ? "true" : "false"); + + spin_lock(&media_lgrp->media_jack->data_lock); + media_lgrp->cable_info->cold_client_ready = value; + spin_unlock(&media_lgrp->media_jack->data_lock); +} + +int sl_media_lgrp_vendor_get(struct sl_media_lgrp *media_lgrp, u32 *vendor) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *vendor = media_lgrp->cable_info->media_attr.vendor; + break; + case CABLE_MEDIA_ATTR_STASHED: + *vendor = media_lgrp->cable_info->stashed_media_attr.vendor; + break; + default: + *vendor = SL_MEDIA_VENDOR_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_vendor_pn_str_get(struct sl_media_lgrp *media_lgrp, char *vendor_pn_str) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + strscpy_pad(vendor_pn_str, + media_lgrp->cable_info->media_attr.vendor_pn_str, SL_MEDIA_VENDOR_PN_SIZE); + break; + case CABLE_MEDIA_ATTR_STASHED: + strscpy_pad(vendor_pn_str, + media_lgrp->cable_info->stashed_media_attr.vendor_pn_str, SL_MEDIA_VENDOR_PN_SIZE); + break; + default: + memset(vendor_pn_str, '0', SL_MEDIA_VENDOR_PN_SIZE - 2); + vendor_pn_str[SL_MEDIA_VENDOR_PN_SIZE - 1] = '\0'; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_type_get(struct sl_media_lgrp *media_lgrp, u32 *type) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *type = media_lgrp->cable_info->media_attr.type; + break; + case CABLE_MEDIA_ATTR_STASHED: + *type = media_lgrp->cable_info->stashed_media_attr.type; + break; + default: + *type = SL_MEDIA_TYPE_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_shape_get(struct sl_media_lgrp *media_lgrp, u32 *shape) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *shape = media_lgrp->cable_info->media_attr.shape; + break; + case CABLE_MEDIA_ATTR_STASHED: + *shape = media_lgrp->cable_info->stashed_media_attr.shape; + break; + default: + *shape = SL_MEDIA_SHAPE_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_length_get(struct sl_media_lgrp *media_lgrp, u32 *length_cm) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *length_cm = media_lgrp->cable_info->media_attr.length_cm; + break; + case CABLE_MEDIA_ATTR_STASHED: + *length_cm = media_lgrp->cable_info->stashed_media_attr.length_cm; + break; + default: + *length_cm = 0; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_max_speed_get(struct sl_media_lgrp *media_lgrp, u32 *max_speed) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *max_speed = media_lgrp->cable_info->media_attr.max_speed; + break; + case CABLE_MEDIA_ATTR_STASHED: + *max_speed = media_lgrp->cable_info->stashed_media_attr.max_speed; + break; + default: + *max_speed = 0; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_serial_num_str_get(struct sl_media_lgrp *media_lgrp, char *serial_num_str) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + strscpy_pad(serial_num_str, + media_lgrp->cable_info->media_attr.serial_num_str, SL_MEDIA_SERIAL_NUM_SIZE); + break; + case CABLE_MEDIA_ATTR_STASHED: + strscpy_pad(serial_num_str, + media_lgrp->cable_info->stashed_media_attr.serial_num_str, SL_MEDIA_SERIAL_NUM_SIZE); + break; + default: + memset(serial_num_str, '0', (SL_MEDIA_SERIAL_NUM_SIZE - 1)); + serial_num_str[SL_MEDIA_SERIAL_NUM_SIZE - 1] = '\0'; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_hpe_pn_str_get(struct sl_media_lgrp *media_lgrp, char *hpe_pn_str) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + strscpy_pad(hpe_pn_str, + media_lgrp->cable_info->media_attr.hpe_pn_str, SL_MEDIA_HPE_PN_SIZE); + break; + case CABLE_MEDIA_ATTR_STASHED: + strscpy_pad(hpe_pn_str, + media_lgrp->cable_info->stashed_media_attr.hpe_pn_str, SL_MEDIA_HPE_PN_SIZE); + break; + default: + memset(hpe_pn_str, '0', SL_MEDIA_HPE_PN_SIZE - 2); + hpe_pn_str[SL_MEDIA_HPE_PN_SIZE - 1] = '\0'; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_jack_type_get(struct sl_media_lgrp *media_lgrp, u32 *jack_type) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *jack_type = media_lgrp->cable_info->media_attr.jack_type; + break; + case CABLE_MEDIA_ATTR_STASHED: + *jack_type = media_lgrp->cable_info->stashed_media_attr.jack_type; + break; + default: + *jack_type = SL_MEDIA_JACK_TYPE_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_jack_type_qsfp_density_get(struct sl_media_lgrp *media_lgrp, u32 *density) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *density = media_lgrp->cable_info->media_attr.jack_type_info.qsfp.density; + break; + case CABLE_MEDIA_ATTR_STASHED: + *density = media_lgrp->cable_info->stashed_media_attr.jack_type_info.qsfp.density; + break; + default: + *density = SL_MEDIA_QSFP_DENSITY_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_furcation_get(struct sl_media_lgrp *media_lgrp, u32 *furcation) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + *furcation = media_lgrp->cable_info->media_attr.furcation; + break; + case CABLE_MEDIA_ATTR_STASHED: + *furcation = media_lgrp->cable_info->stashed_media_attr.furcation; + break; + default: + *furcation = SL_MEDIA_FURCATION_UNKNOWN; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +bool sl_media_lgrp_is_cable_unsupported(struct sl_media_lgrp *media_lgrp) +{ + bool unsupported; + + spin_lock(&media_lgrp->media_jack->data_lock); + unsupported = media_lgrp->media_jack->is_cable_unsupported; + spin_unlock(&media_lgrp->media_jack->data_lock); + + return unsupported; +} + +int sl_media_lgrp_date_code_str_get(struct sl_media_lgrp *media_lgrp, char *date_code_str) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + strscpy_pad(date_code_str, + media_lgrp->cable_info->media_attr.date_code_str, SL_MEDIA_DATE_CODE_SIZE); + break; + case CABLE_MEDIA_ATTR_STASHED: + strscpy_pad(date_code_str, + media_lgrp->cable_info->stashed_media_attr.date_code_str, SL_MEDIA_DATE_CODE_SIZE); + break; + default: + memset(date_code_str, '0', SL_MEDIA_DATE_CODE_SIZE - 2); + date_code_str[SL_MEDIA_DATE_CODE_SIZE - 1] = '\0'; + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +int sl_media_lgrp_fw_ver_str_get(struct sl_media_lgrp *media_lgrp, u8 *fw_ver) +{ + spin_lock(&media_lgrp->media_jack->data_lock); + switch (media_lgrp->cable_info->real_cable_status) { + case CABLE_MEDIA_ATTR_ADDED: + memcpy(fw_ver, media_lgrp->cable_info->media_attr.fw_ver, SL_MEDIA_FIRMWARE_VERSION_SIZE); + break; + case CABLE_MEDIA_ATTR_STASHED: + memcpy(fw_ver, media_lgrp->cable_info->stashed_media_attr.fw_ver, SL_MEDIA_FIRMWARE_VERSION_SIZE); + break; + default: + memset(fw_ver, 0, SL_MEDIA_FIRMWARE_VERSION_SIZE); + } + spin_unlock(&media_lgrp->media_jack->data_lock); + + return 0; +} + +bool sl_media_lgrp_is_signal_status_supported(u8 ldev_num, u8 lgrp_num) +{ + struct sl_media_attr media_attr; + + sl_media_lgrp_media_attr_get(ldev_num, lgrp_num, &media_attr); + + return (media_attr.supported_flags_advertised[0] & SL_MEDIA_ADVERTISED_FLAGS_TX_LOS) || + (media_attr.supported_flags_advertised[0] & SL_MEDIA_ADVERTISED_FLAGS_TX_LOL) || + (media_attr.supported_flags_advertised[1] & SL_MEDIA_ADVERTISED_FLAGS_RX_LOS) || + (media_attr.supported_flags_advertised[1] & SL_MEDIA_ADVERTISED_FLAGS_RX_LOL); +}
--
2.56.0