2 * This file is based on code from OCTEON SDK by Cavium Networks.
4 * Copyright (c) 2003-2007 Cavium Networks
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License, Version 2, as
8 * published by the Free Software Foundation.
11 #include <linux/kernel.h>
12 #include <linux/netdevice.h>
13 #include <linux/interrupt.h>
14 #include <linux/phy.h>
15 #include <linux/ratelimit.h>
18 #include <asm/octeon/octeon.h>
20 #include "ethernet-defines.h"
21 #include "octeon-ethernet.h"
22 #include "ethernet-util.h"
23 #include "ethernet-mdio.h"
25 #include <asm/octeon/cvmx-helper.h>
27 #include <asm/octeon/cvmx-ipd-defs.h>
28 #include <asm/octeon/cvmx-npi-defs.h>
29 #include <asm/octeon/cvmx-gmxx-defs.h>
31 static DEFINE_SPINLOCK(global_register_lock);
33 static int number_rgmii_ports;
35 static void cvm_oct_set_hw_preamble(struct octeon_ethernet *priv, bool enable)
37 union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl;
38 union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs;
39 union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
40 int interface = INTERFACE(priv->port);
41 int index = INDEX(priv->port);
43 /* Set preamble checking. */
44 gmxx_rxx_frm_ctl.u64 = cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index,
46 gmxx_rxx_frm_ctl.s.pre_chk = enable;
47 cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface),
48 gmxx_rxx_frm_ctl.u64);
50 /* Set FCS stripping. */
51 ipd_sub_port_fcs.u64 = cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS);
53 ipd_sub_port_fcs.s.port_bit |= 1ull << priv->port;
55 ipd_sub_port_fcs.s.port_bit &=
56 0xffffffffull ^ (1ull << priv->port);
57 cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, ipd_sub_port_fcs.u64);
59 /* Clear any error bits. */
60 gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG(index,
62 cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
63 gmxx_rxx_int_reg.u64);
66 static void cvm_oct_rgmii_poll(struct net_device *dev)
68 struct octeon_ethernet *priv = netdev_priv(dev);
69 unsigned long flags = 0;
70 cvmx_helper_link_info_t link_info;
71 int use_global_register_lock = (priv->phydev == NULL);
73 BUG_ON(in_interrupt());
74 if (use_global_register_lock) {
76 * Take the global register lock since we are going to
77 * touch registers that affect more than one port.
79 spin_lock_irqsave(&global_register_lock, flags);
81 mutex_lock(&priv->phydev->bus->mdio_lock);
84 link_info = cvmx_helper_link_get(priv->port);
85 if (link_info.u64 == priv->link_info) {
86 if (link_info.s.speed == 10) {
88 * Read the GMXX_RXX_INT_REG[PCTERR] bit and
89 * see if we are getting preamble errors.
91 int interface = INTERFACE(priv->port);
92 int index = INDEX(priv->port);
93 union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
95 gmxx_rxx_int_reg.u64 =
96 cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
98 if (gmxx_rxx_int_reg.s.pcterr) {
100 * We are getting preamble errors at
101 * 10Mbps. Most likely the PHY is
102 * giving us packets with mis aligned
103 * preambles. In order to get these
104 * packets we need to disable preamble
105 * checking and do it in software.
107 cvm_oct_set_hw_preamble(priv, false);
108 printk_ratelimited("%s: Using 10Mbps with software preamble removal\n",
113 if (use_global_register_lock)
114 spin_unlock_irqrestore(&global_register_lock, flags);
116 mutex_unlock(&priv->phydev->bus->mdio_lock);
120 /* Since the 10Mbps preamble workaround is allowed we need to enable
121 preamble checking, FCS stripping, and clear error bits on
122 every speed change. If errors occur during 10Mbps operation
123 the above code will change this stuff */
124 cvm_oct_set_hw_preamble(priv, true);
126 if (priv->phydev == NULL) {
127 link_info = cvmx_helper_link_autoconf(priv->port);
128 priv->link_info = link_info.u64;
131 if (use_global_register_lock)
132 spin_unlock_irqrestore(&global_register_lock, flags);
134 mutex_unlock(&priv->phydev->bus->mdio_lock);
136 if (priv->phydev == NULL) {
138 if (link_info.s.link_up) {
139 if (!netif_carrier_ok(dev))
140 netif_carrier_on(dev);
141 } else if (netif_carrier_ok(dev)) {
142 netif_carrier_off(dev);
144 cvm_oct_note_carrier(priv, link_info);
148 static int cmv_oct_rgmii_gmx_interrupt(int interface)
153 /* Loop through every port of this interface */
155 index < cvmx_helper_ports_on_interface(interface);
157 union cvmx_gmxx_rxx_int_reg gmx_rx_int_reg;
159 /* Read the GMX interrupt status bits */
160 gmx_rx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
162 gmx_rx_int_reg.u64 &= cvmx_read_csr(CVMX_GMXX_RXX_INT_EN
165 /* Poll the port if inband status changed */
166 if (gmx_rx_int_reg.s.phy_dupx || gmx_rx_int_reg.s.phy_link ||
167 gmx_rx_int_reg.s.phy_spd) {
168 struct net_device *dev =
169 cvm_oct_device[cvmx_helper_get_ipd_port
171 struct octeon_ethernet *priv = netdev_priv(dev);
173 if (dev && !atomic_read(&cvm_oct_poll_queue_stopping))
174 queue_work(cvm_oct_poll_queue,
177 gmx_rx_int_reg.u64 = 0;
178 gmx_rx_int_reg.s.phy_dupx = 1;
179 gmx_rx_int_reg.s.phy_link = 1;
180 gmx_rx_int_reg.s.phy_spd = 1;
181 cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
189 static irqreturn_t cvm_oct_rgmii_rml_interrupt(int cpl, void *dev_id)
191 union cvmx_npi_rsl_int_blocks rsl_int_blocks;
194 rsl_int_blocks.u64 = cvmx_read_csr(CVMX_NPI_RSL_INT_BLOCKS);
196 /* Check and see if this interrupt was caused by the GMX0 block */
197 if (rsl_int_blocks.s.gmx0)
198 count += cmv_oct_rgmii_gmx_interrupt(0);
200 /* Check and see if this interrupt was caused by the GMX1 block */
201 if (rsl_int_blocks.s.gmx1)
202 count += cmv_oct_rgmii_gmx_interrupt(1);
204 return count ? IRQ_HANDLED : IRQ_NONE;
207 int cvm_oct_rgmii_open(struct net_device *dev)
209 return cvm_oct_common_open(dev, cvm_oct_rgmii_poll, false);
212 static void cvm_oct_rgmii_immediate_poll(struct work_struct *work)
214 struct octeon_ethernet *priv =
215 container_of(work, struct octeon_ethernet, port_work);
216 cvm_oct_rgmii_poll(cvm_oct_device[priv->port]);
219 int cvm_oct_rgmii_init(struct net_device *dev)
221 struct octeon_ethernet *priv = netdev_priv(dev);
224 cvm_oct_common_init(dev);
225 INIT_WORK(&priv->port_work, cvm_oct_rgmii_immediate_poll);
227 * Due to GMX errata in CN3XXX series chips, it is necessary
228 * to take the link down immediately when the PHY changes
229 * state. In order to do this we call the poll function every
230 * time the RGMII inband status changes. This may cause
231 * problems if the PHY doesn't implement inband status
234 if (number_rgmii_ports == 0) {
235 r = request_irq(OCTEON_IRQ_RML, cvm_oct_rgmii_rml_interrupt,
236 IRQF_SHARED, "RGMII", &number_rgmii_ports);
240 number_rgmii_ports++;
243 * Only true RGMII ports need to be polled. In GMII mode, port
244 * 0 is really a RGMII port.
246 if (((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII)
247 && (priv->port == 0))
248 || (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) {
250 if (!octeon_is_simulation()) {
252 union cvmx_gmxx_rxx_int_en gmx_rx_int_en;
253 int interface = INTERFACE(priv->port);
254 int index = INDEX(priv->port);
257 * Enable interrupts on inband status changes
260 gmx_rx_int_en.u64 = 0;
261 gmx_rx_int_en.s.phy_dupx = 1;
262 gmx_rx_int_en.s.phy_link = 1;
263 gmx_rx_int_en.s.phy_spd = 1;
264 cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface),
272 void cvm_oct_rgmii_uninit(struct net_device *dev)
274 struct octeon_ethernet *priv = netdev_priv(dev);
276 cvm_oct_common_uninit(dev);
279 * Only true RGMII ports need to be polled. In GMII mode, port
280 * 0 is really a RGMII port.
282 if (((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII)
283 && (priv->port == 0))
284 || (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) {
286 if (!octeon_is_simulation()) {
288 union cvmx_gmxx_rxx_int_en gmx_rx_int_en;
289 int interface = INTERFACE(priv->port);
290 int index = INDEX(priv->port);
293 * Disable interrupts on inband status changes
297 cvmx_read_csr(CVMX_GMXX_RXX_INT_EN
299 gmx_rx_int_en.s.phy_dupx = 0;
300 gmx_rx_int_en.s.phy_link = 0;
301 gmx_rx_int_en.s.phy_spd = 0;
302 cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface),
307 /* Remove the interrupt handler when the last port is removed. */
308 number_rgmii_ports--;
309 if (number_rgmii_ports == 0)
310 free_irq(OCTEON_IRQ_RML, &number_rgmii_ports);
311 cancel_work_sync(&priv->port_work);