162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright(c) 2015, 2016 Intel Corporation. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/firmware.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "hfi.h" 962306a36Sopenharmony_ci#include "efivar.h" 1062306a36Sopenharmony_ci#include "eprom.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define DEFAULT_PLATFORM_CONFIG_NAME "hfi1_platform.dat" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cistatic int validate_scratch_checksum(struct hfi1_devdata *dd) 1562306a36Sopenharmony_ci{ 1662306a36Sopenharmony_ci u64 checksum = 0, temp_scratch = 0; 1762306a36Sopenharmony_ci int i, j, version; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci temp_scratch = read_csr(dd, ASIC_CFG_SCRATCH); 2062306a36Sopenharmony_ci version = (temp_scratch & BITMAP_VERSION_SMASK) >> BITMAP_VERSION_SHIFT; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci /* Prevent power on default of all zeroes from passing checksum */ 2362306a36Sopenharmony_ci if (!version) { 2462306a36Sopenharmony_ci dd_dev_err(dd, "%s: Config bitmap uninitialized\n", __func__); 2562306a36Sopenharmony_ci dd_dev_err(dd, 2662306a36Sopenharmony_ci "%s: Please update your BIOS to support active channels\n", 2762306a36Sopenharmony_ci __func__); 2862306a36Sopenharmony_ci return 0; 2962306a36Sopenharmony_ci } 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci /* 3262306a36Sopenharmony_ci * ASIC scratch 0 only contains the checksum and bitmap version as 3362306a36Sopenharmony_ci * fields of interest, both of which are handled separately from the 3462306a36Sopenharmony_ci * loop below, so skip it 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ci checksum += version; 3762306a36Sopenharmony_ci for (i = 1; i < ASIC_NUM_SCRATCH; i++) { 3862306a36Sopenharmony_ci temp_scratch = read_csr(dd, ASIC_CFG_SCRATCH + (8 * i)); 3962306a36Sopenharmony_ci for (j = sizeof(u64); j != 0; j -= 2) { 4062306a36Sopenharmony_ci checksum += (temp_scratch & 0xFFFF); 4162306a36Sopenharmony_ci temp_scratch >>= 16; 4262306a36Sopenharmony_ci } 4362306a36Sopenharmony_ci } 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci while (checksum >> 16) 4662306a36Sopenharmony_ci checksum = (checksum & CHECKSUM_MASK) + (checksum >> 16); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci temp_scratch = read_csr(dd, ASIC_CFG_SCRATCH); 4962306a36Sopenharmony_ci temp_scratch &= CHECKSUM_SMASK; 5062306a36Sopenharmony_ci temp_scratch >>= CHECKSUM_SHIFT; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (checksum + temp_scratch == 0xFFFF) 5362306a36Sopenharmony_ci return 1; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci dd_dev_err(dd, "%s: Configuration bitmap corrupted\n", __func__); 5662306a36Sopenharmony_ci return 0; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void save_platform_config_fields(struct hfi1_devdata *dd) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct hfi1_pportdata *ppd = dd->pport; 6262306a36Sopenharmony_ci u64 temp_scratch = 0, temp_dest = 0; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci temp_scratch = read_csr(dd, ASIC_CFG_SCRATCH_1); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci temp_dest = temp_scratch & 6762306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_PORT_TYPE_SMASK : 6862306a36Sopenharmony_ci PORT0_PORT_TYPE_SMASK); 6962306a36Sopenharmony_ci ppd->port_type = temp_dest >> 7062306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_PORT_TYPE_SHIFT : 7162306a36Sopenharmony_ci PORT0_PORT_TYPE_SHIFT); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci temp_dest = temp_scratch & 7462306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_LOCAL_ATTEN_SMASK : 7562306a36Sopenharmony_ci PORT0_LOCAL_ATTEN_SMASK); 7662306a36Sopenharmony_ci ppd->local_atten = temp_dest >> 7762306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_LOCAL_ATTEN_SHIFT : 7862306a36Sopenharmony_ci PORT0_LOCAL_ATTEN_SHIFT); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci temp_dest = temp_scratch & 8162306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_REMOTE_ATTEN_SMASK : 8262306a36Sopenharmony_ci PORT0_REMOTE_ATTEN_SMASK); 8362306a36Sopenharmony_ci ppd->remote_atten = temp_dest >> 8462306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_REMOTE_ATTEN_SHIFT : 8562306a36Sopenharmony_ci PORT0_REMOTE_ATTEN_SHIFT); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci temp_dest = temp_scratch & 8862306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_DEFAULT_ATTEN_SMASK : 8962306a36Sopenharmony_ci PORT0_DEFAULT_ATTEN_SMASK); 9062306a36Sopenharmony_ci ppd->default_atten = temp_dest >> 9162306a36Sopenharmony_ci (dd->hfi1_id ? PORT1_DEFAULT_ATTEN_SHIFT : 9262306a36Sopenharmony_ci PORT0_DEFAULT_ATTEN_SHIFT); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci temp_scratch = read_csr(dd, dd->hfi1_id ? ASIC_CFG_SCRATCH_3 : 9562306a36Sopenharmony_ci ASIC_CFG_SCRATCH_2); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci ppd->tx_preset_eq = (temp_scratch & TX_EQ_SMASK) >> TX_EQ_SHIFT; 9862306a36Sopenharmony_ci ppd->tx_preset_noeq = (temp_scratch & TX_NO_EQ_SMASK) >> TX_NO_EQ_SHIFT; 9962306a36Sopenharmony_ci ppd->rx_preset = (temp_scratch & RX_SMASK) >> RX_SHIFT; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci ppd->max_power_class = (temp_scratch & QSFP_MAX_POWER_SMASK) >> 10262306a36Sopenharmony_ci QSFP_MAX_POWER_SHIFT; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci ppd->config_from_scratch = true; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_civoid get_platform_config(struct hfi1_devdata *dd) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci int ret = 0; 11062306a36Sopenharmony_ci u8 *temp_platform_config = NULL; 11162306a36Sopenharmony_ci u32 esize; 11262306a36Sopenharmony_ci const struct firmware *platform_config_file = NULL; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (is_integrated(dd)) { 11562306a36Sopenharmony_ci if (validate_scratch_checksum(dd)) { 11662306a36Sopenharmony_ci save_platform_config_fields(dd); 11762306a36Sopenharmony_ci return; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci } else { 12062306a36Sopenharmony_ci ret = eprom_read_platform_config(dd, 12162306a36Sopenharmony_ci (void **)&temp_platform_config, 12262306a36Sopenharmony_ci &esize); 12362306a36Sopenharmony_ci if (!ret) { 12462306a36Sopenharmony_ci /* success */ 12562306a36Sopenharmony_ci dd->platform_config.data = temp_platform_config; 12662306a36Sopenharmony_ci dd->platform_config.size = esize; 12762306a36Sopenharmony_ci return; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci dd_dev_err(dd, 13162306a36Sopenharmony_ci "%s: Failed to get platform config, falling back to sub-optimal default file\n", 13262306a36Sopenharmony_ci __func__); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = request_firmware(&platform_config_file, 13562306a36Sopenharmony_ci DEFAULT_PLATFORM_CONFIG_NAME, 13662306a36Sopenharmony_ci &dd->pcidev->dev); 13762306a36Sopenharmony_ci if (ret) { 13862306a36Sopenharmony_ci dd_dev_err(dd, 13962306a36Sopenharmony_ci "%s: No default platform config file found\n", 14062306a36Sopenharmony_ci __func__); 14162306a36Sopenharmony_ci return; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* 14562306a36Sopenharmony_ci * Allocate separate memory block to store data and free firmware 14662306a36Sopenharmony_ci * structure. This allows free_platform_config to treat EPROM and 14762306a36Sopenharmony_ci * fallback configs in the same manner. 14862306a36Sopenharmony_ci */ 14962306a36Sopenharmony_ci dd->platform_config.data = kmemdup(platform_config_file->data, 15062306a36Sopenharmony_ci platform_config_file->size, 15162306a36Sopenharmony_ci GFP_KERNEL); 15262306a36Sopenharmony_ci dd->platform_config.size = platform_config_file->size; 15362306a36Sopenharmony_ci release_firmware(platform_config_file); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_civoid free_platform_config(struct hfi1_devdata *dd) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci /* Release memory allocated for eprom or fallback file read. */ 15962306a36Sopenharmony_ci kfree(dd->platform_config.data); 16062306a36Sopenharmony_ci dd->platform_config.data = NULL; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_civoid get_port_type(struct hfi1_pportdata *ppd) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci int ret; 16662306a36Sopenharmony_ci u32 temp; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci ret = get_platform_config_field(ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 16962306a36Sopenharmony_ci PORT_TABLE_PORT_TYPE, &temp, 17062306a36Sopenharmony_ci 4); 17162306a36Sopenharmony_ci if (ret) { 17262306a36Sopenharmony_ci ppd->port_type = PORT_TYPE_UNKNOWN; 17362306a36Sopenharmony_ci return; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci ppd->port_type = temp; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ciint set_qsfp_tx(struct hfi1_pportdata *ppd, int on) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci u8 tx_ctrl_byte = on ? 0x0 : 0xF; 18162306a36Sopenharmony_ci int ret = 0; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci ret = qsfp_write(ppd, ppd->dd->hfi1_id, QSFP_TX_CTRL_BYTE_OFFS, 18462306a36Sopenharmony_ci &tx_ctrl_byte, 1); 18562306a36Sopenharmony_ci /* we expected 1, so consider 0 an error */ 18662306a36Sopenharmony_ci if (ret == 0) 18762306a36Sopenharmony_ci ret = -EIO; 18862306a36Sopenharmony_ci else if (ret == 1) 18962306a36Sopenharmony_ci ret = 0; 19062306a36Sopenharmony_ci return ret; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int qual_power(struct hfi1_pportdata *ppd) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci u32 cable_power_class = 0, power_class_max = 0; 19662306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 19762306a36Sopenharmony_ci int ret = 0; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci ret = get_platform_config_field( 20062306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_SYSTEM_TABLE, 0, 20162306a36Sopenharmony_ci SYSTEM_TABLE_QSFP_POWER_CLASS_MAX, &power_class_max, 4); 20262306a36Sopenharmony_ci if (ret) 20362306a36Sopenharmony_ci return ret; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci cable_power_class = get_qsfp_power_class(cache[QSFP_MOD_PWR_OFFS]); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (cable_power_class > power_class_max) 20862306a36Sopenharmony_ci ppd->offline_disabled_reason = 20962306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_POWER_POLICY); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (ppd->offline_disabled_reason == 21262306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_POWER_POLICY)) { 21362306a36Sopenharmony_ci dd_dev_err( 21462306a36Sopenharmony_ci ppd->dd, 21562306a36Sopenharmony_ci "%s: Port disabled due to system power restrictions\n", 21662306a36Sopenharmony_ci __func__); 21762306a36Sopenharmony_ci ret = -EPERM; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci return ret; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int qual_bitrate(struct hfi1_pportdata *ppd) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci u16 lss = ppd->link_speed_supported, lse = ppd->link_speed_enabled; 22562306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if ((lss & OPA_LINK_SPEED_25G) && (lse & OPA_LINK_SPEED_25G) && 22862306a36Sopenharmony_ci cache[QSFP_NOM_BIT_RATE_250_OFFS] < 0x64) 22962306a36Sopenharmony_ci ppd->offline_disabled_reason = 23062306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_LINKSPEED_POLICY); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if ((lss & OPA_LINK_SPEED_12_5G) && (lse & OPA_LINK_SPEED_12_5G) && 23362306a36Sopenharmony_ci cache[QSFP_NOM_BIT_RATE_100_OFFS] < 0x7D) 23462306a36Sopenharmony_ci ppd->offline_disabled_reason = 23562306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_LINKSPEED_POLICY); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (ppd->offline_disabled_reason == 23862306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_LINKSPEED_POLICY)) { 23962306a36Sopenharmony_ci dd_dev_err( 24062306a36Sopenharmony_ci ppd->dd, 24162306a36Sopenharmony_ci "%s: Cable failed bitrate check, disabling port\n", 24262306a36Sopenharmony_ci __func__); 24362306a36Sopenharmony_ci return -EPERM; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci return 0; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic int set_qsfp_high_power(struct hfi1_pportdata *ppd) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci u8 cable_power_class = 0, power_ctrl_byte = 0; 25162306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 25262306a36Sopenharmony_ci int ret; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci cable_power_class = get_qsfp_power_class(cache[QSFP_MOD_PWR_OFFS]); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (cable_power_class > QSFP_POWER_CLASS_1) { 25762306a36Sopenharmony_ci power_ctrl_byte = cache[QSFP_PWR_CTRL_BYTE_OFFS]; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci power_ctrl_byte |= 1; 26062306a36Sopenharmony_ci power_ctrl_byte &= ~(0x2); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ret = qsfp_write(ppd, ppd->dd->hfi1_id, 26362306a36Sopenharmony_ci QSFP_PWR_CTRL_BYTE_OFFS, 26462306a36Sopenharmony_ci &power_ctrl_byte, 1); 26562306a36Sopenharmony_ci if (ret != 1) 26662306a36Sopenharmony_ci return -EIO; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (cable_power_class > QSFP_POWER_CLASS_4) { 26962306a36Sopenharmony_ci power_ctrl_byte |= (1 << 2); 27062306a36Sopenharmony_ci ret = qsfp_write(ppd, ppd->dd->hfi1_id, 27162306a36Sopenharmony_ci QSFP_PWR_CTRL_BYTE_OFFS, 27262306a36Sopenharmony_ci &power_ctrl_byte, 1); 27362306a36Sopenharmony_ci if (ret != 1) 27462306a36Sopenharmony_ci return -EIO; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci /* SFF 8679 rev 1.7 LPMode Deassert time */ 27862306a36Sopenharmony_ci msleep(300); 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci return 0; 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic void apply_rx_cdr(struct hfi1_pportdata *ppd, 28462306a36Sopenharmony_ci u32 rx_preset_index, 28562306a36Sopenharmony_ci u8 *cdr_ctrl_byte) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci u32 rx_preset; 28862306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 28962306a36Sopenharmony_ci int cable_power_class; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (!((cache[QSFP_MOD_PWR_OFFS] & 0x4) && 29262306a36Sopenharmony_ci (cache[QSFP_CDR_INFO_OFFS] & 0x40))) 29362306a36Sopenharmony_ci return; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci /* RX CDR present, bypass supported */ 29662306a36Sopenharmony_ci cable_power_class = get_qsfp_power_class(cache[QSFP_MOD_PWR_OFFS]); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (cable_power_class <= QSFP_POWER_CLASS_3) { 29962306a36Sopenharmony_ci /* Power class <= 3, ignore config & turn RX CDR on */ 30062306a36Sopenharmony_ci *cdr_ctrl_byte |= 0xF; 30162306a36Sopenharmony_ci return; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci get_platform_config_field( 30562306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_RX_PRESET_TABLE, 30662306a36Sopenharmony_ci rx_preset_index, RX_PRESET_TABLE_QSFP_RX_CDR_APPLY, 30762306a36Sopenharmony_ci &rx_preset, 4); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (!rx_preset) { 31062306a36Sopenharmony_ci dd_dev_info( 31162306a36Sopenharmony_ci ppd->dd, 31262306a36Sopenharmony_ci "%s: RX_CDR_APPLY is set to disabled\n", 31362306a36Sopenharmony_ci __func__); 31462306a36Sopenharmony_ci return; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci get_platform_config_field( 31762306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_RX_PRESET_TABLE, 31862306a36Sopenharmony_ci rx_preset_index, RX_PRESET_TABLE_QSFP_RX_CDR, 31962306a36Sopenharmony_ci &rx_preset, 4); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci /* Expand cdr setting to all 4 lanes */ 32262306a36Sopenharmony_ci rx_preset = (rx_preset | (rx_preset << 1) | 32362306a36Sopenharmony_ci (rx_preset << 2) | (rx_preset << 3)); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (rx_preset) { 32662306a36Sopenharmony_ci *cdr_ctrl_byte |= rx_preset; 32762306a36Sopenharmony_ci } else { 32862306a36Sopenharmony_ci *cdr_ctrl_byte &= rx_preset; 32962306a36Sopenharmony_ci /* Preserve current TX CDR status */ 33062306a36Sopenharmony_ci *cdr_ctrl_byte |= (cache[QSFP_CDR_CTRL_BYTE_OFFS] & 0xF0); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cistatic void apply_tx_cdr(struct hfi1_pportdata *ppd, 33562306a36Sopenharmony_ci u32 tx_preset_index, 33662306a36Sopenharmony_ci u8 *cdr_ctrl_byte) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci u32 tx_preset; 33962306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 34062306a36Sopenharmony_ci int cable_power_class; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (!((cache[QSFP_MOD_PWR_OFFS] & 0x8) && 34362306a36Sopenharmony_ci (cache[QSFP_CDR_INFO_OFFS] & 0x80))) 34462306a36Sopenharmony_ci return; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci /* TX CDR present, bypass supported */ 34762306a36Sopenharmony_ci cable_power_class = get_qsfp_power_class(cache[QSFP_MOD_PWR_OFFS]); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci if (cable_power_class <= QSFP_POWER_CLASS_3) { 35062306a36Sopenharmony_ci /* Power class <= 3, ignore config & turn TX CDR on */ 35162306a36Sopenharmony_ci *cdr_ctrl_byte |= 0xF0; 35262306a36Sopenharmony_ci return; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci get_platform_config_field( 35662306a36Sopenharmony_ci ppd->dd, 35762306a36Sopenharmony_ci PLATFORM_CONFIG_TX_PRESET_TABLE, tx_preset_index, 35862306a36Sopenharmony_ci TX_PRESET_TABLE_QSFP_TX_CDR_APPLY, &tx_preset, 4); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (!tx_preset) { 36162306a36Sopenharmony_ci dd_dev_info( 36262306a36Sopenharmony_ci ppd->dd, 36362306a36Sopenharmony_ci "%s: TX_CDR_APPLY is set to disabled\n", 36462306a36Sopenharmony_ci __func__); 36562306a36Sopenharmony_ci return; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci get_platform_config_field( 36862306a36Sopenharmony_ci ppd->dd, 36962306a36Sopenharmony_ci PLATFORM_CONFIG_TX_PRESET_TABLE, 37062306a36Sopenharmony_ci tx_preset_index, 37162306a36Sopenharmony_ci TX_PRESET_TABLE_QSFP_TX_CDR, &tx_preset, 4); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* Expand cdr setting to all 4 lanes */ 37462306a36Sopenharmony_ci tx_preset = (tx_preset | (tx_preset << 1) | 37562306a36Sopenharmony_ci (tx_preset << 2) | (tx_preset << 3)); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci if (tx_preset) 37862306a36Sopenharmony_ci *cdr_ctrl_byte |= (tx_preset << 4); 37962306a36Sopenharmony_ci else 38062306a36Sopenharmony_ci /* Preserve current/determined RX CDR status */ 38162306a36Sopenharmony_ci *cdr_ctrl_byte &= ((tx_preset << 4) | 0xF); 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic void apply_cdr_settings( 38562306a36Sopenharmony_ci struct hfi1_pportdata *ppd, u32 rx_preset_index, 38662306a36Sopenharmony_ci u32 tx_preset_index) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 38962306a36Sopenharmony_ci u8 cdr_ctrl_byte = cache[QSFP_CDR_CTRL_BYTE_OFFS]; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci apply_rx_cdr(ppd, rx_preset_index, &cdr_ctrl_byte); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci apply_tx_cdr(ppd, tx_preset_index, &cdr_ctrl_byte); 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, QSFP_CDR_CTRL_BYTE_OFFS, 39662306a36Sopenharmony_ci &cdr_ctrl_byte, 1); 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic void apply_tx_eq_auto(struct hfi1_pportdata *ppd) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 40262306a36Sopenharmony_ci u8 tx_eq; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci if (!(cache[QSFP_EQ_INFO_OFFS] & 0x8)) 40562306a36Sopenharmony_ci return; 40662306a36Sopenharmony_ci /* Disable adaptive TX EQ if present */ 40762306a36Sopenharmony_ci tx_eq = cache[(128 * 3) + 241]; 40862306a36Sopenharmony_ci tx_eq &= 0xF0; 40962306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 241, &tx_eq, 1); 41062306a36Sopenharmony_ci} 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_cistatic void apply_tx_eq_prog(struct hfi1_pportdata *ppd, u32 tx_preset_index) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 41562306a36Sopenharmony_ci u32 tx_preset; 41662306a36Sopenharmony_ci u8 tx_eq; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci if (!(cache[QSFP_EQ_INFO_OFFS] & 0x4)) 41962306a36Sopenharmony_ci return; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci get_platform_config_field( 42262306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_TX_PRESET_TABLE, 42362306a36Sopenharmony_ci tx_preset_index, TX_PRESET_TABLE_QSFP_TX_EQ_APPLY, 42462306a36Sopenharmony_ci &tx_preset, 4); 42562306a36Sopenharmony_ci if (!tx_preset) { 42662306a36Sopenharmony_ci dd_dev_info( 42762306a36Sopenharmony_ci ppd->dd, 42862306a36Sopenharmony_ci "%s: TX_EQ_APPLY is set to disabled\n", 42962306a36Sopenharmony_ci __func__); 43062306a36Sopenharmony_ci return; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci get_platform_config_field( 43362306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_TX_PRESET_TABLE, 43462306a36Sopenharmony_ci tx_preset_index, TX_PRESET_TABLE_QSFP_TX_EQ, 43562306a36Sopenharmony_ci &tx_preset, 4); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci if (((cache[(128 * 3) + 224] & 0xF0) >> 4) < tx_preset) { 43862306a36Sopenharmony_ci dd_dev_info( 43962306a36Sopenharmony_ci ppd->dd, 44062306a36Sopenharmony_ci "%s: TX EQ %x unsupported\n", 44162306a36Sopenharmony_ci __func__, tx_preset); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci dd_dev_info( 44462306a36Sopenharmony_ci ppd->dd, 44562306a36Sopenharmony_ci "%s: Applying EQ %x\n", 44662306a36Sopenharmony_ci __func__, cache[608] & 0xF0); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci tx_preset = (cache[608] & 0xF0) >> 4; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci tx_eq = tx_preset | (tx_preset << 4); 45262306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 234, &tx_eq, 1); 45362306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 235, &tx_eq, 1); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic void apply_rx_eq_emp(struct hfi1_pportdata *ppd, u32 rx_preset_index) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci u32 rx_preset; 45962306a36Sopenharmony_ci u8 rx_eq, *cache = ppd->qsfp_info.cache; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci if (!(cache[QSFP_EQ_INFO_OFFS] & 0x2)) 46262306a36Sopenharmony_ci return; 46362306a36Sopenharmony_ci get_platform_config_field( 46462306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_RX_PRESET_TABLE, 46562306a36Sopenharmony_ci rx_preset_index, RX_PRESET_TABLE_QSFP_RX_EMP_APPLY, 46662306a36Sopenharmony_ci &rx_preset, 4); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci if (!rx_preset) { 46962306a36Sopenharmony_ci dd_dev_info( 47062306a36Sopenharmony_ci ppd->dd, 47162306a36Sopenharmony_ci "%s: RX_EMP_APPLY is set to disabled\n", 47262306a36Sopenharmony_ci __func__); 47362306a36Sopenharmony_ci return; 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci get_platform_config_field( 47662306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_RX_PRESET_TABLE, 47762306a36Sopenharmony_ci rx_preset_index, RX_PRESET_TABLE_QSFP_RX_EMP, 47862306a36Sopenharmony_ci &rx_preset, 4); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci if ((cache[(128 * 3) + 224] & 0xF) < rx_preset) { 48162306a36Sopenharmony_ci dd_dev_info( 48262306a36Sopenharmony_ci ppd->dd, 48362306a36Sopenharmony_ci "%s: Requested RX EMP %x\n", 48462306a36Sopenharmony_ci __func__, rx_preset); 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci dd_dev_info( 48762306a36Sopenharmony_ci ppd->dd, 48862306a36Sopenharmony_ci "%s: Applying supported EMP %x\n", 48962306a36Sopenharmony_ci __func__, cache[608] & 0xF); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci rx_preset = cache[608] & 0xF; 49262306a36Sopenharmony_ci } 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci rx_eq = rx_preset | (rx_preset << 4); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 236, &rx_eq, 1); 49762306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 237, &rx_eq, 1); 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic void apply_eq_settings(struct hfi1_pportdata *ppd, 50162306a36Sopenharmony_ci u32 rx_preset_index, u32 tx_preset_index) 50262306a36Sopenharmony_ci{ 50362306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci /* no point going on w/o a page 3 */ 50662306a36Sopenharmony_ci if (cache[2] & 4) { 50762306a36Sopenharmony_ci dd_dev_info(ppd->dd, 50862306a36Sopenharmony_ci "%s: Upper page 03 not present\n", 50962306a36Sopenharmony_ci __func__); 51062306a36Sopenharmony_ci return; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci apply_tx_eq_auto(ppd); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci apply_tx_eq_prog(ppd, tx_preset_index); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci apply_rx_eq_emp(ppd, rx_preset_index); 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_cistatic void apply_rx_amplitude_settings( 52162306a36Sopenharmony_ci struct hfi1_pportdata *ppd, u32 rx_preset_index, 52262306a36Sopenharmony_ci u32 tx_preset_index) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci u32 rx_preset; 52562306a36Sopenharmony_ci u8 rx_amp = 0, i = 0, preferred = 0, *cache = ppd->qsfp_info.cache; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci /* no point going on w/o a page 3 */ 52862306a36Sopenharmony_ci if (cache[2] & 4) { 52962306a36Sopenharmony_ci dd_dev_info(ppd->dd, 53062306a36Sopenharmony_ci "%s: Upper page 03 not present\n", 53162306a36Sopenharmony_ci __func__); 53262306a36Sopenharmony_ci return; 53362306a36Sopenharmony_ci } 53462306a36Sopenharmony_ci if (!(cache[QSFP_EQ_INFO_OFFS] & 0x1)) { 53562306a36Sopenharmony_ci dd_dev_info(ppd->dd, 53662306a36Sopenharmony_ci "%s: RX_AMP_APPLY is set to disabled\n", 53762306a36Sopenharmony_ci __func__); 53862306a36Sopenharmony_ci return; 53962306a36Sopenharmony_ci } 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci get_platform_config_field(ppd->dd, 54262306a36Sopenharmony_ci PLATFORM_CONFIG_RX_PRESET_TABLE, 54362306a36Sopenharmony_ci rx_preset_index, 54462306a36Sopenharmony_ci RX_PRESET_TABLE_QSFP_RX_AMP_APPLY, 54562306a36Sopenharmony_ci &rx_preset, 4); 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci if (!rx_preset) { 54862306a36Sopenharmony_ci dd_dev_info(ppd->dd, 54962306a36Sopenharmony_ci "%s: RX_AMP_APPLY is set to disabled\n", 55062306a36Sopenharmony_ci __func__); 55162306a36Sopenharmony_ci return; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci get_platform_config_field(ppd->dd, 55462306a36Sopenharmony_ci PLATFORM_CONFIG_RX_PRESET_TABLE, 55562306a36Sopenharmony_ci rx_preset_index, 55662306a36Sopenharmony_ci RX_PRESET_TABLE_QSFP_RX_AMP, 55762306a36Sopenharmony_ci &rx_preset, 4); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci dd_dev_info(ppd->dd, 56062306a36Sopenharmony_ci "%s: Requested RX AMP %x\n", 56162306a36Sopenharmony_ci __func__, 56262306a36Sopenharmony_ci rx_preset); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 56562306a36Sopenharmony_ci if (cache[(128 * 3) + 225] & (1 << i)) { 56662306a36Sopenharmony_ci preferred = i; 56762306a36Sopenharmony_ci if (preferred == rx_preset) 56862306a36Sopenharmony_ci break; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci /* 57362306a36Sopenharmony_ci * Verify that preferred RX amplitude is not just a 57462306a36Sopenharmony_ci * fall through of the default 57562306a36Sopenharmony_ci */ 57662306a36Sopenharmony_ci if (!preferred && !(cache[(128 * 3) + 225] & 0x1)) { 57762306a36Sopenharmony_ci dd_dev_info(ppd->dd, "No supported RX AMP, not applying\n"); 57862306a36Sopenharmony_ci return; 57962306a36Sopenharmony_ci } 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci dd_dev_info(ppd->dd, 58262306a36Sopenharmony_ci "%s: Applying RX AMP %x\n", __func__, preferred); 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci rx_amp = preferred | (preferred << 4); 58562306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 238, &rx_amp, 1); 58662306a36Sopenharmony_ci qsfp_write(ppd, ppd->dd->hfi1_id, (256 * 3) + 239, &rx_amp, 1); 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci#define OPA_INVALID_INDEX 0xFFF 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic void apply_tx_lanes(struct hfi1_pportdata *ppd, u8 field_id, 59262306a36Sopenharmony_ci u32 config_data, const char *message) 59362306a36Sopenharmony_ci{ 59462306a36Sopenharmony_ci u8 i; 59562306a36Sopenharmony_ci int ret; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 59862306a36Sopenharmony_ci ret = load_8051_config(ppd->dd, field_id, i, config_data); 59962306a36Sopenharmony_ci if (ret != HCMD_SUCCESS) { 60062306a36Sopenharmony_ci dd_dev_err( 60162306a36Sopenharmony_ci ppd->dd, 60262306a36Sopenharmony_ci "%s: %s for lane %u failed\n", 60362306a36Sopenharmony_ci message, __func__, i); 60462306a36Sopenharmony_ci } 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci/* 60962306a36Sopenharmony_ci * Return a special SerDes setting for low power AOC cables. The power class 61062306a36Sopenharmony_ci * threshold and setting being used were all found by empirical testing. 61162306a36Sopenharmony_ci * 61262306a36Sopenharmony_ci * Summary of the logic: 61362306a36Sopenharmony_ci * 61462306a36Sopenharmony_ci * if (QSFP and QSFP_TYPE == AOC and QSFP_POWER_CLASS < 4) 61562306a36Sopenharmony_ci * return 0xe 61662306a36Sopenharmony_ci * return 0; // leave at default 61762306a36Sopenharmony_ci */ 61862306a36Sopenharmony_cistatic u8 aoc_low_power_setting(struct hfi1_pportdata *ppd) 61962306a36Sopenharmony_ci{ 62062306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 62162306a36Sopenharmony_ci int power_class; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci /* QSFP only */ 62462306a36Sopenharmony_ci if (ppd->port_type != PORT_TYPE_QSFP) 62562306a36Sopenharmony_ci return 0; /* leave at default */ 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci /* active optical cables only */ 62862306a36Sopenharmony_ci switch ((cache[QSFP_MOD_TECH_OFFS] & 0xF0) >> 4) { 62962306a36Sopenharmony_ci case 0x0 ... 0x9: fallthrough; 63062306a36Sopenharmony_ci case 0xC: fallthrough; 63162306a36Sopenharmony_ci case 0xE: 63262306a36Sopenharmony_ci /* active AOC */ 63362306a36Sopenharmony_ci power_class = get_qsfp_power_class(cache[QSFP_MOD_PWR_OFFS]); 63462306a36Sopenharmony_ci if (power_class < QSFP_POWER_CLASS_4) 63562306a36Sopenharmony_ci return 0xe; 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci return 0; /* leave at default */ 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic void apply_tunings( 64162306a36Sopenharmony_ci struct hfi1_pportdata *ppd, u32 tx_preset_index, 64262306a36Sopenharmony_ci u8 tuning_method, u32 total_atten, u8 limiting_active) 64362306a36Sopenharmony_ci{ 64462306a36Sopenharmony_ci int ret = 0; 64562306a36Sopenharmony_ci u32 config_data = 0, tx_preset = 0; 64662306a36Sopenharmony_ci u8 precur = 0, attn = 0, postcur = 0, external_device_config = 0; 64762306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci /* Pass tuning method to 8051 */ 65062306a36Sopenharmony_ci read_8051_config(ppd->dd, LINK_TUNING_PARAMETERS, GENERAL_CONFIG, 65162306a36Sopenharmony_ci &config_data); 65262306a36Sopenharmony_ci config_data &= ~(0xff << TUNING_METHOD_SHIFT); 65362306a36Sopenharmony_ci config_data |= ((u32)tuning_method << TUNING_METHOD_SHIFT); 65462306a36Sopenharmony_ci ret = load_8051_config(ppd->dd, LINK_TUNING_PARAMETERS, GENERAL_CONFIG, 65562306a36Sopenharmony_ci config_data); 65662306a36Sopenharmony_ci if (ret != HCMD_SUCCESS) 65762306a36Sopenharmony_ci dd_dev_err(ppd->dd, "%s: Failed to set tuning method\n", 65862306a36Sopenharmony_ci __func__); 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci /* Set same channel loss for both TX and RX */ 66162306a36Sopenharmony_ci config_data = 0 | (total_atten << 16) | (total_atten << 24); 66262306a36Sopenharmony_ci apply_tx_lanes(ppd, CHANNEL_LOSS_SETTINGS, config_data, 66362306a36Sopenharmony_ci "Setting channel loss"); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci /* Inform 8051 of cable capabilities */ 66662306a36Sopenharmony_ci if (ppd->qsfp_info.cache_valid) { 66762306a36Sopenharmony_ci external_device_config = 66862306a36Sopenharmony_ci ((cache[QSFP_MOD_PWR_OFFS] & 0x4) << 3) | 66962306a36Sopenharmony_ci ((cache[QSFP_MOD_PWR_OFFS] & 0x8) << 2) | 67062306a36Sopenharmony_ci ((cache[QSFP_EQ_INFO_OFFS] & 0x2) << 1) | 67162306a36Sopenharmony_ci (cache[QSFP_EQ_INFO_OFFS] & 0x4); 67262306a36Sopenharmony_ci ret = read_8051_config(ppd->dd, DC_HOST_COMM_SETTINGS, 67362306a36Sopenharmony_ci GENERAL_CONFIG, &config_data); 67462306a36Sopenharmony_ci /* Clear, then set the external device config field */ 67562306a36Sopenharmony_ci config_data &= ~(u32)0xFF; 67662306a36Sopenharmony_ci config_data |= external_device_config; 67762306a36Sopenharmony_ci ret = load_8051_config(ppd->dd, DC_HOST_COMM_SETTINGS, 67862306a36Sopenharmony_ci GENERAL_CONFIG, config_data); 67962306a36Sopenharmony_ci if (ret != HCMD_SUCCESS) 68062306a36Sopenharmony_ci dd_dev_err(ppd->dd, 68162306a36Sopenharmony_ci "%s: Failed set ext device config params\n", 68262306a36Sopenharmony_ci __func__); 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci if (tx_preset_index == OPA_INVALID_INDEX) { 68662306a36Sopenharmony_ci if (ppd->port_type == PORT_TYPE_QSFP && limiting_active) 68762306a36Sopenharmony_ci dd_dev_err(ppd->dd, "%s: Invalid Tx preset index\n", 68862306a36Sopenharmony_ci __func__); 68962306a36Sopenharmony_ci return; 69062306a36Sopenharmony_ci } 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci /* Following for limiting active channels only */ 69362306a36Sopenharmony_ci get_platform_config_field( 69462306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_TX_PRESET_TABLE, tx_preset_index, 69562306a36Sopenharmony_ci TX_PRESET_TABLE_PRECUR, &tx_preset, 4); 69662306a36Sopenharmony_ci precur = tx_preset; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci get_platform_config_field( 69962306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_TX_PRESET_TABLE, 70062306a36Sopenharmony_ci tx_preset_index, TX_PRESET_TABLE_ATTN, &tx_preset, 4); 70162306a36Sopenharmony_ci attn = tx_preset; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci get_platform_config_field( 70462306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_TX_PRESET_TABLE, 70562306a36Sopenharmony_ci tx_preset_index, TX_PRESET_TABLE_POSTCUR, &tx_preset, 4); 70662306a36Sopenharmony_ci postcur = tx_preset; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci /* 70962306a36Sopenharmony_ci * NOTES: 71062306a36Sopenharmony_ci * o The aoc_low_power_setting is applied to all lanes even 71162306a36Sopenharmony_ci * though only lane 0's value is examined by the firmware. 71262306a36Sopenharmony_ci * o A lingering low power setting after a cable swap does 71362306a36Sopenharmony_ci * not occur. On cable unplug the 8051 is reset and 71462306a36Sopenharmony_ci * restarted on cable insert. This resets all settings to 71562306a36Sopenharmony_ci * their default, erasing any previous low power setting. 71662306a36Sopenharmony_ci */ 71762306a36Sopenharmony_ci config_data = precur | (attn << 8) | (postcur << 16) | 71862306a36Sopenharmony_ci (aoc_low_power_setting(ppd) << 24); 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci apply_tx_lanes(ppd, TX_EQ_SETTINGS, config_data, 72162306a36Sopenharmony_ci "Applying TX settings"); 72262306a36Sopenharmony_ci} 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci/* Must be holding the QSFP i2c resource */ 72562306a36Sopenharmony_cistatic int tune_active_qsfp(struct hfi1_pportdata *ppd, u32 *ptr_tx_preset, 72662306a36Sopenharmony_ci u32 *ptr_rx_preset, u32 *ptr_total_atten) 72762306a36Sopenharmony_ci{ 72862306a36Sopenharmony_ci int ret; 72962306a36Sopenharmony_ci u16 lss = ppd->link_speed_supported, lse = ppd->link_speed_enabled; 73062306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci ppd->qsfp_info.limiting_active = 1; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci ret = set_qsfp_tx(ppd, 0); 73562306a36Sopenharmony_ci if (ret) 73662306a36Sopenharmony_ci return ret; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci ret = qual_power(ppd); 73962306a36Sopenharmony_ci if (ret) 74062306a36Sopenharmony_ci return ret; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci ret = qual_bitrate(ppd); 74362306a36Sopenharmony_ci if (ret) 74462306a36Sopenharmony_ci return ret; 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci /* 74762306a36Sopenharmony_ci * We'll change the QSFP memory contents from here on out, thus we set a 74862306a36Sopenharmony_ci * flag here to remind ourselves to reset the QSFP module. This prevents 74962306a36Sopenharmony_ci * reuse of stale settings established in our previous pass through. 75062306a36Sopenharmony_ci */ 75162306a36Sopenharmony_ci if (ppd->qsfp_info.reset_needed) { 75262306a36Sopenharmony_ci ret = reset_qsfp(ppd); 75362306a36Sopenharmony_ci if (ret) 75462306a36Sopenharmony_ci return ret; 75562306a36Sopenharmony_ci refresh_qsfp_cache(ppd, &ppd->qsfp_info); 75662306a36Sopenharmony_ci } else { 75762306a36Sopenharmony_ci ppd->qsfp_info.reset_needed = 1; 75862306a36Sopenharmony_ci } 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci ret = set_qsfp_high_power(ppd); 76162306a36Sopenharmony_ci if (ret) 76262306a36Sopenharmony_ci return ret; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci if (cache[QSFP_EQ_INFO_OFFS] & 0x4) { 76562306a36Sopenharmony_ci ret = get_platform_config_field( 76662306a36Sopenharmony_ci ppd->dd, 76762306a36Sopenharmony_ci PLATFORM_CONFIG_PORT_TABLE, 0, 76862306a36Sopenharmony_ci PORT_TABLE_TX_PRESET_IDX_ACTIVE_EQ, 76962306a36Sopenharmony_ci ptr_tx_preset, 4); 77062306a36Sopenharmony_ci if (ret) { 77162306a36Sopenharmony_ci *ptr_tx_preset = OPA_INVALID_INDEX; 77262306a36Sopenharmony_ci return ret; 77362306a36Sopenharmony_ci } 77462306a36Sopenharmony_ci } else { 77562306a36Sopenharmony_ci ret = get_platform_config_field( 77662306a36Sopenharmony_ci ppd->dd, 77762306a36Sopenharmony_ci PLATFORM_CONFIG_PORT_TABLE, 0, 77862306a36Sopenharmony_ci PORT_TABLE_TX_PRESET_IDX_ACTIVE_NO_EQ, 77962306a36Sopenharmony_ci ptr_tx_preset, 4); 78062306a36Sopenharmony_ci if (ret) { 78162306a36Sopenharmony_ci *ptr_tx_preset = OPA_INVALID_INDEX; 78262306a36Sopenharmony_ci return ret; 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci ret = get_platform_config_field( 78762306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 78862306a36Sopenharmony_ci PORT_TABLE_RX_PRESET_IDX, ptr_rx_preset, 4); 78962306a36Sopenharmony_ci if (ret) { 79062306a36Sopenharmony_ci *ptr_rx_preset = OPA_INVALID_INDEX; 79162306a36Sopenharmony_ci return ret; 79262306a36Sopenharmony_ci } 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci if ((lss & OPA_LINK_SPEED_25G) && (lse & OPA_LINK_SPEED_25G)) 79562306a36Sopenharmony_ci get_platform_config_field( 79662306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 79762306a36Sopenharmony_ci PORT_TABLE_LOCAL_ATTEN_25G, ptr_total_atten, 4); 79862306a36Sopenharmony_ci else if ((lss & OPA_LINK_SPEED_12_5G) && (lse & OPA_LINK_SPEED_12_5G)) 79962306a36Sopenharmony_ci get_platform_config_field( 80062306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 80162306a36Sopenharmony_ci PORT_TABLE_LOCAL_ATTEN_12G, ptr_total_atten, 4); 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci apply_cdr_settings(ppd, *ptr_rx_preset, *ptr_tx_preset); 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci apply_eq_settings(ppd, *ptr_rx_preset, *ptr_tx_preset); 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci apply_rx_amplitude_settings(ppd, *ptr_rx_preset, *ptr_tx_preset); 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci ret = set_qsfp_tx(ppd, 1); 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci return ret; 81262306a36Sopenharmony_ci} 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_cistatic int tune_qsfp(struct hfi1_pportdata *ppd, 81562306a36Sopenharmony_ci u32 *ptr_tx_preset, u32 *ptr_rx_preset, 81662306a36Sopenharmony_ci u8 *ptr_tuning_method, u32 *ptr_total_atten) 81762306a36Sopenharmony_ci{ 81862306a36Sopenharmony_ci u32 cable_atten = 0, remote_atten = 0, platform_atten = 0; 81962306a36Sopenharmony_ci u16 lss = ppd->link_speed_supported, lse = ppd->link_speed_enabled; 82062306a36Sopenharmony_ci int ret = 0; 82162306a36Sopenharmony_ci u8 *cache = ppd->qsfp_info.cache; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci switch ((cache[QSFP_MOD_TECH_OFFS] & 0xF0) >> 4) { 82462306a36Sopenharmony_ci case 0xA ... 0xB: 82562306a36Sopenharmony_ci ret = get_platform_config_field( 82662306a36Sopenharmony_ci ppd->dd, 82762306a36Sopenharmony_ci PLATFORM_CONFIG_PORT_TABLE, 0, 82862306a36Sopenharmony_ci PORT_TABLE_LOCAL_ATTEN_25G, 82962306a36Sopenharmony_ci &platform_atten, 4); 83062306a36Sopenharmony_ci if (ret) 83162306a36Sopenharmony_ci return ret; 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci if ((lss & OPA_LINK_SPEED_25G) && (lse & OPA_LINK_SPEED_25G)) 83462306a36Sopenharmony_ci cable_atten = cache[QSFP_CU_ATTEN_12G_OFFS]; 83562306a36Sopenharmony_ci else if ((lss & OPA_LINK_SPEED_12_5G) && 83662306a36Sopenharmony_ci (lse & OPA_LINK_SPEED_12_5G)) 83762306a36Sopenharmony_ci cable_atten = cache[QSFP_CU_ATTEN_7G_OFFS]; 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci /* Fallback to configured attenuation if cable memory is bad */ 84062306a36Sopenharmony_ci if (cable_atten == 0 || cable_atten > 36) { 84162306a36Sopenharmony_ci ret = get_platform_config_field( 84262306a36Sopenharmony_ci ppd->dd, 84362306a36Sopenharmony_ci PLATFORM_CONFIG_SYSTEM_TABLE, 0, 84462306a36Sopenharmony_ci SYSTEM_TABLE_QSFP_ATTENUATION_DEFAULT_25G, 84562306a36Sopenharmony_ci &cable_atten, 4); 84662306a36Sopenharmony_ci if (ret) 84762306a36Sopenharmony_ci return ret; 84862306a36Sopenharmony_ci } 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci ret = get_platform_config_field( 85162306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 85262306a36Sopenharmony_ci PORT_TABLE_REMOTE_ATTEN_25G, &remote_atten, 4); 85362306a36Sopenharmony_ci if (ret) 85462306a36Sopenharmony_ci return ret; 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci *ptr_total_atten = platform_atten + cable_atten + remote_atten; 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci *ptr_tuning_method = OPA_PASSIVE_TUNING; 85962306a36Sopenharmony_ci break; 86062306a36Sopenharmony_ci case 0x0 ... 0x9: fallthrough; 86162306a36Sopenharmony_ci case 0xC: fallthrough; 86262306a36Sopenharmony_ci case 0xE: 86362306a36Sopenharmony_ci ret = tune_active_qsfp(ppd, ptr_tx_preset, ptr_rx_preset, 86462306a36Sopenharmony_ci ptr_total_atten); 86562306a36Sopenharmony_ci if (ret) 86662306a36Sopenharmony_ci return ret; 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci *ptr_tuning_method = OPA_ACTIVE_TUNING; 86962306a36Sopenharmony_ci break; 87062306a36Sopenharmony_ci case 0xD: fallthrough; 87162306a36Sopenharmony_ci case 0xF: 87262306a36Sopenharmony_ci default: 87362306a36Sopenharmony_ci dd_dev_warn(ppd->dd, "%s: Unknown/unsupported cable\n", 87462306a36Sopenharmony_ci __func__); 87562306a36Sopenharmony_ci break; 87662306a36Sopenharmony_ci } 87762306a36Sopenharmony_ci return ret; 87862306a36Sopenharmony_ci} 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci/* 88162306a36Sopenharmony_ci * This function communicates its success or failure via ppd->driver_link_ready 88262306a36Sopenharmony_ci * Thus, it depends on its association with start_link(...) which checks 88362306a36Sopenharmony_ci * driver_link_ready before proceeding with the link negotiation and 88462306a36Sopenharmony_ci * initialization process. 88562306a36Sopenharmony_ci */ 88662306a36Sopenharmony_civoid tune_serdes(struct hfi1_pportdata *ppd) 88762306a36Sopenharmony_ci{ 88862306a36Sopenharmony_ci int ret = 0; 88962306a36Sopenharmony_ci u32 total_atten = 0; 89062306a36Sopenharmony_ci u32 remote_atten = 0, platform_atten = 0; 89162306a36Sopenharmony_ci u32 rx_preset_index, tx_preset_index; 89262306a36Sopenharmony_ci u8 tuning_method = 0, limiting_active = 0; 89362306a36Sopenharmony_ci struct hfi1_devdata *dd = ppd->dd; 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci rx_preset_index = OPA_INVALID_INDEX; 89662306a36Sopenharmony_ci tx_preset_index = OPA_INVALID_INDEX; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci /* the link defaults to enabled */ 89962306a36Sopenharmony_ci ppd->link_enabled = 1; 90062306a36Sopenharmony_ci /* the driver link ready state defaults to not ready */ 90162306a36Sopenharmony_ci ppd->driver_link_ready = 0; 90262306a36Sopenharmony_ci ppd->offline_disabled_reason = HFI1_ODR_MASK(OPA_LINKDOWN_REASON_NONE); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci /* Skip the tuning for testing (loopback != none) and simulations */ 90562306a36Sopenharmony_ci if (loopback != LOOPBACK_NONE || 90662306a36Sopenharmony_ci ppd->dd->icode == ICODE_FUNCTIONAL_SIMULATOR) { 90762306a36Sopenharmony_ci ppd->driver_link_ready = 1; 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci if (qsfp_mod_present(ppd)) { 91062306a36Sopenharmony_ci ret = acquire_chip_resource(ppd->dd, 91162306a36Sopenharmony_ci qsfp_resource(ppd->dd), 91262306a36Sopenharmony_ci QSFP_WAIT); 91362306a36Sopenharmony_ci if (ret) { 91462306a36Sopenharmony_ci dd_dev_err(ppd->dd, "%s: hfi%d: cannot lock i2c chain\n", 91562306a36Sopenharmony_ci __func__, (int)ppd->dd->hfi1_id); 91662306a36Sopenharmony_ci goto bail; 91762306a36Sopenharmony_ci } 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci refresh_qsfp_cache(ppd, &ppd->qsfp_info); 92062306a36Sopenharmony_ci release_chip_resource(ppd->dd, qsfp_resource(ppd->dd)); 92162306a36Sopenharmony_ci } 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci return; 92462306a36Sopenharmony_ci } 92562306a36Sopenharmony_ci 92662306a36Sopenharmony_ci switch (ppd->port_type) { 92762306a36Sopenharmony_ci case PORT_TYPE_DISCONNECTED: 92862306a36Sopenharmony_ci ppd->offline_disabled_reason = 92962306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_DISCONNECTED); 93062306a36Sopenharmony_ci dd_dev_warn(dd, "%s: Port disconnected, disabling port\n", 93162306a36Sopenharmony_ci __func__); 93262306a36Sopenharmony_ci goto bail; 93362306a36Sopenharmony_ci case PORT_TYPE_FIXED: 93462306a36Sopenharmony_ci /* platform_atten, remote_atten pre-zeroed to catch error */ 93562306a36Sopenharmony_ci get_platform_config_field( 93662306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 93762306a36Sopenharmony_ci PORT_TABLE_LOCAL_ATTEN_25G, &platform_atten, 4); 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci get_platform_config_field( 94062306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 94162306a36Sopenharmony_ci PORT_TABLE_REMOTE_ATTEN_25G, &remote_atten, 4); 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci total_atten = platform_atten + remote_atten; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci tuning_method = OPA_PASSIVE_TUNING; 94662306a36Sopenharmony_ci break; 94762306a36Sopenharmony_ci case PORT_TYPE_VARIABLE: 94862306a36Sopenharmony_ci if (qsfp_mod_present(ppd)) { 94962306a36Sopenharmony_ci /* 95062306a36Sopenharmony_ci * platform_atten, remote_atten pre-zeroed to 95162306a36Sopenharmony_ci * catch error 95262306a36Sopenharmony_ci */ 95362306a36Sopenharmony_ci get_platform_config_field( 95462306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 95562306a36Sopenharmony_ci PORT_TABLE_LOCAL_ATTEN_25G, 95662306a36Sopenharmony_ci &platform_atten, 4); 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_ci get_platform_config_field( 95962306a36Sopenharmony_ci ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, 96062306a36Sopenharmony_ci PORT_TABLE_REMOTE_ATTEN_25G, 96162306a36Sopenharmony_ci &remote_atten, 4); 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci total_atten = platform_atten + remote_atten; 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci tuning_method = OPA_PASSIVE_TUNING; 96662306a36Sopenharmony_ci } else { 96762306a36Sopenharmony_ci ppd->offline_disabled_reason = 96862306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_CHASSIS_CONFIG); 96962306a36Sopenharmony_ci goto bail; 97062306a36Sopenharmony_ci } 97162306a36Sopenharmony_ci break; 97262306a36Sopenharmony_ci case PORT_TYPE_QSFP: 97362306a36Sopenharmony_ci if (qsfp_mod_present(ppd)) { 97462306a36Sopenharmony_ci ret = acquire_chip_resource(ppd->dd, 97562306a36Sopenharmony_ci qsfp_resource(ppd->dd), 97662306a36Sopenharmony_ci QSFP_WAIT); 97762306a36Sopenharmony_ci if (ret) { 97862306a36Sopenharmony_ci dd_dev_err(ppd->dd, "%s: hfi%d: cannot lock i2c chain\n", 97962306a36Sopenharmony_ci __func__, (int)ppd->dd->hfi1_id); 98062306a36Sopenharmony_ci goto bail; 98162306a36Sopenharmony_ci } 98262306a36Sopenharmony_ci refresh_qsfp_cache(ppd, &ppd->qsfp_info); 98362306a36Sopenharmony_ci 98462306a36Sopenharmony_ci if (ppd->qsfp_info.cache_valid) { 98562306a36Sopenharmony_ci ret = tune_qsfp(ppd, 98662306a36Sopenharmony_ci &tx_preset_index, 98762306a36Sopenharmony_ci &rx_preset_index, 98862306a36Sopenharmony_ci &tuning_method, 98962306a36Sopenharmony_ci &total_atten); 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci /* 99262306a36Sopenharmony_ci * We may have modified the QSFP memory, so 99362306a36Sopenharmony_ci * update the cache to reflect the changes 99462306a36Sopenharmony_ci */ 99562306a36Sopenharmony_ci refresh_qsfp_cache(ppd, &ppd->qsfp_info); 99662306a36Sopenharmony_ci limiting_active = 99762306a36Sopenharmony_ci ppd->qsfp_info.limiting_active; 99862306a36Sopenharmony_ci } else { 99962306a36Sopenharmony_ci dd_dev_err(dd, 100062306a36Sopenharmony_ci "%s: Reading QSFP memory failed\n", 100162306a36Sopenharmony_ci __func__); 100262306a36Sopenharmony_ci ret = -EINVAL; /* a fail indication */ 100362306a36Sopenharmony_ci } 100462306a36Sopenharmony_ci release_chip_resource(ppd->dd, qsfp_resource(ppd->dd)); 100562306a36Sopenharmony_ci if (ret) 100662306a36Sopenharmony_ci goto bail; 100762306a36Sopenharmony_ci } else { 100862306a36Sopenharmony_ci ppd->offline_disabled_reason = 100962306a36Sopenharmony_ci HFI1_ODR_MASK( 101062306a36Sopenharmony_ci OPA_LINKDOWN_REASON_LOCAL_MEDIA_NOT_INSTALLED); 101162306a36Sopenharmony_ci goto bail; 101262306a36Sopenharmony_ci } 101362306a36Sopenharmony_ci break; 101462306a36Sopenharmony_ci default: 101562306a36Sopenharmony_ci dd_dev_warn(ppd->dd, "%s: Unknown port type\n", __func__); 101662306a36Sopenharmony_ci ppd->port_type = PORT_TYPE_UNKNOWN; 101762306a36Sopenharmony_ci tuning_method = OPA_UNKNOWN_TUNING; 101862306a36Sopenharmony_ci total_atten = 0; 101962306a36Sopenharmony_ci limiting_active = 0; 102062306a36Sopenharmony_ci tx_preset_index = OPA_INVALID_INDEX; 102162306a36Sopenharmony_ci break; 102262306a36Sopenharmony_ci } 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_ci if (ppd->offline_disabled_reason == 102562306a36Sopenharmony_ci HFI1_ODR_MASK(OPA_LINKDOWN_REASON_NONE)) 102662306a36Sopenharmony_ci apply_tunings(ppd, tx_preset_index, tuning_method, 102762306a36Sopenharmony_ci total_atten, limiting_active); 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci if (!ret) 103062306a36Sopenharmony_ci ppd->driver_link_ready = 1; 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_ci return; 103362306a36Sopenharmony_cibail: 103462306a36Sopenharmony_ci ppd->driver_link_ready = 0; 103562306a36Sopenharmony_ci} 1036