123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /*
- * This file is based on code from OCTEON SDK by Cavium Networks.
- *
- * Copyright (c) 2003-2007 Cavium Networks
- *
- * 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.
- */
- #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 <asm/octeon/octeon.h>
- #include "ethernet-defines.h"
- #include "octeon-ethernet.h"
- #include "ethernet-util.h"
- #include "ethernet-mdio.h"
- #include <asm/octeon/cvmx-helper.h>
- #include <asm/octeon/cvmx-ipd-defs.h>
- #include <asm/octeon/cvmx-npi-defs.h>
- #include <asm/octeon/cvmx-gmxx-defs.h>
- static DEFINE_SPINLOCK(global_register_lock);
- static int number_rgmii_ports;
- 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_rgmii_poll(struct net_device *dev)
- {
- struct octeon_ethernet *priv = netdev_priv(dev);
- unsigned long flags = 0;
- cvmx_helper_link_info_t link_info;
- int use_global_register_lock = (priv->phydev == NULL);
- BUG_ON(in_interrupt());
- if (use_global_register_lock) {
- /*
- * 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);
- } else {
- mutex_lock(&priv->phydev->bus->mdio_lock);
- }
- link_info = cvmx_helper_link_get(priv->port);
- if (link_info.u64 == priv->link_info) {
- if (link_info.s.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 mis aligned
- * 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);
- }
- }
- if (use_global_register_lock)
- spin_unlock_irqrestore(&global_register_lock, flags);
- else
- mutex_unlock(&priv->phydev->bus->mdio_lock);
- return;
- }
- /* 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
- */
- cvm_oct_set_hw_preamble(priv, true);
- if (priv->phydev == NULL) {
- link_info = cvmx_helper_link_autoconf(priv->port);
- priv->link_info = link_info.u64;
- }
- if (use_global_register_lock)
- spin_unlock_irqrestore(&global_register_lock, flags);
- else
- mutex_unlock(&priv->phydev->bus->mdio_lock);
- if (priv->phydev == NULL) {
- /* 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);
- }
- }
- static int cmv_oct_rgmii_gmx_interrupt(int interface)
- {
- int index;
- int count = 0;
- /* Loop through every port of this interface */
- for (index = 0;
- index < cvmx_helper_ports_on_interface(interface);
- index++) {
- union cvmx_gmxx_rxx_int_reg gmx_rx_int_reg;
- /* Read the GMX interrupt status bits */
- gmx_rx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
- (index, interface));
- gmx_rx_int_reg.u64 &= cvmx_read_csr(CVMX_GMXX_RXX_INT_EN
- (index, interface));
- /* Poll the port if inband status changed */
- if (gmx_rx_int_reg.s.phy_dupx || gmx_rx_int_reg.s.phy_link ||
- gmx_rx_int_reg.s.phy_spd) {
- struct net_device *dev =
- cvm_oct_device[cvmx_helper_get_ipd_port
- (interface, index)];
- struct octeon_ethernet *priv = netdev_priv(dev);
- if (dev && !atomic_read(&cvm_oct_poll_queue_stopping))
- queue_work(cvm_oct_poll_queue,
- &priv->port_work);
- gmx_rx_int_reg.u64 = 0;
- gmx_rx_int_reg.s.phy_dupx = 1;
- gmx_rx_int_reg.s.phy_link = 1;
- gmx_rx_int_reg.s.phy_spd = 1;
- cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
- gmx_rx_int_reg.u64);
- count++;
- }
- }
- return count;
- }
- static irqreturn_t cvm_oct_rgmii_rml_interrupt(int cpl, void *dev_id)
- {
- union cvmx_npi_rsl_int_blocks rsl_int_blocks;
- int count = 0;
- rsl_int_blocks.u64 = cvmx_read_csr(CVMX_NPI_RSL_INT_BLOCKS);
- /* Check and see if this interrupt was caused by the GMX0 block */
- if (rsl_int_blocks.s.gmx0)
- count += cmv_oct_rgmii_gmx_interrupt(0);
- /* Check and see if this interrupt was caused by the GMX1 block */
- if (rsl_int_blocks.s.gmx1)
- count += cmv_oct_rgmii_gmx_interrupt(1);
- return count ? IRQ_HANDLED : IRQ_NONE;
- }
- int cvm_oct_rgmii_open(struct net_device *dev)
- {
- return cvm_oct_common_open(dev, cvm_oct_rgmii_poll);
- }
- static void cvm_oct_rgmii_immediate_poll(struct work_struct *work)
- {
- struct octeon_ethernet *priv =
- container_of(work, struct octeon_ethernet, port_work);
- cvm_oct_rgmii_poll(cvm_oct_device[priv->port]);
- }
- int cvm_oct_rgmii_init(struct net_device *dev)
- {
- struct octeon_ethernet *priv = netdev_priv(dev);
- int r;
- cvm_oct_common_init(dev);
- INIT_WORK(&priv->port_work, cvm_oct_rgmii_immediate_poll);
- /*
- * Due to GMX errata in CN3XXX series chips, it is necessary
- * to take the link down immediately when the PHY changes
- * state. In order to do this we call the poll function every
- * time the RGMII inband status changes. This may cause
- * problems if the PHY doesn't implement inband status
- * properly.
- */
- if (number_rgmii_ports == 0) {
- r = request_irq(OCTEON_IRQ_RML, cvm_oct_rgmii_rml_interrupt,
- IRQF_SHARED, "RGMII", &number_rgmii_ports);
- if (r != 0)
- return r;
- }
- number_rgmii_ports++;
- /*
- * 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)) {
- if (!octeon_is_simulation()) {
- union cvmx_gmxx_rxx_int_en gmx_rx_int_en;
- int interface = INTERFACE(priv->port);
- int index = INDEX(priv->port);
- /*
- * Enable interrupts on inband status changes
- * for this port.
- */
- gmx_rx_int_en.u64 = 0;
- gmx_rx_int_en.s.phy_dupx = 1;
- gmx_rx_int_en.s.phy_link = 1;
- gmx_rx_int_en.s.phy_spd = 1;
- cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface),
- gmx_rx_int_en.u64);
- }
- }
- return 0;
- }
- void cvm_oct_rgmii_uninit(struct net_device *dev)
- {
- struct octeon_ethernet *priv = netdev_priv(dev);
- cvm_oct_common_uninit(dev);
- /*
- * 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)) {
- if (!octeon_is_simulation()) {
- union cvmx_gmxx_rxx_int_en gmx_rx_int_en;
- int interface = INTERFACE(priv->port);
- int index = INDEX(priv->port);
- /*
- * Disable interrupts on inband status changes
- * for this port.
- */
- gmx_rx_int_en.u64 =
- cvmx_read_csr(CVMX_GMXX_RXX_INT_EN
- (index, interface));
- gmx_rx_int_en.s.phy_dupx = 0;
- gmx_rx_int_en.s.phy_link = 0;
- gmx_rx_int_en.s.phy_spd = 0;
- cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface),
- gmx_rx_int_en.u64);
- }
- }
- /* Remove the interrupt handler when the last port is removed. */
- number_rgmii_ports--;
- if (number_rgmii_ports == 0)
- free_irq(OCTEON_IRQ_RML, &number_rgmii_ports);
- cancel_work_sync(&priv->port_work);
- }
|