diff --git a/drivers/staging/octeon/ethernet-mdio.c b/drivers/staging/octeon/ethernet-mdio.c index fd9b3d899c1f..55abd83c38b6 100644 --- a/drivers/staging/octeon/ethernet-mdio.c +++ b/drivers/staging/octeon/ethernet-mdio.c @@ -118,13 +118,20 @@ void cvm_oct_adjust_link(struct net_device *dev) struct octeon_ethernet *priv = netdev_priv(dev); cvmx_helper_link_info_t link_info; + link_info.u64 = 0; + link_info.s.link_up = priv->phydev->link ? 1 : 0; + link_info.s.full_duplex = priv->phydev->duplex ? 1 : 0; + link_info.s.speed = priv->phydev->speed; + priv->link_info = link_info.u64; + + /* + * The polling task need to know about link status changes. + */ + if (priv->poll) + priv->poll(dev); + if (priv->last_link != priv->phydev->link) { priv->last_link = priv->phydev->link; - link_info.u64 = 0; - link_info.s.link_up = priv->last_link ? 1 : 0; - link_info.s.full_duplex = priv->phydev->duplex ? 1 : 0; - link_info.s.speed = priv->phydev->speed; - cvmx_helper_link_set(priv->port, link_info); cvm_oct_note_carrier(priv, link_info); } diff --git a/drivers/staging/octeon/ethernet-rgmii.c b/drivers/staging/octeon/ethernet-rgmii.c index 9353796af0b6..91b148cfcbdb 100644 --- a/drivers/staging/octeon/ethernet-rgmii.c +++ b/drivers/staging/octeon/ethernet-rgmii.c @@ -30,8 +30,6 @@ 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; @@ -63,247 +61,106 @@ static void cvm_oct_set_hw_preamble(struct octeon_ethernet *priv, bool enable) gmxx_rxx_int_reg.u64); } +static void cvm_oct_check_preamble_errors(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvmx_helper_link_info_t 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); - unsigned long flags = 0; cvmx_helper_link_info_t link_info; - int use_global_register_lock = (priv->phydev == NULL); + bool status_change; - 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->mdio.bus->mdio_lock); - } + link_info = cvmx_helper_link_autoconf(priv->port); + status_change = priv->link_info != link_info.u64; + priv->link_info = link_info.u64; - 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; + cvm_oct_check_preamble_errors(dev); - 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->mdio.bus->mdio_lock); + 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); } - - /* 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->mdio.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; + cvm_oct_note_carrier(priv, link_info); } 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; + int ret; - 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++; + ret = cvm_oct_common_open(dev, cvm_oct_rgmii_poll); + if (ret) + return ret; - /* - * 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); + if (priv->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; } - -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); -} diff --git a/drivers/staging/octeon/ethernet.c b/drivers/staging/octeon/ethernet.c index f69fb5cc7cb8..8d239e23e5c7 100644 --- a/drivers/staging/octeon/ethernet.c +++ b/drivers/staging/octeon/ethernet.c @@ -601,8 +601,8 @@ static const struct net_device_ops cvm_oct_spi_netdev_ops = { #endif }; static const struct net_device_ops cvm_oct_rgmii_netdev_ops = { - .ndo_init = cvm_oct_rgmii_init, - .ndo_uninit = cvm_oct_rgmii_uninit, + .ndo_init = cvm_oct_common_init, + .ndo_uninit = cvm_oct_common_uninit, .ndo_open = cvm_oct_rgmii_open, .ndo_stop = cvm_oct_common_stop, .ndo_start_xmit = cvm_oct_xmit, diff --git a/drivers/staging/octeon/octeon-ethernet.h b/drivers/staging/octeon/octeon-ethernet.h index fdf24d120e77..5b4fdd21ef57 100644 --- a/drivers/staging/octeon/octeon-ethernet.h +++ b/drivers/staging/octeon/octeon-ethernet.h @@ -41,20 +41,18 @@ struct octeon_ethernet { /* Device statistics */ struct net_device_stats stats; struct phy_device *phydev; + unsigned int last_speed; unsigned int last_link; /* Last negotiated link state */ u64 link_info; /* Called periodically to check link status */ void (*poll)(struct net_device *dev); struct delayed_work port_periodic_work; - struct work_struct port_work; /* may be unused. */ struct device_node *of_node; }; int cvm_oct_free_work(void *work_queue_entry); -int cvm_oct_rgmii_init(struct net_device *dev); -void cvm_oct_rgmii_uninit(struct net_device *dev); int cvm_oct_rgmii_open(struct net_device *dev); int cvm_oct_sgmii_init(struct net_device *dev);