From c23c5c1663b36c1428df32d1645232662459e02f Mon Sep 17 00:00:00 2001 From: Jeff Kirsher Date: Wed, 15 Jun 2011 09:51:27 -0700 Subject: octeon: Move the Cavium driver Move the Cavium driver to drivers/net/ethernet/octeon/ and make the necessary Kconfig and Makefile changes. CC: David Daney Signed-off-by: Jeff Kirsher Acked-by: David Daney --- drivers/net/Kconfig | 2 - drivers/net/Makefile | 1 - drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/octeon/Kconfig | 14 + drivers/net/ethernet/octeon/Makefile | 5 + drivers/net/ethernet/octeon/octeon_mgmt.c | 1166 +++++++++++++++++++++++++++++ drivers/net/octeon/Kconfig | 10 - drivers/net/octeon/Makefile | 2 - drivers/net/octeon/octeon_mgmt.c | 1166 ----------------------------- 10 files changed, 1187 insertions(+), 1181 deletions(-) create mode 100644 drivers/net/ethernet/octeon/Kconfig create mode 100644 drivers/net/ethernet/octeon/Makefile create mode 100644 drivers/net/ethernet/octeon/octeon_mgmt.c delete mode 100644 drivers/net/octeon/Kconfig delete mode 100644 drivers/net/octeon/Makefile delete mode 100644 drivers/net/octeon/octeon_mgmt.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 99b209e0b78..b8966218282 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -506,8 +506,6 @@ config LANTIQ_ETOP help Support for the MII0 inside the Lantiq SoC -source "drivers/net/octeon/Kconfig" - endif # NET_ETHERNET # diff --git a/drivers/net/Makefile b/drivers/net/Makefile index d8c286effbb..648fa5cac45 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -105,5 +105,4 @@ obj-$(CONFIG_VIRTIO_NET) += virtio_net.o obj-$(CONFIG_WIMAX) += wimax/ obj-$(CONFIG_CAIF) += caif/ -obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon/ obj-$(CONFIG_TILE_NET) += tile/ diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 140dd73420d..70c60ef7ac7 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -38,6 +38,7 @@ source "drivers/net/ethernet/myricom/Kconfig" source "drivers/net/ethernet/natsemi/Kconfig" source "drivers/net/ethernet/8390/Kconfig" source "drivers/net/ethernet/nuvoton/Kconfig" +source "drivers/net/ethernet/octeon/Kconfig" source "drivers/net/ethernet/oki-semi/Kconfig" source "drivers/net/ethernet/packetengines/Kconfig" source "drivers/net/ethernet/pasemi/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 8a97b193f6e..929ee29007c 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_NET_VENDOR_MICREL) += micrel/ obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/ obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/ obj-$(CONFIG_NET_VENDOR_NUVOTON) += nuvoton/ +obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon/ obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/ obj-$(CONFIG_NET_PACKET_ENGINE) += packetengines/ obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/ diff --git a/drivers/net/ethernet/octeon/Kconfig b/drivers/net/ethernet/octeon/Kconfig new file mode 100644 index 00000000000..3de52ffd287 --- /dev/null +++ b/drivers/net/ethernet/octeon/Kconfig @@ -0,0 +1,14 @@ +# +# Cavium network device configuration +# + +config OCTEON_MGMT_ETHERNET + tristate "Octeon Management port ethernet driver (CN5XXX, CN6XXX)" + depends on CPU_CAVIUM_OCTEON + select PHYLIB + select MDIO_OCTEON + default y + ---help--- + This option enables the ethernet driver for the management + port on Cavium Networks' Octeon CN57XX, CN56XX, CN55XX, + CN54XX, CN52XX, and CN6XXX chips. diff --git a/drivers/net/ethernet/octeon/Makefile b/drivers/net/ethernet/octeon/Makefile new file mode 100644 index 00000000000..efa41c1d91c --- /dev/null +++ b/drivers/net/ethernet/octeon/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Cavium network device drivers. +# + +obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon_mgmt.o diff --git a/drivers/net/ethernet/octeon/octeon_mgmt.c b/drivers/net/ethernet/octeon/octeon_mgmt.c new file mode 100644 index 00000000000..429e08c84e9 --- /dev/null +++ b/drivers/net/ethernet/octeon/octeon_mgmt.c @@ -0,0 +1,1166 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009 Cavium Networks + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRV_NAME "octeon_mgmt" +#define DRV_VERSION "2.0" +#define DRV_DESCRIPTION \ + "Cavium Networks Octeon MII (management) port Network Driver" + +#define OCTEON_MGMT_NAPI_WEIGHT 16 + +/* + * Ring sizes that are powers of two allow for more efficient modulo + * opertions. + */ +#define OCTEON_MGMT_RX_RING_SIZE 512 +#define OCTEON_MGMT_TX_RING_SIZE 128 + +/* Allow 8 bytes for vlan and FCS. */ +#define OCTEON_MGMT_RX_HEADROOM (ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN) + +union mgmt_port_ring_entry { + u64 d64; + struct { + u64 reserved_62_63:2; + /* Length of the buffer/packet in bytes */ + u64 len:14; + /* For TX, signals that the packet should be timestamped */ + u64 tstamp:1; + /* The RX error code */ + u64 code:7; +#define RING_ENTRY_CODE_DONE 0xf +#define RING_ENTRY_CODE_MORE 0x10 + /* Physical address of the buffer */ + u64 addr:40; + } s; +}; + +struct octeon_mgmt { + struct net_device *netdev; + int port; + int irq; + u64 *tx_ring; + dma_addr_t tx_ring_handle; + unsigned int tx_next; + unsigned int tx_next_clean; + unsigned int tx_current_fill; + /* The tx_list lock also protects the ring related variables */ + struct sk_buff_head tx_list; + + /* RX variables only touched in napi_poll. No locking necessary. */ + u64 *rx_ring; + dma_addr_t rx_ring_handle; + unsigned int rx_next; + unsigned int rx_next_fill; + unsigned int rx_current_fill; + struct sk_buff_head rx_list; + + spinlock_t lock; + unsigned int last_duplex; + unsigned int last_link; + struct device *dev; + struct napi_struct napi; + struct tasklet_struct tx_clean_tasklet; + struct phy_device *phydev; +}; + +static void octeon_mgmt_set_rx_irq(struct octeon_mgmt *p, int enable) +{ + int port = p->port; + union cvmx_mixx_intena mix_intena; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + mix_intena.u64 = cvmx_read_csr(CVMX_MIXX_INTENA(port)); + mix_intena.s.ithena = enable ? 1 : 0; + cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); + spin_unlock_irqrestore(&p->lock, flags); +} + +static void octeon_mgmt_set_tx_irq(struct octeon_mgmt *p, int enable) +{ + int port = p->port; + union cvmx_mixx_intena mix_intena; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + mix_intena.u64 = cvmx_read_csr(CVMX_MIXX_INTENA(port)); + mix_intena.s.othena = enable ? 1 : 0; + cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); + spin_unlock_irqrestore(&p->lock, flags); +} + +static inline void octeon_mgmt_enable_rx_irq(struct octeon_mgmt *p) +{ + octeon_mgmt_set_rx_irq(p, 1); +} + +static inline void octeon_mgmt_disable_rx_irq(struct octeon_mgmt *p) +{ + octeon_mgmt_set_rx_irq(p, 0); +} + +static inline void octeon_mgmt_enable_tx_irq(struct octeon_mgmt *p) +{ + octeon_mgmt_set_tx_irq(p, 1); +} + +static inline void octeon_mgmt_disable_tx_irq(struct octeon_mgmt *p) +{ + octeon_mgmt_set_tx_irq(p, 0); +} + +static unsigned int ring_max_fill(unsigned int ring_size) +{ + return ring_size - 8; +} + +static unsigned int ring_size_to_bytes(unsigned int ring_size) +{ + return ring_size * sizeof(union mgmt_port_ring_entry); +} + +static void octeon_mgmt_rx_fill_ring(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + + while (p->rx_current_fill < ring_max_fill(OCTEON_MGMT_RX_RING_SIZE)) { + unsigned int size; + union mgmt_port_ring_entry re; + struct sk_buff *skb; + + /* CN56XX pass 1 needs 8 bytes of padding. */ + size = netdev->mtu + OCTEON_MGMT_RX_HEADROOM + 8 + NET_IP_ALIGN; + + skb = netdev_alloc_skb(netdev, size); + if (!skb) + break; + skb_reserve(skb, NET_IP_ALIGN); + __skb_queue_tail(&p->rx_list, skb); + + re.d64 = 0; + re.s.len = size; + re.s.addr = dma_map_single(p->dev, skb->data, + size, + DMA_FROM_DEVICE); + + /* Put it in the ring. */ + p->rx_ring[p->rx_next_fill] = re.d64; + dma_sync_single_for_device(p->dev, p->rx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + DMA_BIDIRECTIONAL); + p->rx_next_fill = + (p->rx_next_fill + 1) % OCTEON_MGMT_RX_RING_SIZE; + p->rx_current_fill++; + /* Ring the bell. */ + cvmx_write_csr(CVMX_MIXX_IRING2(port), 1); + } +} + +static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p) +{ + int port = p->port; + union cvmx_mixx_orcnt mix_orcnt; + union mgmt_port_ring_entry re; + struct sk_buff *skb; + int cleaned = 0; + unsigned long flags; + + mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); + while (mix_orcnt.s.orcnt) { + spin_lock_irqsave(&p->tx_list.lock, flags); + + mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); + + if (mix_orcnt.s.orcnt == 0) { + spin_unlock_irqrestore(&p->tx_list.lock, flags); + break; + } + + dma_sync_single_for_cpu(p->dev, p->tx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + DMA_BIDIRECTIONAL); + + re.d64 = p->tx_ring[p->tx_next_clean]; + p->tx_next_clean = + (p->tx_next_clean + 1) % OCTEON_MGMT_TX_RING_SIZE; + skb = __skb_dequeue(&p->tx_list); + + mix_orcnt.u64 = 0; + mix_orcnt.s.orcnt = 1; + + /* Acknowledge to hardware that we have the buffer. */ + cvmx_write_csr(CVMX_MIXX_ORCNT(port), mix_orcnt.u64); + p->tx_current_fill--; + + spin_unlock_irqrestore(&p->tx_list.lock, flags); + + dma_unmap_single(p->dev, re.s.addr, re.s.len, + DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + cleaned++; + + mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); + } + + if (cleaned && netif_queue_stopped(p->netdev)) + netif_wake_queue(p->netdev); +} + +static void octeon_mgmt_clean_tx_tasklet(unsigned long arg) +{ + struct octeon_mgmt *p = (struct octeon_mgmt *)arg; + octeon_mgmt_clean_tx_buffers(p); + octeon_mgmt_enable_tx_irq(p); +} + +static void octeon_mgmt_update_rx_stats(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + unsigned long flags; + u64 drop, bad; + + /* These reads also clear the count registers. */ + drop = cvmx_read_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_DRP(port)); + bad = cvmx_read_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_BAD(port)); + + if (drop || bad) { + /* Do an atomic update. */ + spin_lock_irqsave(&p->lock, flags); + netdev->stats.rx_errors += bad; + netdev->stats.rx_dropped += drop; + spin_unlock_irqrestore(&p->lock, flags); + } +} + +static void octeon_mgmt_update_tx_stats(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + unsigned long flags; + + union cvmx_agl_gmx_txx_stat0 s0; + union cvmx_agl_gmx_txx_stat1 s1; + + /* These reads also clear the count registers. */ + s0.u64 = cvmx_read_csr(CVMX_AGL_GMX_TXX_STAT0(port)); + s1.u64 = cvmx_read_csr(CVMX_AGL_GMX_TXX_STAT1(port)); + + if (s0.s.xsdef || s0.s.xscol || s1.s.scol || s1.s.mcol) { + /* Do an atomic update. */ + spin_lock_irqsave(&p->lock, flags); + netdev->stats.tx_errors += s0.s.xsdef + s0.s.xscol; + netdev->stats.collisions += s1.s.scol + s1.s.mcol; + spin_unlock_irqrestore(&p->lock, flags); + } +} + +/* + * Dequeue a receive skb and its corresponding ring entry. The ring + * entry is returned, *pskb is updated to point to the skb. + */ +static u64 octeon_mgmt_dequeue_rx_buffer(struct octeon_mgmt *p, + struct sk_buff **pskb) +{ + union mgmt_port_ring_entry re; + + dma_sync_single_for_cpu(p->dev, p->rx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + DMA_BIDIRECTIONAL); + + re.d64 = p->rx_ring[p->rx_next]; + p->rx_next = (p->rx_next + 1) % OCTEON_MGMT_RX_RING_SIZE; + p->rx_current_fill--; + *pskb = __skb_dequeue(&p->rx_list); + + dma_unmap_single(p->dev, re.s.addr, + ETH_FRAME_LEN + OCTEON_MGMT_RX_HEADROOM, + DMA_FROM_DEVICE); + + return re.d64; +} + + +static int octeon_mgmt_receive_one(struct octeon_mgmt *p) +{ + int port = p->port; + struct net_device *netdev = p->netdev; + union cvmx_mixx_ircnt mix_ircnt; + union mgmt_port_ring_entry re; + struct sk_buff *skb; + struct sk_buff *skb2; + struct sk_buff *skb_new; + union mgmt_port_ring_entry re2; + int rc = 1; + + + re.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb); + if (likely(re.s.code == RING_ENTRY_CODE_DONE)) { + /* A good packet, send it up. */ + skb_put(skb, re.s.len); +good: + skb->protocol = eth_type_trans(skb, netdev); + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += skb->len; + netif_receive_skb(skb); + rc = 0; + } else if (re.s.code == RING_ENTRY_CODE_MORE) { + /* + * Packet split across skbs. This can happen if we + * increase the MTU. Buffers that are already in the + * rx ring can then end up being too small. As the rx + * ring is refilled, buffers sized for the new MTU + * will be used and we should go back to the normal + * non-split case. + */ + skb_put(skb, re.s.len); + do { + re2.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb2); + if (re2.s.code != RING_ENTRY_CODE_MORE + && re2.s.code != RING_ENTRY_CODE_DONE) + goto split_error; + skb_put(skb2, re2.s.len); + skb_new = skb_copy_expand(skb, 0, skb2->len, + GFP_ATOMIC); + if (!skb_new) + goto split_error; + if (skb_copy_bits(skb2, 0, skb_tail_pointer(skb_new), + skb2->len)) + goto split_error; + skb_put(skb_new, skb2->len); + dev_kfree_skb_any(skb); + dev_kfree_skb_any(skb2); + skb = skb_new; + } while (re2.s.code == RING_ENTRY_CODE_MORE); + goto good; + } else { + /* Some other error, discard it. */ + dev_kfree_skb_any(skb); + /* + * Error statistics are accumulated in + * octeon_mgmt_update_rx_stats. + */ + } + goto done; +split_error: + /* Discard the whole mess. */ + dev_kfree_skb_any(skb); + dev_kfree_skb_any(skb2); + while (re2.s.code == RING_ENTRY_CODE_MORE) { + re2.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb2); + dev_kfree_skb_any(skb2); + } + netdev->stats.rx_errors++; + +done: + /* Tell the hardware we processed a packet. */ + mix_ircnt.u64 = 0; + mix_ircnt.s.ircnt = 1; + cvmx_write_csr(CVMX_MIXX_IRCNT(port), mix_ircnt.u64); + return rc; +} + +static int octeon_mgmt_receive_packets(struct octeon_mgmt *p, int budget) +{ + int port = p->port; + unsigned int work_done = 0; + union cvmx_mixx_ircnt mix_ircnt; + int rc; + + mix_ircnt.u64 = cvmx_read_csr(CVMX_MIXX_IRCNT(port)); + while (work_done < budget && mix_ircnt.s.ircnt) { + + rc = octeon_mgmt_receive_one(p); + if (!rc) + work_done++; + + /* Check for more packets. */ + mix_ircnt.u64 = cvmx_read_csr(CVMX_MIXX_IRCNT(port)); + } + + octeon_mgmt_rx_fill_ring(p->netdev); + + return work_done; +} + +static int octeon_mgmt_napi_poll(struct napi_struct *napi, int budget) +{ + struct octeon_mgmt *p = container_of(napi, struct octeon_mgmt, napi); + struct net_device *netdev = p->netdev; + unsigned int work_done = 0; + + work_done = octeon_mgmt_receive_packets(p, budget); + + if (work_done < budget) { + /* We stopped because no more packets were available. */ + napi_complete(napi); + octeon_mgmt_enable_rx_irq(p); + } + octeon_mgmt_update_rx_stats(netdev); + + return work_done; +} + +/* Reset the hardware to clean state. */ +static void octeon_mgmt_reset_hw(struct octeon_mgmt *p) +{ + union cvmx_mixx_ctl mix_ctl; + union cvmx_mixx_bist mix_bist; + union cvmx_agl_gmx_bist agl_gmx_bist; + + mix_ctl.u64 = 0; + cvmx_write_csr(CVMX_MIXX_CTL(p->port), mix_ctl.u64); + do { + mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(p->port)); + } while (mix_ctl.s.busy); + mix_ctl.s.reset = 1; + cvmx_write_csr(CVMX_MIXX_CTL(p->port), mix_ctl.u64); + cvmx_read_csr(CVMX_MIXX_CTL(p->port)); + cvmx_wait(64); + + mix_bist.u64 = cvmx_read_csr(CVMX_MIXX_BIST(p->port)); + if (mix_bist.u64) + dev_warn(p->dev, "MIX failed BIST (0x%016llx)\n", + (unsigned long long)mix_bist.u64); + + agl_gmx_bist.u64 = cvmx_read_csr(CVMX_AGL_GMX_BIST); + if (agl_gmx_bist.u64) + dev_warn(p->dev, "AGL failed BIST (0x%016llx)\n", + (unsigned long long)agl_gmx_bist.u64); +} + +struct octeon_mgmt_cam_state { + u64 cam[6]; + u64 cam_mask; + int cam_index; +}; + +static void octeon_mgmt_cam_state_add(struct octeon_mgmt_cam_state *cs, + unsigned char *addr) +{ + int i; + + for (i = 0; i < 6; i++) + cs->cam[i] |= (u64)addr[i] << (8 * (cs->cam_index)); + cs->cam_mask |= (1ULL << cs->cam_index); + cs->cam_index++; +} + +static void octeon_mgmt_set_rx_filtering(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + union cvmx_agl_gmx_rxx_adr_ctl adr_ctl; + union cvmx_agl_gmx_prtx_cfg agl_gmx_prtx; + unsigned long flags; + unsigned int prev_packet_enable; + unsigned int cam_mode = 1; /* 1 - Accept on CAM match */ + unsigned int multicast_mode = 1; /* 1 - Reject all multicast. */ + struct octeon_mgmt_cam_state cam_state; + struct netdev_hw_addr *ha; + int available_cam_entries; + + memset(&cam_state, 0, sizeof(cam_state)); + + if ((netdev->flags & IFF_PROMISC) || netdev->uc.count > 7) { + cam_mode = 0; + available_cam_entries = 8; + } else { + /* + * One CAM entry for the primary address, leaves seven + * for the secondary addresses. + */ + available_cam_entries = 7 - netdev->uc.count; + } + + if (netdev->flags & IFF_MULTICAST) { + if (cam_mode == 0 || (netdev->flags & IFF_ALLMULTI) || + netdev_mc_count(netdev) > available_cam_entries) + multicast_mode = 2; /* 2 - Accept all multicast. */ + else + multicast_mode = 0; /* 0 - Use CAM. */ + } + + if (cam_mode == 1) { + /* Add primary address. */ + octeon_mgmt_cam_state_add(&cam_state, netdev->dev_addr); + netdev_for_each_uc_addr(ha, netdev) + octeon_mgmt_cam_state_add(&cam_state, ha->addr); + } + if (multicast_mode == 0) { + netdev_for_each_mc_addr(ha, netdev) + octeon_mgmt_cam_state_add(&cam_state, ha->addr); + } + + spin_lock_irqsave(&p->lock, flags); + + /* Disable packet I/O. */ + agl_gmx_prtx.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); + prev_packet_enable = agl_gmx_prtx.s.en; + agl_gmx_prtx.s.en = 0; + cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), agl_gmx_prtx.u64); + + adr_ctl.u64 = 0; + adr_ctl.s.cam_mode = cam_mode; + adr_ctl.s.mcst = multicast_mode; + adr_ctl.s.bcst = 1; /* Allow broadcast */ + + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CTL(port), adr_ctl.u64); + + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM0(port), cam_state.cam[0]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM1(port), cam_state.cam[1]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM2(port), cam_state.cam[2]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM3(port), cam_state.cam[3]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM4(port), cam_state.cam[4]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM5(port), cam_state.cam[5]); + cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM_EN(port), cam_state.cam_mask); + + /* Restore packet I/O. */ + agl_gmx_prtx.s.en = prev_packet_enable; + cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), agl_gmx_prtx.u64); + + spin_unlock_irqrestore(&p->lock, flags); +} + +static int octeon_mgmt_set_mac_address(struct net_device *netdev, void *addr) +{ + struct sockaddr *sa = addr; + + if (!is_valid_ether_addr(sa->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(netdev->dev_addr, sa->sa_data, ETH_ALEN); + + octeon_mgmt_set_rx_filtering(netdev); + + return 0; +} + +static int octeon_mgmt_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + int size_without_fcs = new_mtu + OCTEON_MGMT_RX_HEADROOM; + + /* + * Limit the MTU to make sure the ethernet packets are between + * 64 bytes and 16383 bytes. + */ + if (size_without_fcs < 64 || size_without_fcs > 16383) { + dev_warn(p->dev, "MTU must be between %d and %d.\n", + 64 - OCTEON_MGMT_RX_HEADROOM, + 16383 - OCTEON_MGMT_RX_HEADROOM); + return -EINVAL; + } + + netdev->mtu = new_mtu; + + cvmx_write_csr(CVMX_AGL_GMX_RXX_FRM_MAX(port), size_without_fcs); + cvmx_write_csr(CVMX_AGL_GMX_RXX_JABBER(port), + (size_without_fcs + 7) & 0xfff8); + + return 0; +} + +static irqreturn_t octeon_mgmt_interrupt(int cpl, void *dev_id) +{ + struct net_device *netdev = dev_id; + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + union cvmx_mixx_isr mixx_isr; + + mixx_isr.u64 = cvmx_read_csr(CVMX_MIXX_ISR(port)); + + /* Clear any pending interrupts */ + cvmx_write_csr(CVMX_MIXX_ISR(port), mixx_isr.u64); + cvmx_read_csr(CVMX_MIXX_ISR(port)); + + if (mixx_isr.s.irthresh) { + octeon_mgmt_disable_rx_irq(p); + napi_schedule(&p->napi); + } + if (mixx_isr.s.orthresh) { + octeon_mgmt_disable_tx_irq(p); + tasklet_schedule(&p->tx_clean_tasklet); + } + + return IRQ_HANDLED; +} + +static int octeon_mgmt_ioctl(struct net_device *netdev, + struct ifreq *rq, int cmd) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + + if (!netif_running(netdev)) + return -EINVAL; + + if (!p->phydev) + return -EINVAL; + + return phy_mii_ioctl(p->phydev, rq, cmd); +} + +static void octeon_mgmt_adjust_link(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + union cvmx_agl_gmx_prtx_cfg prtx_cfg; + unsigned long flags; + int link_changed = 0; + + spin_lock_irqsave(&p->lock, flags); + if (p->phydev->link) { + if (!p->last_link) + link_changed = 1; + if (p->last_duplex != p->phydev->duplex) { + p->last_duplex = p->phydev->duplex; + prtx_cfg.u64 = + cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); + prtx_cfg.s.duplex = p->phydev->duplex; + cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), + prtx_cfg.u64); + } + } else { + if (p->last_link) + link_changed = -1; + } + p->last_link = p->phydev->link; + spin_unlock_irqrestore(&p->lock, flags); + + if (link_changed != 0) { + if (link_changed > 0) { + netif_carrier_on(netdev); + pr_info("%s: Link is up - %d/%s\n", netdev->name, + p->phydev->speed, + DUPLEX_FULL == p->phydev->duplex ? + "Full" : "Half"); + } else { + netif_carrier_off(netdev); + pr_info("%s: Link is down\n", netdev->name); + } + } +} + +static int octeon_mgmt_init_phy(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + char phy_id[20]; + + if (octeon_is_simulation()) { + /* No PHYs in the simulator. */ + netif_carrier_on(netdev); + return 0; + } + + snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, "0", p->port); + + p->phydev = phy_connect(netdev, phy_id, octeon_mgmt_adjust_link, 0, + PHY_INTERFACE_MODE_MII); + + if (IS_ERR(p->phydev)) { + p->phydev = NULL; + return -1; + } + + phy_start_aneg(p->phydev); + + return 0; +} + +static int octeon_mgmt_open(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + union cvmx_mixx_ctl mix_ctl; + union cvmx_agl_gmx_inf_mode agl_gmx_inf_mode; + union cvmx_mixx_oring1 oring1; + union cvmx_mixx_iring1 iring1; + union cvmx_agl_gmx_prtx_cfg prtx_cfg; + union cvmx_agl_gmx_rxx_frm_ctl rxx_frm_ctl; + union cvmx_mixx_irhwm mix_irhwm; + union cvmx_mixx_orhwm mix_orhwm; + union cvmx_mixx_intena mix_intena; + struct sockaddr sa; + + /* Allocate ring buffers. */ + p->tx_ring = kzalloc(ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + GFP_KERNEL); + if (!p->tx_ring) + return -ENOMEM; + p->tx_ring_handle = + dma_map_single(p->dev, p->tx_ring, + ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + DMA_BIDIRECTIONAL); + p->tx_next = 0; + p->tx_next_clean = 0; + p->tx_current_fill = 0; + + + p->rx_ring = kzalloc(ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + GFP_KERNEL); + if (!p->rx_ring) + goto err_nomem; + p->rx_ring_handle = + dma_map_single(p->dev, p->rx_ring, + ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + DMA_BIDIRECTIONAL); + + p->rx_next = 0; + p->rx_next_fill = 0; + p->rx_current_fill = 0; + + octeon_mgmt_reset_hw(p); + + mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(port)); + + /* Bring it out of reset if needed. */ + if (mix_ctl.s.reset) { + mix_ctl.s.reset = 0; + cvmx_write_csr(CVMX_MIXX_CTL(port), mix_ctl.u64); + do { + mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(port)); + } while (mix_ctl.s.reset); + } + + agl_gmx_inf_mode.u64 = 0; + agl_gmx_inf_mode.s.en = 1; + cvmx_write_csr(CVMX_AGL_GMX_INF_MODE, agl_gmx_inf_mode.u64); + + oring1.u64 = 0; + oring1.s.obase = p->tx_ring_handle >> 3; + oring1.s.osize = OCTEON_MGMT_TX_RING_SIZE; + cvmx_write_csr(CVMX_MIXX_ORING1(port), oring1.u64); + + iring1.u64 = 0; + iring1.s.ibase = p->rx_ring_handle >> 3; + iring1.s.isize = OCTEON_MGMT_RX_RING_SIZE; + cvmx_write_csr(CVMX_MIXX_IRING1(port), iring1.u64); + + /* Disable packet I/O. */ + prtx_cfg.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); + prtx_cfg.s.en = 0; + cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), prtx_cfg.u64); + + memcpy(sa.sa_data, netdev->dev_addr, ETH_ALEN); + octeon_mgmt_set_mac_address(netdev, &sa); + + octeon_mgmt_change_mtu(netdev, netdev->mtu); + + /* + * Enable the port HW. Packets are not allowed until + * cvmx_mgmt_port_enable() is called. + */ + mix_ctl.u64 = 0; + mix_ctl.s.crc_strip = 1; /* Strip the ending CRC */ + mix_ctl.s.en = 1; /* Enable the port */ + mix_ctl.s.nbtarb = 0; /* Arbitration mode */ + /* MII CB-request FIFO programmable high watermark */ + mix_ctl.s.mrq_hwm = 1; + cvmx_write_csr(CVMX_MIXX_CTL(port), mix_ctl.u64); + + if (OCTEON_IS_MODEL(OCTEON_CN56XX_PASS1_X) + || OCTEON_IS_MODEL(OCTEON_CN52XX_PASS1_X)) { + /* + * Force compensation values, as they are not + * determined properly by HW + */ + union cvmx_agl_gmx_drv_ctl drv_ctl; + + drv_ctl.u64 = cvmx_read_csr(CVMX_AGL_GMX_DRV_CTL); + if (port) { + drv_ctl.s.byp_en1 = 1; + drv_ctl.s.nctl1 = 6; + drv_ctl.s.pctl1 = 6; + } else { + drv_ctl.s.byp_en = 1; + drv_ctl.s.nctl = 6; + drv_ctl.s.pctl = 6; + } + cvmx_write_csr(CVMX_AGL_GMX_DRV_CTL, drv_ctl.u64); + } + + octeon_mgmt_rx_fill_ring(netdev); + + /* Clear statistics. */ + /* Clear on read. */ + cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_CTL(port), 1); + cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_DRP(port), 0); + cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_BAD(port), 0); + + cvmx_write_csr(CVMX_AGL_GMX_TXX_STATS_CTL(port), 1); + cvmx_write_csr(CVMX_AGL_GMX_TXX_STAT0(port), 0); + cvmx_write_csr(CVMX_AGL_GMX_TXX_STAT1(port), 0); + + /* Clear any pending interrupts */ + cvmx_write_csr(CVMX_MIXX_ISR(port), cvmx_read_csr(CVMX_MIXX_ISR(port))); + + if (request_irq(p->irq, octeon_mgmt_interrupt, 0, netdev->name, + netdev)) { + dev_err(p->dev, "request_irq(%d) failed.\n", p->irq); + goto err_noirq; + } + + /* Interrupt every single RX packet */ + mix_irhwm.u64 = 0; + mix_irhwm.s.irhwm = 0; + cvmx_write_csr(CVMX_MIXX_IRHWM(port), mix_irhwm.u64); + + /* Interrupt when we have 1 or more packets to clean. */ + mix_orhwm.u64 = 0; + mix_orhwm.s.orhwm = 1; + cvmx_write_csr(CVMX_MIXX_ORHWM(port), mix_orhwm.u64); + + /* Enable receive and transmit interrupts */ + mix_intena.u64 = 0; + mix_intena.s.ithena = 1; + mix_intena.s.othena = 1; + cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); + + + /* Enable packet I/O. */ + + rxx_frm_ctl.u64 = 0; + rxx_frm_ctl.s.pre_align = 1; + /* + * When set, disables the length check for non-min sized pkts + * with padding in the client data. + */ + rxx_frm_ctl.s.pad_len = 1; + /* When set, disables the length check for VLAN pkts */ + rxx_frm_ctl.s.vlan_len = 1; + /* When set, PREAMBLE checking is less strict */ + rxx_frm_ctl.s.pre_free = 1; + /* Control Pause Frames can match station SMAC */ + rxx_frm_ctl.s.ctl_smac = 0; + /* Control Pause Frames can match globally assign Multicast address */ + rxx_frm_ctl.s.ctl_mcst = 1; + /* Forward pause information to TX block */ + rxx_frm_ctl.s.ctl_bck = 1; + /* Drop Control Pause Frames */ + rxx_frm_ctl.s.ctl_drp = 1; + /* Strip off the preamble */ + rxx_frm_ctl.s.pre_strp = 1; + /* + * This port is configured to send PREAMBLE+SFD to begin every + * frame. GMX checks that the PREAMBLE is sent correctly. + */ + rxx_frm_ctl.s.pre_chk = 1; + cvmx_write_csr(CVMX_AGL_GMX_RXX_FRM_CTL(port), rxx_frm_ctl.u64); + + /* Enable the AGL block */ + agl_gmx_inf_mode.u64 = 0; + agl_gmx_inf_mode.s.en = 1; + cvmx_write_csr(CVMX_AGL_GMX_INF_MODE, agl_gmx_inf_mode.u64); + + /* Configure the port duplex and enables */ + prtx_cfg.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); + prtx_cfg.s.tx_en = 1; + prtx_cfg.s.rx_en = 1; + prtx_cfg.s.en = 1; + p->last_duplex = 1; + prtx_cfg.s.duplex = p->last_duplex; + cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), prtx_cfg.u64); + + p->last_link = 0; + netif_carrier_off(netdev); + + if (octeon_mgmt_init_phy(netdev)) { + dev_err(p->dev, "Cannot initialize PHY.\n"); + goto err_noirq; + } + + netif_wake_queue(netdev); + napi_enable(&p->napi); + + return 0; +err_noirq: + octeon_mgmt_reset_hw(p); + dma_unmap_single(p->dev, p->rx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + DMA_BIDIRECTIONAL); + kfree(p->rx_ring); +err_nomem: + dma_unmap_single(p->dev, p->tx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + DMA_BIDIRECTIONAL); + kfree(p->tx_ring); + return -ENOMEM; +} + +static int octeon_mgmt_stop(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + + napi_disable(&p->napi); + netif_stop_queue(netdev); + + if (p->phydev) + phy_disconnect(p->phydev); + + netif_carrier_off(netdev); + + octeon_mgmt_reset_hw(p); + + free_irq(p->irq, netdev); + + /* dma_unmap is a nop on Octeon, so just free everything. */ + skb_queue_purge(&p->tx_list); + skb_queue_purge(&p->rx_list); + + dma_unmap_single(p->dev, p->rx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), + DMA_BIDIRECTIONAL); + kfree(p->rx_ring); + + dma_unmap_single(p->dev, p->tx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + DMA_BIDIRECTIONAL); + kfree(p->tx_ring); + + return 0; +} + +static int octeon_mgmt_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + int port = p->port; + union mgmt_port_ring_entry re; + unsigned long flags; + int rv = NETDEV_TX_BUSY; + + re.d64 = 0; + re.s.len = skb->len; + re.s.addr = dma_map_single(p->dev, skb->data, + skb->len, + DMA_TO_DEVICE); + + spin_lock_irqsave(&p->tx_list.lock, flags); + + if (unlikely(p->tx_current_fill >= ring_max_fill(OCTEON_MGMT_TX_RING_SIZE) - 1)) { + spin_unlock_irqrestore(&p->tx_list.lock, flags); + netif_stop_queue(netdev); + spin_lock_irqsave(&p->tx_list.lock, flags); + } + + if (unlikely(p->tx_current_fill >= + ring_max_fill(OCTEON_MGMT_TX_RING_SIZE))) { + spin_unlock_irqrestore(&p->tx_list.lock, flags); + dma_unmap_single(p->dev, re.s.addr, re.s.len, + DMA_TO_DEVICE); + goto out; + } + + __skb_queue_tail(&p->tx_list, skb); + + /* Put it in the ring. */ + p->tx_ring[p->tx_next] = re.d64; + p->tx_next = (p->tx_next + 1) % OCTEON_MGMT_TX_RING_SIZE; + p->tx_current_fill++; + + spin_unlock_irqrestore(&p->tx_list.lock, flags); + + dma_sync_single_for_device(p->dev, p->tx_ring_handle, + ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), + DMA_BIDIRECTIONAL); + + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += skb->len; + + /* Ring the bell. */ + cvmx_write_csr(CVMX_MIXX_ORING2(port), 1); + + rv = NETDEV_TX_OK; +out: + octeon_mgmt_update_tx_stats(netdev); + return rv; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void octeon_mgmt_poll_controller(struct net_device *netdev) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + + octeon_mgmt_receive_packets(p, 16); + octeon_mgmt_update_rx_stats(netdev); +} +#endif + +static void octeon_mgmt_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strncpy(info->driver, DRV_NAME, sizeof(info->driver)); + strncpy(info->version, DRV_VERSION, sizeof(info->version)); + strncpy(info->fw_version, "N/A", sizeof(info->fw_version)); + strncpy(info->bus_info, "N/A", sizeof(info->bus_info)); + info->n_stats = 0; + info->testinfo_len = 0; + info->regdump_len = 0; + info->eedump_len = 0; +} + +static int octeon_mgmt_get_settings(struct net_device *netdev, + struct ethtool_cmd *cmd) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + + if (p->phydev) + return phy_ethtool_gset(p->phydev, cmd); + + return -EINVAL; +} + +static int octeon_mgmt_set_settings(struct net_device *netdev, + struct ethtool_cmd *cmd) +{ + struct octeon_mgmt *p = netdev_priv(netdev); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (p->phydev) + return phy_ethtool_sset(p->phydev, cmd); + + return -EINVAL; +} + +static const struct ethtool_ops octeon_mgmt_ethtool_ops = { + .get_drvinfo = octeon_mgmt_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_settings = octeon_mgmt_get_settings, + .set_settings = octeon_mgmt_set_settings +}; + +static const struct net_device_ops octeon_mgmt_ops = { + .ndo_open = octeon_mgmt_open, + .ndo_stop = octeon_mgmt_stop, + .ndo_start_xmit = octeon_mgmt_xmit, + .ndo_set_rx_mode = octeon_mgmt_set_rx_filtering, + .ndo_set_multicast_list = octeon_mgmt_set_rx_filtering, + .ndo_set_mac_address = octeon_mgmt_set_mac_address, + .ndo_do_ioctl = octeon_mgmt_ioctl, + .ndo_change_mtu = octeon_mgmt_change_mtu, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = octeon_mgmt_poll_controller, +#endif +}; + +static int __devinit octeon_mgmt_probe(struct platform_device *pdev) +{ + struct resource *res_irq; + struct net_device *netdev; + struct octeon_mgmt *p; + int i; + + netdev = alloc_etherdev(sizeof(struct octeon_mgmt)); + if (netdev == NULL) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, netdev); + p = netdev_priv(netdev); + netif_napi_add(netdev, &p->napi, octeon_mgmt_napi_poll, + OCTEON_MGMT_NAPI_WEIGHT); + + p->netdev = netdev; + p->dev = &pdev->dev; + + p->port = pdev->id; + snprintf(netdev->name, IFNAMSIZ, "mgmt%d", p->port); + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) + goto err; + + p->irq = res_irq->start; + spin_lock_init(&p->lock); + + skb_queue_head_init(&p->tx_list); + skb_queue_head_init(&p->rx_list); + tasklet_init(&p->tx_clean_tasklet, + octeon_mgmt_clean_tx_tasklet, (unsigned long)p); + + netdev->netdev_ops = &octeon_mgmt_ops; + netdev->ethtool_ops = &octeon_mgmt_ethtool_ops; + + /* The mgmt ports get the first N MACs. */ + for (i = 0; i < 6; i++) + netdev->dev_addr[i] = octeon_bootinfo->mac_addr_base[i]; + netdev->dev_addr[5] += p->port; + + if (p->port >= octeon_bootinfo->mac_addr_count) + dev_err(&pdev->dev, + "Error %s: Using MAC outside of the assigned range: %pM\n", + netdev->name, netdev->dev_addr); + + if (register_netdev(netdev)) + goto err; + + dev_info(&pdev->dev, "Version " DRV_VERSION "\n"); + return 0; +err: + free_netdev(netdev); + return -ENOENT; +} + +static int __devexit octeon_mgmt_remove(struct platform_device *pdev) +{ + struct net_device *netdev = dev_get_drvdata(&pdev->dev); + + unregister_netdev(netdev); + free_netdev(netdev); + return 0; +} + +static struct platform_driver octeon_mgmt_driver = { + .driver = { + .name = "octeon_mgmt", + .owner = THIS_MODULE, + }, + .probe = octeon_mgmt_probe, + .remove = __devexit_p(octeon_mgmt_remove), +}; + +extern void octeon_mdiobus_force_mod_depencency(void); + +static int __init octeon_mgmt_mod_init(void) +{ + /* Force our mdiobus driver module to be loaded first. */ + octeon_mdiobus_force_mod_depencency(); + return platform_driver_register(&octeon_mgmt_driver); +} + +static void __exit octeon_mgmt_mod_exit(void) +{ + platform_driver_unregister(&octeon_mgmt_driver); +} + +module_init(octeon_mgmt_mod_init); +module_exit(octeon_mgmt_mod_exit); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); diff --git a/drivers/net/octeon/Kconfig b/drivers/net/octeon/Kconfig deleted file mode 100644 index 1e56bbf3f5c..00000000000 --- a/drivers/net/octeon/Kconfig +++ /dev/null @@ -1,10 +0,0 @@ -config OCTEON_MGMT_ETHERNET - tristate "Octeon Management port ethernet driver (CN5XXX, CN6XXX)" - depends on CPU_CAVIUM_OCTEON - select PHYLIB - select MDIO_OCTEON - default y - help - This option enables the ethernet driver for the management - port on Cavium Networks' Octeon CN57XX, CN56XX, CN55XX, - CN54XX, CN52XX, and CN6XXX chips. diff --git a/drivers/net/octeon/Makefile b/drivers/net/octeon/Makefile deleted file mode 100644 index 906edecacfd..00000000000 --- a/drivers/net/octeon/Makefile +++ /dev/null @@ -1,2 +0,0 @@ - -obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon_mgmt.o diff --git a/drivers/net/octeon/octeon_mgmt.c b/drivers/net/octeon/octeon_mgmt.c deleted file mode 100644 index 429e08c84e9..00000000000 --- a/drivers/net/octeon/octeon_mgmt.c +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - * - * Copyright (C) 2009 Cavium Networks - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DRV_NAME "octeon_mgmt" -#define DRV_VERSION "2.0" -#define DRV_DESCRIPTION \ - "Cavium Networks Octeon MII (management) port Network Driver" - -#define OCTEON_MGMT_NAPI_WEIGHT 16 - -/* - * Ring sizes that are powers of two allow for more efficient modulo - * opertions. - */ -#define OCTEON_MGMT_RX_RING_SIZE 512 -#define OCTEON_MGMT_TX_RING_SIZE 128 - -/* Allow 8 bytes for vlan and FCS. */ -#define OCTEON_MGMT_RX_HEADROOM (ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN) - -union mgmt_port_ring_entry { - u64 d64; - struct { - u64 reserved_62_63:2; - /* Length of the buffer/packet in bytes */ - u64 len:14; - /* For TX, signals that the packet should be timestamped */ - u64 tstamp:1; - /* The RX error code */ - u64 code:7; -#define RING_ENTRY_CODE_DONE 0xf -#define RING_ENTRY_CODE_MORE 0x10 - /* Physical address of the buffer */ - u64 addr:40; - } s; -}; - -struct octeon_mgmt { - struct net_device *netdev; - int port; - int irq; - u64 *tx_ring; - dma_addr_t tx_ring_handle; - unsigned int tx_next; - unsigned int tx_next_clean; - unsigned int tx_current_fill; - /* The tx_list lock also protects the ring related variables */ - struct sk_buff_head tx_list; - - /* RX variables only touched in napi_poll. No locking necessary. */ - u64 *rx_ring; - dma_addr_t rx_ring_handle; - unsigned int rx_next; - unsigned int rx_next_fill; - unsigned int rx_current_fill; - struct sk_buff_head rx_list; - - spinlock_t lock; - unsigned int last_duplex; - unsigned int last_link; - struct device *dev; - struct napi_struct napi; - struct tasklet_struct tx_clean_tasklet; - struct phy_device *phydev; -}; - -static void octeon_mgmt_set_rx_irq(struct octeon_mgmt *p, int enable) -{ - int port = p->port; - union cvmx_mixx_intena mix_intena; - unsigned long flags; - - spin_lock_irqsave(&p->lock, flags); - mix_intena.u64 = cvmx_read_csr(CVMX_MIXX_INTENA(port)); - mix_intena.s.ithena = enable ? 1 : 0; - cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); - spin_unlock_irqrestore(&p->lock, flags); -} - -static void octeon_mgmt_set_tx_irq(struct octeon_mgmt *p, int enable) -{ - int port = p->port; - union cvmx_mixx_intena mix_intena; - unsigned long flags; - - spin_lock_irqsave(&p->lock, flags); - mix_intena.u64 = cvmx_read_csr(CVMX_MIXX_INTENA(port)); - mix_intena.s.othena = enable ? 1 : 0; - cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); - spin_unlock_irqrestore(&p->lock, flags); -} - -static inline void octeon_mgmt_enable_rx_irq(struct octeon_mgmt *p) -{ - octeon_mgmt_set_rx_irq(p, 1); -} - -static inline void octeon_mgmt_disable_rx_irq(struct octeon_mgmt *p) -{ - octeon_mgmt_set_rx_irq(p, 0); -} - -static inline void octeon_mgmt_enable_tx_irq(struct octeon_mgmt *p) -{ - octeon_mgmt_set_tx_irq(p, 1); -} - -static inline void octeon_mgmt_disable_tx_irq(struct octeon_mgmt *p) -{ - octeon_mgmt_set_tx_irq(p, 0); -} - -static unsigned int ring_max_fill(unsigned int ring_size) -{ - return ring_size - 8; -} - -static unsigned int ring_size_to_bytes(unsigned int ring_size) -{ - return ring_size * sizeof(union mgmt_port_ring_entry); -} - -static void octeon_mgmt_rx_fill_ring(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - - while (p->rx_current_fill < ring_max_fill(OCTEON_MGMT_RX_RING_SIZE)) { - unsigned int size; - union mgmt_port_ring_entry re; - struct sk_buff *skb; - - /* CN56XX pass 1 needs 8 bytes of padding. */ - size = netdev->mtu + OCTEON_MGMT_RX_HEADROOM + 8 + NET_IP_ALIGN; - - skb = netdev_alloc_skb(netdev, size); - if (!skb) - break; - skb_reserve(skb, NET_IP_ALIGN); - __skb_queue_tail(&p->rx_list, skb); - - re.d64 = 0; - re.s.len = size; - re.s.addr = dma_map_single(p->dev, skb->data, - size, - DMA_FROM_DEVICE); - - /* Put it in the ring. */ - p->rx_ring[p->rx_next_fill] = re.d64; - dma_sync_single_for_device(p->dev, p->rx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - DMA_BIDIRECTIONAL); - p->rx_next_fill = - (p->rx_next_fill + 1) % OCTEON_MGMT_RX_RING_SIZE; - p->rx_current_fill++; - /* Ring the bell. */ - cvmx_write_csr(CVMX_MIXX_IRING2(port), 1); - } -} - -static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p) -{ - int port = p->port; - union cvmx_mixx_orcnt mix_orcnt; - union mgmt_port_ring_entry re; - struct sk_buff *skb; - int cleaned = 0; - unsigned long flags; - - mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); - while (mix_orcnt.s.orcnt) { - spin_lock_irqsave(&p->tx_list.lock, flags); - - mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); - - if (mix_orcnt.s.orcnt == 0) { - spin_unlock_irqrestore(&p->tx_list.lock, flags); - break; - } - - dma_sync_single_for_cpu(p->dev, p->tx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - DMA_BIDIRECTIONAL); - - re.d64 = p->tx_ring[p->tx_next_clean]; - p->tx_next_clean = - (p->tx_next_clean + 1) % OCTEON_MGMT_TX_RING_SIZE; - skb = __skb_dequeue(&p->tx_list); - - mix_orcnt.u64 = 0; - mix_orcnt.s.orcnt = 1; - - /* Acknowledge to hardware that we have the buffer. */ - cvmx_write_csr(CVMX_MIXX_ORCNT(port), mix_orcnt.u64); - p->tx_current_fill--; - - spin_unlock_irqrestore(&p->tx_list.lock, flags); - - dma_unmap_single(p->dev, re.s.addr, re.s.len, - DMA_TO_DEVICE); - dev_kfree_skb_any(skb); - cleaned++; - - mix_orcnt.u64 = cvmx_read_csr(CVMX_MIXX_ORCNT(port)); - } - - if (cleaned && netif_queue_stopped(p->netdev)) - netif_wake_queue(p->netdev); -} - -static void octeon_mgmt_clean_tx_tasklet(unsigned long arg) -{ - struct octeon_mgmt *p = (struct octeon_mgmt *)arg; - octeon_mgmt_clean_tx_buffers(p); - octeon_mgmt_enable_tx_irq(p); -} - -static void octeon_mgmt_update_rx_stats(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - unsigned long flags; - u64 drop, bad; - - /* These reads also clear the count registers. */ - drop = cvmx_read_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_DRP(port)); - bad = cvmx_read_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_BAD(port)); - - if (drop || bad) { - /* Do an atomic update. */ - spin_lock_irqsave(&p->lock, flags); - netdev->stats.rx_errors += bad; - netdev->stats.rx_dropped += drop; - spin_unlock_irqrestore(&p->lock, flags); - } -} - -static void octeon_mgmt_update_tx_stats(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - unsigned long flags; - - union cvmx_agl_gmx_txx_stat0 s0; - union cvmx_agl_gmx_txx_stat1 s1; - - /* These reads also clear the count registers. */ - s0.u64 = cvmx_read_csr(CVMX_AGL_GMX_TXX_STAT0(port)); - s1.u64 = cvmx_read_csr(CVMX_AGL_GMX_TXX_STAT1(port)); - - if (s0.s.xsdef || s0.s.xscol || s1.s.scol || s1.s.mcol) { - /* Do an atomic update. */ - spin_lock_irqsave(&p->lock, flags); - netdev->stats.tx_errors += s0.s.xsdef + s0.s.xscol; - netdev->stats.collisions += s1.s.scol + s1.s.mcol; - spin_unlock_irqrestore(&p->lock, flags); - } -} - -/* - * Dequeue a receive skb and its corresponding ring entry. The ring - * entry is returned, *pskb is updated to point to the skb. - */ -static u64 octeon_mgmt_dequeue_rx_buffer(struct octeon_mgmt *p, - struct sk_buff **pskb) -{ - union mgmt_port_ring_entry re; - - dma_sync_single_for_cpu(p->dev, p->rx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - DMA_BIDIRECTIONAL); - - re.d64 = p->rx_ring[p->rx_next]; - p->rx_next = (p->rx_next + 1) % OCTEON_MGMT_RX_RING_SIZE; - p->rx_current_fill--; - *pskb = __skb_dequeue(&p->rx_list); - - dma_unmap_single(p->dev, re.s.addr, - ETH_FRAME_LEN + OCTEON_MGMT_RX_HEADROOM, - DMA_FROM_DEVICE); - - return re.d64; -} - - -static int octeon_mgmt_receive_one(struct octeon_mgmt *p) -{ - int port = p->port; - struct net_device *netdev = p->netdev; - union cvmx_mixx_ircnt mix_ircnt; - union mgmt_port_ring_entry re; - struct sk_buff *skb; - struct sk_buff *skb2; - struct sk_buff *skb_new; - union mgmt_port_ring_entry re2; - int rc = 1; - - - re.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb); - if (likely(re.s.code == RING_ENTRY_CODE_DONE)) { - /* A good packet, send it up. */ - skb_put(skb, re.s.len); -good: - skb->protocol = eth_type_trans(skb, netdev); - netdev->stats.rx_packets++; - netdev->stats.rx_bytes += skb->len; - netif_receive_skb(skb); - rc = 0; - } else if (re.s.code == RING_ENTRY_CODE_MORE) { - /* - * Packet split across skbs. This can happen if we - * increase the MTU. Buffers that are already in the - * rx ring can then end up being too small. As the rx - * ring is refilled, buffers sized for the new MTU - * will be used and we should go back to the normal - * non-split case. - */ - skb_put(skb, re.s.len); - do { - re2.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb2); - if (re2.s.code != RING_ENTRY_CODE_MORE - && re2.s.code != RING_ENTRY_CODE_DONE) - goto split_error; - skb_put(skb2, re2.s.len); - skb_new = skb_copy_expand(skb, 0, skb2->len, - GFP_ATOMIC); - if (!skb_new) - goto split_error; - if (skb_copy_bits(skb2, 0, skb_tail_pointer(skb_new), - skb2->len)) - goto split_error; - skb_put(skb_new, skb2->len); - dev_kfree_skb_any(skb); - dev_kfree_skb_any(skb2); - skb = skb_new; - } while (re2.s.code == RING_ENTRY_CODE_MORE); - goto good; - } else { - /* Some other error, discard it. */ - dev_kfree_skb_any(skb); - /* - * Error statistics are accumulated in - * octeon_mgmt_update_rx_stats. - */ - } - goto done; -split_error: - /* Discard the whole mess. */ - dev_kfree_skb_any(skb); - dev_kfree_skb_any(skb2); - while (re2.s.code == RING_ENTRY_CODE_MORE) { - re2.d64 = octeon_mgmt_dequeue_rx_buffer(p, &skb2); - dev_kfree_skb_any(skb2); - } - netdev->stats.rx_errors++; - -done: - /* Tell the hardware we processed a packet. */ - mix_ircnt.u64 = 0; - mix_ircnt.s.ircnt = 1; - cvmx_write_csr(CVMX_MIXX_IRCNT(port), mix_ircnt.u64); - return rc; -} - -static int octeon_mgmt_receive_packets(struct octeon_mgmt *p, int budget) -{ - int port = p->port; - unsigned int work_done = 0; - union cvmx_mixx_ircnt mix_ircnt; - int rc; - - mix_ircnt.u64 = cvmx_read_csr(CVMX_MIXX_IRCNT(port)); - while (work_done < budget && mix_ircnt.s.ircnt) { - - rc = octeon_mgmt_receive_one(p); - if (!rc) - work_done++; - - /* Check for more packets. */ - mix_ircnt.u64 = cvmx_read_csr(CVMX_MIXX_IRCNT(port)); - } - - octeon_mgmt_rx_fill_ring(p->netdev); - - return work_done; -} - -static int octeon_mgmt_napi_poll(struct napi_struct *napi, int budget) -{ - struct octeon_mgmt *p = container_of(napi, struct octeon_mgmt, napi); - struct net_device *netdev = p->netdev; - unsigned int work_done = 0; - - work_done = octeon_mgmt_receive_packets(p, budget); - - if (work_done < budget) { - /* We stopped because no more packets were available. */ - napi_complete(napi); - octeon_mgmt_enable_rx_irq(p); - } - octeon_mgmt_update_rx_stats(netdev); - - return work_done; -} - -/* Reset the hardware to clean state. */ -static void octeon_mgmt_reset_hw(struct octeon_mgmt *p) -{ - union cvmx_mixx_ctl mix_ctl; - union cvmx_mixx_bist mix_bist; - union cvmx_agl_gmx_bist agl_gmx_bist; - - mix_ctl.u64 = 0; - cvmx_write_csr(CVMX_MIXX_CTL(p->port), mix_ctl.u64); - do { - mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(p->port)); - } while (mix_ctl.s.busy); - mix_ctl.s.reset = 1; - cvmx_write_csr(CVMX_MIXX_CTL(p->port), mix_ctl.u64); - cvmx_read_csr(CVMX_MIXX_CTL(p->port)); - cvmx_wait(64); - - mix_bist.u64 = cvmx_read_csr(CVMX_MIXX_BIST(p->port)); - if (mix_bist.u64) - dev_warn(p->dev, "MIX failed BIST (0x%016llx)\n", - (unsigned long long)mix_bist.u64); - - agl_gmx_bist.u64 = cvmx_read_csr(CVMX_AGL_GMX_BIST); - if (agl_gmx_bist.u64) - dev_warn(p->dev, "AGL failed BIST (0x%016llx)\n", - (unsigned long long)agl_gmx_bist.u64); -} - -struct octeon_mgmt_cam_state { - u64 cam[6]; - u64 cam_mask; - int cam_index; -}; - -static void octeon_mgmt_cam_state_add(struct octeon_mgmt_cam_state *cs, - unsigned char *addr) -{ - int i; - - for (i = 0; i < 6; i++) - cs->cam[i] |= (u64)addr[i] << (8 * (cs->cam_index)); - cs->cam_mask |= (1ULL << cs->cam_index); - cs->cam_index++; -} - -static void octeon_mgmt_set_rx_filtering(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - union cvmx_agl_gmx_rxx_adr_ctl adr_ctl; - union cvmx_agl_gmx_prtx_cfg agl_gmx_prtx; - unsigned long flags; - unsigned int prev_packet_enable; - unsigned int cam_mode = 1; /* 1 - Accept on CAM match */ - unsigned int multicast_mode = 1; /* 1 - Reject all multicast. */ - struct octeon_mgmt_cam_state cam_state; - struct netdev_hw_addr *ha; - int available_cam_entries; - - memset(&cam_state, 0, sizeof(cam_state)); - - if ((netdev->flags & IFF_PROMISC) || netdev->uc.count > 7) { - cam_mode = 0; - available_cam_entries = 8; - } else { - /* - * One CAM entry for the primary address, leaves seven - * for the secondary addresses. - */ - available_cam_entries = 7 - netdev->uc.count; - } - - if (netdev->flags & IFF_MULTICAST) { - if (cam_mode == 0 || (netdev->flags & IFF_ALLMULTI) || - netdev_mc_count(netdev) > available_cam_entries) - multicast_mode = 2; /* 2 - Accept all multicast. */ - else - multicast_mode = 0; /* 0 - Use CAM. */ - } - - if (cam_mode == 1) { - /* Add primary address. */ - octeon_mgmt_cam_state_add(&cam_state, netdev->dev_addr); - netdev_for_each_uc_addr(ha, netdev) - octeon_mgmt_cam_state_add(&cam_state, ha->addr); - } - if (multicast_mode == 0) { - netdev_for_each_mc_addr(ha, netdev) - octeon_mgmt_cam_state_add(&cam_state, ha->addr); - } - - spin_lock_irqsave(&p->lock, flags); - - /* Disable packet I/O. */ - agl_gmx_prtx.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); - prev_packet_enable = agl_gmx_prtx.s.en; - agl_gmx_prtx.s.en = 0; - cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), agl_gmx_prtx.u64); - - adr_ctl.u64 = 0; - adr_ctl.s.cam_mode = cam_mode; - adr_ctl.s.mcst = multicast_mode; - adr_ctl.s.bcst = 1; /* Allow broadcast */ - - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CTL(port), adr_ctl.u64); - - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM0(port), cam_state.cam[0]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM1(port), cam_state.cam[1]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM2(port), cam_state.cam[2]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM3(port), cam_state.cam[3]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM4(port), cam_state.cam[4]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM5(port), cam_state.cam[5]); - cvmx_write_csr(CVMX_AGL_GMX_RXX_ADR_CAM_EN(port), cam_state.cam_mask); - - /* Restore packet I/O. */ - agl_gmx_prtx.s.en = prev_packet_enable; - cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), agl_gmx_prtx.u64); - - spin_unlock_irqrestore(&p->lock, flags); -} - -static int octeon_mgmt_set_mac_address(struct net_device *netdev, void *addr) -{ - struct sockaddr *sa = addr; - - if (!is_valid_ether_addr(sa->sa_data)) - return -EADDRNOTAVAIL; - - memcpy(netdev->dev_addr, sa->sa_data, ETH_ALEN); - - octeon_mgmt_set_rx_filtering(netdev); - - return 0; -} - -static int octeon_mgmt_change_mtu(struct net_device *netdev, int new_mtu) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - int size_without_fcs = new_mtu + OCTEON_MGMT_RX_HEADROOM; - - /* - * Limit the MTU to make sure the ethernet packets are between - * 64 bytes and 16383 bytes. - */ - if (size_without_fcs < 64 || size_without_fcs > 16383) { - dev_warn(p->dev, "MTU must be between %d and %d.\n", - 64 - OCTEON_MGMT_RX_HEADROOM, - 16383 - OCTEON_MGMT_RX_HEADROOM); - return -EINVAL; - } - - netdev->mtu = new_mtu; - - cvmx_write_csr(CVMX_AGL_GMX_RXX_FRM_MAX(port), size_without_fcs); - cvmx_write_csr(CVMX_AGL_GMX_RXX_JABBER(port), - (size_without_fcs + 7) & 0xfff8); - - return 0; -} - -static irqreturn_t octeon_mgmt_interrupt(int cpl, void *dev_id) -{ - struct net_device *netdev = dev_id; - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - union cvmx_mixx_isr mixx_isr; - - mixx_isr.u64 = cvmx_read_csr(CVMX_MIXX_ISR(port)); - - /* Clear any pending interrupts */ - cvmx_write_csr(CVMX_MIXX_ISR(port), mixx_isr.u64); - cvmx_read_csr(CVMX_MIXX_ISR(port)); - - if (mixx_isr.s.irthresh) { - octeon_mgmt_disable_rx_irq(p); - napi_schedule(&p->napi); - } - if (mixx_isr.s.orthresh) { - octeon_mgmt_disable_tx_irq(p); - tasklet_schedule(&p->tx_clean_tasklet); - } - - return IRQ_HANDLED; -} - -static int octeon_mgmt_ioctl(struct net_device *netdev, - struct ifreq *rq, int cmd) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - - if (!netif_running(netdev)) - return -EINVAL; - - if (!p->phydev) - return -EINVAL; - - return phy_mii_ioctl(p->phydev, rq, cmd); -} - -static void octeon_mgmt_adjust_link(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - union cvmx_agl_gmx_prtx_cfg prtx_cfg; - unsigned long flags; - int link_changed = 0; - - spin_lock_irqsave(&p->lock, flags); - if (p->phydev->link) { - if (!p->last_link) - link_changed = 1; - if (p->last_duplex != p->phydev->duplex) { - p->last_duplex = p->phydev->duplex; - prtx_cfg.u64 = - cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); - prtx_cfg.s.duplex = p->phydev->duplex; - cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), - prtx_cfg.u64); - } - } else { - if (p->last_link) - link_changed = -1; - } - p->last_link = p->phydev->link; - spin_unlock_irqrestore(&p->lock, flags); - - if (link_changed != 0) { - if (link_changed > 0) { - netif_carrier_on(netdev); - pr_info("%s: Link is up - %d/%s\n", netdev->name, - p->phydev->speed, - DUPLEX_FULL == p->phydev->duplex ? - "Full" : "Half"); - } else { - netif_carrier_off(netdev); - pr_info("%s: Link is down\n", netdev->name); - } - } -} - -static int octeon_mgmt_init_phy(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - char phy_id[20]; - - if (octeon_is_simulation()) { - /* No PHYs in the simulator. */ - netif_carrier_on(netdev); - return 0; - } - - snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, "0", p->port); - - p->phydev = phy_connect(netdev, phy_id, octeon_mgmt_adjust_link, 0, - PHY_INTERFACE_MODE_MII); - - if (IS_ERR(p->phydev)) { - p->phydev = NULL; - return -1; - } - - phy_start_aneg(p->phydev); - - return 0; -} - -static int octeon_mgmt_open(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - union cvmx_mixx_ctl mix_ctl; - union cvmx_agl_gmx_inf_mode agl_gmx_inf_mode; - union cvmx_mixx_oring1 oring1; - union cvmx_mixx_iring1 iring1; - union cvmx_agl_gmx_prtx_cfg prtx_cfg; - union cvmx_agl_gmx_rxx_frm_ctl rxx_frm_ctl; - union cvmx_mixx_irhwm mix_irhwm; - union cvmx_mixx_orhwm mix_orhwm; - union cvmx_mixx_intena mix_intena; - struct sockaddr sa; - - /* Allocate ring buffers. */ - p->tx_ring = kzalloc(ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - GFP_KERNEL); - if (!p->tx_ring) - return -ENOMEM; - p->tx_ring_handle = - dma_map_single(p->dev, p->tx_ring, - ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - DMA_BIDIRECTIONAL); - p->tx_next = 0; - p->tx_next_clean = 0; - p->tx_current_fill = 0; - - - p->rx_ring = kzalloc(ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - GFP_KERNEL); - if (!p->rx_ring) - goto err_nomem; - p->rx_ring_handle = - dma_map_single(p->dev, p->rx_ring, - ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - DMA_BIDIRECTIONAL); - - p->rx_next = 0; - p->rx_next_fill = 0; - p->rx_current_fill = 0; - - octeon_mgmt_reset_hw(p); - - mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(port)); - - /* Bring it out of reset if needed. */ - if (mix_ctl.s.reset) { - mix_ctl.s.reset = 0; - cvmx_write_csr(CVMX_MIXX_CTL(port), mix_ctl.u64); - do { - mix_ctl.u64 = cvmx_read_csr(CVMX_MIXX_CTL(port)); - } while (mix_ctl.s.reset); - } - - agl_gmx_inf_mode.u64 = 0; - agl_gmx_inf_mode.s.en = 1; - cvmx_write_csr(CVMX_AGL_GMX_INF_MODE, agl_gmx_inf_mode.u64); - - oring1.u64 = 0; - oring1.s.obase = p->tx_ring_handle >> 3; - oring1.s.osize = OCTEON_MGMT_TX_RING_SIZE; - cvmx_write_csr(CVMX_MIXX_ORING1(port), oring1.u64); - - iring1.u64 = 0; - iring1.s.ibase = p->rx_ring_handle >> 3; - iring1.s.isize = OCTEON_MGMT_RX_RING_SIZE; - cvmx_write_csr(CVMX_MIXX_IRING1(port), iring1.u64); - - /* Disable packet I/O. */ - prtx_cfg.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); - prtx_cfg.s.en = 0; - cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), prtx_cfg.u64); - - memcpy(sa.sa_data, netdev->dev_addr, ETH_ALEN); - octeon_mgmt_set_mac_address(netdev, &sa); - - octeon_mgmt_change_mtu(netdev, netdev->mtu); - - /* - * Enable the port HW. Packets are not allowed until - * cvmx_mgmt_port_enable() is called. - */ - mix_ctl.u64 = 0; - mix_ctl.s.crc_strip = 1; /* Strip the ending CRC */ - mix_ctl.s.en = 1; /* Enable the port */ - mix_ctl.s.nbtarb = 0; /* Arbitration mode */ - /* MII CB-request FIFO programmable high watermark */ - mix_ctl.s.mrq_hwm = 1; - cvmx_write_csr(CVMX_MIXX_CTL(port), mix_ctl.u64); - - if (OCTEON_IS_MODEL(OCTEON_CN56XX_PASS1_X) - || OCTEON_IS_MODEL(OCTEON_CN52XX_PASS1_X)) { - /* - * Force compensation values, as they are not - * determined properly by HW - */ - union cvmx_agl_gmx_drv_ctl drv_ctl; - - drv_ctl.u64 = cvmx_read_csr(CVMX_AGL_GMX_DRV_CTL); - if (port) { - drv_ctl.s.byp_en1 = 1; - drv_ctl.s.nctl1 = 6; - drv_ctl.s.pctl1 = 6; - } else { - drv_ctl.s.byp_en = 1; - drv_ctl.s.nctl = 6; - drv_ctl.s.pctl = 6; - } - cvmx_write_csr(CVMX_AGL_GMX_DRV_CTL, drv_ctl.u64); - } - - octeon_mgmt_rx_fill_ring(netdev); - - /* Clear statistics. */ - /* Clear on read. */ - cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_CTL(port), 1); - cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_DRP(port), 0); - cvmx_write_csr(CVMX_AGL_GMX_RXX_STATS_PKTS_BAD(port), 0); - - cvmx_write_csr(CVMX_AGL_GMX_TXX_STATS_CTL(port), 1); - cvmx_write_csr(CVMX_AGL_GMX_TXX_STAT0(port), 0); - cvmx_write_csr(CVMX_AGL_GMX_TXX_STAT1(port), 0); - - /* Clear any pending interrupts */ - cvmx_write_csr(CVMX_MIXX_ISR(port), cvmx_read_csr(CVMX_MIXX_ISR(port))); - - if (request_irq(p->irq, octeon_mgmt_interrupt, 0, netdev->name, - netdev)) { - dev_err(p->dev, "request_irq(%d) failed.\n", p->irq); - goto err_noirq; - } - - /* Interrupt every single RX packet */ - mix_irhwm.u64 = 0; - mix_irhwm.s.irhwm = 0; - cvmx_write_csr(CVMX_MIXX_IRHWM(port), mix_irhwm.u64); - - /* Interrupt when we have 1 or more packets to clean. */ - mix_orhwm.u64 = 0; - mix_orhwm.s.orhwm = 1; - cvmx_write_csr(CVMX_MIXX_ORHWM(port), mix_orhwm.u64); - - /* Enable receive and transmit interrupts */ - mix_intena.u64 = 0; - mix_intena.s.ithena = 1; - mix_intena.s.othena = 1; - cvmx_write_csr(CVMX_MIXX_INTENA(port), mix_intena.u64); - - - /* Enable packet I/O. */ - - rxx_frm_ctl.u64 = 0; - rxx_frm_ctl.s.pre_align = 1; - /* - * When set, disables the length check for non-min sized pkts - * with padding in the client data. - */ - rxx_frm_ctl.s.pad_len = 1; - /* When set, disables the length check for VLAN pkts */ - rxx_frm_ctl.s.vlan_len = 1; - /* When set, PREAMBLE checking is less strict */ - rxx_frm_ctl.s.pre_free = 1; - /* Control Pause Frames can match station SMAC */ - rxx_frm_ctl.s.ctl_smac = 0; - /* Control Pause Frames can match globally assign Multicast address */ - rxx_frm_ctl.s.ctl_mcst = 1; - /* Forward pause information to TX block */ - rxx_frm_ctl.s.ctl_bck = 1; - /* Drop Control Pause Frames */ - rxx_frm_ctl.s.ctl_drp = 1; - /* Strip off the preamble */ - rxx_frm_ctl.s.pre_strp = 1; - /* - * This port is configured to send PREAMBLE+SFD to begin every - * frame. GMX checks that the PREAMBLE is sent correctly. - */ - rxx_frm_ctl.s.pre_chk = 1; - cvmx_write_csr(CVMX_AGL_GMX_RXX_FRM_CTL(port), rxx_frm_ctl.u64); - - /* Enable the AGL block */ - agl_gmx_inf_mode.u64 = 0; - agl_gmx_inf_mode.s.en = 1; - cvmx_write_csr(CVMX_AGL_GMX_INF_MODE, agl_gmx_inf_mode.u64); - - /* Configure the port duplex and enables */ - prtx_cfg.u64 = cvmx_read_csr(CVMX_AGL_GMX_PRTX_CFG(port)); - prtx_cfg.s.tx_en = 1; - prtx_cfg.s.rx_en = 1; - prtx_cfg.s.en = 1; - p->last_duplex = 1; - prtx_cfg.s.duplex = p->last_duplex; - cvmx_write_csr(CVMX_AGL_GMX_PRTX_CFG(port), prtx_cfg.u64); - - p->last_link = 0; - netif_carrier_off(netdev); - - if (octeon_mgmt_init_phy(netdev)) { - dev_err(p->dev, "Cannot initialize PHY.\n"); - goto err_noirq; - } - - netif_wake_queue(netdev); - napi_enable(&p->napi); - - return 0; -err_noirq: - octeon_mgmt_reset_hw(p); - dma_unmap_single(p->dev, p->rx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - DMA_BIDIRECTIONAL); - kfree(p->rx_ring); -err_nomem: - dma_unmap_single(p->dev, p->tx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - DMA_BIDIRECTIONAL); - kfree(p->tx_ring); - return -ENOMEM; -} - -static int octeon_mgmt_stop(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - - napi_disable(&p->napi); - netif_stop_queue(netdev); - - if (p->phydev) - phy_disconnect(p->phydev); - - netif_carrier_off(netdev); - - octeon_mgmt_reset_hw(p); - - free_irq(p->irq, netdev); - - /* dma_unmap is a nop on Octeon, so just free everything. */ - skb_queue_purge(&p->tx_list); - skb_queue_purge(&p->rx_list); - - dma_unmap_single(p->dev, p->rx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_RX_RING_SIZE), - DMA_BIDIRECTIONAL); - kfree(p->rx_ring); - - dma_unmap_single(p->dev, p->tx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - DMA_BIDIRECTIONAL); - kfree(p->tx_ring); - - return 0; -} - -static int octeon_mgmt_xmit(struct sk_buff *skb, struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - int port = p->port; - union mgmt_port_ring_entry re; - unsigned long flags; - int rv = NETDEV_TX_BUSY; - - re.d64 = 0; - re.s.len = skb->len; - re.s.addr = dma_map_single(p->dev, skb->data, - skb->len, - DMA_TO_DEVICE); - - spin_lock_irqsave(&p->tx_list.lock, flags); - - if (unlikely(p->tx_current_fill >= ring_max_fill(OCTEON_MGMT_TX_RING_SIZE) - 1)) { - spin_unlock_irqrestore(&p->tx_list.lock, flags); - netif_stop_queue(netdev); - spin_lock_irqsave(&p->tx_list.lock, flags); - } - - if (unlikely(p->tx_current_fill >= - ring_max_fill(OCTEON_MGMT_TX_RING_SIZE))) { - spin_unlock_irqrestore(&p->tx_list.lock, flags); - dma_unmap_single(p->dev, re.s.addr, re.s.len, - DMA_TO_DEVICE); - goto out; - } - - __skb_queue_tail(&p->tx_list, skb); - - /* Put it in the ring. */ - p->tx_ring[p->tx_next] = re.d64; - p->tx_next = (p->tx_next + 1) % OCTEON_MGMT_TX_RING_SIZE; - p->tx_current_fill++; - - spin_unlock_irqrestore(&p->tx_list.lock, flags); - - dma_sync_single_for_device(p->dev, p->tx_ring_handle, - ring_size_to_bytes(OCTEON_MGMT_TX_RING_SIZE), - DMA_BIDIRECTIONAL); - - netdev->stats.tx_packets++; - netdev->stats.tx_bytes += skb->len; - - /* Ring the bell. */ - cvmx_write_csr(CVMX_MIXX_ORING2(port), 1); - - rv = NETDEV_TX_OK; -out: - octeon_mgmt_update_tx_stats(netdev); - return rv; -} - -#ifdef CONFIG_NET_POLL_CONTROLLER -static void octeon_mgmt_poll_controller(struct net_device *netdev) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - - octeon_mgmt_receive_packets(p, 16); - octeon_mgmt_update_rx_stats(netdev); -} -#endif - -static void octeon_mgmt_get_drvinfo(struct net_device *netdev, - struct ethtool_drvinfo *info) -{ - strncpy(info->driver, DRV_NAME, sizeof(info->driver)); - strncpy(info->version, DRV_VERSION, sizeof(info->version)); - strncpy(info->fw_version, "N/A", sizeof(info->fw_version)); - strncpy(info->bus_info, "N/A", sizeof(info->bus_info)); - info->n_stats = 0; - info->testinfo_len = 0; - info->regdump_len = 0; - info->eedump_len = 0; -} - -static int octeon_mgmt_get_settings(struct net_device *netdev, - struct ethtool_cmd *cmd) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - - if (p->phydev) - return phy_ethtool_gset(p->phydev, cmd); - - return -EINVAL; -} - -static int octeon_mgmt_set_settings(struct net_device *netdev, - struct ethtool_cmd *cmd) -{ - struct octeon_mgmt *p = netdev_priv(netdev); - - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - - if (p->phydev) - return phy_ethtool_sset(p->phydev, cmd); - - return -EINVAL; -} - -static const struct ethtool_ops octeon_mgmt_ethtool_ops = { - .get_drvinfo = octeon_mgmt_get_drvinfo, - .get_link = ethtool_op_get_link, - .get_settings = octeon_mgmt_get_settings, - .set_settings = octeon_mgmt_set_settings -}; - -static const struct net_device_ops octeon_mgmt_ops = { - .ndo_open = octeon_mgmt_open, - .ndo_stop = octeon_mgmt_stop, - .ndo_start_xmit = octeon_mgmt_xmit, - .ndo_set_rx_mode = octeon_mgmt_set_rx_filtering, - .ndo_set_multicast_list = octeon_mgmt_set_rx_filtering, - .ndo_set_mac_address = octeon_mgmt_set_mac_address, - .ndo_do_ioctl = octeon_mgmt_ioctl, - .ndo_change_mtu = octeon_mgmt_change_mtu, -#ifdef CONFIG_NET_POLL_CONTROLLER - .ndo_poll_controller = octeon_mgmt_poll_controller, -#endif -}; - -static int __devinit octeon_mgmt_probe(struct platform_device *pdev) -{ - struct resource *res_irq; - struct net_device *netdev; - struct octeon_mgmt *p; - int i; - - netdev = alloc_etherdev(sizeof(struct octeon_mgmt)); - if (netdev == NULL) - return -ENOMEM; - - dev_set_drvdata(&pdev->dev, netdev); - p = netdev_priv(netdev); - netif_napi_add(netdev, &p->napi, octeon_mgmt_napi_poll, - OCTEON_MGMT_NAPI_WEIGHT); - - p->netdev = netdev; - p->dev = &pdev->dev; - - p->port = pdev->id; - snprintf(netdev->name, IFNAMSIZ, "mgmt%d", p->port); - - res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res_irq) - goto err; - - p->irq = res_irq->start; - spin_lock_init(&p->lock); - - skb_queue_head_init(&p->tx_list); - skb_queue_head_init(&p->rx_list); - tasklet_init(&p->tx_clean_tasklet, - octeon_mgmt_clean_tx_tasklet, (unsigned long)p); - - netdev->netdev_ops = &octeon_mgmt_ops; - netdev->ethtool_ops = &octeon_mgmt_ethtool_ops; - - /* The mgmt ports get the first N MACs. */ - for (i = 0; i < 6; i++) - netdev->dev_addr[i] = octeon_bootinfo->mac_addr_base[i]; - netdev->dev_addr[5] += p->port; - - if (p->port >= octeon_bootinfo->mac_addr_count) - dev_err(&pdev->dev, - "Error %s: Using MAC outside of the assigned range: %pM\n", - netdev->name, netdev->dev_addr); - - if (register_netdev(netdev)) - goto err; - - dev_info(&pdev->dev, "Version " DRV_VERSION "\n"); - return 0; -err: - free_netdev(netdev); - return -ENOENT; -} - -static int __devexit octeon_mgmt_remove(struct platform_device *pdev) -{ - struct net_device *netdev = dev_get_drvdata(&pdev->dev); - - unregister_netdev(netdev); - free_netdev(netdev); - return 0; -} - -static struct platform_driver octeon_mgmt_driver = { - .driver = { - .name = "octeon_mgmt", - .owner = THIS_MODULE, - }, - .probe = octeon_mgmt_probe, - .remove = __devexit_p(octeon_mgmt_remove), -}; - -extern void octeon_mdiobus_force_mod_depencency(void); - -static int __init octeon_mgmt_mod_init(void) -{ - /* Force our mdiobus driver module to be loaded first. */ - octeon_mdiobus_force_mod_depencency(); - return platform_driver_register(&octeon_mgmt_driver); -} - -static void __exit octeon_mgmt_mod_exit(void) -{ - platform_driver_unregister(&octeon_mgmt_driver); -} - -module_init(octeon_mgmt_mod_init); -module_exit(octeon_mgmt_mod_exit); - -MODULE_DESCRIPTION(DRV_DESCRIPTION); -MODULE_AUTHOR("David Daney"); -MODULE_LICENSE("GPL"); -MODULE_VERSION(DRV_VERSION); -- cgit v1.2.3-70-g09d2