mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-19 15:39:07 +00:00
9e81d331f2
pasemi_mac: clear out old errors on interface open Clear out any pending errors when an interface is brought up. Since the bits are sticky, they might be from interface shutdown time after firmware has used it, etc. Signed-off-by: Olof Johansson <olof@lixom.net> Signed-off-by: Jeff Garzik <jeff@garzik.org>
1463 lines
36 KiB
C
1463 lines
36 KiB
C
/*
|
|
* Copyright (C) 2006-2007 PA Semi, Inc
|
|
*
|
|
* Driver for the PA Semi PWRficient onchip 1G/10G Ethernet MACs
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <asm/dma-mapping.h>
|
|
#include <linux/in.h>
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
#include <net/checksum.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include "pasemi_mac.h"
|
|
|
|
/* We have our own align, since ppc64 in general has it at 0 because
|
|
* of design flaws in some of the server bridge chips. However, for
|
|
* PWRficient doing the unaligned copies is more expensive than doing
|
|
* unaligned DMA, so make sure the data is aligned instead.
|
|
*/
|
|
#define LOCAL_SKB_ALIGN 2
|
|
|
|
/* TODO list
|
|
*
|
|
* - Multicast support
|
|
* - Large MTU support
|
|
* - SW LRO
|
|
* - Multiqueue RX/TX
|
|
*/
|
|
|
|
|
|
/* Must be a power of two */
|
|
#define RX_RING_SIZE 4096
|
|
#define TX_RING_SIZE 4096
|
|
|
|
#define DEFAULT_MSG_ENABLE \
|
|
(NETIF_MSG_DRV | \
|
|
NETIF_MSG_PROBE | \
|
|
NETIF_MSG_LINK | \
|
|
NETIF_MSG_TIMER | \
|
|
NETIF_MSG_IFDOWN | \
|
|
NETIF_MSG_IFUP | \
|
|
NETIF_MSG_RX_ERR | \
|
|
NETIF_MSG_TX_ERR)
|
|
|
|
#define TX_RING(mac, num) ((mac)->tx->ring[(num) & (TX_RING_SIZE-1)])
|
|
#define TX_RING_INFO(mac, num) ((mac)->tx->ring_info[(num) & (TX_RING_SIZE-1)])
|
|
#define RX_RING(mac, num) ((mac)->rx->ring[(num) & (RX_RING_SIZE-1)])
|
|
#define RX_RING_INFO(mac, num) ((mac)->rx->ring_info[(num) & (RX_RING_SIZE-1)])
|
|
#define RX_BUFF(mac, num) ((mac)->rx->buffers[(num) & (RX_RING_SIZE-1)])
|
|
|
|
#define RING_USED(ring) (((ring)->next_to_fill - (ring)->next_to_clean) \
|
|
& ((ring)->size - 1))
|
|
#define RING_AVAIL(ring) ((ring->size) - RING_USED(ring))
|
|
|
|
#define BUF_SIZE 1646 /* 1500 MTU + ETH_HLEN + VLAN_HLEN + 2 64B cachelines */
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR ("Olof Johansson <olof@lixom.net>");
|
|
MODULE_DESCRIPTION("PA Semi PWRficient Ethernet driver");
|
|
|
|
static int debug = -1; /* -1 == use DEFAULT_MSG_ENABLE as value */
|
|
module_param(debug, int, 0);
|
|
MODULE_PARM_DESC(debug, "PA Semi MAC bitmapped debugging message enable value");
|
|
|
|
static struct pasdma_status *dma_status;
|
|
|
|
static void write_iob_reg(struct pasemi_mac *mac, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
out_le32(mac->iob_regs+reg, val);
|
|
}
|
|
|
|
static unsigned int read_mac_reg(struct pasemi_mac *mac, unsigned int reg)
|
|
{
|
|
return in_le32(mac->regs+reg);
|
|
}
|
|
|
|
static void write_mac_reg(struct pasemi_mac *mac, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
out_le32(mac->regs+reg, val);
|
|
}
|
|
|
|
static unsigned int read_dma_reg(struct pasemi_mac *mac, unsigned int reg)
|
|
{
|
|
return in_le32(mac->dma_regs+reg);
|
|
}
|
|
|
|
static void write_dma_reg(struct pasemi_mac *mac, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
out_le32(mac->dma_regs+reg, val);
|
|
}
|
|
|
|
static int pasemi_get_mac_addr(struct pasemi_mac *mac)
|
|
{
|
|
struct pci_dev *pdev = mac->pdev;
|
|
struct device_node *dn = pci_device_to_OF_node(pdev);
|
|
int len;
|
|
const u8 *maddr;
|
|
u8 addr[6];
|
|
|
|
if (!dn) {
|
|
dev_dbg(&pdev->dev,
|
|
"No device node for mac, not configuring\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
maddr = of_get_property(dn, "local-mac-address", &len);
|
|
|
|
if (maddr && len == 6) {
|
|
memcpy(mac->mac_addr, maddr, 6);
|
|
return 0;
|
|
}
|
|
|
|
/* Some old versions of firmware mistakenly uses mac-address
|
|
* (and as a string) instead of a byte array in local-mac-address.
|
|
*/
|
|
|
|
if (maddr == NULL)
|
|
maddr = of_get_property(dn, "mac-address", NULL);
|
|
|
|
if (maddr == NULL) {
|
|
dev_warn(&pdev->dev,
|
|
"no mac address in device tree, not configuring\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
|
|
if (sscanf(maddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &addr[0],
|
|
&addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) != 6) {
|
|
dev_warn(&pdev->dev,
|
|
"can't parse mac address, not configuring\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(mac->mac_addr, addr, 6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pasemi_mac_unmap_tx_skb(struct pasemi_mac *mac,
|
|
struct sk_buff *skb,
|
|
dma_addr_t *dmas)
|
|
{
|
|
int f;
|
|
int nfrags = skb_shinfo(skb)->nr_frags;
|
|
|
|
pci_unmap_single(mac->dma_pdev, dmas[0], skb_headlen(skb),
|
|
PCI_DMA_TODEVICE);
|
|
|
|
for (f = 0; f < nfrags; f++) {
|
|
skb_frag_t *frag = &skb_shinfo(skb)->frags[f];
|
|
|
|
pci_unmap_page(mac->dma_pdev, dmas[f+1], frag->size,
|
|
PCI_DMA_TODEVICE);
|
|
}
|
|
dev_kfree_skb_irq(skb);
|
|
|
|
/* Freed descriptor slot + main SKB ptr + nfrags additional ptrs,
|
|
* aligned up to a power of 2
|
|
*/
|
|
return (nfrags + 3) & ~1;
|
|
}
|
|
|
|
static int pasemi_mac_setup_rx_resources(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac_rxring *ring;
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
int chan_id = mac->dma_rxch;
|
|
|
|
ring = kzalloc(sizeof(*ring), GFP_KERNEL);
|
|
|
|
if (!ring)
|
|
goto out_ring;
|
|
|
|
spin_lock_init(&ring->lock);
|
|
|
|
ring->size = RX_RING_SIZE;
|
|
ring->ring_info = kzalloc(sizeof(struct pasemi_mac_buffer) *
|
|
RX_RING_SIZE, GFP_KERNEL);
|
|
|
|
if (!ring->ring_info)
|
|
goto out_ring_info;
|
|
|
|
/* Allocate descriptors */
|
|
ring->ring = dma_alloc_coherent(&mac->dma_pdev->dev,
|
|
RX_RING_SIZE * sizeof(u64),
|
|
&ring->dma, GFP_KERNEL);
|
|
|
|
if (!ring->ring)
|
|
goto out_ring_desc;
|
|
|
|
memset(ring->ring, 0, RX_RING_SIZE * sizeof(u64));
|
|
|
|
ring->buffers = dma_alloc_coherent(&mac->dma_pdev->dev,
|
|
RX_RING_SIZE * sizeof(u64),
|
|
&ring->buf_dma, GFP_KERNEL);
|
|
if (!ring->buffers)
|
|
goto out_buffers;
|
|
|
|
memset(ring->buffers, 0, RX_RING_SIZE * sizeof(u64));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_BASEL(chan_id), PAS_DMA_RXCHAN_BASEL_BRBL(ring->dma));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_BASEU(chan_id),
|
|
PAS_DMA_RXCHAN_BASEU_BRBH(ring->dma >> 32) |
|
|
PAS_DMA_RXCHAN_BASEU_SIZ(RX_RING_SIZE >> 3));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_CFG(chan_id),
|
|
PAS_DMA_RXCHAN_CFG_HBU(2));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXINT_BASEL(mac->dma_if),
|
|
PAS_DMA_RXINT_BASEL_BRBL(__pa(ring->buffers)));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXINT_BASEU(mac->dma_if),
|
|
PAS_DMA_RXINT_BASEU_BRBH(__pa(ring->buffers) >> 32) |
|
|
PAS_DMA_RXINT_BASEU_SIZ(RX_RING_SIZE >> 3));
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXINT_CFG(mac->dma_if),
|
|
PAS_DMA_RXINT_CFG_DHL(3) |
|
|
PAS_DMA_RXINT_CFG_L2 |
|
|
PAS_DMA_RXINT_CFG_LW);
|
|
|
|
ring->next_to_fill = 0;
|
|
ring->next_to_clean = 0;
|
|
|
|
snprintf(ring->irq_name, sizeof(ring->irq_name),
|
|
"%s rx", dev->name);
|
|
mac->rx = ring;
|
|
|
|
return 0;
|
|
|
|
out_buffers:
|
|
dma_free_coherent(&mac->dma_pdev->dev,
|
|
RX_RING_SIZE * sizeof(u64),
|
|
mac->rx->ring, mac->rx->dma);
|
|
out_ring_desc:
|
|
kfree(ring->ring_info);
|
|
out_ring_info:
|
|
kfree(ring);
|
|
out_ring:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
|
|
static int pasemi_mac_setup_tx_resources(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
u32 val;
|
|
int chan_id = mac->dma_txch;
|
|
struct pasemi_mac_txring *ring;
|
|
|
|
ring = kzalloc(sizeof(*ring), GFP_KERNEL);
|
|
if (!ring)
|
|
goto out_ring;
|
|
|
|
spin_lock_init(&ring->lock);
|
|
|
|
ring->size = TX_RING_SIZE;
|
|
ring->ring_info = kzalloc(sizeof(struct pasemi_mac_buffer) *
|
|
TX_RING_SIZE, GFP_KERNEL);
|
|
if (!ring->ring_info)
|
|
goto out_ring_info;
|
|
|
|
/* Allocate descriptors */
|
|
ring->ring = dma_alloc_coherent(&mac->dma_pdev->dev,
|
|
TX_RING_SIZE * sizeof(u64),
|
|
&ring->dma, GFP_KERNEL);
|
|
if (!ring->ring)
|
|
goto out_ring_desc;
|
|
|
|
memset(ring->ring, 0, TX_RING_SIZE * sizeof(u64));
|
|
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_BASEL(chan_id),
|
|
PAS_DMA_TXCHAN_BASEL_BRBL(ring->dma));
|
|
val = PAS_DMA_TXCHAN_BASEU_BRBH(ring->dma >> 32);
|
|
val |= PAS_DMA_TXCHAN_BASEU_SIZ(TX_RING_SIZE >> 3);
|
|
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_BASEU(chan_id), val);
|
|
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_CFG(chan_id),
|
|
PAS_DMA_TXCHAN_CFG_TY_IFACE |
|
|
PAS_DMA_TXCHAN_CFG_TATTR(mac->dma_if) |
|
|
PAS_DMA_TXCHAN_CFG_UP |
|
|
PAS_DMA_TXCHAN_CFG_WT(2));
|
|
|
|
ring->next_to_fill = 0;
|
|
ring->next_to_clean = 0;
|
|
|
|
snprintf(ring->irq_name, sizeof(ring->irq_name),
|
|
"%s tx", dev->name);
|
|
mac->tx = ring;
|
|
|
|
return 0;
|
|
|
|
out_ring_desc:
|
|
kfree(ring->ring_info);
|
|
out_ring_info:
|
|
kfree(ring);
|
|
out_ring:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void pasemi_mac_free_tx_resources(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int i, j;
|
|
struct pasemi_mac_buffer *info;
|
|
dma_addr_t dmas[MAX_SKB_FRAGS+1];
|
|
int freed;
|
|
int start, limit;
|
|
|
|
start = mac->tx->next_to_clean;
|
|
limit = mac->tx->next_to_fill;
|
|
|
|
/* Compensate for when fill has wrapped and clean has not */
|
|
if (start > limit)
|
|
limit += TX_RING_SIZE;
|
|
|
|
for (i = start; i < limit; i += freed) {
|
|
info = &TX_RING_INFO(mac, i+1);
|
|
if (info->dma && info->skb) {
|
|
for (j = 0; j <= skb_shinfo(info->skb)->nr_frags; j++)
|
|
dmas[j] = TX_RING_INFO(mac, i+1+j).dma;
|
|
freed = pasemi_mac_unmap_tx_skb(mac, info->skb, dmas);
|
|
} else
|
|
freed = 2;
|
|
}
|
|
|
|
for (i = 0; i < TX_RING_SIZE; i++)
|
|
TX_RING(mac, i) = 0;
|
|
|
|
dma_free_coherent(&mac->dma_pdev->dev,
|
|
TX_RING_SIZE * sizeof(u64),
|
|
mac->tx->ring, mac->tx->dma);
|
|
|
|
kfree(mac->tx->ring_info);
|
|
kfree(mac->tx);
|
|
mac->tx = NULL;
|
|
}
|
|
|
|
static void pasemi_mac_free_rx_resources(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int i;
|
|
struct pasemi_mac_buffer *info;
|
|
|
|
for (i = 0; i < RX_RING_SIZE; i++) {
|
|
info = &RX_RING_INFO(mac, i);
|
|
if (info->skb && info->dma) {
|
|
pci_unmap_single(mac->dma_pdev,
|
|
info->dma,
|
|
info->skb->len,
|
|
PCI_DMA_FROMDEVICE);
|
|
dev_kfree_skb_any(info->skb);
|
|
}
|
|
info->dma = 0;
|
|
info->skb = NULL;
|
|
}
|
|
|
|
for (i = 0; i < RX_RING_SIZE; i++)
|
|
RX_RING(mac, i) = 0;
|
|
|
|
dma_free_coherent(&mac->dma_pdev->dev,
|
|
RX_RING_SIZE * sizeof(u64),
|
|
mac->rx->ring, mac->rx->dma);
|
|
|
|
dma_free_coherent(&mac->dma_pdev->dev, RX_RING_SIZE * sizeof(u64),
|
|
mac->rx->buffers, mac->rx->buf_dma);
|
|
|
|
kfree(mac->rx->ring_info);
|
|
kfree(mac->rx);
|
|
mac->rx = NULL;
|
|
}
|
|
|
|
static void pasemi_mac_replenish_rx_ring(struct net_device *dev, int limit)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
int start = mac->rx->next_to_fill;
|
|
unsigned int fill, count;
|
|
|
|
if (limit <= 0)
|
|
return;
|
|
|
|
fill = start;
|
|
for (count = 0; count < limit; count++) {
|
|
struct pasemi_mac_buffer *info = &RX_RING_INFO(mac, fill);
|
|
u64 *buff = &RX_BUFF(mac, fill);
|
|
struct sk_buff *skb;
|
|
dma_addr_t dma;
|
|
|
|
/* Entry in use? */
|
|
WARN_ON(*buff);
|
|
|
|
/* skb might still be in there for recycle on short receives */
|
|
if (info->skb)
|
|
skb = info->skb;
|
|
else {
|
|
skb = dev_alloc_skb(BUF_SIZE);
|
|
skb_reserve(skb, LOCAL_SKB_ALIGN);
|
|
}
|
|
|
|
if (unlikely(!skb))
|
|
break;
|
|
|
|
dma = pci_map_single(mac->dma_pdev, skb->data,
|
|
BUF_SIZE - LOCAL_SKB_ALIGN,
|
|
PCI_DMA_FROMDEVICE);
|
|
|
|
if (unlikely(dma_mapping_error(dma))) {
|
|
dev_kfree_skb_irq(info->skb);
|
|
break;
|
|
}
|
|
|
|
info->skb = skb;
|
|
info->dma = dma;
|
|
*buff = XCT_RXB_LEN(BUF_SIZE) | XCT_RXB_ADDR(dma);
|
|
fill++;
|
|
}
|
|
|
|
wmb();
|
|
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_INCR(mac->dma_rxch), count);
|
|
write_dma_reg(mac, PAS_DMA_RXINT_INCR(mac->dma_if), count);
|
|
|
|
mac->rx->next_to_fill += count;
|
|
}
|
|
|
|
static void pasemi_mac_restart_rx_intr(struct pasemi_mac *mac)
|
|
{
|
|
unsigned int reg, pcnt;
|
|
/* Re-enable packet count interrupts: finally
|
|
* ack the packet count interrupt we got in rx_intr.
|
|
*/
|
|
|
|
pcnt = *mac->rx_status & PAS_STATUS_PCNT_M;
|
|
|
|
reg = PAS_IOB_DMA_RXCH_RESET_PCNT(pcnt) | PAS_IOB_DMA_RXCH_RESET_PINTC;
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_RXCH_RESET(mac->dma_rxch), reg);
|
|
}
|
|
|
|
static void pasemi_mac_restart_tx_intr(struct pasemi_mac *mac)
|
|
{
|
|
unsigned int reg, pcnt;
|
|
|
|
/* Re-enable packet count interrupts */
|
|
pcnt = *mac->tx_status & PAS_STATUS_PCNT_M;
|
|
|
|
reg = PAS_IOB_DMA_TXCH_RESET_PCNT(pcnt) | PAS_IOB_DMA_TXCH_RESET_PINTC;
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_TXCH_RESET(mac->dma_txch), reg);
|
|
}
|
|
|
|
|
|
static inline void pasemi_mac_rx_error(struct pasemi_mac *mac, u64 macrx)
|
|
{
|
|
unsigned int rcmdsta, ccmdsta;
|
|
|
|
if (!netif_msg_rx_err(mac))
|
|
return;
|
|
|
|
rcmdsta = read_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if));
|
|
ccmdsta = read_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch));
|
|
|
|
printk(KERN_ERR "pasemi_mac: rx error. macrx %016lx, rx status %lx\n",
|
|
macrx, *mac->rx_status);
|
|
|
|
printk(KERN_ERR "pasemi_mac: rcmdsta %08x ccmdsta %08x\n",
|
|
rcmdsta, ccmdsta);
|
|
}
|
|
|
|
static inline void pasemi_mac_tx_error(struct pasemi_mac *mac, u64 mactx)
|
|
{
|
|
unsigned int cmdsta;
|
|
|
|
if (!netif_msg_tx_err(mac))
|
|
return;
|
|
|
|
cmdsta = read_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch));
|
|
|
|
printk(KERN_ERR "pasemi_mac: tx error. mactx 0x%016lx, "\
|
|
"tx status 0x%016lx\n", mactx, *mac->tx_status);
|
|
|
|
printk(KERN_ERR "pasemi_mac: tcmdsta 0x%08x\n", cmdsta);
|
|
}
|
|
|
|
static int pasemi_mac_clean_rx(struct pasemi_mac *mac, int limit)
|
|
{
|
|
unsigned int n;
|
|
int count;
|
|
struct pasemi_mac_buffer *info;
|
|
struct sk_buff *skb;
|
|
unsigned int i, len;
|
|
u64 macrx;
|
|
dma_addr_t dma;
|
|
|
|
spin_lock(&mac->rx->lock);
|
|
|
|
n = mac->rx->next_to_clean;
|
|
|
|
for (count = limit; count; count--) {
|
|
macrx = RX_RING(mac, n);
|
|
|
|
if ((macrx & XCT_MACRX_E) ||
|
|
(*mac->rx_status & PAS_STATUS_ERROR))
|
|
pasemi_mac_rx_error(mac, macrx);
|
|
|
|
if (!(macrx & XCT_MACRX_O))
|
|
break;
|
|
|
|
info = NULL;
|
|
|
|
/* We have to scan for our skb since there's no way
|
|
* to back-map them from the descriptor, and if we
|
|
* have several receive channels then they might not
|
|
* show up in the same order as they were put on the
|
|
* interface ring.
|
|
*/
|
|
|
|
dma = (RX_RING(mac, n+1) & XCT_PTR_ADDR_M);
|
|
for (i = mac->rx->next_to_fill;
|
|
i < (mac->rx->next_to_fill + RX_RING_SIZE);
|
|
i++) {
|
|
info = &RX_RING_INFO(mac, i);
|
|
if (info->dma == dma)
|
|
break;
|
|
}
|
|
|
|
skb = info->skb;
|
|
|
|
prefetch(skb);
|
|
prefetch(&skb->data_len);
|
|
|
|
len = (macrx & XCT_MACRX_LLEN_M) >> XCT_MACRX_LLEN_S;
|
|
|
|
if (len < 256) {
|
|
struct sk_buff *new_skb;
|
|
|
|
new_skb = netdev_alloc_skb(mac->netdev,
|
|
len + LOCAL_SKB_ALIGN);
|
|
if (new_skb) {
|
|
skb_reserve(new_skb, LOCAL_SKB_ALIGN);
|
|
memcpy(new_skb->data, skb->data, len);
|
|
/* save the skb in buffer_info as good */
|
|
skb = new_skb;
|
|
}
|
|
/* else just continue with the old one */
|
|
} else
|
|
info->skb = NULL;
|
|
|
|
pci_unmap_single(mac->dma_pdev, dma, len, PCI_DMA_FROMDEVICE);
|
|
|
|
info->dma = 0;
|
|
|
|
skb_put(skb, len);
|
|
|
|
if (likely((macrx & XCT_MACRX_HTY_M) == XCT_MACRX_HTY_IPV4_OK)) {
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
skb->csum = (macrx & XCT_MACRX_CSUM_M) >>
|
|
XCT_MACRX_CSUM_S;
|
|
} else
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
|
|
mac->netdev->stats.rx_bytes += len;
|
|
mac->netdev->stats.rx_packets++;
|
|
|
|
skb->protocol = eth_type_trans(skb, mac->netdev);
|
|
netif_receive_skb(skb);
|
|
|
|
RX_RING(mac, n) = 0;
|
|
RX_RING(mac, n+1) = 0;
|
|
|
|
/* Need to zero it out since hardware doesn't, since the
|
|
* replenish loop uses it to tell when it's done.
|
|
*/
|
|
RX_BUFF(mac, i) = 0;
|
|
|
|
n += 2;
|
|
}
|
|
|
|
if (n > RX_RING_SIZE) {
|
|
/* Errata 5971 workaround: L2 target of headers */
|
|
write_iob_reg(mac, PAS_IOB_COM_PKTHDRCNT, 0);
|
|
n &= (RX_RING_SIZE-1);
|
|
}
|
|
mac->rx->next_to_clean = n;
|
|
pasemi_mac_replenish_rx_ring(mac->netdev, limit-count);
|
|
|
|
spin_unlock(&mac->rx->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
/* Can't make this too large or we blow the kernel stack limits */
|
|
#define TX_CLEAN_BATCHSIZE (128/MAX_SKB_FRAGS)
|
|
|
|
static int pasemi_mac_clean_tx(struct pasemi_mac *mac)
|
|
{
|
|
int i, j;
|
|
unsigned int start, descr_count, buf_count, batch_limit;
|
|
unsigned int ring_limit;
|
|
unsigned int total_count;
|
|
unsigned long flags;
|
|
struct sk_buff *skbs[TX_CLEAN_BATCHSIZE];
|
|
dma_addr_t dmas[TX_CLEAN_BATCHSIZE][MAX_SKB_FRAGS+1];
|
|
|
|
total_count = 0;
|
|
batch_limit = TX_CLEAN_BATCHSIZE;
|
|
restart:
|
|
spin_lock_irqsave(&mac->tx->lock, flags);
|
|
|
|
start = mac->tx->next_to_clean;
|
|
ring_limit = mac->tx->next_to_fill;
|
|
|
|
/* Compensate for when fill has wrapped but clean has not */
|
|
if (start > ring_limit)
|
|
ring_limit += TX_RING_SIZE;
|
|
|
|
buf_count = 0;
|
|
descr_count = 0;
|
|
|
|
for (i = start;
|
|
descr_count < batch_limit && i < ring_limit;
|
|
i += buf_count) {
|
|
u64 mactx = TX_RING(mac, i);
|
|
struct sk_buff *skb;
|
|
|
|
if ((mactx & XCT_MACTX_E) ||
|
|
(*mac->tx_status & PAS_STATUS_ERROR))
|
|
pasemi_mac_tx_error(mac, mactx);
|
|
|
|
if (unlikely(mactx & XCT_MACTX_O))
|
|
/* Not yet transmitted */
|
|
break;
|
|
|
|
skb = TX_RING_INFO(mac, i+1).skb;
|
|
skbs[descr_count] = skb;
|
|
|
|
buf_count = 2 + skb_shinfo(skb)->nr_frags;
|
|
for (j = 0; j <= skb_shinfo(skb)->nr_frags; j++)
|
|
dmas[descr_count][j] = TX_RING_INFO(mac, i+1+j).dma;
|
|
|
|
TX_RING(mac, i) = 0;
|
|
TX_RING(mac, i+1) = 0;
|
|
|
|
/* Since we always fill with an even number of entries, make
|
|
* sure we skip any unused one at the end as well.
|
|
*/
|
|
if (buf_count & 1)
|
|
buf_count++;
|
|
descr_count++;
|
|
}
|
|
mac->tx->next_to_clean = i & (TX_RING_SIZE-1);
|
|
|
|
spin_unlock_irqrestore(&mac->tx->lock, flags);
|
|
netif_wake_queue(mac->netdev);
|
|
|
|
for (i = 0; i < descr_count; i++)
|
|
pasemi_mac_unmap_tx_skb(mac, skbs[i], dmas[i]);
|
|
|
|
total_count += descr_count;
|
|
|
|
/* If the batch was full, try to clean more */
|
|
if (descr_count == batch_limit)
|
|
goto restart;
|
|
|
|
return total_count;
|
|
}
|
|
|
|
|
|
static irqreturn_t pasemi_mac_rx_intr(int irq, void *data)
|
|
{
|
|
struct net_device *dev = data;
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int reg;
|
|
|
|
if (!(*mac->rx_status & PAS_STATUS_CAUSE_M))
|
|
return IRQ_NONE;
|
|
|
|
/* Don't reset packet count so it won't fire again but clear
|
|
* all others.
|
|
*/
|
|
|
|
reg = 0;
|
|
if (*mac->rx_status & PAS_STATUS_SOFT)
|
|
reg |= PAS_IOB_DMA_RXCH_RESET_SINTC;
|
|
if (*mac->rx_status & PAS_STATUS_ERROR)
|
|
reg |= PAS_IOB_DMA_RXCH_RESET_DINTC;
|
|
if (*mac->rx_status & PAS_STATUS_TIMER)
|
|
reg |= PAS_IOB_DMA_RXCH_RESET_TINTC;
|
|
|
|
netif_rx_schedule(dev, &mac->napi);
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_RXCH_RESET(mac->dma_rxch), reg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t pasemi_mac_tx_intr(int irq, void *data)
|
|
{
|
|
struct net_device *dev = data;
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int reg, pcnt;
|
|
|
|
if (!(*mac->tx_status & PAS_STATUS_CAUSE_M))
|
|
return IRQ_NONE;
|
|
|
|
pasemi_mac_clean_tx(mac);
|
|
|
|
pcnt = *mac->tx_status & PAS_STATUS_PCNT_M;
|
|
|
|
reg = PAS_IOB_DMA_TXCH_RESET_PCNT(pcnt) | PAS_IOB_DMA_TXCH_RESET_PINTC;
|
|
|
|
if (*mac->tx_status & PAS_STATUS_SOFT)
|
|
reg |= PAS_IOB_DMA_TXCH_RESET_SINTC;
|
|
if (*mac->tx_status & PAS_STATUS_ERROR)
|
|
reg |= PAS_IOB_DMA_TXCH_RESET_DINTC;
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_TXCH_RESET(mac->dma_txch), reg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void pasemi_adjust_link(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
int msg;
|
|
unsigned int flags;
|
|
unsigned int new_flags;
|
|
|
|
if (!mac->phydev->link) {
|
|
/* If no link, MAC speed settings don't matter. Just report
|
|
* link down and return.
|
|
*/
|
|
if (mac->link && netif_msg_link(mac))
|
|
printk(KERN_INFO "%s: Link is down.\n", dev->name);
|
|
|
|
netif_carrier_off(dev);
|
|
mac->link = 0;
|
|
|
|
return;
|
|
} else
|
|
netif_carrier_on(dev);
|
|
|
|
flags = read_mac_reg(mac, PAS_MAC_CFG_PCFG);
|
|
new_flags = flags & ~(PAS_MAC_CFG_PCFG_HD | PAS_MAC_CFG_PCFG_SPD_M |
|
|
PAS_MAC_CFG_PCFG_TSR_M);
|
|
|
|
if (!mac->phydev->duplex)
|
|
new_flags |= PAS_MAC_CFG_PCFG_HD;
|
|
|
|
switch (mac->phydev->speed) {
|
|
case 1000:
|
|
new_flags |= PAS_MAC_CFG_PCFG_SPD_1G |
|
|
PAS_MAC_CFG_PCFG_TSR_1G;
|
|
break;
|
|
case 100:
|
|
new_flags |= PAS_MAC_CFG_PCFG_SPD_100M |
|
|
PAS_MAC_CFG_PCFG_TSR_100M;
|
|
break;
|
|
case 10:
|
|
new_flags |= PAS_MAC_CFG_PCFG_SPD_10M |
|
|
PAS_MAC_CFG_PCFG_TSR_10M;
|
|
break;
|
|
default:
|
|
printk("Unsupported speed %d\n", mac->phydev->speed);
|
|
}
|
|
|
|
/* Print on link or speed/duplex change */
|
|
msg = mac->link != mac->phydev->link || flags != new_flags;
|
|
|
|
mac->duplex = mac->phydev->duplex;
|
|
mac->speed = mac->phydev->speed;
|
|
mac->link = mac->phydev->link;
|
|
|
|
if (new_flags != flags)
|
|
write_mac_reg(mac, PAS_MAC_CFG_PCFG, new_flags);
|
|
|
|
if (msg && netif_msg_link(mac))
|
|
printk(KERN_INFO "%s: Link is up at %d Mbps, %s duplex.\n",
|
|
dev->name, mac->speed, mac->duplex ? "full" : "half");
|
|
}
|
|
|
|
static int pasemi_mac_phy_init(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
struct device_node *dn, *phy_dn;
|
|
struct phy_device *phydev;
|
|
unsigned int phy_id;
|
|
const phandle *ph;
|
|
const unsigned int *prop;
|
|
struct resource r;
|
|
int ret;
|
|
|
|
dn = pci_device_to_OF_node(mac->pdev);
|
|
ph = of_get_property(dn, "phy-handle", NULL);
|
|
if (!ph)
|
|
return -ENODEV;
|
|
phy_dn = of_find_node_by_phandle(*ph);
|
|
|
|
prop = of_get_property(phy_dn, "reg", NULL);
|
|
ret = of_address_to_resource(phy_dn->parent, 0, &r);
|
|
if (ret)
|
|
goto err;
|
|
|
|
phy_id = *prop;
|
|
snprintf(mac->phy_id, BUS_ID_SIZE, PHY_ID_FMT, (int)r.start, phy_id);
|
|
|
|
of_node_put(phy_dn);
|
|
|
|
mac->link = 0;
|
|
mac->speed = 0;
|
|
mac->duplex = -1;
|
|
|
|
phydev = phy_connect(dev, mac->phy_id, &pasemi_adjust_link, 0, PHY_INTERFACE_MODE_SGMII);
|
|
|
|
if (IS_ERR(phydev)) {
|
|
printk(KERN_ERR "%s: Could not attach to phy\n", dev->name);
|
|
return PTR_ERR(phydev);
|
|
}
|
|
|
|
mac->phydev = phydev;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
of_node_put(phy_dn);
|
|
return -ENODEV;
|
|
}
|
|
|
|
|
|
static int pasemi_mac_open(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
int base_irq;
|
|
unsigned int flags;
|
|
int ret;
|
|
|
|
/* enable rx section */
|
|
write_dma_reg(mac, PAS_DMA_COM_RXCMD, PAS_DMA_COM_RXCMD_EN);
|
|
|
|
/* enable tx section */
|
|
write_dma_reg(mac, PAS_DMA_COM_TXCMD, PAS_DMA_COM_TXCMD_EN);
|
|
|
|
flags = PAS_MAC_CFG_TXP_FCE | PAS_MAC_CFG_TXP_FPC(3) |
|
|
PAS_MAC_CFG_TXP_SL(3) | PAS_MAC_CFG_TXP_COB(0xf) |
|
|
PAS_MAC_CFG_TXP_TIFT(8) | PAS_MAC_CFG_TXP_TIFG(12);
|
|
|
|
write_mac_reg(mac, PAS_MAC_CFG_TXP, flags);
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_RXCH_CFG(mac->dma_rxch),
|
|
PAS_IOB_DMA_RXCH_CFG_CNTTH(0));
|
|
|
|
write_iob_reg(mac, PAS_IOB_DMA_TXCH_CFG(mac->dma_txch),
|
|
PAS_IOB_DMA_TXCH_CFG_CNTTH(128));
|
|
|
|
/* Clear out any residual packet count state from firmware */
|
|
pasemi_mac_restart_rx_intr(mac);
|
|
pasemi_mac_restart_tx_intr(mac);
|
|
|
|
/* 0xffffff is max value, about 16ms */
|
|
write_iob_reg(mac, PAS_IOB_DMA_COM_TIMEOUTCFG,
|
|
PAS_IOB_DMA_COM_TIMEOUTCFG_TCNT(0xffffff));
|
|
|
|
ret = pasemi_mac_setup_rx_resources(dev);
|
|
if (ret)
|
|
goto out_rx_resources;
|
|
|
|
ret = pasemi_mac_setup_tx_resources(dev);
|
|
if (ret)
|
|
goto out_tx_resources;
|
|
|
|
write_mac_reg(mac, PAS_MAC_IPC_CHNL,
|
|
PAS_MAC_IPC_CHNL_DCHNO(mac->dma_rxch) |
|
|
PAS_MAC_IPC_CHNL_BCH(mac->dma_rxch));
|
|
|
|
/* enable rx if */
|
|
write_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if),
|
|
PAS_DMA_RXINT_RCMDSTA_EN |
|
|
PAS_DMA_RXINT_RCMDSTA_DROPS_M |
|
|
PAS_DMA_RXINT_RCMDSTA_BP |
|
|
PAS_DMA_RXINT_RCMDSTA_OO |
|
|
PAS_DMA_RXINT_RCMDSTA_BT);
|
|
|
|
/* enable rx channel */
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch),
|
|
PAS_DMA_RXCHAN_CCMDSTA_EN |
|
|
PAS_DMA_RXCHAN_CCMDSTA_DU |
|
|
PAS_DMA_RXCHAN_CCMDSTA_OD |
|
|
PAS_DMA_RXCHAN_CCMDSTA_FD |
|
|
PAS_DMA_RXCHAN_CCMDSTA_DT);
|
|
|
|
/* enable tx channel */
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch),
|
|
PAS_DMA_TXCHAN_TCMDSTA_EN |
|
|
PAS_DMA_TXCHAN_TCMDSTA_SZ |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DB |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DE |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DA);
|
|
|
|
pasemi_mac_replenish_rx_ring(dev, RX_RING_SIZE);
|
|
|
|
flags = PAS_MAC_CFG_PCFG_S1 | PAS_MAC_CFG_PCFG_PE |
|
|
PAS_MAC_CFG_PCFG_PR | PAS_MAC_CFG_PCFG_CE;
|
|
|
|
if (mac->type == MAC_TYPE_GMAC)
|
|
flags |= PAS_MAC_CFG_PCFG_TSR_1G | PAS_MAC_CFG_PCFG_SPD_1G;
|
|
else
|
|
flags |= PAS_MAC_CFG_PCFG_TSR_10G | PAS_MAC_CFG_PCFG_SPD_10G;
|
|
|
|
/* Enable interface in MAC */
|
|
write_mac_reg(mac, PAS_MAC_CFG_PCFG, flags);
|
|
|
|
ret = pasemi_mac_phy_init(dev);
|
|
/* Some configs don't have PHYs (XAUI etc), so don't complain about
|
|
* failed init due to -ENODEV.
|
|
*/
|
|
if (ret && ret != -ENODEV)
|
|
dev_warn(&mac->pdev->dev, "phy init failed: %d\n", ret);
|
|
|
|
netif_start_queue(dev);
|
|
napi_enable(&mac->napi);
|
|
|
|
/* Interrupts are a bit different for our DMA controller: While
|
|
* it's got one a regular PCI device header, the interrupt there
|
|
* is really the base of the range it's using. Each tx and rx
|
|
* channel has it's own interrupt source.
|
|
*/
|
|
|
|
base_irq = virq_to_hw(mac->dma_pdev->irq);
|
|
|
|
mac->tx_irq = irq_create_mapping(NULL, base_irq + mac->dma_txch);
|
|
mac->rx_irq = irq_create_mapping(NULL, base_irq + 20 + mac->dma_txch);
|
|
|
|
ret = request_irq(mac->tx_irq, &pasemi_mac_tx_intr, IRQF_DISABLED,
|
|
mac->tx->irq_name, dev);
|
|
if (ret) {
|
|
dev_err(&mac->pdev->dev, "request_irq of irq %d failed: %d\n",
|
|
base_irq + mac->dma_txch, ret);
|
|
goto out_tx_int;
|
|
}
|
|
|
|
ret = request_irq(mac->rx_irq, &pasemi_mac_rx_intr, IRQF_DISABLED,
|
|
mac->rx->irq_name, dev);
|
|
if (ret) {
|
|
dev_err(&mac->pdev->dev, "request_irq of irq %d failed: %d\n",
|
|
base_irq + 20 + mac->dma_rxch, ret);
|
|
goto out_rx_int;
|
|
}
|
|
|
|
if (mac->phydev)
|
|
phy_start(mac->phydev);
|
|
|
|
return 0;
|
|
|
|
out_rx_int:
|
|
free_irq(mac->tx_irq, dev);
|
|
out_tx_int:
|
|
napi_disable(&mac->napi);
|
|
netif_stop_queue(dev);
|
|
pasemi_mac_free_tx_resources(dev);
|
|
out_tx_resources:
|
|
pasemi_mac_free_rx_resources(dev);
|
|
out_rx_resources:
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define MAX_RETRIES 5000
|
|
|
|
static int pasemi_mac_close(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int sta;
|
|
int retries;
|
|
|
|
if (mac->phydev) {
|
|
phy_stop(mac->phydev);
|
|
phy_disconnect(mac->phydev);
|
|
}
|
|
|
|
netif_stop_queue(dev);
|
|
napi_disable(&mac->napi);
|
|
|
|
sta = read_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if));
|
|
if (sta & (PAS_DMA_RXINT_RCMDSTA_BP |
|
|
PAS_DMA_RXINT_RCMDSTA_OO |
|
|
PAS_DMA_RXINT_RCMDSTA_BT))
|
|
printk(KERN_DEBUG "pasemi_mac: rcmdsta error: 0x%08x\n", sta);
|
|
|
|
sta = read_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch));
|
|
if (sta & (PAS_DMA_RXCHAN_CCMDSTA_DU |
|
|
PAS_DMA_RXCHAN_CCMDSTA_OD |
|
|
PAS_DMA_RXCHAN_CCMDSTA_FD |
|
|
PAS_DMA_RXCHAN_CCMDSTA_DT))
|
|
printk(KERN_DEBUG "pasemi_mac: ccmdsta error: 0x%08x\n", sta);
|
|
|
|
sta = read_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch));
|
|
if (sta & (PAS_DMA_TXCHAN_TCMDSTA_SZ |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DB |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DE |
|
|
PAS_DMA_TXCHAN_TCMDSTA_DA))
|
|
printk(KERN_DEBUG "pasemi_mac: tcmdsta error: 0x%08x\n", sta);
|
|
|
|
/* Clean out any pending buffers */
|
|
pasemi_mac_clean_tx(mac);
|
|
pasemi_mac_clean_rx(mac, RX_RING_SIZE);
|
|
|
|
/* Disable interface */
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch), PAS_DMA_TXCHAN_TCMDSTA_ST);
|
|
write_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if), PAS_DMA_RXINT_RCMDSTA_ST);
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch), PAS_DMA_RXCHAN_CCMDSTA_ST);
|
|
|
|
for (retries = 0; retries < MAX_RETRIES; retries++) {
|
|
sta = read_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch));
|
|
if (!(sta & PAS_DMA_TXCHAN_TCMDSTA_ACT))
|
|
break;
|
|
cond_resched();
|
|
}
|
|
|
|
if (sta & PAS_DMA_TXCHAN_TCMDSTA_ACT)
|
|
dev_err(&mac->dma_pdev->dev, "Failed to stop tx channel\n");
|
|
|
|
for (retries = 0; retries < MAX_RETRIES; retries++) {
|
|
sta = read_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch));
|
|
if (!(sta & PAS_DMA_RXCHAN_CCMDSTA_ACT))
|
|
break;
|
|
cond_resched();
|
|
}
|
|
|
|
if (sta & PAS_DMA_RXCHAN_CCMDSTA_ACT)
|
|
dev_err(&mac->dma_pdev->dev, "Failed to stop rx channel\n");
|
|
|
|
for (retries = 0; retries < MAX_RETRIES; retries++) {
|
|
sta = read_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if));
|
|
if (!(sta & PAS_DMA_RXINT_RCMDSTA_ACT))
|
|
break;
|
|
cond_resched();
|
|
}
|
|
|
|
if (sta & PAS_DMA_RXINT_RCMDSTA_ACT)
|
|
dev_err(&mac->dma_pdev->dev, "Failed to stop rx interface\n");
|
|
|
|
/* Then, disable the channel. This must be done separately from
|
|
* stopping, since you can't disable when active.
|
|
*/
|
|
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_TCMDSTA(mac->dma_txch), 0);
|
|
write_dma_reg(mac, PAS_DMA_RXCHAN_CCMDSTA(mac->dma_rxch), 0);
|
|
write_dma_reg(mac, PAS_DMA_RXINT_RCMDSTA(mac->dma_if), 0);
|
|
|
|
free_irq(mac->tx_irq, dev);
|
|
free_irq(mac->rx_irq, dev);
|
|
|
|
/* Free resources */
|
|
pasemi_mac_free_rx_resources(dev);
|
|
pasemi_mac_free_tx_resources(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pasemi_mac_start_tx(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
struct pasemi_mac_txring *txring;
|
|
u64 dflags, mactx;
|
|
dma_addr_t map[MAX_SKB_FRAGS+1];
|
|
unsigned int map_size[MAX_SKB_FRAGS+1];
|
|
unsigned long flags;
|
|
int i, nfrags;
|
|
|
|
dflags = XCT_MACTX_O | XCT_MACTX_ST | XCT_MACTX_SS | XCT_MACTX_CRC_PAD;
|
|
|
|
if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
|
const unsigned char *nh = skb_network_header(skb);
|
|
|
|
switch (ip_hdr(skb)->protocol) {
|
|
case IPPROTO_TCP:
|
|
dflags |= XCT_MACTX_CSUM_TCP;
|
|
dflags |= XCT_MACTX_IPH(skb_network_header_len(skb) >> 2);
|
|
dflags |= XCT_MACTX_IPO(nh - skb->data);
|
|
break;
|
|
case IPPROTO_UDP:
|
|
dflags |= XCT_MACTX_CSUM_UDP;
|
|
dflags |= XCT_MACTX_IPH(skb_network_header_len(skb) >> 2);
|
|
dflags |= XCT_MACTX_IPO(nh - skb->data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
nfrags = skb_shinfo(skb)->nr_frags;
|
|
|
|
map[0] = pci_map_single(mac->dma_pdev, skb->data, skb_headlen(skb),
|
|
PCI_DMA_TODEVICE);
|
|
map_size[0] = skb_headlen(skb);
|
|
if (dma_mapping_error(map[0]))
|
|
goto out_err_nolock;
|
|
|
|
for (i = 0; i < nfrags; i++) {
|
|
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
|
|
|
|
map[i+1] = pci_map_page(mac->dma_pdev, frag->page,
|
|
frag->page_offset, frag->size,
|
|
PCI_DMA_TODEVICE);
|
|
map_size[i+1] = frag->size;
|
|
if (dma_mapping_error(map[i+1])) {
|
|
nfrags = i;
|
|
goto out_err_nolock;
|
|
}
|
|
}
|
|
|
|
mactx = dflags | XCT_MACTX_LLEN(skb->len);
|
|
|
|
txring = mac->tx;
|
|
|
|
spin_lock_irqsave(&txring->lock, flags);
|
|
|
|
/* Avoid stepping on the same cache line that the DMA controller
|
|
* is currently about to send, so leave at least 8 words available.
|
|
* Total free space needed is mactx + fragments + 8
|
|
*/
|
|
if (RING_AVAIL(txring) < nfrags + 10) {
|
|
/* no room -- stop the queue and wait for tx intr */
|
|
netif_stop_queue(dev);
|
|
goto out_err;
|
|
}
|
|
|
|
TX_RING(mac, txring->next_to_fill) = mactx;
|
|
txring->next_to_fill++;
|
|
TX_RING_INFO(mac, txring->next_to_fill).skb = skb;
|
|
for (i = 0; i <= nfrags; i++) {
|
|
TX_RING(mac, txring->next_to_fill+i) =
|
|
XCT_PTR_LEN(map_size[i]) | XCT_PTR_ADDR(map[i]);
|
|
TX_RING_INFO(mac, txring->next_to_fill+i).dma = map[i];
|
|
}
|
|
|
|
/* We have to add an even number of 8-byte entries to the ring
|
|
* even if the last one is unused. That means always an odd number
|
|
* of pointers + one mactx descriptor.
|
|
*/
|
|
if (nfrags & 1)
|
|
nfrags++;
|
|
|
|
txring->next_to_fill = (txring->next_to_fill + nfrags + 1) &
|
|
(TX_RING_SIZE-1);
|
|
|
|
dev->stats.tx_packets++;
|
|
dev->stats.tx_bytes += skb->len;
|
|
|
|
spin_unlock_irqrestore(&txring->lock, flags);
|
|
|
|
write_dma_reg(mac, PAS_DMA_TXCHAN_INCR(mac->dma_txch), (nfrags+2) >> 1);
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
out_err:
|
|
spin_unlock_irqrestore(&txring->lock, flags);
|
|
out_err_nolock:
|
|
while (nfrags--)
|
|
pci_unmap_single(mac->dma_pdev, map[nfrags], map_size[nfrags],
|
|
PCI_DMA_TODEVICE);
|
|
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
static void pasemi_mac_set_rx_mode(struct net_device *dev)
|
|
{
|
|
struct pasemi_mac *mac = netdev_priv(dev);
|
|
unsigned int flags;
|
|
|
|
flags = read_mac_reg(mac, PAS_MAC_CFG_PCFG);
|
|
|
|
/* Set promiscuous */
|
|
if (dev->flags & IFF_PROMISC)
|
|
flags |= PAS_MAC_CFG_PCFG_PR;
|
|
else
|
|
flags &= ~PAS_MAC_CFG_PCFG_PR;
|
|
|
|
write_mac_reg(mac, PAS_MAC_CFG_PCFG, flags);
|
|
}
|
|
|
|
|
|
static int pasemi_mac_poll(struct napi_struct *napi, int budget)
|
|
{
|
|
struct pasemi_mac *mac = container_of(napi, struct pasemi_mac, napi);
|
|
struct net_device *dev = mac->netdev;
|
|
int pkts;
|
|
|
|
pasemi_mac_clean_tx(mac);
|
|
pkts = pasemi_mac_clean_rx(mac, budget);
|
|
if (pkts < budget) {
|
|
/* all done, no more packets present */
|
|
netif_rx_complete(dev, napi);
|
|
|
|
pasemi_mac_restart_rx_intr(mac);
|
|
}
|
|
return pkts;
|
|
}
|
|
|
|
static void __iomem * __devinit map_onedev(struct pci_dev *p, int index)
|
|
{
|
|
struct device_node *dn;
|
|
void __iomem *ret;
|
|
|
|
dn = pci_device_to_OF_node(p);
|
|
if (!dn)
|
|
goto fallback;
|
|
|
|
ret = of_iomap(dn, index);
|
|
if (!ret)
|
|
goto fallback;
|
|
|
|
return ret;
|
|
fallback:
|
|
/* This is hardcoded and ugly, but we have some firmware versions
|
|
* that don't provide the register space in the device tree. Luckily
|
|
* they are at well-known locations so we can just do the math here.
|
|
*/
|
|
return ioremap(0xe0000000 + (p->devfn << 12), 0x2000);
|
|
}
|
|
|
|
static int __devinit pasemi_mac_map_regs(struct pasemi_mac *mac)
|
|
{
|
|
struct resource res;
|
|
struct device_node *dn;
|
|
int err;
|
|
|
|
mac->dma_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa007, NULL);
|
|
if (!mac->dma_pdev) {
|
|
dev_err(&mac->pdev->dev, "Can't find DMA Controller\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mac->iob_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
|
|
if (!mac->iob_pdev) {
|
|
dev_err(&mac->pdev->dev, "Can't find I/O Bridge\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mac->regs = map_onedev(mac->pdev, 0);
|
|
mac->dma_regs = map_onedev(mac->dma_pdev, 0);
|
|
mac->iob_regs = map_onedev(mac->iob_pdev, 0);
|
|
|
|
if (!mac->regs || !mac->dma_regs || !mac->iob_regs) {
|
|
dev_err(&mac->pdev->dev, "Can't map registers\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* The dma status structure is located in the I/O bridge, and
|
|
* is cache coherent.
|
|
*/
|
|
if (!dma_status) {
|
|
dn = pci_device_to_OF_node(mac->iob_pdev);
|
|
if (dn)
|
|
err = of_address_to_resource(dn, 1, &res);
|
|
if (!dn || err) {
|
|
/* Fallback for old firmware */
|
|
res.start = 0xfd800000;
|
|
res.end = res.start + 0x1000;
|
|
}
|
|
dma_status = __ioremap(res.start, res.end-res.start, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit
|
|
pasemi_mac_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
static int index = 0;
|
|
struct net_device *dev;
|
|
struct pasemi_mac *mac;
|
|
int err;
|
|
DECLARE_MAC_BUF(mac_buf);
|
|
|
|
err = pci_enable_device(pdev);
|
|
if (err)
|
|
return err;
|
|
|
|
dev = alloc_etherdev(sizeof(struct pasemi_mac));
|
|
if (dev == NULL) {
|
|
dev_err(&pdev->dev,
|
|
"pasemi_mac: Could not allocate ethernet device.\n");
|
|
err = -ENOMEM;
|
|
goto out_disable_device;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, dev);
|
|
SET_NETDEV_DEV(dev, &pdev->dev);
|
|
|
|
mac = netdev_priv(dev);
|
|
|
|
mac->pdev = pdev;
|
|
mac->netdev = dev;
|
|
|
|
netif_napi_add(dev, &mac->napi, pasemi_mac_poll, 64);
|
|
|
|
dev->features = NETIF_F_HW_CSUM | NETIF_F_LLTX | NETIF_F_SG;
|
|
|
|
/* These should come out of the device tree eventually */
|
|
mac->dma_txch = index;
|
|
mac->dma_rxch = index;
|
|
|
|
/* We probe GMAC before XAUI, but the DMA interfaces are
|
|
* in XAUI, GMAC order.
|
|
*/
|
|
if (index < 4)
|
|
mac->dma_if = index + 2;
|
|
else
|
|
mac->dma_if = index - 4;
|
|
index++;
|
|
|
|
switch (pdev->device) {
|
|
case 0xa005:
|
|
mac->type = MAC_TYPE_GMAC;
|
|
break;
|
|
case 0xa006:
|
|
mac->type = MAC_TYPE_XAUI;
|
|
break;
|
|
default:
|
|
err = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
/* get mac addr from device tree */
|
|
if (pasemi_get_mac_addr(mac) || !is_valid_ether_addr(mac->mac_addr)) {
|
|
err = -ENODEV;
|
|
goto out;
|
|
}
|
|
memcpy(dev->dev_addr, mac->mac_addr, sizeof(mac->mac_addr));
|
|
|
|
dev->open = pasemi_mac_open;
|
|
dev->stop = pasemi_mac_close;
|
|
dev->hard_start_xmit = pasemi_mac_start_tx;
|
|
dev->set_multicast_list = pasemi_mac_set_rx_mode;
|
|
|
|
err = pasemi_mac_map_regs(mac);
|
|
if (err)
|
|
goto out;
|
|
|
|
mac->rx_status = &dma_status->rx_sta[mac->dma_rxch];
|
|
mac->tx_status = &dma_status->tx_sta[mac->dma_txch];
|
|
|
|
mac->msg_enable = netif_msg_init(debug, DEFAULT_MSG_ENABLE);
|
|
|
|
/* Enable most messages by default */
|
|
mac->msg_enable = (NETIF_MSG_IFUP << 1 ) - 1;
|
|
|
|
err = register_netdev(dev);
|
|
|
|
if (err) {
|
|
dev_err(&mac->pdev->dev, "register_netdev failed with error %d\n",
|
|
err);
|
|
goto out;
|
|
} else if netif_msg_probe(mac)
|
|
printk(KERN_INFO "%s: PA Semi %s: intf %d, txch %d, rxch %d, "
|
|
"hw addr %s\n",
|
|
dev->name, mac->type == MAC_TYPE_GMAC ? "GMAC" : "XAUI",
|
|
mac->dma_if, mac->dma_txch, mac->dma_rxch,
|
|
print_mac(mac_buf, dev->dev_addr));
|
|
|
|
return err;
|
|
|
|
out:
|
|
if (mac->iob_pdev)
|
|
pci_dev_put(mac->iob_pdev);
|
|
if (mac->dma_pdev)
|
|
pci_dev_put(mac->dma_pdev);
|
|
if (mac->dma_regs)
|
|
iounmap(mac->dma_regs);
|
|
if (mac->iob_regs)
|
|
iounmap(mac->iob_regs);
|
|
if (mac->regs)
|
|
iounmap(mac->regs);
|
|
|
|
free_netdev(dev);
|
|
out_disable_device:
|
|
pci_disable_device(pdev);
|
|
return err;
|
|
|
|
}
|
|
|
|
static void __devexit pasemi_mac_remove(struct pci_dev *pdev)
|
|
{
|
|
struct net_device *netdev = pci_get_drvdata(pdev);
|
|
struct pasemi_mac *mac;
|
|
|
|
if (!netdev)
|
|
return;
|
|
|
|
mac = netdev_priv(netdev);
|
|
|
|
unregister_netdev(netdev);
|
|
|
|
pci_disable_device(pdev);
|
|
pci_dev_put(mac->dma_pdev);
|
|
pci_dev_put(mac->iob_pdev);
|
|
|
|
iounmap(mac->regs);
|
|
iounmap(mac->dma_regs);
|
|
iounmap(mac->iob_regs);
|
|
|
|
pci_set_drvdata(pdev, NULL);
|
|
free_netdev(netdev);
|
|
}
|
|
|
|
static struct pci_device_id pasemi_mac_pci_tbl[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_PASEMI, 0xa005) },
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_PASEMI, 0xa006) },
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, pasemi_mac_pci_tbl);
|
|
|
|
static struct pci_driver pasemi_mac_driver = {
|
|
.name = "pasemi_mac",
|
|
.id_table = pasemi_mac_pci_tbl,
|
|
.probe = pasemi_mac_probe,
|
|
.remove = __devexit_p(pasemi_mac_remove),
|
|
};
|
|
|
|
static void __exit pasemi_mac_cleanup_module(void)
|
|
{
|
|
pci_unregister_driver(&pasemi_mac_driver);
|
|
__iounmap(dma_status);
|
|
dma_status = NULL;
|
|
}
|
|
|
|
int pasemi_mac_init_module(void)
|
|
{
|
|
return pci_register_driver(&pasemi_mac_driver);
|
|
}
|
|
|
|
module_init(pasemi_mac_init_module);
|
|
module_exit(pasemi_mac_cleanup_module);
|