root/drivers/staging/octeon/ethernet-rgmii.c
// 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;
}