mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-30 19:48:19 +00:00 
			
		
		
		
	Import cvmx-helper-sfp.c from 2013 U-Boot. It will be used by the later added drivers to support networking on the MIPS Octeon II / III platforms. Signed-off-by: Aaron Williams <awilliams@marvell.com> Signed-off-by: Stefan Roese <sr@denx.de>
		
			
				
	
	
		
			1310 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1310 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (C) 2018-2022 Marvell International Ltd.
 | |
|  */
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <i2c.h>
 | |
| #include <log.h>
 | |
| #include <malloc.h>
 | |
| #include <linux/delay.h>
 | |
| #include <display_options.h>
 | |
| 
 | |
| #include <mach/cvmx-regs.h>
 | |
| #include <mach/cvmx-csr.h>
 | |
| #include <mach/cvmx-bootmem.h>
 | |
| #include <mach/octeon-model.h>
 | |
| #include <mach/cvmx-fuse.h>
 | |
| #include <mach/octeon-feature.h>
 | |
| #include <mach/cvmx-qlm.h>
 | |
| #include <mach/octeon_qlm.h>
 | |
| #include <mach/cvmx-pcie.h>
 | |
| #include <mach/cvmx-coremask.h>
 | |
| 
 | |
| #include <mach/cvmx-helper.h>
 | |
| #include <mach/cvmx-helper-board.h>
 | |
| #include <mach/cvmx-helper-fdt.h>
 | |
| #include <mach/cvmx-helper-cfg.h>
 | |
| #include <mach/cvmx-helper-gpio.h>
 | |
| #include <mach/cvmx-helper-util.h>
 | |
| 
 | |
| extern void octeon_i2c_unblock(int bus);
 | |
| 
 | |
| static struct cvmx_fdt_sfp_info *sfp_list;
 | |
| 
 | |
| /**
 | |
|  * Local allocator to handle both SE and U-Boot that also zeroes out memory
 | |
|  *
 | |
|  * @param	size	number of bytes to allocate
 | |
|  *
 | |
|  * @return	pointer to allocated memory or NULL if out of memory.
 | |
|  *		Alignment is set to 8-bytes.
 | |
|  */
 | |
| static void *cvm_sfp_alloc(size_t size)
 | |
