// SPDX-License-Identifier: GPL-2.0
|
/*
|
* This file is based on code from OCTEON SDK by Cavium Networks.
|
*
|
* Copyright (c) 2003-2007 Cavium Networks
|
*/
|
|
#include <linux/kernel.h>
|
#include <linux/netdevice.h>
|
#include <linux/interrupt.h>
|
#include <linux/phy.h>
|
#include <linux/ratelimit.h>
|
#include <net/dst.h>
|
|
#include "octeon-ethernet.h"
|
#include "ethernet-defines.h"
|
#include "ethernet-util.h"
|
#include "ethernet-mdio.h"
|
|
static DEFINE_SPINLOCK(global_register_lock);
|
|
static void cvm_oct_set_hw_preamble(struct octeon_ethernet *priv, bool enable)
|
{
|
union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl;
|
union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs;
|
union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
|
int interface = INTERFACE(priv->port);
|
int index = INDEX(priv->port);
|
|
/* Set preamble checking. */
|
gmxx_rxx_frm_ctl.u64 = cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index,
|
interface));
|
gmxx_rxx_frm_ctl.s.pre_chk = enable;
|
cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface),
|
gmxx_rxx_frm_ctl.u64);
|
|
/* Set FCS stripping. */
|
ipd_sub_port_fcs.u64 = cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS);
|
if (enable)
|
ipd_sub_port_fcs.s.port_bit |= 1ull << priv->port;
|
else
|
ipd_sub_port_fcs.s.port_bit &=
|
0xffffffffull ^ (1ull << priv->port);
|
cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, ipd_sub_port_fcs.u64);
|
|
/* Clear any error bits. */
|
gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG(index,
|
interface));
|
cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
|
gmxx_rxx_int_reg.u64);
|
}
|
|
static void cvm_oct_check_preamble_errors(struct net_device *dev)
|
{
|
struct octeon_ethernet *priv = netdev_priv(dev);
|
union cvmx_helper_link_info link_info;
|
unsigned long flags;
|
|
link_info.u64 = priv->link_info;
|
|
/*
|
* Take the global register lock since we are going to
|
* touch registers that affect more than one port.
|
*/
|
spin_lock_irqsave(&global_register_lock, flags);
|
|
if (link_info.s.speed == 10 && priv->last_speed == 10) {
|
/*
|
* Read the GMXX_RXX_INT_REG[PCTERR] bit and see if we are
|
* getting preamble errors.
|
*/
|
int interface = INTERFACE(priv->port);
|
int index = INDEX(priv->port);
|
union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
|
|
gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
|
(index, interface));
|
if (gmxx_rxx_int_reg.s.pcterr) {
|
/*
|
* We are getting preamble errors at 10Mbps. Most
|
* likely the PHY is giving us packets with misaligned
|
* preambles. In order to get these packets we need to
|
* disable preamble checking and do it in software.
|
*/
|
cvm_oct_set_hw_preamble(priv, false);
|
printk_ratelimited("%s: Using 10Mbps with software preamble removal\n",
|
dev->name);
|
}
|
} else {
|
/*
|
* Since the 10Mbps preamble workaround is allowed we need to
|
* enable preamble checking, FCS stripping, and clear error
|
* bits on every speed change. If errors occur during 10Mbps
|
* operation the above code will change this stuff
|
*/
|
if (priv->last_speed != link_info.s.speed)
|
cvm_oct_set_hw_preamble(priv, true);
|
priv->last_speed = link_info.s.speed;
|
}
|
spin_unlock_irqrestore(&global_register_lock, flags);
|
}
|
|
static void cvm_oct_rgmii_poll(struct net_device *dev)
|
{
|
struct octeon_ethernet *priv = netdev_priv(dev);
|
union cvmx_helper_link_info link_info;
|
bool status_change;
|
|
link_info = cvmx_helper_link_get(priv->port);
|
if (priv->link_info != link_info.u64 &&
|
cvmx_helper_link_set(priv->port, link_info))
|
link_info.u64 = priv->link_info;
|
status_change = priv->link_info != link_info.u64;
|
priv->link_info = link_info.u64;
|
|
cvm_oct_check_preamble_errors(dev);
|
|
if (likely(!status_change))
|
return;
|
|
/* Tell core. */
|
if (link_info.s.link_up) {
|
if (!netif_carrier_ok(dev))
|
netif_carrier_on(dev);
|
} else if (netif_carrier_ok(dev)) {
|
netif_carrier_off(dev);
|
}
|
cvm_oct_note_carrier(priv, link_info);
|
}
|
|
int cvm_oct_rgmii_open(struct net_device *dev)
|
{
|
struct octeon_ethernet *priv = netdev_priv(dev);
|
int ret;
|
|
ret = cvm_oct_common_open(dev, cvm_oct_rgmii_poll);
|
if (ret)
|
return ret;
|
|
if (dev->phydev) {
|
/*
|
* In phydev mode, we need still periodic polling for the
|
* preamble error checking, and we also need to call this
|
* function on every link state change.
|
*
|
* Only true RGMII ports need to be polled. In GMII mode, port
|
* 0 is really a RGMII port.
|
*/
|
if ((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII &&
|
priv->port == 0) ||
|
(priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) {
|
priv->poll = cvm_oct_check_preamble_errors;
|
cvm_oct_check_preamble_errors(dev);
|
}
|
}
|
|
return 0;
|
}
|