|  | /* | 
|  | * 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 <linux/capability.h> | 
|  | #include <linux/dma-mapping.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/etherdevice.h> | 
|  | #include <linux/if_vlan.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/spinlock.h> | 
|  |  | 
|  | #include <asm/octeon/octeon.h> | 
|  | #include <asm/octeon/cvmx-mixx-defs.h> | 
|  | #include <asm/octeon/cvmx-agl-defs.h> | 
|  |  | 
|  | #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); |