mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
515 lines
16 KiB
515 lines
16 KiB
/***********************license start*************** |
|
* Author: Cavium Networks |
|
* |
|
* Contact: [email protected] |
|
* This file is part of the OCTEON SDK |
|
* |
|
* Copyright (C) 2003-2018 Cavium, Inc. |
|
* |
|
* This file is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License, Version 2, as |
|
* published by the Free Software Foundation. |
|
* |
|
* This file is distributed in the hope that it will be useful, but |
|
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty |
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or |
|
* NONINFRINGEMENT. See the GNU General Public License for more |
|
* details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this file; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
* or visit http://www.gnu.org/licenses/. |
|
* |
|
* This file may also be available under a different license from Cavium. |
|
* Contact Cavium Networks for more information |
|
***********************license end**************************************/ |
|
|
|
/* |
|
* Functions for SGMII initialization, configuration, |
|
* and monitoring. |
|
*/ |
|
|
|
#include <asm/octeon/octeon.h> |
|
|
|
#include <asm/octeon/cvmx-config.h> |
|
|
|
#include <asm/octeon/cvmx-helper.h> |
|
#include <asm/octeon/cvmx-helper-board.h> |
|
|
|
#include <asm/octeon/cvmx-gmxx-defs.h> |
|
#include <asm/octeon/cvmx-pcsx-defs.h> |
|
#include <asm/octeon/cvmx-pcsxx-defs.h> |
|
|
|
/** |
|
* Perform initialization required only once for an SGMII port. |
|
* |
|
* @interface: Interface to init |
|
* @index: Index of prot on the interface |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
static int __cvmx_helper_sgmii_hardware_init_one_time(int interface, int index) |
|
{ |
|
const uint64_t clock_mhz = cvmx_sysinfo_get()->cpu_clock_hz / 1000000; |
|
union cvmx_pcsx_miscx_ctl_reg pcs_misc_ctl_reg; |
|
union cvmx_pcsx_linkx_timer_count_reg pcsx_linkx_timer_count_reg; |
|
union cvmx_gmxx_prtx_cfg gmxx_prtx_cfg; |
|
|
|
/* Disable GMX */ |
|
gmxx_prtx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
|
gmxx_prtx_cfg.s.en = 0; |
|
cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmxx_prtx_cfg.u64); |
|
|
|
/* |
|
* Write PCS*_LINK*_TIMER_COUNT_REG[COUNT] with the |
|
* appropriate value. 1000BASE-X specifies a 10ms |
|
* interval. SGMII specifies a 1.6ms interval. |
|
*/ |
|
pcs_misc_ctl_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface)); |
|
pcsx_linkx_timer_count_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_LINKX_TIMER_COUNT_REG(index, interface)); |
|
if (pcs_misc_ctl_reg.s.mode) { |
|
/* 1000BASE-X */ |
|
pcsx_linkx_timer_count_reg.s.count = |
|
(10000ull * clock_mhz) >> 10; |
|
} else { |
|
/* SGMII */ |
|
pcsx_linkx_timer_count_reg.s.count = |
|
(1600ull * clock_mhz) >> 10; |
|
} |
|
cvmx_write_csr(CVMX_PCSX_LINKX_TIMER_COUNT_REG(index, interface), |
|
pcsx_linkx_timer_count_reg.u64); |
|
|
|
/* |
|
* Write the advertisement register to be used as the |
|
* tx_Config_Reg<D15:D0> of the autonegotiation. In |
|
* 1000BASE-X mode, tx_Config_Reg<D15:D0> is PCS*_AN*_ADV_REG. |
|
* In SGMII PHY mode, tx_Config_Reg<D15:D0> is |
|
* PCS*_SGM*_AN_ADV_REG. In SGMII MAC mode, |
|
* tx_Config_Reg<D15:D0> is the fixed value 0x4001, so this |
|
* step can be skipped. |
|
*/ |
|
if (pcs_misc_ctl_reg.s.mode) { |
|
/* 1000BASE-X */ |
|
union cvmx_pcsx_anx_adv_reg pcsx_anx_adv_reg; |
|
pcsx_anx_adv_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_ANX_ADV_REG(index, interface)); |
|
pcsx_anx_adv_reg.s.rem_flt = 0; |
|
pcsx_anx_adv_reg.s.pause = 3; |
|
pcsx_anx_adv_reg.s.hfd = 1; |
|
pcsx_anx_adv_reg.s.fd = 1; |
|
cvmx_write_csr(CVMX_PCSX_ANX_ADV_REG(index, interface), |
|
pcsx_anx_adv_reg.u64); |
|
} else { |
|
union cvmx_pcsx_miscx_ctl_reg pcsx_miscx_ctl_reg; |
|
pcsx_miscx_ctl_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface)); |
|
if (pcsx_miscx_ctl_reg.s.mac_phy) { |
|
/* PHY Mode */ |
|
union cvmx_pcsx_sgmx_an_adv_reg pcsx_sgmx_an_adv_reg; |
|
pcsx_sgmx_an_adv_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_SGMX_AN_ADV_REG |
|
(index, interface)); |
|
pcsx_sgmx_an_adv_reg.s.link = 1; |
|
pcsx_sgmx_an_adv_reg.s.dup = 1; |
|
pcsx_sgmx_an_adv_reg.s.speed = 2; |
|
cvmx_write_csr(CVMX_PCSX_SGMX_AN_ADV_REG |
|
(index, interface), |
|
pcsx_sgmx_an_adv_reg.u64); |
|
} else { |
|
/* MAC Mode - Nothing to do */ |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
/** |
|
* Initialize the SERTES link for the first time or after a loss |
|
* of link. |
|
* |
|
* @interface: Interface to init |
|
* @index: Index of prot on the interface |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
static int __cvmx_helper_sgmii_hardware_init_link(int interface, int index) |
|
{ |
|
union cvmx_pcsx_mrx_control_reg control_reg; |
|
|
|
/* |
|
* Take PCS through a reset sequence. |
|
* PCS*_MR*_CONTROL_REG[PWR_DN] should be cleared to zero. |
|
* Write PCS*_MR*_CONTROL_REG[RESET]=1 (while not changing the |
|
* value of the other PCS*_MR*_CONTROL_REG bits). Read |
|
* PCS*_MR*_CONTROL_REG[RESET] until it changes value to |
|
* zero. |
|
*/ |
|
control_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MRX_CONTROL_REG(index, interface)); |
|
if (cvmx_sysinfo_get()->board_type != CVMX_BOARD_TYPE_SIM) { |
|
control_reg.s.reset = 1; |
|
cvmx_write_csr(CVMX_PCSX_MRX_CONTROL_REG(index, interface), |
|
control_reg.u64); |
|
if (CVMX_WAIT_FOR_FIELD64 |
|
(CVMX_PCSX_MRX_CONTROL_REG(index, interface), |
|
union cvmx_pcsx_mrx_control_reg, reset, ==, 0, 10000)) { |
|
cvmx_dprintf("SGMII%d: Timeout waiting for port %d " |
|
"to finish reset\n", |
|
interface, index); |
|
return -1; |
|
} |
|
} |
|
|
|
/* |
|
* Write PCS*_MR*_CONTROL_REG[RST_AN]=1 to ensure a fresh |
|
* sgmii negotiation starts. |
|
*/ |
|
control_reg.s.rst_an = 1; |
|
control_reg.s.an_en = 1; |
|
control_reg.s.pwr_dn = 0; |
|
cvmx_write_csr(CVMX_PCSX_MRX_CONTROL_REG(index, interface), |
|
control_reg.u64); |
|
|
|
/* |
|
* Wait for PCS*_MR*_STATUS_REG[AN_CPT] to be set, indicating |
|
* that sgmii autonegotiation is complete. In MAC mode this |
|
* isn't an ethernet link, but a link between Octeon and the |
|
* PHY. |
|
*/ |
|
if ((cvmx_sysinfo_get()->board_type != CVMX_BOARD_TYPE_SIM) && |
|
CVMX_WAIT_FOR_FIELD64(CVMX_PCSX_MRX_STATUS_REG(index, interface), |
|
union cvmx_pcsx_mrx_status_reg, an_cpt, ==, 1, |
|
10000)) { |
|
/* cvmx_dprintf("SGMII%d: Port %d link timeout\n", interface, index); */ |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/** |
|
* Configure an SGMII link to the specified speed after the SERTES |
|
* link is up. |
|
* |
|
* @interface: Interface to init |
|
* @index: Index of prot on the interface |
|
* @link_info: Link state to configure |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
static int __cvmx_helper_sgmii_hardware_init_link_speed(int interface, |
|
int index, |
|
union cvmx_helper_link_info |
|
link_info) |
|
{ |
|
int is_enabled; |
|
union cvmx_gmxx_prtx_cfg gmxx_prtx_cfg; |
|
union cvmx_pcsx_miscx_ctl_reg pcsx_miscx_ctl_reg; |
|
|
|
/* Disable GMX before we make any changes. Remember the enable state */ |
|
gmxx_prtx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
|
is_enabled = gmxx_prtx_cfg.s.en; |
|
gmxx_prtx_cfg.s.en = 0; |
|
cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmxx_prtx_cfg.u64); |
|
|
|
/* Wait for GMX to be idle */ |
|
if (CVMX_WAIT_FOR_FIELD64 |
|
(CVMX_GMXX_PRTX_CFG(index, interface), union cvmx_gmxx_prtx_cfg, |
|
rx_idle, ==, 1, 10000) |
|
|| CVMX_WAIT_FOR_FIELD64(CVMX_GMXX_PRTX_CFG(index, interface), |
|
union cvmx_gmxx_prtx_cfg, tx_idle, ==, 1, |
|
10000)) { |
|
cvmx_dprintf |
|
("SGMII%d: Timeout waiting for port %d to be idle\n", |
|
interface, index); |
|
return -1; |
|
} |
|
|
|
/* Read GMX CFG again to make sure the disable completed */ |
|
gmxx_prtx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
|
|
|
/* |
|
* Get the misc control for PCS. We will need to set the |
|
* duplication amount. |
|
*/ |
|
pcsx_miscx_ctl_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface)); |
|
|
|
/* |
|
* Use GMXENO to force the link down if the status we get says |
|
* it should be down. |
|
*/ |
|
pcsx_miscx_ctl_reg.s.gmxeno = !link_info.s.link_up; |
|
|
|
/* Only change the duplex setting if the link is up */ |
|
if (link_info.s.link_up) |
|
gmxx_prtx_cfg.s.duplex = link_info.s.full_duplex; |
|
|
|
/* Do speed based setting for GMX */ |
|
switch (link_info.s.speed) { |
|
case 10: |
|
gmxx_prtx_cfg.s.speed = 0; |
|
gmxx_prtx_cfg.s.speed_msb = 1; |
|
gmxx_prtx_cfg.s.slottime = 0; |
|
/* Setting from GMX-603 */ |
|
pcsx_miscx_ctl_reg.s.samp_pt = 25; |
|
cvmx_write_csr(CVMX_GMXX_TXX_SLOT(index, interface), 64); |
|
cvmx_write_csr(CVMX_GMXX_TXX_BURST(index, interface), 0); |
|
break; |
|
case 100: |
|
gmxx_prtx_cfg.s.speed = 0; |
|
gmxx_prtx_cfg.s.speed_msb = 0; |
|
gmxx_prtx_cfg.s.slottime = 0; |
|
pcsx_miscx_ctl_reg.s.samp_pt = 0x5; |
|
cvmx_write_csr(CVMX_GMXX_TXX_SLOT(index, interface), 64); |
|
cvmx_write_csr(CVMX_GMXX_TXX_BURST(index, interface), 0); |
|
break; |
|
case 1000: |
|
gmxx_prtx_cfg.s.speed = 1; |
|
gmxx_prtx_cfg.s.speed_msb = 0; |
|
gmxx_prtx_cfg.s.slottime = 1; |
|
pcsx_miscx_ctl_reg.s.samp_pt = 1; |
|
cvmx_write_csr(CVMX_GMXX_TXX_SLOT(index, interface), 512); |
|
cvmx_write_csr(CVMX_GMXX_TXX_BURST(index, interface), 8192); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
/* Write the new misc control for PCS */ |
|
cvmx_write_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface), |
|
pcsx_miscx_ctl_reg.u64); |
|
|
|
/* Write the new GMX settings with the port still disabled */ |
|
cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmxx_prtx_cfg.u64); |
|
|
|
/* Read GMX CFG again to make sure the config completed */ |
|
gmxx_prtx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
|
|
|
/* Restore the enabled / disabled state */ |
|
gmxx_prtx_cfg.s.en = is_enabled; |
|
cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmxx_prtx_cfg.u64); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* Bring up the SGMII interface to be ready for packet I/O but |
|
* leave I/O disabled using the GMX override. This function |
|
* follows the bringup documented in 10.6.3 of the manual. |
|
* |
|
* @interface: Interface to bringup |
|
* @num_ports: Number of ports on the interface |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
static int __cvmx_helper_sgmii_hardware_init(int interface, int num_ports) |
|
{ |
|
int index; |
|
|
|
__cvmx_helper_setup_gmx(interface, num_ports); |
|
|
|
for (index = 0; index < num_ports; index++) { |
|
int ipd_port = cvmx_helper_get_ipd_port(interface, index); |
|
__cvmx_helper_sgmii_hardware_init_one_time(interface, index); |
|
/* Linux kernel driver will call ....link_set with the |
|
* proper link state. In the simulator there is no |
|
* link state polling and hence it is set from |
|
* here. |
|
*/ |
|
if (cvmx_sysinfo_get()->board_type == CVMX_BOARD_TYPE_SIM) |
|
__cvmx_helper_sgmii_link_set(ipd_port, |
|
__cvmx_helper_sgmii_link_get(ipd_port)); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int __cvmx_helper_sgmii_enumerate(int interface) |
|
{ |
|
return 4; |
|
} |
|
/** |
|
* Probe a SGMII interface and determine the number of ports |
|
* connected to it. The SGMII interface should still be down after |
|
* this call. |
|
* |
|
* @interface: Interface to probe |
|
* |
|
* Returns Number of ports on the interface. Zero to disable. |
|
*/ |
|
int __cvmx_helper_sgmii_probe(int interface) |
|
{ |
|
union cvmx_gmxx_inf_mode mode; |
|
|
|
/* |
|
* Due to errata GMX-700 on CN56XXp1.x and CN52XXp1.x, the |
|
* interface needs to be enabled before IPD otherwise per port |
|
* backpressure may not work properly |
|
*/ |
|
mode.u64 = cvmx_read_csr(CVMX_GMXX_INF_MODE(interface)); |
|
mode.s.en = 1; |
|
cvmx_write_csr(CVMX_GMXX_INF_MODE(interface), mode.u64); |
|
return __cvmx_helper_sgmii_enumerate(interface); |
|
} |
|
|
|
/** |
|
* Bringup and enable a SGMII interface. After this call packet |
|
* I/O should be fully functional. This is called with IPD |
|
* enabled but PKO disabled. |
|
* |
|
* @interface: Interface to bring up |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
int __cvmx_helper_sgmii_enable(int interface) |
|
{ |
|
int num_ports = cvmx_helper_ports_on_interface(interface); |
|
int index; |
|
|
|
__cvmx_helper_sgmii_hardware_init(interface, num_ports); |
|
|
|
for (index = 0; index < num_ports; index++) { |
|
union cvmx_gmxx_prtx_cfg gmxx_prtx_cfg; |
|
gmxx_prtx_cfg.u64 = |
|
cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
|
gmxx_prtx_cfg.s.en = 1; |
|
cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), |
|
gmxx_prtx_cfg.u64); |
|
__cvmx_interrupt_pcsx_intx_en_reg_enable(index, interface); |
|
} |
|
__cvmx_interrupt_pcsxx_int_en_reg_enable(interface); |
|
__cvmx_interrupt_gmxx_enable(interface); |
|
return 0; |
|
} |
|
|
|
/** |
|
* Return the link state of an IPD/PKO port as returned by |
|
* auto negotiation. The result of this function may not match |
|
* Octeon's link config if auto negotiation has changed since |
|
* the last call to cvmx_helper_link_set(). |
|
* |
|
* @ipd_port: IPD/PKO port to query |
|
* |
|
* Returns Link state |
|
*/ |
|
union cvmx_helper_link_info __cvmx_helper_sgmii_link_get(int ipd_port) |
|
{ |
|
union cvmx_helper_link_info result; |
|
union cvmx_pcsx_miscx_ctl_reg pcs_misc_ctl_reg; |
|
int interface = cvmx_helper_get_interface_num(ipd_port); |
|
int index = cvmx_helper_get_interface_index_num(ipd_port); |
|
union cvmx_pcsx_mrx_control_reg pcsx_mrx_control_reg; |
|
|
|
result.u64 = 0; |
|
|
|
if (cvmx_sysinfo_get()->board_type == CVMX_BOARD_TYPE_SIM) { |
|
/* The simulator gives you a simulated 1Gbps full duplex link */ |
|
result.s.link_up = 1; |
|
result.s.full_duplex = 1; |
|
result.s.speed = 1000; |
|
return result; |
|
} |
|
|
|
pcsx_mrx_control_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MRX_CONTROL_REG(index, interface)); |
|
if (pcsx_mrx_control_reg.s.loopbck1) { |
|
/* Force 1Gbps full duplex link for internal loopback */ |
|
result.s.link_up = 1; |
|
result.s.full_duplex = 1; |
|
result.s.speed = 1000; |
|
return result; |
|
} |
|
|
|
pcs_misc_ctl_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface)); |
|
if (pcs_misc_ctl_reg.s.mode) { |
|
/* 1000BASE-X */ |
|
/* FIXME */ |
|
} else { |
|
union cvmx_pcsx_miscx_ctl_reg pcsx_miscx_ctl_reg; |
|
pcsx_miscx_ctl_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MISCX_CTL_REG(index, interface)); |
|
if (pcsx_miscx_ctl_reg.s.mac_phy) { |
|
/* PHY Mode */ |
|
union cvmx_pcsx_mrx_status_reg pcsx_mrx_status_reg; |
|
union cvmx_pcsx_anx_results_reg pcsx_anx_results_reg; |
|
|
|
/* |
|
* Don't bother continuing if the SERTES low |
|
* level link is down |
|
*/ |
|
pcsx_mrx_status_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_MRX_STATUS_REG |
|
(index, interface)); |
|
if (pcsx_mrx_status_reg.s.lnk_st == 0) { |
|
if (__cvmx_helper_sgmii_hardware_init_link |
|
(interface, index) != 0) |
|
return result; |
|
} |
|
|
|
/* Read the autoneg results */ |
|
pcsx_anx_results_reg.u64 = |
|
cvmx_read_csr(CVMX_PCSX_ANX_RESULTS_REG |
|
(index, interface)); |
|
if (pcsx_anx_results_reg.s.an_cpt) { |
|
/* |
|
* Auto negotiation is complete. Set |
|
* status accordingly. |
|
*/ |
|
result.s.full_duplex = |
|
pcsx_anx_results_reg.s.dup; |
|
result.s.link_up = |
|
pcsx_anx_results_reg.s.link_ok; |
|
switch (pcsx_anx_results_reg.s.spd) { |
|
case 0: |
|
result.s.speed = 10; |
|
break; |
|
case 1: |
|
result.s.speed = 100; |
|
break; |
|
case 2: |
|
result.s.speed = 1000; |
|
break; |
|
default: |
|
result.s.speed = 0; |
|
result.s.link_up = 0; |
|
break; |
|
} |
|
} else { |
|
/* |
|
* Auto negotiation isn't |
|
* complete. Return link down. |
|
*/ |
|
result.s.speed = 0; |
|
result.s.link_up = 0; |
|
} |
|
} else { /* MAC Mode */ |
|
|
|
result = __cvmx_helper_board_link_get(ipd_port); |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
/** |
|
* Configure an IPD/PKO port for the specified link state. This |
|
* function does not influence auto negotiation at the PHY level. |
|
* The passed link state must always match the link state returned |
|
* by cvmx_helper_link_get(). |
|
* |
|
* @ipd_port: IPD/PKO port to configure |
|
* @link_info: The new link state |
|
* |
|
* Returns Zero on success, negative on failure |
|
*/ |
|
int __cvmx_helper_sgmii_link_set(int ipd_port, |
|
union cvmx_helper_link_info link_info) |
|
{ |
|
int interface = cvmx_helper_get_interface_num(ipd_port); |
|
int index = cvmx_helper_get_interface_index_num(ipd_port); |
|
__cvmx_helper_sgmii_hardware_init_link(interface, index); |
|
return __cvmx_helper_sgmii_hardware_init_link_speed(interface, index, |
|
link_info); |
|
}
|
|
|