516 lines
16 KiB
C
516 lines
16 KiB
C
/***********************license start***************
|
|
* Author: Cavium Networks
|
|
*
|
|
* Contact: support@caviumnetworks.com
|
|
* 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);
|
|
}
|