| {
 | |
| 	return calloc(size, 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free allocated memory.
 | |
|  *
 | |
|  * @param	ptr	pointer to memory to free
 | |
|  *
 | |
|  * NOTE: This only works in U-Boot since SE does not really have a freeing
 | |
|  *	 mechanism.  In SE the memory is zeroed out and not freed so this
 | |
|  *	 is a memory leak if errors occur.
 | |
|  */
 | |
| static inline void cvm_sfp_free(void *ptr, size_t size)
 | |
| {
 | |
| 	free(ptr);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Select a QSFP device before accessing the EEPROM
 | |
|  *
 | |
|  * @param	sfp	handle for sfp/qsfp connector
 | |
|  * @param	enable	Set true to select, false to deselect
 | |
|  *
 | |
|  * @return	0 on success or if SFP or no select GPIO, -1 on GPIO error
 | |
|  */
 | |
| static int cvmx_qsfp_select(const struct cvmx_fdt_sfp_info *sfp, bool enable)
 | |
| {
 | |
| 	/* Select is only needed for QSFP modules */
 | |
| 	if (!sfp->is_qsfp) {
 | |
| 		debug("%s(%s, %d): not QSFP\n", __func__, sfp->name, enable);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (dm_gpio_is_valid(&sfp->select)) {
 | |
| 		/* Note that select is active low */
 | |
| 		return dm_gpio_set_value(&sfp->select, !enable);
 | |
| 	}
 | |
| 
 | |
| 	debug("%s: select GPIO unknown\n", __func__);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int cvmx_sfp_parse_sfp_buffer(struct cvmx_sfp_mod_info *sfp_info,
 | |
| 				     const uint8_t *buffer)
 | |
| {
 | |
| 	u8 csum = 0;
 | |
| 	bool csum_good = false;
 | |
| 	int i;
 | |
| 
 | |
| 	/* Validate the checksum */
 | |
| 	for (i = 0; i < 0x3f; i++)
 | |
| 		csum += buffer[i];
 | |
| 	csum_good = csum == buffer[0x3f];
 | |
| 	debug("%s: Lower checksum: 0x%02x, expected: 0x%02x\n", __func__, csum,
 | |
| 	      buffer[0x3f]);
 | |
| 	csum = 0;
 | |
| 	for (i = 0x40; i < 0x5f; i++)
 | |
| 		csum += buffer[i];
 | |
| 	debug("%s: Upper checksum: 0x%02x, expected: 0x%02x\n", __func__, csum,
 | |
| 	      buffer[0x5f]);
 | |
| 	if (csum != buffer[0x5f] || !csum_good) {
 | |
| 		debug("Error: SFP EEPROM checksum information is incorrect\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	sfp_info->conn_type = buffer[0];
 | |
| 	if (buffer[1] < 1 || buffer[1] > 7) { /* Extended ID */
 | |
| 		debug("Error: Unknown SFP extended identifier 0x%x\n",
 | |
| 		      buffer[1]);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	if (buffer[1] != 4) {
 | |
| 		debug("Module is not SFP/SFP+/SFP28/QSFP+\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	sfp_info->mod_type = buffer[2];
 | |
| 	sfp_info->eth_comp = buffer[3] & 0xf0;
 | |
| 	sfp_info->cable_comp = buffer[0x24];
 | |
| 
 | |
| 	/* There are several ways a cable can be marked as active or
 | |
| 	 * passive.  8.[2-3] specify the SFP+ cable technology.  Some
 | |
| 	 * modules also use 3.[0-1] for Infiniband, though it's
 | |
| 	 * redundant.
 | |
| 	 */
 | |
| 	if ((buffer[8] & 0x0C) == 0x08) {
 | |
| 		sfp_info->limiting = true;
 | |
| 		sfp_info->active_cable = true;
 | |
| 	} else if ((buffer[8] & 0xC) == 0x4) {
 | |
| 		sfp_info->limiting = false;
 | |
| 		sfp_info->active_cable = false;
 | |
| 	}
 | |
| 	if ((buffer[3] & 3) == 2) {
 | |
| 		sfp_info->active_cable = true;
 | |
| 		sfp_info->limiting = true;
 | |
| 	}
 | |
| 
 | |
| 	switch (sfp_info->mod_type) {
 | |
| 	case CVMX_SFP_MOD_OPTICAL_LC:
 | |
| 	case CVMX_SFP_MOD_OPTICAL_PIGTAIL:
 | |
| 		sfp_info->copper_cable = false;
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_COPPER_PIGTAIL:
 | |
| 		sfp_info->copper_cable = true;
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_NO_SEP_CONN:
 | |
| 		switch (sfp_info->cable_comp) {
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_HIGH_BER:
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_LOW_BER:
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_LOW_BER:
 | |
| 			sfp_info->copper_cable = false;
 | |
| 			sfp_info->limiting = true;
 | |
| 			sfp_info->active_cable = true;
 | |
| 			break;
 | |
| 
 | |
| 		case CVMX_SFP_CABLE_100G_SR4_25G_SR:
 | |
| 		case CVMX_SFP_CABLE_100G_LR4_25G_LR:
 | |
| 		case CVMX_SFP_CABLE_100G_ER4_25G_ER:
 | |
| 		case CVMX_SFP_CABLE_100G_SR10:
 | |
| 		case CVMX_SFP_CABLE_100G_CWDM4_MSA:
 | |
| 		case CVMX_SFP_CABLE_100G_PSM4:
 | |
| 		case CVMX_SFP_CABLE_100G_CWDM4:
 | |
| 		case CVMX_SFP_CABLE_40G_ER4:
 | |
| 		case CVMX_SFP_CABLE_4X10G_SR:
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1I1_2D1:
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1S1_2D2:
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1L1_2D2:
 | |
| 		case CVMX_SFP_CABLE_100G_CLR4:
 | |
| 		case CVMX_SFP_CABLE_100G_2_LAMBDA_DWDM:
 | |
| 		case CVMX_SFP_CABLE_40G_SWDM4:
 | |
| 		case CVMX_SFP_CABLE_100G_SWDM4:
 | |
| 		case CVMX_SFP_CABLE_100G_PAM4_BIDI:
 | |
| 			sfp_info->copper_cable = false;
 | |
| 			break;
 | |
| 
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_HIGH_BER:
 | |
| 		case CVMX_SFP_CABLE_10GBASE_T:
 | |
| 		case CVMX_SFP_CABLE_10GBASE_T_SR:
 | |
| 		case CVMX_SFP_CABLE_5GBASE_T:
 | |
| 		case CVMX_SFP_CABLE_2_5GBASE_T:
 | |
| 			sfp_info->copper_cable = true;
 | |
| 			sfp_info->limiting = true;
 | |
| 			sfp_info->active_cable = true;
 | |
| 			break;
 | |
| 
 | |
| 		case CVMX_SFP_CABLE_100G_CR4_25G_CR_CA_L:
 | |
| 		case CVMX_SFP_CABLE_25G_CR_CA_S:
 | |
| 		case CVMX_SFP_CABLE_25G_CR_CA_N:
 | |
| 		case CVMX_SFP_CABLE_40G_PSM4:
 | |
| 			sfp_info->copper_cable = true;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			switch (sfp_info->eth_comp) {
 | |
| 			case CVMX_SFP_CABLE_10GBASE_ER:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_LRM:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_LR:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_SR:
 | |
| 				sfp_info->copper_cable = false;
 | |
| 				break;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		break;
 | |
| 
 | |
| 	case CVMX_SFP_MOD_RJ45:
 | |
| 		debug("%s: RJ45 adapter\n", __func__);
 | |
| 		sfp_info->copper_cable = true;
 | |
| 		sfp_info->active_cable = true;
 | |
| 		sfp_info->limiting = true;
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_UNKNOWN:
 | |
| 		/* The Avago 1000Base-X to 1000Base-T module reports that it
 | |
| 		 * is an unknown module type but the Ethernet compliance code
 | |
| 		 * says it is 1000Base-T.  We'll change the reporting to RJ45.
 | |
| 		 */
 | |
| 		if (buffer[6] & 8) {
 | |
| 			debug("RJ45 gigabit module detected\n");
 | |
| 			sfp_info->mod_type = CVMX_SFP_MOD_RJ45;
 | |
| 			sfp_info->copper_cable = false;
 | |
| 			sfp_info->limiting = true;
 | |
| 			sfp_info->active_cable = true;
 | |
| 			sfp_info->max_copper_cable_len = buffer[0x12];
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_1G;
 | |
| 		} else {
 | |
| 			debug("Unknown module type 0x%x\n", sfp_info->mod_type);
 | |
| 		}
 | |
| 		sfp_info->limiting = true;
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_MXC_2X16:
 | |
| 		debug("%s: MXC 2X16\n", __func__);
 | |
| 		break;
 | |
| 	default:
 | |
| 		sfp_info->limiting = true;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (sfp_info->copper_cable)
 | |
| 		sfp_info->max_copper_cable_len = buffer[0x12];
 | |
| 	else
 | |
| 		sfp_info->max_50um_om4_cable_length = buffer[0x12] * 10;
 | |
| 
 | |
| 	if (buffer[0xe])
 | |
| 		sfp_info->max_single_mode_cable_length = buffer[0xe] * 1000;
 | |
| 	else
 | |
| 		sfp_info->max_single_mode_cable_length = buffer[0xf] * 100000;
 | |
| 
 | |
| 	sfp_info->max_50um_om2_cable_length = buffer[0x10] * 10;
 | |
| 	sfp_info->max_62_5um_om1_cable_length = buffer[0x11] * 10;
 | |
| 	sfp_info->max_50um_om3_cable_length = buffer[0x13] * 10;
 | |
| 
 | |
| 	if (buffer[0xc] == 0xff) {
 | |
| 		if (buffer[0x42] >= 255)
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_100G;
 | |
| 		else if (buffer[0x42] >= 160)
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_40G;
 | |
| 		else if (buffer[0x42] >= 100)
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_25G;
 | |
| 		else
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_UNKNOWN;
 | |
| 	} else if (buffer[0xc] >= 100) {
 | |
| 		sfp_info->rate = CVMX_SFP_RATE_10G;
 | |
| 	} else if (buffer[0xc] >= 10) {
 | |
| 		sfp_info->rate = CVMX_SFP_RATE_1G;
 | |
| 	} else {
 | |
| 		sfp_info->rate = CVMX_SFP_RATE_UNKNOWN;
 | |
| 	}
 | |
| 
 | |
| 	if (sfp_info->rate == CVMX_SFP_RATE_UNKNOWN) {
 | |
| 		switch (sfp_info->cable_comp) {
 | |
| 		case CVMX_SFP_CABLE_100G_SR10:
 | |
| 		case CVMX_SFP_CABLE_100G_CWDM4_MSA:
 | |
| 		case CVMX_SFP_CABLE_100G_PSM4:
 | |
| 		case CVMX_SFP_CABLE_100G_CWDM4:
 | |
| 		case CVMX_SFP_CABLE_100G_CLR4:
 | |
| 		case CVMX_SFP_CABLE_100G_2_LAMBDA_DWDM:
 | |
| 		case CVMX_SFP_CABLE_100G_SWDM4:
 | |
| 		case CVMX_SFP_CABLE_100G_PAM4_BIDI:
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_100G;
 | |
| 			break;
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_HIGH_BER:
 | |
| 		case CVMX_SFP_CABLE_100G_SR4_25G_SR:
 | |
| 		case CVMX_SFP_CABLE_100G_LR4_25G_LR:
 | |
| 		case CVMX_SFP_CABLE_100G_ER4_25G_ER:
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_HIGH_BER:
 | |
| 		case CVMX_SFP_CABLE_100G_CR4_25G_CR_CA_L:
 | |
| 		case CVMX_SFP_CABLE_25G_CR_CA_S:
 | |
| 		case CVMX_SFP_CABLE_25G_CR_CA_N:
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_LOW_BER:
 | |
| 		case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_LOW_BER:
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_25G;
 | |
| 			break;
 | |
| 		case CVMX_SFP_CABLE_40G_ER4:
 | |
| 		case CVMX_SFP_CABLE_4X10G_SR:
 | |
| 		case CVMX_SFP_CABLE_40G_PSM4:
 | |
| 		case CVMX_SFP_CABLE_40G_SWDM4:
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_40G;
 | |
| 			break;
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1I1_2D1:
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1S1_2D2:
 | |
| 		case CVMX_SFP_CABLE_G959_1_P1L1_2D2:
 | |
| 		case CVMX_SFP_CABLE_10GBASE_T:
 | |
| 		case CVMX_SFP_CABLE_10GBASE_T_SR:
 | |
| 		case CVMX_SFP_CABLE_5GBASE_T:
 | |
| 		case CVMX_SFP_CABLE_2_5GBASE_T:
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_10G;
 | |
| 			break;
 | |
| 		default:
 | |
| 			switch (sfp_info->eth_comp) {
 | |
| 			case CVMX_SFP_CABLE_10GBASE_ER:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_LRM:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_LR:
 | |
| 			case CVMX_SFP_CABLE_10GBASE_SR:
 | |
| 				sfp_info->rate = CVMX_SFP_RATE_10G;
 | |
| 				break;
 | |
| 			default:
 | |
| 				sfp_info->rate = CVMX_SFP_RATE_UNKNOWN;
 | |
| 				break;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (buffer[0xc] < 0xff)
 | |
| 		sfp_info->bitrate_max = buffer[0xc] * 100;
 | |
| 	else
 | |
| 		sfp_info->bitrate_max = buffer[0x42] * 250;
 | |
| 
 | |
| 	if ((buffer[8] & 0xc) == 8) {
 | |
| 		if (buffer[0x3c] & 0x4)
 | |
| 			sfp_info->limiting = true;
 | |
| 	}
 | |
| 
 | |
| 	/* Currently we only set this for 25G.  FEC is required for CA-S cables
 | |
| 	 * and for cable lengths >= 5M as of this writing.
 | |
| 	 */
 | |
| 	if ((sfp_info->rate == CVMX_SFP_RATE_25G &&
 | |
| 	     sfp_info->copper_cable) &&
 | |
| 	    (sfp_info->cable_comp == CVMX_SFP_CABLE_25G_CR_CA_S ||
 | |
| 	     sfp_info->max_copper_cable_len >= 5))
 | |
| 		sfp_info->fec_required = true;
 | |
| 
 | |
| 	/* copy strings and vendor info, strings will be automatically NUL
 | |
| 	 * terminated.
 | |
| 	 */
 | |
| 	memcpy(sfp_info->vendor_name, &buffer[0x14], 16);
 | |
| 	memcpy(sfp_info->vendor_oui, &buffer[0x25], 3);
 | |
| 	memcpy(sfp_info->vendor_pn, &buffer[0x28], 16);
 | |
| 	memcpy(sfp_info->vendor_rev, &buffer[0x38], 4);
 | |
| 	memcpy(sfp_info->vendor_sn, &buffer[0x44], 16);
 | |
| 	memcpy(sfp_info->date_code, &buffer[0x54], 8);
 | |
| 
 | |
| 	sfp_info->cooled_laser = !!(buffer[0x40] & 4);
 | |
| 	sfp_info->internal_cdr = !!(buffer[0x40] & 8);
 | |
| 
 | |
| 	if (buffer[0x40] & 0x20)
 | |
| 		sfp_info->power_level = 3;
 | |
| 	else
 | |
| 		sfp_info->power_level = (buffer[0x40] & 2) ? 2 : 1;
 | |
| 
 | |
| 	sfp_info->diag_paging = !!(buffer[0x40] & 0x10);
 | |
| 	sfp_info->linear_rx_output = !(buffer[0x40] & 1);
 | |
| 	sfp_info->los_implemented = !!(buffer[0x41] & 2);
 | |
| 	sfp_info->los_inverted = !!(buffer[0x41] & 4);
 | |
| 	sfp_info->tx_fault_implemented = !!(buffer[0x41] & 8);
 | |
| 	sfp_info->tx_disable_implemented = !!(buffer[0x41] & 0x10);
 | |
| 	sfp_info->rate_select_implemented = !!(buffer[0x41] & 0x20);
 | |
| 	sfp_info->tuneable_transmitter = !!(buffer[0x41] & 0x40);
 | |
| 	sfp_info->rx_decision_threshold_implemented = !!(buffer[0x41] & 0x80);
 | |
| 
 | |
| 	sfp_info->diag_monitoring = !!(buffer[0x5c] & 0x40);
 | |
| 	sfp_info->diag_rx_power_averaged = !!(buffer[0x5c] & 0x8);
 | |
| 	sfp_info->diag_externally_calibrated = !!(buffer[0x5c] & 0x10);
 | |
| 	sfp_info->diag_internally_calibrated = !!(buffer[0x5c] & 0x20);
 | |
| 	sfp_info->diag_addr_change_required = !!(buffer[0x5c] & 0x4);
 | |
| 	sfp_info->diag_soft_rate_select_control = !!(buffer[0x5d] & 2);
 | |
| 	sfp_info->diag_app_select_control = !!(buffer[0x5d] & 4);
 | |
| 	sfp_info->diag_soft_rate_select_control = !!(buffer[0x5d] & 8);
 | |
| 	sfp_info->diag_soft_rx_los_implemented = !!(buffer[0x5d] & 0x10);
 | |
| 	sfp_info->diag_soft_tx_fault_implemented = !!(buffer[0x5d] & 0x20);
 | |
| 	sfp_info->diag_soft_tx_disable_implemented = !!(buffer[0x5d] & 0x40);
 | |
| 	sfp_info->diag_alarm_warning_flags_implemented =
 | |
| 		!!(buffer[0x5d] & 0x80);
 | |
| 	sfp_info->diag_rev = buffer[0x5e];
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int cvmx_sfp_parse_qsfp_buffer(struct cvmx_sfp_mod_info *sfp_info,
 | |
| 				      const uint8_t *buffer)
 | |
| {
 | |
| 	u8 csum = 0;
 | |
| 	bool csum_good = false;
 | |
| 	int i;
 | |
| 
 | |
| 	/* Validate the checksum */
 | |
| 	for (i = 0x80; i < 0xbf; i++)
 | |
| 		csum += buffer[i];
 | |
| 	csum_good = csum == buffer[0xbf];
 | |
| 	debug("%s: Lower checksum: 0x%02x, expected: 0x%02x\n", __func__, csum,
 | |
| 	      buffer[0xbf]);
 | |
| 	csum = 0;
 | |
| 	for (i = 0xc0; i < 0xdf; i++)
 | |
| 		csum += buffer[i];
 | |
| 	debug("%s: Upper checksum: 0x%02x, expected: 0x%02x\n", __func__, csum,
 | |
| 	      buffer[0xdf]);
 | |
| 	if (csum != buffer[0xdf] || !csum_good) {
 | |
| 		debug("Error: SFP EEPROM checksum information is incorrect\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	sfp_info->conn_type = buffer[0x80];
 | |
| 	sfp_info->mod_type = buffer[0x82];
 | |
| 	sfp_info->eth_comp = buffer[0x83] & 0xf0;
 | |
| 	sfp_info->cable_comp = buffer[0xa4];
 | |
| 
 | |
| 	switch (sfp_info->mod_type) {
 | |
| 	case CVMX_SFP_MOD_COPPER_PIGTAIL:
 | |
| 	case CVMX_SFP_MOD_NO_SEP_CONN:
 | |
| 		debug("%s: copper pigtail or no separable cable\n", __func__);
 | |
| 		/* There are several ways a cable can be marked as active or
 | |
| 		 * passive.  8.[2-3] specify the SFP+ cable technology.  Some
 | |
| 		 * modules also use 3.[0-1] for Infiniband, though it's
 | |
| 		 * redundant.
 | |
| 		 */
 | |
| 		sfp_info->copper_cable = true;
 | |
| 		if ((buffer[0x88] & 0x0C) == 0x08) {
 | |
| 			sfp_info->limiting = true;
 | |
| 			sfp_info->active_cable = true;
 | |
| 		} else if ((buffer[0x88] & 0xC) == 0x4) {
 | |
| 			sfp_info->limiting = false;
 | |
| 			sfp_info->active_cable = false;
 | |
| 		}
 | |
| 		if ((buffer[0x83] & 3) == 2) {
 | |
| 			sfp_info->active_cable = true;
 | |
| 			sfp_info->limiting = true;
 | |
| 		}
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_RJ45:
 | |
| 		debug("%s: RJ45 adapter\n", __func__);
 | |
| 		sfp_info->copper_cable = true;
 | |
| 		sfp_info->active_cable = true;
 | |
| 		sfp_info->limiting = true;
 | |
| 		break;
 | |
| 	case CVMX_SFP_MOD_UNKNOWN:
 | |
| 		debug("Unknown module type\n");
 | |
| 		/* The Avago 1000Base-X to 1000Base-T module reports that it
 | |
| 		 * is an unknown module type but the Ethernet compliance code
 | |
| 		 * says it is 1000Base-T.  We'll change the reporting to RJ45.
 | |
| 		 */
 | |
| 		if (buffer[0x86] & 8) {
 | |
| 			sfp_info->mod_type = CVMX_SFP_MOD_RJ45;
 | |
| 			sfp_info->copper_cable = false;
 | |
| 			sfp_info->limiting = true;
 | |
| 			sfp_info->active_cable = true;
 | |
| 			sfp_info->max_copper_cable_len = buffer[0x92];
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_1G;
 | |
| 		}
 | |
| 		fallthrough;
 | |
| 	default:
 | |
| 		sfp_info->limiting = true;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (sfp_info->copper_cable)
 | |
| 		sfp_info->max_copper_cable_len = buffer[0x92];
 | |
| 	else
 | |
| 		sfp_info->max_50um_om4_cable_length = buffer[0x92] * 10;
 | |
| 
 | |
| 	debug("%s: copper cable: %d, max copper cable len: %d\n", __func__,
 | |
| 	      sfp_info->copper_cable, sfp_info->max_copper_cable_len);
 | |
| 	if (buffer[0xe])
 | |
| 		sfp_info->max_single_mode_cable_length = buffer[0x8e] * 1000;
 | |
| 	else
 | |
| 		sfp_info->max_single_mode_cable_length = buffer[0x8f] * 100000;
 | |
| 
 | |
| 	sfp_info->max_50um_om2_cable_length = buffer[0x90] * 10;
 | |
| 	sfp_info->max_62_5um_om1_cable_length = buffer[0x91] * 10;
 | |
| 	sfp_info->max_50um_om3_cable_length = buffer[0x93] * 10;
 | |
| 
 | |
| 	if (buffer[0x8c] == 12) {
 | |
| 		sfp_info->rate = CVMX_SFP_RATE_1G;
 | |
| 	} else if (buffer[0x8c] == 103) {
 | |
| 		sfp_info->rate = CVMX_SFP_RATE_10G;
 | |
| 	} else if (buffer[0x8c] == 0xff) {
 | |
| 		if (buffer[0xc2] == 103)
 | |
| 			sfp_info->rate = CVMX_SFP_RATE_100G;
 | |
| 	}
 | |
| 
 | |
| 	if (buffer[0x8c] < 0xff)
 | |
| 		sfp_info->bitrate_max = buffer[0x8c] * 100;
 | |
| 	else
 | |
| 		sfp_info->bitrate_max = buffer[0xc2] * 250;
 | |
| 
 | |
| 	if ((buffer[0x88] & 0xc) == 8) {
 | |
| 		if (buffer[0xbc] & 0x4)
 | |
| 			sfp_info->limiting = true;
 | |
| 	}
 | |
| 
 | |
| 	/* Currently we only set this for 25G.  FEC is required for CA-S cables
 | |
| 	 * and for cable lengths >= 5M as of this writing.
 | |
| 	 */
 | |
| 	/* copy strings and vendor info, strings will be automatically NUL
 | |
| 	 * terminated.
 | |
| 	 */
 | |
| 	memcpy(sfp_info->vendor_name, &buffer[0x94], 16);
 | |
| 	memcpy(sfp_info->vendor_oui, &buffer[0xa5], 3);
 | |
| 	memcpy(sfp_info->vendor_pn, &buffer[0xa8], 16);
 | |
| 	memcpy(sfp_info->vendor_rev, &buffer[0xb8], 4);
 | |
| 	memcpy(sfp_info->vendor_sn, &buffer[0xc4], 16);
 | |
| 	memcpy(sfp_info->date_code, &buffer[0xd4], 8);
 | |
| 
 | |
| 	sfp_info->linear_rx_output = !!(buffer[0xc0] & 1);
 | |
| 	sfp_info->cooled_laser = !!(buffer[0xc0] & 4);
 | |
| 	sfp_info->internal_cdr = !!(buffer[0xc0] & 8);
 | |
| 
 | |
| 	if (buffer[0xc0] & 0x20)
 | |
| 		sfp_info->power_level = 3;
 | |
| 	else
 | |
| 		sfp_info->power_level = (buffer[0xc0] & 2) ? 2 : 1;
 | |
| 
 | |
| 	sfp_info->diag_paging = !!(buffer[0xc0] & 0x10);
 | |
| 	sfp_info->los_implemented = !!(buffer[0xc1] & 2);
 | |
| 	sfp_info->los_inverted = !!(buffer[0xc1] & 4);
 | |
| 	sfp_info->tx_fault_implemented = !!(buffer[0xc1] & 8);
 | |
| 	sfp_info->tx_disable_implemented = !!(buffer[0xc1] & 0x10);
 | |
| 	sfp_info->rate_select_implemented = !!(buffer[0xc1] & 0x20);
 | |
| 	sfp_info->tuneable_transmitter = !!(buffer[0xc1] & 0x40);
 | |
| 	sfp_info->rx_decision_threshold_implemented = !!(buffer[0xc1] & 0x80);
 | |
| 
 | |
| 	sfp_info->diag_monitoring = !!(buffer[0xdc] & 0x40);
 | |
| 	sfp_info->diag_rx_power_averaged = !!(buffer[0xdc] & 0x8);
 | |
| 	sfp_info->diag_externally_calibrated = !!(buffer[0xdc] & 0x10);
 | |
| 	sfp_info->diag_internally_calibrated = !!(buffer[0xdc] & 0x20);
 | |
| 	sfp_info->diag_addr_change_required = !!(buffer[0xdc] & 0x4);
 | |
| 	sfp_info->diag_soft_rate_select_control = !!(buffer[0xdd] & 2);
 | |
| 	sfp_info->diag_app_select_control = !!(buffer[0xdd] & 4);
 | |
| 	sfp_info->diag_soft_rate_select_control = !!(buffer[0xdd] & 8);
 | |
| 	sfp_info->diag_soft_rx_los_implemented = !!(buffer[0xdd] & 0x10);
 | |
| 	sfp_info->diag_soft_tx_fault_implemented = !!(buffer[0xdd] & 0x20);
 | |
| 	sfp_info->diag_soft_tx_disable_implemented = !!(buffer[0xdd] & 0x40);
 | |
| 	sfp_info->diag_alarm_warning_flags_implemented =
 | |
| 		!!(buffer[0xdd] & 0x80);
 | |
| 	sfp_info->diag_rev = buffer[0xde];
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static bool sfp_verify_checksum(const uint8_t *buffer)
 | |
| {
 | |
| 	u8 csum = 0;
 | |
| 	u8 offset;
 | |
| 	bool csum_good = false;
 | |
| 	int i;
 | |
| 
 | |
| 	switch (buffer[0]) {
 | |
| 	case CVMX_SFP_CONN_QSFP:
 | |
| 	case CVMX_SFP_CONN_QSFPP:
 | |
| 	case CVMX_SFP_CONN_QSFP28:
 | |
| 	case CVMX_SFP_CONN_MICRO_QSFP:
 | |
| 	case CVMX_SFP_CONN_QSFP_DD:
 | |
| 		offset = 0x80;
 | |
| 		break;
 | |
| 	default:
 | |
| 		offset = 0;
 | |
| 		break;
 | |
| 	}
 | |
| 	for (i = offset; i < offset + 0x3f; i++)
 | |
| 		csum += buffer[i];
 | |
| 	csum_good = csum == buffer[offset + 0x3f];
 | |
| 	if (!csum_good) {
 | |
| 		debug("%s: Lower checksum bad, got 0x%x, expected 0x%x\n",
 | |
| 		      __func__, csum, buffer[offset + 0x3f]);
 | |
| 		return false;
 | |
| 	}
 | |
| 	csum = 0;
 | |
| 	for (i = offset + 0x40; i < offset + 0x5f; i++)
 | |
| 		csum += buffer[i];
 | |
| 	if (csum != buffer[offset + 0x5f]) {
 | |
| 		debug("%s: Upper checksum bad, got 0x%x, expected 0x%x\n",
 | |
| 		      __func__, csum, buffer[offset + 0x5f]);
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads and parses SFP/QSFP EEPROM
 | |
|  *
 | |
|  * @param	sfp	sfp handle to read
 | |
|  *
 | |
|  * @return	0 for success, -1 on error.
 | |
|  */
 | |
| int cvmx_sfp_read_i2c_eeprom(struct cvmx_fdt_sfp_info *sfp)
 | |
| {
 | |
| 	const struct cvmx_fdt_i2c_bus_info *bus = sfp->i2c_bus;
 | |
| 	int oct_bus = cvmx_fdt_i2c_get_root_bus(bus);
 | |
| 	struct udevice *dev;
 | |
| 	u8 buffer[256];
 | |
| 	bool is_qsfp;
 | |
| 	int retry;
 | |
| 	int err;
 | |
| 
 | |
| 	if (!bus) {
 | |
| 		debug("%s(%s): Error: i2c bus undefined for eeprom\n", __func__,
 | |
| 		      sfp->name);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	is_qsfp = (sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFP ||
 | |
| 		   sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFPP ||
 | |
| 		   sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFP28 ||
 | |
| 		   sfp->sfp_info.conn_type == CVMX_SFP_CONN_MICRO_QSFP) ||
 | |
| 		  sfp->is_qsfp;
 | |
| 
 | |
| 	err = cvmx_qsfp_select(sfp, true);
 | |
| 	if (err) {
 | |
| 		debug("%s: Error selecting SFP/QSFP slot\n", __func__);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	debug("%s: Reading eeprom from i2c address %d:0x%x\n", __func__,
 | |
| 	      oct_bus, sfp->i2c_eeprom_addr);
 | |
| 	for (retry = 0; retry < 3; retry++) {
 | |
| 		err = i2c_get_chip(bus->i2c_bus, sfp->i2c_eeprom_addr, 1, &dev);
 | |
| 		if (err) {
 | |
| 			debug("Cannot find I2C device: %d\n", err);
 | |
| 			goto error;
 | |
| 		}
 | |
| 
 | |
| 		err = dm_i2c_read(dev, 0, buffer, 256);
 | |
| 		if (err || !sfp_verify_checksum(buffer)) {
 | |
| 			debug("%s: Error %d reading eeprom at 0x%x, bus %d\n",
 | |
| 			      __func__, err, sfp->i2c_eeprom_addr, oct_bus);
 | |
| 			debug("%s: Retry %d\n", __func__, retry + 1);
 | |
| 			mdelay(1000);
 | |
| 		} else {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (err) {
 | |
| 		debug("%s: Error reading eeprom from SFP %s\n", __func__,
 | |
| 		      sfp->name);
 | |
| 		return -1;
 | |
| 	}
 | |
| #ifdef DEBUG
 | |
| 	print_buffer(0, buffer, 1, 256, 0);
 | |
| #endif
 | |
| 	memset(&sfp->sfp_info, 0, sizeof(struct cvmx_sfp_mod_info));
 | |
| 
 | |
| 	switch (buffer[0]) {
 | |
| 	case CVMX_SFP_CONN_SFP:
 | |
| 		err = cvmx_sfp_parse_sfp_buffer(&sfp->sfp_info, buffer);
 | |
| 		break;
 | |
| 	case CVMX_SFP_CONN_QSFP:
 | |
| 	case CVMX_SFP_CONN_QSFPP:
 | |
| 	case CVMX_SFP_CONN_QSFP28:
 | |
| 	case CVMX_SFP_CONN_MICRO_QSFP:
 | |
| 		err = cvmx_sfp_parse_qsfp_buffer(&sfp->sfp_info, buffer);
 | |
| 		break;
 | |
| 	default:
 | |
| 		debug("%s: Unknown SFP transceiver type 0x%x\n", __func__,
 | |
| 		      buffer[0]);
 | |
| 		err = -1;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| error:
 | |
| 	if (is_qsfp)
 | |
| 		err |= cvmx_qsfp_select(sfp, false);
 | |
| 
 | |
| 	if (!err) {
 | |
| 		sfp->valid = true;
 | |
| 		sfp->sfp_info.valid = true;
 | |
| 	} else {
 | |
| 		sfp->valid = false;
 | |
| 		sfp->sfp_info.valid = false;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Function called to check and return the status of the mod_abs pin or
 | |
|  * mod_pres pin for QSFPs.
 | |
|  *
 | |
|  * @param	sfp	Handle to SFP information.
 | |
|  * @param	data	User-defined data passed to the function
 | |
|  *
 | |
|  * @return	0 if absent, 1 if present, -1 on error
 | |
|  */
 | |
| int cvmx_sfp_check_mod_abs(struct cvmx_fdt_sfp_info *sfp, void *data)
 | |
| {
 | |
| 	int val;
 | |
| 	int err = 0;
 | |
| 	int mode;
 | |
| 
 | |
| 	if (!dm_gpio_is_valid(&sfp->mod_abs)) {
 | |
| 		debug("%s: Error: mod_abs not set for %s\n", __func__,
 | |
| 		      sfp->name);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	val = dm_gpio_get_value(&sfp->mod_abs);
 | |
| 	debug("%s(%s, %p) mod_abs: %d\n", __func__, sfp->name, data, val);
 | |
| 	if (val >= 0 && val != sfp->last_mod_abs && sfp->mod_abs_changed) {
 | |
| 		err = 0;
 | |
| 		if (!val) {
 | |
| 			err = cvmx_sfp_read_i2c_eeprom(sfp);
 | |
| 			if (err)
 | |
| 				debug("%s: Error reading SFP %s EEPROM\n",
 | |
| 				      __func__, sfp->name);
 | |
| 		}
 | |
| 		err = sfp->mod_abs_changed(sfp, val, sfp->mod_abs_changed_data);
 | |
| 	}
 | |
| 	debug("%s(%s (%p)): Last mod_abs: %d, current: %d, changed: %p, rc: %d, next: %p, caller: %p\n",
 | |
| 	      __func__, sfp->name, sfp, sfp->last_mod_abs, val,
 | |
| 	      sfp->mod_abs_changed, err, sfp->next_iface_sfp,
 | |
| 	      __builtin_return_address(0));
 | |
| 
 | |
| 	if (err >= 0) {
 | |
| 		sfp->last_mod_abs = val;
 | |
| 		mode = cvmx_helper_interface_get_mode(sfp->xiface);
 | |
| 		cvmx_sfp_validate_module(sfp, mode);
 | |
| 	} else {
 | |
| 		debug("%s: mod_abs_changed for %s returned error\n", __func__,
 | |
| 		      sfp->name);
 | |
| 	}
 | |
| 
 | |
| 	return err < 0 ? err : val;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads the EEPROMs of all SFP modules.
 | |
|  *
 | |
|  * @return 0 for success
 | |
|  */
 | |
| int cvmx_sfp_read_all_modules(void)
 | |
| {
 | |
| 	struct cvmx_fdt_sfp_info *sfp;
 | |
| 	int val;
 | |
| 	bool error = false;
 | |
| 	int rc;
 | |
| 
 | |
| 	for (sfp = sfp_list; sfp; sfp = sfp->next) {
 | |
| 		if (dm_gpio_is_valid(&sfp->mod_abs)) {
 | |
| 			/* Check if module absent */
 | |
| 			val = dm_gpio_get_value(&sfp->mod_abs);
 | |
| 			sfp->last_mod_abs = val;
 | |
| 			if (val)
 | |
| 				continue;
 | |
| 		}
 | |
| 		rc = cvmx_sfp_read_i2c_eeprom(sfp);
 | |
| 		if (rc) {
 | |
| 			debug("%s: Error reading eeprom from SFP %s\n",
 | |
| 			      __func__, sfp->name);
 | |
| 			error = true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return error ? -1 : 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Registers a function to be called whenever the mod_abs/mod_pres signal
 | |
|  * changes.
 | |
|  *
 | |
|  * @param	sfp		Handle to SFP data structure
 | |
|  * @param	mod_abs_changed	Function called whenever mod_abs is changed
 | |
|  *				or NULL to remove.
 | |
|  * @param	mod_abs_changed_data	User-defined data passed to
 | |
|  *					mod_abs_changed
 | |
|  *
 | |
|  * @return	0 for success
 | |
|  *
 | |
|  * @NOTE: If multiple SFP slots are linked together, all subsequent slots
 | |
|  *	  will also be registered for the same handler.
 | |
|  */
 | |
| int cvmx_sfp_register_mod_abs_changed(struct cvmx_fdt_sfp_info *sfp,
 | |
| 				      int (*mod_abs_changed)(struct cvmx_fdt_sfp_info *sfp,
 | |
| 							     int val, void *data),
 | |
| 				      void *mod_abs_changed_data)
 | |
| {
 | |
| 	sfp->mod_abs_changed = mod_abs_changed;
 | |
| 	sfp->mod_abs_changed_data = mod_abs_changed_data;
 | |
| 
 | |
| 	sfp->last_mod_abs = -2; /* undefined */
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parses a SFP slot from the device tree
 | |
|  *
 | |
|  * @param	sfp		SFP handle to store data in
 | |
|  * @param	fdt_addr	Address of flat device tree
 | |
|  * @param	of_offset	Node in device tree for SFP slot
 | |
|  *
 | |
|  * @return	0 on success, -1 on error
 | |
|  */
 | |
| static int cvmx_sfp_parse_sfp(struct cvmx_fdt_sfp_info *sfp, ofnode node)
 | |
| {
 | |
| 	struct ofnode_phandle_args phandle;
 | |
| 	int err;
 | |
| 
 | |
| 	sfp->name = ofnode_get_name(node);
 | |
| 	sfp->of_offset = ofnode_to_offset(node);
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "tx_disable", 0,
 | |
| 					 &sfp->tx_disable, GPIOD_IS_OUT);
 | |
| 	if (err) {
 | |
| 		printf("%s: tx_disable not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 	dm_gpio_set_value(&sfp->tx_disable, 0);
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "mod_abs", 0,
 | |
| 					 &sfp->mod_abs, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: mod_abs not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "tx_error", 0,
 | |
| 					 &sfp->tx_error, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: tx_error not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "rx_los", 0,
 | |
| 					 &sfp->rx_los, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: rx_los not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = ofnode_parse_phandle_with_args(node, "eeprom", NULL, 0, 0,
 | |
| 					     &phandle);
 | |
| 	if (!err) {
 | |
| 		sfp->i2c_eeprom_addr = ofnode_get_addr(phandle.node);
 | |
| 		debug("%s: eeprom address: 0x%x\n", __func__,
 | |
| 		      sfp->i2c_eeprom_addr);
 | |
| 
 | |
| 		debug("%s: Getting eeprom i2c bus for %s\n", __func__,
 | |
| 		      sfp->name);
 | |
| 		sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node));
 | |
| 	}
 | |
| 
 | |
| 	err = ofnode_parse_phandle_with_args(node, "diag", NULL, 0, 0,
 | |
| 					     &phandle);
 | |
| 	if (!err) {
 | |
| 		sfp->i2c_diag_addr = ofnode_get_addr(phandle.node);
 | |
| 		if (!sfp->i2c_bus)
 | |
| 			sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node));
 | |
| 	}
 | |
| 
 | |
| 	sfp->last_mod_abs = -2;
 | |
| 	sfp->last_rx_los = -2;
 | |
| 
 | |
| 	if (!sfp->i2c_bus) {
 | |
| 		debug("%s(%s): Error: could not get i2c bus from device tree\n",
 | |
| 		      __func__, sfp->name);
 | |
| 		err = -1;
 | |
| 	}
 | |
| 
 | |
| 	if (err) {
 | |
| 		dm_gpio_free(sfp->tx_disable.dev, &sfp->tx_disable);
 | |
| 		dm_gpio_free(sfp->mod_abs.dev, &sfp->mod_abs);
 | |
| 		dm_gpio_free(sfp->tx_error.dev, &sfp->tx_error);
 | |
| 		dm_gpio_free(sfp->rx_los.dev, &sfp->rx_los);
 | |
| 	} else {
 | |
| 		sfp->valid = true;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parses a QSFP slot from the device tree
 | |
|  *
 | |
|  * @param	sfp		SFP handle to store data in
 | |
|  * @param	fdt_addr	Address of flat device tree
 | |
|  * @param	of_offset	Node in device tree for SFP slot
 | |
|  *
 | |
|  * @return	0 on success, -1 on error
 | |
|  */
 | |
| static int cvmx_sfp_parse_qsfp(struct cvmx_fdt_sfp_info *sfp, ofnode node)
 | |
| {
 | |
| 	struct ofnode_phandle_args phandle;
 | |
| 	int err;
 | |
| 
 | |
| 	sfp->is_qsfp = true;
 | |
| 	sfp->name = ofnode_get_name(node);
 | |
| 	sfp->of_offset = ofnode_to_offset(node);
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "lp_mode", 0,
 | |
| 					 &sfp->lp_mode, GPIOD_IS_OUT);
 | |
| 	if (err) {
 | |
| 		printf("%s: lp_mode not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "mod_prs", 0,
 | |
| 					 &sfp->mod_abs, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: mod_prs not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "select", 0,
 | |
| 					 &sfp->select, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: select not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "reset", 0,
 | |
| 					 &sfp->reset, GPIOD_IS_OUT);
 | |
| 	if (err) {
 | |
| 		printf("%s: reset not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = gpio_request_by_name_nodev(node, "interrupt", 0,
 | |
| 					 &sfp->interrupt, GPIOD_IS_IN);
 | |
| 	if (err) {
 | |
| 		printf("%s: interrupt not found in DT!\n", __func__);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	err = ofnode_parse_phandle_with_args(node, "eeprom", NULL, 0, 0,
 | |
| 					     &phandle);
 | |
| 	if (!err) {
 | |
| 		sfp->i2c_eeprom_addr = ofnode_get_addr(phandle.node);
 | |
| 		sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node));
 | |
| 	}
 | |
| 
 | |
| 	err = ofnode_parse_phandle_with_args(node, "diag", NULL, 0, 0,
 | |
| 					     &phandle);
 | |
| 	if (!err) {
 | |
| 		sfp->i2c_diag_addr = ofnode_get_addr(phandle.node);
 | |
| 		if (!sfp->i2c_bus)
 | |
| 			sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node));
 | |
| 	}
 | |
| 
 | |
| 	sfp->last_mod_abs = -2;
 | |
| 	sfp->last_rx_los = -2;
 | |
| 
 | |
| 	if (!sfp->i2c_bus) {
 | |
| 		cvmx_printf("%s(%s): Error: could not get i2c bus from device tree\n",
 | |
| 			    __func__, sfp->name);
 | |
| 		err = -1;
 | |
| 	}
 | |
| 
 | |
| 	if (err) {
 | |
| 		dm_gpio_free(sfp->lp_mode.dev, &sfp->lp_mode);
 | |
| 		dm_gpio_free(sfp->mod_abs.dev, &sfp->mod_abs);
 | |
| 		dm_gpio_free(sfp->select.dev, &sfp->select);
 | |
| 		dm_gpio_free(sfp->reset.dev, &sfp->reset);
 | |
| 		dm_gpio_free(sfp->interrupt.dev, &sfp->interrupt);
 | |
| 	} else {
 | |
| 		sfp->valid = true;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parses the device tree for SFP and QSFP slots
 | |
|  *
 | |
|  * @param	fdt_addr	Address of flat device-tree
 | |
|  *
 | |
|  * @return	0 for success, -1 on error
 | |
|  */
 | |
| int cvmx_sfp_parse_device_tree(const void *fdt_addr)
 | |
| {
 | |
| 	struct cvmx_fdt_sfp_info *sfp, *first_sfp = NULL, *last_sfp = NULL;
 | |
| 	ofnode node;
 | |
| 	int err = 0;
 | |
| 	int reg;
 | |
| 	static bool parsed;
 | |
| 
 | |
| 	debug("%s(%p): Parsing...\n", __func__, fdt_addr);
 | |
| 	if (parsed) {
 | |
| 		debug("%s(%p): Already parsed\n", __func__, fdt_addr);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	ofnode_for_each_compatible_node(node, "ethernet,sfp-slot") {
 | |
| 		if (!ofnode_valid(node))
 | |
| 			continue;
 | |
| 
 | |
| 		sfp = cvm_sfp_alloc(sizeof(*sfp));
 | |
| 		if (!sfp)
 | |
| 			return -1;
 | |
| 
 | |
| 		err = cvmx_sfp_parse_sfp(sfp, node);
 | |
| 		if (!err) {
 | |
| 			if (!sfp_list)
 | |
| 				sfp_list = sfp;
 | |
| 			if (last_sfp)
 | |
| 				last_sfp->next = sfp;
 | |
| 			sfp->prev = last_sfp;
 | |
| 			last_sfp = sfp;
 | |
| 			debug("%s: parsed %s\n", __func__, sfp->name);
 | |
| 		} else {
 | |
| 			debug("%s: Error parsing SFP at node %s\n",
 | |
| 			      __func__, ofnode_get_name(node));
 | |
| 			return err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ofnode_for_each_compatible_node(node, "ethernet,qsfp-slot") {
 | |
| 		if (!ofnode_valid(node))
 | |
| 			continue;
 | |
| 
 | |
| 		sfp = cvm_sfp_alloc(sizeof(*sfp));
 | |
| 		if (!sfp)
 | |
| 			return -1;
 | |
| 
 | |
| 		err = cvmx_sfp_parse_qsfp(sfp, node);
 | |
| 		if (!err) {
 | |
| 			if (!sfp_list)
 | |
| 				sfp_list = sfp;
 | |
| 			if (last_sfp)
 | |
| 				last_sfp->next = sfp;
 | |
| 			sfp->prev = last_sfp;
 | |
| 			last_sfp = sfp;
 | |
| 			debug("%s: parsed %s\n", __func__, sfp->name);
 | |
| 		} else {
 | |
| 			debug("%s: Error parsing QSFP at node %s\n",
 | |
| 			      __func__, ofnode_get_name(node));
 | |
| 			return err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!octeon_has_feature(OCTEON_FEATURE_BGX))
 | |
| 		return 0;
 | |
| 
 | |
| 	err = 0;
 | |
| 	ofnode_for_each_compatible_node(node, "cavium,octeon-7890-bgx-port") {
 | |
| 		int sfp_nodes[4];
 | |
| 		ofnode sfp_ofnodes[4];
 | |
| 		int num_sfp_nodes;
 | |
| 		u64 reg_addr;
 | |
| 		struct cvmx_xiface xi;
 | |
| 		int xiface, index;
 | |
| 		cvmx_helper_interface_mode_t mode;
 | |
| 		int i;
 | |
| 		int rc;
 | |
| 
 | |
| 		if (!ofnode_valid(node))
 | |
| 			break;
 | |
| 
 | |
| 		num_sfp_nodes = ARRAY_SIZE(sfp_nodes);
 | |
| 		rc = cvmx_ofnode_lookup_phandles(node, "sfp-slot",
 | |
| 						 &num_sfp_nodes, sfp_ofnodes);
 | |
| 		if (rc != 0 || num_sfp_nodes < 1)
 | |
| 			rc = cvmx_ofnode_lookup_phandles(node, "qsfp-slot",
 | |
| 							 &num_sfp_nodes,
 | |
| 							 sfp_ofnodes);
 | |
| 		/* If no SFP or QSFP slot found, go to next port */
 | |
| 		if (rc < 0)
 | |
| 			continue;
 | |
| 
 | |
| 		last_sfp = NULL;
 | |
| 		for (i = 0; i < num_sfp_nodes; i++) {
 | |
| 			sfp = cvmx_sfp_find_slot_by_fdt_node(ofnode_to_offset(sfp_ofnodes[i]));
 | |
| 			debug("%s: Adding sfp %s (%p) to BGX port\n",
 | |
| 			      __func__, sfp->name, sfp);
 | |
| 			if (last_sfp)
 | |
| 				last_sfp->next_iface_sfp = sfp;
 | |
| 			else
 | |
| 				first_sfp = sfp;
 | |
| 			last_sfp = sfp;
 | |
| 		}
 | |
| 		if (!first_sfp) {
 | |
| 			debug("%s: Error: could not find SFP slot for BGX port %s\n",
 | |
| 			      __func__,
 | |
| 			      fdt_get_name(fdt_addr, sfp_nodes[0],
 | |
| 					   NULL));
 | |
| 			err = -1;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		/* Get the port index */
 | |
| 		reg = ofnode_get_addr(node);
 | |
| 		if (reg < 0) {
 | |
| 			debug("%s: Error: could not get BGX port reg value\n",
 | |
| 			      __func__);
 | |
| 			err = -1;
 | |
| 			break;
 | |
| 		}
 | |
| 		index = reg;
 | |
| 
 | |
| 		/* Get BGX node and address */
 | |
| 		reg_addr = ofnode_get_addr(ofnode_get_parent(node));
 | |
| 		/* Extrace node */
 | |
| 		xi.node = cvmx_csr_addr_to_node(reg_addr);
 | |
| 		/* Extract reg address */
 | |
| 		reg_addr = cvmx_csr_addr_strip_node(reg_addr);
 | |
| 		if ((reg_addr & 0xFFFFFFFFF0000000) !=
 | |
| 		    0x00011800E0000000) {
 | |
| 			debug("%s: Invalid BGX address 0x%llx\n",
 | |
| 			      __func__, (unsigned long long)reg_addr);
 | |
| 			xi.node = -1;
 | |
| 			err = -1;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		/* Extract interface from address */
 | |
| 		xi.interface = (reg_addr >> 24) & 0x0F;
 | |
| 		/* Convert to xiface */
 | |
| 		xiface = cvmx_helper_node_interface_to_xiface(xi.node,
 | |
| 							      xi.interface);
 | |
| 		debug("%s: Parsed %d SFP slots for interface 0x%x, index %d\n",
 | |
| 		      __func__, num_sfp_nodes, xiface, index);
 | |
| 
 | |
| 		mode = cvmx_helper_interface_get_mode(xiface);
 | |
| 		for (sfp = first_sfp; sfp; sfp = sfp->next_iface_sfp) {
 | |
| 			sfp->xiface = xiface;
 | |
| 			sfp->index = index;
 | |
| 			/* Convert to IPD port */
 | |
| 			sfp->ipd_port[0] =
 | |
| 				cvmx_helper_get_ipd_port(xiface, index);
 | |
| 			debug("%s: sfp %s (%p) xi: 0x%x, index: 0x%x, node: %d, mode: 0x%x, next: %p\n",
 | |
| 			      __func__, sfp->name, sfp, sfp->xiface,
 | |
| 			      sfp->index, xi.node, mode,
 | |
| 			      sfp->next_iface_sfp);
 | |
| 			if (mode == CVMX_HELPER_INTERFACE_MODE_XLAUI ||
 | |
| 			    mode == CVMX_HELPER_INTERFACE_MODE_40G_KR4)
 | |
| 				for (i = 1; i < 4; i++)
 | |
| 					sfp->ipd_port[i] = -1;
 | |
| 			else
 | |
| 				for (i = 1; i < 4; i++)
 | |
| 					sfp->ipd_port[i] =
 | |
| 						cvmx_helper_get_ipd_port(
 | |
| 							xiface, i);
 | |
| 		}
 | |
| 		cvmx_helper_cfg_set_sfp_info(xiface, index, first_sfp);
 | |
| 	}
 | |
| 
 | |
| 	if (!err) {
 | |
| 		parsed = true;
 | |
| 		cvmx_sfp_read_all_modules();
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Given a fdt node offset find the corresponding SFP or QSFP slot
 | |
|  *
 | |
|  * @param	of_offset	flat device tree node offset
 | |
|  *
 | |
|  * @return	pointer to SFP data structure or NULL if not found
 | |
|  */
 | |
| struct cvmx_fdt_sfp_info *cvmx_sfp_find_slot_by_fdt_node(int of_offset)
 | |
| {
 | |
| 	struct cvmx_fdt_sfp_info *sfp = sfp_list;
 | |
| 
 | |
| 	while (sfp) {
 | |
| 		if (sfp->of_offset == of_offset)
 | |
| 			return sfp;
 | |
| 		sfp = sfp->next;
 | |
| 	}
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static bool cvmx_sfp_validate_quad(struct cvmx_fdt_sfp_info *sfp,
 | |
| 				   struct cvmx_phy_gpio_leds *leds)
 | |
| {
 | |
| 	bool multi_led = leds && (leds->next);
 | |
| 	bool error = false;
 | |
| 	int mod_abs;
 | |
| 
 | |
| 	do {
 | |
| 		/* Skip missing modules */
 | |
| 		if (dm_gpio_is_valid(&sfp->mod_abs))
 | |
| 			mod_abs = dm_gpio_get_value(&sfp->mod_abs);
 | |
| 		else
 | |
| 			mod_abs = 0;
 | |
| 		if (!mod_abs) {
 | |
| 			if (cvmx_sfp_read_i2c_eeprom(sfp)) {
 | |
| 				debug("%s: Error reading eeprom for %s\n",
 | |
| 				      __func__, sfp->name);
 | |
| 			}
 | |
| 			if (sfp->sfp_info.rate < CVMX_SFP_RATE_10G) {
 | |
| 				cvmx_helper_leds_show_error(leds, true);
 | |
| 				error = true;
 | |
| 			} else if (sfp->sfp_info.rate >= CVMX_SFP_RATE_10G) {
 | |
| 				/* We don't support 10GBase-T modules in
 | |
| 				 * this mode.
 | |
| 				 */
 | |
| 				switch (sfp->sfp_info.cable_comp) {
 | |
| 				case CVMX_SFP_CABLE_10GBASE_T:
 | |
| 				case CVMX_SFP_CABLE_10GBASE_T_SR:
 | |
| 				case CVMX_SFP_CABLE_5GBASE_T:
 | |
| 				case CVMX_SFP_CABLE_2_5GBASE_T:
 | |
| 					cvmx_helper_leds_show_error(leds, true);
 | |
| 					error = true;
 | |
| 					break;
 | |
| 				default:
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (multi_led) {
 | |
| 			cvmx_helper_leds_show_error(leds, false);
 | |
| 		}
 | |
| 
 | |
| 		if (multi_led && leds->next)
 | |
| 			leds = leds->next;
 | |
| 		sfp = sfp->next_iface_sfp;
 | |
| 	} while (sfp);
 | |
| 
 | |
| 	if (!multi_led)
 | |
| 		cvmx_helper_leds_show_error(leds, error);
 | |
| 
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Validates if the module is correct for the specified port
 | |
|  *
 | |
|  * @param[in]	sfp	SFP port to check
 | |
|  * @param	xiface	interface
 | |
|  * @param	index	port index
 | |
|  * @param	speed	link speed, -1 if unknown
 | |
|  * @param	mode	interface mode
 | |
|  *
 | |
|  * @return	true if module is valid, false if invalid
 | |
|  * NOTE: This will also toggle the error LED, if present
 | |
|  */
 | |
| bool cvmx_sfp_validate_module(struct cvmx_fdt_sfp_info *sfp, int mode)
 | |
| {
 | |
| 	const struct cvmx_sfp_mod_info *mod_info = &sfp->sfp_info;
 | |
| 	int xiface = sfp->xiface;
 | |
| 	int index = sfp->index;
 | |
| 	struct cvmx_phy_gpio_leds *leds;
 | |
| 	bool error = false;
 | |
| 	bool quad_mode = false;
 | |
| 
 | |
| 	debug("%s(%s, 0x%x, 0x%x, 0x%x)\n", __func__, sfp->name, xiface, index,
 | |
| 	      mode);
 | |
| 	if (!sfp) {
 | |
| 		debug("%s: Error: sfp is NULL\n", __func__);
 | |
| 		return false;
 | |
| 	}
 | |
| 	/* No module is valid */
 | |
| 	leds = cvmx_helper_get_port_phy_leds(xiface, index);
 | |
| 	if (!leds)
 | |
| 		debug("%s: No leds for 0x%x:0x%x\n", __func__, xiface, index);
 | |
| 
 | |
| 	if (mode != CVMX_HELPER_INTERFACE_MODE_XLAUI &&
 | |
| 	    mode != CVMX_HELPER_INTERFACE_MODE_40G_KR4 && !sfp->is_qsfp &&
 | |
| 	    sfp->last_mod_abs && leds) {
 | |
| 		cvmx_helper_leds_show_error(leds, false);
 | |
| 		debug("%s: %s: last_mod_abs: %d, no error\n", __func__,
 | |
| 		      sfp->name, sfp->last_mod_abs);
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	switch (mode) {
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_RGMII:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_GMII:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_SGMII:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_QSGMII:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_AGL:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_SPI:
 | |
| 		if ((mod_info->active_cable &&
 | |
| 		     mod_info->rate != CVMX_SFP_RATE_1G) ||
 | |
| 		    mod_info->rate < CVMX_SFP_RATE_1G)
 | |
| 			error = true;
 | |
| 		break;
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_RXAUI:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_XAUI:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_10G_KR:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_XFI:
 | |
| 		if ((mod_info->active_cable &&
 | |
| 		     mod_info->rate != CVMX_SFP_RATE_10G) ||
 | |
| 		    mod_info->rate < CVMX_SFP_RATE_10G)
 | |
| 			error = true;
 | |
| 		break;
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_XLAUI:
 | |
| 	case CVMX_HELPER_INTERFACE_MODE_40G_KR4:
 | |
| 		if (!sfp->is_qsfp) {
 | |
| 			quad_mode = true;
 | |
| 			error = cvmx_sfp_validate_quad(sfp, leds);
 | |
| 		} else {
 | |
| 			if ((mod_info->active_cable &&
 | |
| 			     mod_info->rate != CVMX_SFP_RATE_40G) ||
 | |
| 			    mod_info->rate < CVMX_SFP_RATE_25G)
 | |
| 				error = true;
 | |
| 		}
 | |
| 		break;
 | |
| 	default:
 | |
| 		debug("%s: Unsupported interface mode %d on xiface 0x%x\n",
 | |
| 		      __func__, mode, xiface);
 | |
| 		return false;
 | |
| 	}
 | |
| 	debug("%s: %s: error: %d\n", __func__, sfp->name, error);
 | |
| 	if (leds && !quad_mode)
 | |
| 		cvmx_helper_leds_show_error(leds, error);
 | |
| 
 | |
| 	return !error;
 | |
| }
 |