linux/drivers/net/ethernet/octeon/octeon_mgmt.c
David Daney 368bec0d4a netdev: octeon_mgmt: Convert to use device tree.
The device tree will supply the register bank base addresses, make
register addressing relative to those.  PHY connection is now
described by the device tree.

The OCTEON_IRQ_MII{0,1} symbols are also removed as they are now
unused and interfere with the irq_domain used for device tree irq
mapping.

Signed-off-by: David Daney <david.daney@cavium.com>
Acked-by: David S. Miller <davem@davemloft.net>
Cc: linux-mips@linux-mips.org
Cc: devicetree-discuss@lists.ozlabs.org
Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/3941/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2012-07-23 13:54:53 +01:00

1271 lines
32 KiB
C

/*
* 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/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/etherdevice.h>
#include <linux/capability.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/spinlock.h>
#include <linux/if_vlan.h>
#include <linux/of_mdio.h>
#include <linux/module.h>
#include <linux/of_net.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/phy.h>
#include <linux/io.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;
};
#define MIX_ORING1 0x0
#define MIX_ORING2 0x8
#define MIX_IRING1 0x10
#define MIX_IRING2 0x18
#define MIX_CTL 0x20
#define MIX_IRHWM 0x28
#define MIX_IRCNT 0x30
#define MIX_ORHWM 0x38
#define MIX_ORCNT 0x40
#define MIX_ISR 0x48
#define MIX_INTENA 0x50
#define MIX_REMCNT 0x58
#define MIX_BIST 0x78
#define AGL_GMX_PRT_CFG 0x10
#define AGL_GMX_RX_FRM_CTL 0x18
#define AGL_GMX_RX_FRM_MAX 0x30
#define AGL_GMX_RX_JABBER 0x38
#define AGL_GMX_RX_STATS_CTL 0x50
#define AGL_GMX_RX_STATS_PKTS_DRP 0xb0
#define AGL_GMX_RX_STATS_OCTS_DRP 0xb8
#define AGL_GMX_RX_STATS_PKTS_BAD 0xc0
#define AGL_GMX_RX_ADR_CTL 0x100
#define AGL_GMX_RX_ADR_CAM_EN 0x108
#define AGL_GMX_RX_ADR_CAM0 0x180
#define AGL_GMX_RX_ADR_CAM1 0x188
#define AGL_GMX_RX_ADR_CAM2 0x190
#define AGL_GMX_RX_ADR_CAM3 0x198
#define AGL_GMX_RX_ADR_CAM4 0x1a0
#define AGL_GMX_RX_ADR_CAM5 0x1a8
#define AGL_GMX_TX_STATS_CTL 0x268
#define AGL_GMX_TX_CTL 0x270
#define AGL_GMX_TX_STAT0 0x280
#define AGL_GMX_TX_STAT1 0x288
#define AGL_GMX_TX_STAT2 0x290
#define AGL_GMX_TX_STAT3 0x298
#define AGL_GMX_TX_STAT4 0x2a0
#define AGL_GMX_TX_STAT5 0x2a8
#define AGL_GMX_TX_STAT6 0x2b0
#define AGL_GMX_TX_STAT7 0x2b8
#define AGL_GMX_TX_STAT8 0x2c0
#define AGL_GMX_TX_STAT9 0x2c8
struct octeon_mgmt {
struct net_device *netdev;
u64 mix;
u64 agl;
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;
struct device_node *phy_np;
resource_size_t mix_phys;
resource_size_t mix_size;
resource_size_t agl_phys;
resource_size_t agl_size;
};
static void octeon_mgmt_set_rx_irq(struct octeon_mgmt *p, int enable)
{
union cvmx_mixx_intena mix_intena;
unsigned long flags;
spin_lock_irqsave(&p->lock, flags);
mix_intena.u64 = cvmx_read_csr(p->mix + MIX_INTENA);
mix_intena.s.ithena = enable ? 1 : 0;
cvmx_write_csr(p->mix + MIX_INTENA, mix_intena.u64);
spin_unlock_irqrestore(&p->lock, flags);
}
static void octeon_mgmt_set_tx_irq(struct octeon_mgmt *p, int enable)
{
union cvmx_mixx_intena mix_intena;
unsigned long flags;
spin_lock_irqsave(&p->lock, flags);
mix_intena.u64 = cvmx_read_csr(p->mix + MIX_INTENA);
mix_intena.s.othena = enable ? 1 : 0;
cvmx_write_csr(p->mix + MIX_INTENA, 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);
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(p->mix + MIX_IRING2, 1);
}
}
static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p)
{
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(p->mix + MIX_ORCNT);
while (mix_orcnt.s.orcnt) {
spin_lock_irqsave(&p->tx_list.lock, flags);
mix_orcnt.u64 = cvmx_read_csr(p->mix + MIX_ORCNT);
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(p->mix + MIX_ORCNT, 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(p->mix + MIX_ORCNT);
}
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);
unsigned long flags;
u64 drop, bad;
/* These reads also clear the count registers. */
drop = cvmx_read_csr(p->agl + AGL_GMX_RX_STATS_PKTS_DRP);
bad = cvmx_read_csr(p->agl + AGL_GMX_RX_STATS_PKTS_BAD);
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);
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(p->agl + AGL_GMX_TX_STAT0);
s1.u64 = cvmx_read_csr(p->agl + AGL_GMX_TX_STAT1);
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)
{
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(p->mix + MIX_IRCNT, mix_ircnt.u64);
return rc;
}
static int octeon_mgmt_receive_packets(struct octeon_mgmt *p, int budget)
{
unsigned int work_done = 0;
union cvmx_mixx_ircnt mix_ircnt;
int rc;
mix_ircnt.u64 = cvmx_read_csr(p->mix + MIX_IRCNT);
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(p->mix + MIX_IRCNT);
}
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(p->mix + MIX_CTL, mix_ctl.u64);
do {
mix_ctl.u64 = cvmx_read_csr(p->mix + MIX_CTL);
} while (mix_ctl.s.busy);
mix_ctl.s.reset = 1;
cvmx_write_csr(p->mix + MIX_CTL, mix_ctl.u64);
cvmx_read_csr(p->mix + MIX_CTL);
cvmx_wait(64);
mix_bist.u64 = cvmx_read_csr(p->mix + MIX_BIST);
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);
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(p->agl + AGL_GMX_PRT_CFG);
prev_packet_enable = agl_gmx_prtx.s.en;
agl_gmx_prtx.s.en = 0;
cvmx_write_csr(p->agl + AGL_GMX_PRT_CFG, 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(p->agl + AGL_GMX_RX_ADR_CTL, adr_ctl.u64);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM0, cam_state.cam[0]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM1, cam_state.cam[1]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM2, cam_state.cam[2]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM3, cam_state.cam[3]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM4, cam_state.cam[4]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM5, cam_state.cam[5]);
cvmx_write_csr(p->agl + AGL_GMX_RX_ADR_CAM_EN, cam_state.cam_mask);
/* Restore packet I/O. */
agl_gmx_prtx.s.en = prev_packet_enable;
cvmx_write_csr(p->agl + AGL_GMX_PRT_CFG, 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 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(p->agl + AGL_GMX_RX_FRM_MAX, size_without_fcs);
cvmx_write_csr(p->agl + AGL_GMX_RX_JABBER,
(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);
union cvmx_mixx_isr mixx_isr;
mixx_isr.u64 = cvmx_read_csr(p->mix + MIX_ISR);
/* Clear any pending interrupts */
cvmx_write_csr(p->mix + MIX_ISR, mixx_isr.u64);
cvmx_read_csr(p->mix + MIX_ISR);
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);
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(p->agl + AGL_GMX_PRT_CFG);
prtx_cfg.s.duplex = p->phydev->duplex;
cvmx_write_csr(p->agl + AGL_GMX_PRT_CFG, 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);
if (octeon_is_simulation() || p->phy_np == NULL) {
/* No PHYs in the simulator. */
netif_carrier_on(netdev);
return 0;
}
p->phydev = of_phy_connect(netdev, p->phy_np,
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(p->mix + MIX_CTL);
/* Bring it out of reset if needed. */
if (mix_ctl.s.reset) {
mix_ctl.s.reset = 0;
cvmx_write_csr(p->mix + MIX_CTL, mix_ctl.u64);
do {
mix_ctl.u64 = cvmx_read_csr(p->mix + MIX_CTL);
} 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(p->mix + MIX_ORING1, oring1.u64);
iring1.u64 = 0;
iring1.s.ibase = p->rx_ring_handle >> 3;
iring1.s.isize = OCTEON_MGMT_RX_RING_SIZE;
cvmx_write_csr(p->mix + MIX_IRING1, iring1.u64);
/* Disable packet I/O. */
prtx_cfg.u64 = cvmx_read_csr(p->agl + AGL_GMX_PRT_CFG);
prtx_cfg.s.en = 0;
cvmx_write_csr(p->agl + AGL_GMX_PRT_CFG, 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(p->mix + MIX_CTL, 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(p->agl + AGL_GMX_RX_STATS_CTL, 1);
cvmx_write_csr(p->agl + AGL_GMX_RX_STATS_PKTS_DRP, 0);
cvmx_write_csr(p->agl + AGL_GMX_RX_STATS_PKTS_BAD, 0);
cvmx_write_csr(p->agl + AGL_GMX_TX_STATS_CTL, 1);
cvmx_write_csr(p->agl + AGL_GMX_TX_STAT0, 0);
cvmx_write_csr(p->agl + AGL_GMX_TX_STAT1, 0);
/* Clear any pending interrupts */
cvmx_write_csr(p->mix + MIX_ISR, cvmx_read_csr(p->mix + MIX_ISR));
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(p->mix + MIX_IRHWM, 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(p->mix + MIX_ORHWM, 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(p->mix + MIX_INTENA, 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(p->agl + AGL_GMX_RX_FRM_CTL, 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(p->agl + AGL_GMX_PRT_CFG);
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(p->agl + AGL_GMX_PRT_CFG, 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);
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(p->mix + MIX_ORING2, 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_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 net_device *netdev;
struct octeon_mgmt *p;
const __be32 *data;
const u8 *mac;
struct resource *res_mix;
struct resource *res_agl;
int len;
int result;
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;
data = of_get_property(pdev->dev.of_node, "cell-index", &len);
if (data && len == sizeof(*data)) {
p->port = be32_to_cpup(data);
} else {
dev_err(&pdev->dev, "no 'cell-index' property\n");
result = -ENXIO;
goto err;
}
snprintf(netdev->name, IFNAMSIZ, "mgmt%d", p->port);
result = platform_get_irq(pdev, 0);
if (result < 0)
goto err;
p->irq = result;
res_mix = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res_mix == NULL) {
dev_err(&pdev->dev, "no 'reg' resource\n");
result = -ENXIO;
goto err;
}
res_agl = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res_agl == NULL) {
dev_err(&pdev->dev, "no 'reg' resource\n");
result = -ENXIO;
goto err;
}
p->mix_phys = res_mix->start;
p->mix_size = resource_size(res_mix);
p->agl_phys = res_agl->start;
p->agl_size = resource_size(res_agl);
if (!devm_request_mem_region(&pdev->dev, p->mix_phys, p->mix_size,
res_mix->name)) {
dev_err(&pdev->dev, "request_mem_region (%s) failed\n",
res_mix->name);
result = -ENXIO;
goto err;
}
if (!devm_request_mem_region(&pdev->dev, p->agl_phys, p->agl_size,
res_agl->name)) {
result = -ENXIO;
dev_err(&pdev->dev, "request_mem_region (%s) failed\n",
res_agl->name);
goto err;
}
p->mix = (u64)devm_ioremap(&pdev->dev, p->mix_phys, p->mix_size);
p->agl = (u64)devm_ioremap(&pdev->dev, p->agl_phys, p->agl_size);
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->priv_flags |= IFF_UNICAST_FLT;
netdev->netdev_ops = &octeon_mgmt_ops;
netdev->ethtool_ops = &octeon_mgmt_ethtool_ops;
mac = of_get_mac_address(pdev->dev.of_node);
if (mac)
memcpy(netdev->dev_addr, mac, 6);
p->phy_np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(64);
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
result = register_netdev(netdev);
if (result)
goto err;
dev_info(&pdev->dev, "Version " DRV_VERSION "\n");
return 0;
err:
free_netdev(netdev);
return result;
}
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 of_device_id octeon_mgmt_match[] = {
{
.compatible = "cavium,octeon-5750-mix",
},
{},
};
MODULE_DEVICE_TABLE(of, octeon_mgmt_match);
static struct platform_driver octeon_mgmt_driver = {
.driver = {
.name = "octeon_mgmt",
.owner = THIS_MODULE,
.of_match_table = octeon_mgmt_match,
},
.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);