mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-03 17:40:04 +00:00
![Ilya Yanok](/assets/img/avatar_default.png)
platform_set_drvdata() was used with argument of incorrect type and could cause memory corruption. Moreover, because of not setting drvdata in the correct place not all resources were freed upon module unload. Signed-off-by: Ilya Yanok <yanok@emcraft.com> Signed-off-by: David S. Miller <davem@davemloft.net>
993 lines
25 KiB
C
993 lines
25 KiB
C
/*
|
|
* Dave DNET Ethernet Controller driver
|
|
*
|
|
* Copyright (C) 2008 Dave S.r.l. <www.dave.eu>
|
|
* Copyright (C) 2009 Ilya Yanok, Emcraft Systems Ltd, <yanok@emcraft.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/phy.h>
|
|
|
|
#include "dnet.h"
|
|
|
|
#undef DEBUG
|
|
|
|
/* function for reading internal MAC register */
|
|
static u16 dnet_readw_mac(struct dnet *bp, u16 reg)
|
|
{
|
|
u16 data_read;
|
|
|
|
/* issue a read */
|
|
dnet_writel(bp, reg, MACREG_ADDR);
|
|
|
|
/* since a read/write op to the MAC is very slow,
|
|
* we must wait before reading the data */
|
|
ndelay(500);
|
|
|
|
/* read data read from the MAC register */
|
|
data_read = dnet_readl(bp, MACREG_DATA);
|
|
|
|
/* all done */
|
|
return data_read;
|
|
}
|
|
|
|
/* function for writing internal MAC register */
|
|
static void dnet_writew_mac(struct dnet *bp, u16 reg, u16 val)
|
|
{
|
|
/* load data to write */
|
|
dnet_writel(bp, val, MACREG_DATA);
|
|
|
|
/* issue a write */
|
|
dnet_writel(bp, reg | DNET_INTERNAL_WRITE, MACREG_ADDR);
|
|
|
|
/* since a read/write op to the MAC is very slow,
|
|
* we must wait before exiting */
|
|
ndelay(500);
|
|
}
|
|
|
|
static void __dnet_set_hwaddr(struct dnet *bp)
|
|
{
|
|
u16 tmp;
|
|
|
|
tmp = be16_to_cpup((__be16 *)bp->dev->dev_addr);
|
|
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_0_REG, tmp);
|
|
tmp = be16_to_cpup((__be16 *)(bp->dev->dev_addr + 2));
|
|
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_1_REG, tmp);
|
|
tmp = be16_to_cpup((__be16 *)(bp->dev->dev_addr + 4));
|
|
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_2_REG, tmp);
|
|
}
|
|
|
|
static void __devinit dnet_get_hwaddr(struct dnet *bp)
|
|
{
|
|
u16 tmp;
|
|
u8 addr[6];
|
|
|
|
/*
|
|
* from MAC docs:
|
|
* "Note that the MAC address is stored in the registers in Hexadecimal
|
|
* form. For example, to set the MAC Address to: AC-DE-48-00-00-80
|
|
* would require writing 0xAC (octet 0) to address 0x0B (high byte of
|
|
* Mac_addr[15:0]), 0xDE (octet 1) to address 0x0A (Low byte of
|
|
* Mac_addr[15:0]), 0x48 (octet 2) to address 0x0D (high byte of
|
|
* Mac_addr[15:0]), 0x00 (octet 3) to address 0x0C (Low byte of
|
|
* Mac_addr[15:0]), 0x00 (octet 4) to address 0x0F (high byte of
|
|
* Mac_addr[15:0]), and 0x80 (octet 5) to address * 0x0E (Low byte of
|
|
* Mac_addr[15:0]).
|
|
*/
|
|
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_0_REG);
|
|
*((__be16 *)addr) = cpu_to_be16(tmp);
|
|
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_1_REG);
|
|
*((__be16 *)(addr + 2)) = cpu_to_be16(tmp);
|
|
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_2_REG);
|
|
*((__be16 *)(addr + 4)) = cpu_to_be16(tmp);
|
|
|
|
if (is_valid_ether_addr(addr))
|
|
memcpy(bp->dev->dev_addr, addr, sizeof(addr));
|
|
}
|
|
|
|
static int dnet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
|
|
{
|
|
struct dnet *bp = bus->priv;
|
|
u16 value;
|
|
|
|
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
|
|
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
|
|
cpu_relax();
|
|
|
|
/* only 5 bits allowed for phy-addr and reg_offset */
|
|
mii_id &= 0x1f;
|
|
regnum &= 0x1f;
|
|
|
|
/* prepare reg_value for a read */
|
|
value = (mii_id << 8);
|
|
value |= regnum;
|
|
|
|
/* write control word */
|
|
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG, value);
|
|
|
|
/* wait for end of transfer */
|
|
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
|
|
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
|
|
cpu_relax();
|
|
|
|
value = dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_DAT_REG);
|
|
|
|
pr_debug("mdio_read %02x:%02x <- %04x\n", mii_id, regnum, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
static int dnet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
|
|
u16 value)
|
|
{
|
|
struct dnet *bp = bus->priv;
|
|
u16 tmp;
|
|
|
|
pr_debug("mdio_write %02x:%02x <- %04x\n", mii_id, regnum, value);
|
|
|
|
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
|
|
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
|
|
cpu_relax();
|
|
|
|
/* prepare for a write operation */
|
|
tmp = (1 << 13);
|
|
|
|
/* only 5 bits allowed for phy-addr and reg_offset */
|
|
mii_id &= 0x1f;
|
|
regnum &= 0x1f;
|
|
|
|
/* only 16 bits on data */
|
|
value &= 0xffff;
|
|
|
|
/* prepare reg_value for a write */
|
|
tmp |= (mii_id << 8);
|
|
tmp |= regnum;
|
|
|
|
/* write data to write first */
|
|
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_DAT_REG, value);
|
|
|
|
/* write control word */
|
|
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG, tmp);
|
|
|
|
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
|
|
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
|
|
cpu_relax();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dnet_mdio_reset(struct mii_bus *bus)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void dnet_handle_link_change(struct net_device *dev)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct phy_device *phydev = bp->phy_dev;
|
|
unsigned long flags;
|
|
u32 mode_reg, ctl_reg;
|
|
|
|
int status_change = 0;
|
|
|
|
spin_lock_irqsave(&bp->lock, flags);
|
|
|
|
mode_reg = dnet_readw_mac(bp, DNET_INTERNAL_MODE_REG);
|
|
ctl_reg = dnet_readw_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG);
|
|
|
|
if (phydev->link) {
|
|
if (bp->duplex != phydev->duplex) {
|
|
if (phydev->duplex)
|
|
ctl_reg &=
|
|
~(DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP);
|
|
else
|
|
ctl_reg |=
|
|
DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP;
|
|
|
|
bp->duplex = phydev->duplex;
|
|
status_change = 1;
|
|
}
|
|
|
|
if (bp->speed != phydev->speed) {
|
|
status_change = 1;
|
|
switch (phydev->speed) {
|
|
case 1000:
|
|
mode_reg |= DNET_INTERNAL_MODE_GBITEN;
|
|
break;
|
|
case 100:
|
|
case 10:
|
|
mode_reg &= ~DNET_INTERNAL_MODE_GBITEN;
|
|
break;
|
|
default:
|
|
printk(KERN_WARNING
|
|
"%s: Ack! Speed (%d) is not "
|
|
"10/100/1000!\n", dev->name,
|
|
phydev->speed);
|
|
break;
|
|
}
|
|
bp->speed = phydev->speed;
|
|
}
|
|
}
|
|
|
|
if (phydev->link != bp->link) {
|
|
if (phydev->link) {
|
|
mode_reg |=
|
|
(DNET_INTERNAL_MODE_RXEN | DNET_INTERNAL_MODE_TXEN);
|
|
} else {
|
|
mode_reg &=
|
|
~(DNET_INTERNAL_MODE_RXEN |
|
|
DNET_INTERNAL_MODE_TXEN);
|
|
bp->speed = 0;
|
|
bp->duplex = -1;
|
|
}
|
|
bp->link = phydev->link;
|
|
|
|
status_change = 1;
|
|
}
|
|
|
|
if (status_change) {
|
|
dnet_writew_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG, ctl_reg);
|
|
dnet_writew_mac(bp, DNET_INTERNAL_MODE_REG, mode_reg);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&bp->lock, flags);
|
|
|
|
if (status_change) {
|
|
if (phydev->link)
|
|
printk(KERN_INFO "%s: link up (%d/%s)\n",
|
|
dev->name, phydev->speed,
|
|
DUPLEX_FULL == phydev->duplex ? "Full" : "Half");
|
|
else
|
|
printk(KERN_INFO "%s: link down\n", dev->name);
|
|
}
|
|
}
|
|
|
|
static int dnet_mii_probe(struct net_device *dev)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct phy_device *phydev = NULL;
|
|
int phy_addr;
|
|
|
|
/* find the first phy */
|
|
for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) {
|
|
if (bp->mii_bus->phy_map[phy_addr]) {
|
|
phydev = bp->mii_bus->phy_map[phy_addr];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!phydev) {
|
|
printk(KERN_ERR "%s: no PHY found\n", dev->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* TODO : add pin_irq */
|
|
|
|
/* attach the mac to the phy */
|
|
if (bp->capabilities & DNET_HAS_RMII) {
|
|
phydev = phy_connect(dev, dev_name(&phydev->dev),
|
|
&dnet_handle_link_change, 0,
|
|
PHY_INTERFACE_MODE_RMII);
|
|
} else {
|
|
phydev = phy_connect(dev, dev_name(&phydev->dev),
|
|
&dnet_handle_link_change, 0,
|
|
PHY_INTERFACE_MODE_MII);
|
|
}
|
|
|
|
if (IS_ERR(phydev)) {
|
|
printk(KERN_ERR "%s: Could not attach to PHY\n", dev->name);
|
|
return PTR_ERR(phydev);
|
|
}
|
|
|
|
/* mask with MAC supported features */
|
|
if (bp->capabilities & DNET_HAS_GIGABIT)
|
|
phydev->supported &= PHY_GBIT_FEATURES;
|
|
else
|
|
phydev->supported &= PHY_BASIC_FEATURES;
|
|
|
|
phydev->supported |= SUPPORTED_Asym_Pause | SUPPORTED_Pause;
|
|
|
|
phydev->advertising = phydev->supported;
|
|
|
|
bp->link = 0;
|
|
bp->speed = 0;
|
|
bp->duplex = -1;
|
|
bp->phy_dev = phydev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dnet_mii_init(struct dnet *bp)
|
|
{
|
|
int err, i;
|
|
|
|
bp->mii_bus = mdiobus_alloc();
|
|
if (bp->mii_bus == NULL)
|
|
return -ENOMEM;
|
|
|
|
bp->mii_bus->name = "dnet_mii_bus";
|
|
bp->mii_bus->read = &dnet_mdio_read;
|
|
bp->mii_bus->write = &dnet_mdio_write;
|
|
bp->mii_bus->reset = &dnet_mdio_reset;
|
|
|
|
snprintf(bp->mii_bus->id, MII_BUS_ID_SIZE, "%x", 0);
|
|
|
|
bp->mii_bus->priv = bp;
|
|
|
|
bp->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);
|
|
if (!bp->mii_bus->irq) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
for (i = 0; i < PHY_MAX_ADDR; i++)
|
|
bp->mii_bus->irq[i] = PHY_POLL;
|
|
|
|
if (mdiobus_register(bp->mii_bus)) {
|
|
err = -ENXIO;
|
|
goto err_out_free_mdio_irq;
|
|
}
|
|
|
|
if (dnet_mii_probe(bp->dev) != 0) {
|
|
err = -ENXIO;
|
|
goto err_out_unregister_bus;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_out_unregister_bus:
|
|
mdiobus_unregister(bp->mii_bus);
|
|
err_out_free_mdio_irq:
|
|
kfree(bp->mii_bus->irq);
|
|
err_out:
|
|
mdiobus_free(bp->mii_bus);
|
|
return err;
|
|
}
|
|
|
|
/* For Neptune board: LINK1000 as Link LED and TX as activity LED */
|
|
static int dnet_phy_marvell_fixup(struct phy_device *phydev)
|
|
{
|
|
return phy_write(phydev, 0x18, 0x4148);
|
|
}
|
|
|
|
static void dnet_update_stats(struct dnet *bp)
|
|
{
|
|
u32 __iomem *reg = bp->regs + DNET_RX_PKT_IGNR_CNT;
|
|
u32 *p = &bp->hw_stats.rx_pkt_ignr;
|
|
u32 *end = &bp->hw_stats.rx_byte + 1;
|
|
|
|
WARN_ON((unsigned long)(end - p - 1) !=
|
|
(DNET_RX_BYTE_CNT - DNET_RX_PKT_IGNR_CNT) / 4);
|
|
|
|
for (; p < end; p++, reg++)
|
|
*p += readl(reg);
|
|
|
|
reg = bp->regs + DNET_TX_UNICAST_CNT;
|
|
p = &bp->hw_stats.tx_unicast;
|
|
end = &bp->hw_stats.tx_byte + 1;
|
|
|
|
WARN_ON((unsigned long)(end - p - 1) !=
|
|
(DNET_TX_BYTE_CNT - DNET_TX_UNICAST_CNT) / 4);
|
|
|
|
for (; p < end; p++, reg++)
|
|
*p += readl(reg);
|
|
}
|
|
|
|
static int dnet_poll(struct napi_struct *napi, int budget)
|
|
{
|
|
struct dnet *bp = container_of(napi, struct dnet, napi);
|
|
struct net_device *dev = bp->dev;
|
|
int npackets = 0;
|
|
unsigned int pkt_len;
|
|
struct sk_buff *skb;
|
|
unsigned int *data_ptr;
|
|
u32 int_enable;
|
|
u32 cmd_word;
|
|
int i;
|
|
|
|
while (npackets < budget) {
|
|
/*
|
|
* break out of while loop if there are no more
|
|
* packets waiting
|
|
*/
|
|
if (!(dnet_readl(bp, RX_FIFO_WCNT) >> 16)) {
|
|
napi_complete(napi);
|
|
int_enable = dnet_readl(bp, INTR_ENB);
|
|
int_enable |= DNET_INTR_SRC_RX_CMDFIFOAF;
|
|
dnet_writel(bp, int_enable, INTR_ENB);
|
|
return 0;
|
|
}
|
|
|
|
cmd_word = dnet_readl(bp, RX_LEN_FIFO);
|
|
pkt_len = cmd_word & 0xFFFF;
|
|
|
|
if (cmd_word & 0xDF180000)
|
|
printk(KERN_ERR "%s packet receive error %x\n",
|
|
__func__, cmd_word);
|
|
|
|
skb = dev_alloc_skb(pkt_len + 5);
|
|
if (skb != NULL) {
|
|
/* Align IP on 16 byte boundaries */
|
|
skb_reserve(skb, 2);
|
|
/*
|
|
* 'skb_put()' points to the start of sk_buff
|
|
* data area.
|
|
*/
|
|
data_ptr = (unsigned int *)skb_put(skb, pkt_len);
|
|
for (i = 0; i < (pkt_len + 3) >> 2; i++)
|
|
*data_ptr++ = dnet_readl(bp, RX_DATA_FIFO);
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
netif_receive_skb(skb);
|
|
npackets++;
|
|
} else
|
|
printk(KERN_NOTICE
|
|
"%s: No memory to allocate a sk_buff of "
|
|
"size %u.\n", dev->name, pkt_len);
|
|
}
|
|
|
|
budget -= npackets;
|
|
|
|
if (npackets < budget) {
|
|
/* We processed all packets available. Tell NAPI it can
|
|
* stop polling then re-enable rx interrupts */
|
|
napi_complete(napi);
|
|
int_enable = dnet_readl(bp, INTR_ENB);
|
|
int_enable |= DNET_INTR_SRC_RX_CMDFIFOAF;
|
|
dnet_writel(bp, int_enable, INTR_ENB);
|
|
return 0;
|
|
}
|
|
|
|
/* There are still packets waiting */
|
|
return 1;
|
|
}
|
|
|
|
static irqreturn_t dnet_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct net_device *dev = dev_id;
|
|
struct dnet *bp = netdev_priv(dev);
|
|
u32 int_src, int_enable, int_current;
|
|
unsigned long flags;
|
|
unsigned int handled = 0;
|
|
|
|
spin_lock_irqsave(&bp->lock, flags);
|
|
|
|
/* read and clear the DNET irq (clear on read) */
|
|
int_src = dnet_readl(bp, INTR_SRC);
|
|
int_enable = dnet_readl(bp, INTR_ENB);
|
|
int_current = int_src & int_enable;
|
|
|
|
/* restart the queue if we had stopped it for TX fifo almost full */
|
|
if (int_current & DNET_INTR_SRC_TX_FIFOAE) {
|
|
int_enable = dnet_readl(bp, INTR_ENB);
|
|
int_enable &= ~DNET_INTR_ENB_TX_FIFOAE;
|
|
dnet_writel(bp, int_enable, INTR_ENB);
|
|
netif_wake_queue(dev);
|
|
handled = 1;
|
|
}
|
|
|
|
/* RX FIFO error checking */
|
|
if (int_current &
|
|
(DNET_INTR_SRC_RX_CMDFIFOFF | DNET_INTR_SRC_RX_DATAFIFOFF)) {
|
|
printk(KERN_ERR "%s: RX fifo error %x, irq %x\n", __func__,
|
|
dnet_readl(bp, RX_STATUS), int_current);
|
|
/* we can only flush the RX FIFOs */
|
|
dnet_writel(bp, DNET_SYS_CTL_RXFIFOFLUSH, SYS_CTL);
|
|
ndelay(500);
|
|
dnet_writel(bp, 0, SYS_CTL);
|
|
handled = 1;
|
|
}
|
|
|
|
/* TX FIFO error checking */
|
|
if (int_current &
|
|
(DNET_INTR_SRC_TX_FIFOFULL | DNET_INTR_SRC_TX_DISCFRM)) {
|
|
printk(KERN_ERR "%s: TX fifo error %x, irq %x\n", __func__,
|
|
dnet_readl(bp, TX_STATUS), int_current);
|
|
/* we can only flush the TX FIFOs */
|
|
dnet_writel(bp, DNET_SYS_CTL_TXFIFOFLUSH, SYS_CTL);
|
|
ndelay(500);
|
|
dnet_writel(bp, 0, SYS_CTL);
|
|
handled = 1;
|
|
}
|
|
|
|
if (int_current & DNET_INTR_SRC_RX_CMDFIFOAF) {
|
|
if (napi_schedule_prep(&bp->napi)) {
|
|
/*
|
|
* There's no point taking any more interrupts
|
|
* until we have processed the buffers
|
|
*/
|
|
/* Disable Rx interrupts and schedule NAPI poll */
|
|
int_enable = dnet_readl(bp, INTR_ENB);
|
|
int_enable &= ~DNET_INTR_SRC_RX_CMDFIFOAF;
|
|
dnet_writel(bp, int_enable, INTR_ENB);
|
|
__napi_schedule(&bp->napi);
|
|
}
|
|
handled = 1;
|
|
}
|
|
|
|
if (!handled)
|
|
pr_debug("%s: irq %x remains\n", __func__, int_current);
|
|
|
|
spin_unlock_irqrestore(&bp->lock, flags);
|
|
|
|
return IRQ_RETVAL(handled);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static inline void dnet_print_skb(struct sk_buff *skb)
|
|
{
|
|
int k;
|
|
printk(KERN_DEBUG PFX "data:");
|
|
for (k = 0; k < skb->len; k++)
|
|
printk(" %02x", (unsigned int)skb->data[k]);
|
|
printk("\n");
|
|
}
|
|
#else
|
|
#define dnet_print_skb(skb) do {} while (0)
|
|
#endif
|
|
|
|
static netdev_tx_t dnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
|
|
struct dnet *bp = netdev_priv(dev);
|
|
u32 tx_status, irq_enable;
|
|
unsigned int len, i, tx_cmd, wrsz;
|
|
unsigned long flags;
|
|
unsigned int *bufp;
|
|
|
|
tx_status = dnet_readl(bp, TX_STATUS);
|
|
|
|
pr_debug("start_xmit: len %u head %p data %p\n",
|
|
skb->len, skb->head, skb->data);
|
|
dnet_print_skb(skb);
|
|
|
|
/* frame size (words) */
|
|
len = (skb->len + 3) >> 2;
|
|
|
|
spin_lock_irqsave(&bp->lock, flags);
|
|
|
|
tx_status = dnet_readl(bp, TX_STATUS);
|
|
|
|
bufp = (unsigned int *)(((unsigned long) skb->data) & ~0x3UL);
|
|
wrsz = (u32) skb->len + 3;
|
|
wrsz += ((unsigned long) skb->data) & 0x3;
|
|
wrsz >>= 2;
|
|
tx_cmd = ((((unsigned long)(skb->data)) & 0x03) << 16) | (u32) skb->len;
|
|
|
|
/* check if there is enough room for the current frame */
|
|
if (wrsz < (DNET_FIFO_SIZE - dnet_readl(bp, TX_FIFO_WCNT))) {
|
|
for (i = 0; i < wrsz; i++)
|
|
dnet_writel(bp, *bufp++, TX_DATA_FIFO);
|
|
|
|
/*
|
|
* inform MAC that a packet's written and ready to be
|
|
* shipped out
|
|
*/
|
|
dnet_writel(bp, tx_cmd, TX_LEN_FIFO);
|
|
}
|
|
|
|
if (dnet_readl(bp, TX_FIFO_WCNT) > DNET_FIFO_TX_DATA_AF_TH) {
|
|
netif_stop_queue(dev);
|
|
tx_status = dnet_readl(bp, INTR_SRC);
|
|
irq_enable = dnet_readl(bp, INTR_ENB);
|
|
irq_enable |= DNET_INTR_ENB_TX_FIFOAE;
|
|
dnet_writel(bp, irq_enable, INTR_ENB);
|
|
}
|
|
|
|
/* free the buffer */
|
|
dev_kfree_skb(skb);
|
|
|
|
spin_unlock_irqrestore(&bp->lock, flags);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static void dnet_reset_hw(struct dnet *bp)
|
|
{
|
|
/* put ts_mac in IDLE state i.e. disable rx/tx */
|
|
dnet_writew_mac(bp, DNET_INTERNAL_MODE_REG, DNET_INTERNAL_MODE_FCEN);
|
|
|
|
/*
|
|
* RX FIFO almost full threshold: only cmd FIFO almost full is
|
|
* implemented for RX side
|
|
*/
|
|
dnet_writel(bp, DNET_FIFO_RX_CMD_AF_TH, RX_FIFO_TH);
|
|
/*
|
|
* TX FIFO almost empty threshold: only data FIFO almost empty
|
|
* is implemented for TX side
|
|
*/
|
|
dnet_writel(bp, DNET_FIFO_TX_DATA_AE_TH, TX_FIFO_TH);
|
|
|
|
/* flush rx/tx fifos */
|
|
dnet_writel(bp, DNET_SYS_CTL_RXFIFOFLUSH | DNET_SYS_CTL_TXFIFOFLUSH,
|
|
SYS_CTL);
|
|
msleep(1);
|
|
dnet_writel(bp, 0, SYS_CTL);
|
|
}
|
|
|
|
static void dnet_init_hw(struct dnet *bp)
|
|
{
|
|
u32 config;
|
|
|
|
dnet_reset_hw(bp);
|
|
__dnet_set_hwaddr(bp);
|
|
|
|
config = dnet_readw_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG);
|
|
|
|
if (bp->dev->flags & IFF_PROMISC)
|
|
/* Copy All Frames */
|
|
config |= DNET_INTERNAL_RXTX_CONTROL_ENPROMISC;
|
|
if (!(bp->dev->flags & IFF_BROADCAST))
|
|
/* No BroadCast */
|
|
config |= DNET_INTERNAL_RXTX_CONTROL_RXMULTICAST;
|
|
|
|
config |= DNET_INTERNAL_RXTX_CONTROL_RXPAUSE |
|
|
DNET_INTERNAL_RXTX_CONTROL_RXBROADCAST |
|
|
DNET_INTERNAL_RXTX_CONTROL_DROPCONTROL |
|
|
DNET_INTERNAL_RXTX_CONTROL_DISCFXFCS;
|
|
|
|
dnet_writew_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG, config);
|
|
|
|
/* clear irq before enabling them */
|
|
config = dnet_readl(bp, INTR_SRC);
|
|
|
|
/* enable RX/TX interrupt, recv packet ready interrupt */
|
|
dnet_writel(bp, DNET_INTR_ENB_GLOBAL_ENABLE | DNET_INTR_ENB_RX_SUMMARY |
|
|
DNET_INTR_ENB_TX_SUMMARY | DNET_INTR_ENB_RX_FIFOERR |
|
|
DNET_INTR_ENB_RX_ERROR | DNET_INTR_ENB_RX_FIFOFULL |
|
|
DNET_INTR_ENB_TX_FIFOFULL | DNET_INTR_ENB_TX_DISCFRM |
|
|
DNET_INTR_ENB_RX_PKTRDY, INTR_ENB);
|
|
}
|
|
|
|
static int dnet_open(struct net_device *dev)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
|
|
/* if the phy is not yet register, retry later */
|
|
if (!bp->phy_dev)
|
|
return -EAGAIN;
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr))
|
|
return -EADDRNOTAVAIL;
|
|
|
|
napi_enable(&bp->napi);
|
|
dnet_init_hw(bp);
|
|
|
|
phy_start_aneg(bp->phy_dev);
|
|
|
|
/* schedule a link state check */
|
|
phy_start(bp->phy_dev);
|
|
|
|
netif_start_queue(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dnet_close(struct net_device *dev)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
|
|
netif_stop_queue(dev);
|
|
napi_disable(&bp->napi);
|
|
|
|
if (bp->phy_dev)
|
|
phy_stop(bp->phy_dev);
|
|
|
|
dnet_reset_hw(bp);
|
|
netif_carrier_off(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void dnet_print_pretty_hwstats(struct dnet_stats *hwstat)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
pr_debug("----------------------------- RX statistics "
|
|
"-------------------------------\n");
|
|
pr_debug("RX_PKT_IGNR_CNT %-8x\n", hwstat->rx_pkt_ignr);
|
|
pr_debug("RX_LEN_CHK_ERR_CNT %-8x\n", hwstat->rx_len_chk_err);
|
|
pr_debug("RX_LNG_FRM_CNT %-8x\n", hwstat->rx_lng_frm);
|
|
pr_debug("RX_SHRT_FRM_CNT %-8x\n", hwstat->rx_shrt_frm);
|
|
pr_debug("RX_IPG_VIOL_CNT %-8x\n", hwstat->rx_ipg_viol);
|
|
pr_debug("RX_CRC_ERR_CNT %-8x\n", hwstat->rx_crc_err);
|
|
pr_debug("RX_OK_PKT_CNT %-8x\n", hwstat->rx_ok_pkt);
|
|
pr_debug("RX_CTL_FRM_CNT %-8x\n", hwstat->rx_ctl_frm);
|
|
pr_debug("RX_PAUSE_FRM_CNT %-8x\n", hwstat->rx_pause_frm);
|
|
pr_debug("RX_MULTICAST_CNT %-8x\n", hwstat->rx_multicast);
|
|
pr_debug("RX_BROADCAST_CNT %-8x\n", hwstat->rx_broadcast);
|
|
pr_debug("RX_VLAN_TAG_CNT %-8x\n", hwstat->rx_vlan_tag);
|
|
pr_debug("RX_PRE_SHRINK_CNT %-8x\n", hwstat->rx_pre_shrink);
|
|
pr_debug("RX_DRIB_NIB_CNT %-8x\n", hwstat->rx_drib_nib);
|
|
pr_debug("RX_UNSUP_OPCD_CNT %-8x\n", hwstat->rx_unsup_opcd);
|
|
pr_debug("RX_BYTE_CNT %-8x\n", hwstat->rx_byte);
|
|
pr_debug("----------------------------- TX statistics "
|
|
"-------------------------------\n");
|
|
pr_debug("TX_UNICAST_CNT %-8x\n", hwstat->tx_unicast);
|
|
pr_debug("TX_PAUSE_FRM_CNT %-8x\n", hwstat->tx_pause_frm);
|
|
pr_debug("TX_MULTICAST_CNT %-8x\n", hwstat->tx_multicast);
|
|
pr_debug("TX_BRDCAST_CNT %-8x\n", hwstat->tx_brdcast);
|
|
pr_debug("TX_VLAN_TAG_CNT %-8x\n", hwstat->tx_vlan_tag);
|
|
pr_debug("TX_BAD_FCS_CNT %-8x\n", hwstat->tx_bad_fcs);
|
|
pr_debug("TX_JUMBO_CNT %-8x\n", hwstat->tx_jumbo);
|
|
pr_debug("TX_BYTE_CNT %-8x\n", hwstat->tx_byte);
|
|
}
|
|
|
|
static struct net_device_stats *dnet_get_stats(struct net_device *dev)
|
|
{
|
|
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct net_device_stats *nstat = &dev->stats;
|
|
struct dnet_stats *hwstat = &bp->hw_stats;
|
|
|
|
/* read stats from hardware */
|
|
dnet_update_stats(bp);
|
|
|
|
/* Convert HW stats into netdevice stats */
|
|
nstat->rx_errors = (hwstat->rx_len_chk_err +
|
|
hwstat->rx_lng_frm + hwstat->rx_shrt_frm +
|
|
/* ignore IGP violation error
|
|
hwstat->rx_ipg_viol + */
|
|
hwstat->rx_crc_err +
|
|
hwstat->rx_pre_shrink +
|
|
hwstat->rx_drib_nib + hwstat->rx_unsup_opcd);
|
|
nstat->tx_errors = hwstat->tx_bad_fcs;
|
|
nstat->rx_length_errors = (hwstat->rx_len_chk_err +
|
|
hwstat->rx_lng_frm +
|
|
hwstat->rx_shrt_frm + hwstat->rx_pre_shrink);
|
|
nstat->rx_crc_errors = hwstat->rx_crc_err;
|
|
nstat->rx_frame_errors = hwstat->rx_pre_shrink + hwstat->rx_drib_nib;
|
|
nstat->rx_packets = hwstat->rx_ok_pkt;
|
|
nstat->tx_packets = (hwstat->tx_unicast +
|
|
hwstat->tx_multicast + hwstat->tx_brdcast);
|
|
nstat->rx_bytes = hwstat->rx_byte;
|
|
nstat->tx_bytes = hwstat->tx_byte;
|
|
nstat->multicast = hwstat->rx_multicast;
|
|
nstat->rx_missed_errors = hwstat->rx_pkt_ignr;
|
|
|
|
dnet_print_pretty_hwstats(hwstat);
|
|
|
|
return nstat;
|
|
}
|
|
|
|
static int dnet_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct phy_device *phydev = bp->phy_dev;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_ethtool_gset(phydev, cmd);
|
|
}
|
|
|
|
static int dnet_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct phy_device *phydev = bp->phy_dev;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_ethtool_sset(phydev, cmd);
|
|
}
|
|
|
|
static int dnet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
{
|
|
struct dnet *bp = netdev_priv(dev);
|
|
struct phy_device *phydev = bp->phy_dev;
|
|
|
|
if (!netif_running(dev))
|
|
return -EINVAL;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_mii_ioctl(phydev, rq, cmd);
|
|
}
|
|
|
|
static void dnet_get_drvinfo(struct net_device *dev,
|
|
struct ethtool_drvinfo *info)
|
|
{
|
|
strcpy(info->driver, DRV_NAME);
|
|
strcpy(info->version, DRV_VERSION);
|
|
strcpy(info->bus_info, "0");
|
|
}
|
|
|
|
static const struct ethtool_ops dnet_ethtool_ops = {
|
|
.get_settings = dnet_get_settings,
|
|
.set_settings = dnet_set_settings,
|
|
.get_drvinfo = dnet_get_drvinfo,
|
|
.get_link = ethtool_op_get_link,
|
|
};
|
|
|
|
static const struct net_device_ops dnet_netdev_ops = {
|
|
.ndo_open = dnet_open,
|
|
.ndo_stop = dnet_close,
|
|
.ndo_get_stats = dnet_get_stats,
|
|
.ndo_start_xmit = dnet_start_xmit,
|
|
.ndo_do_ioctl = dnet_ioctl,
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_change_mtu = eth_change_mtu,
|
|
};
|
|
|
|
static int __devinit dnet_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
struct net_device *dev;
|
|
struct dnet *bp;
|
|
struct phy_device *phydev;
|
|
int err = -ENXIO;
|
|
unsigned int mem_base, mem_size, irq;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "no mmio resource defined\n");
|
|
goto err_out;
|
|
}
|
|
mem_base = res->start;
|
|
mem_size = resource_size(res);
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (!request_mem_region(mem_base, mem_size, DRV_NAME)) {
|
|
dev_err(&pdev->dev, "no memory region available\n");
|
|
err = -EBUSY;
|
|
goto err_out;
|
|
}
|
|
|
|
err = -ENOMEM;
|
|
dev = alloc_etherdev(sizeof(*bp));
|
|
if (!dev) {
|
|
dev_err(&pdev->dev, "etherdev alloc failed, aborting.\n");
|
|
goto err_out_release_mem;
|
|
}
|
|
|
|
/* TODO: Actually, we have some interesting features... */
|
|
dev->features |= 0;
|
|
|
|
bp = netdev_priv(dev);
|
|
bp->dev = dev;
|
|
|
|
platform_set_drvdata(pdev, dev);
|
|
SET_NETDEV_DEV(dev, &pdev->dev);
|
|
|
|
spin_lock_init(&bp->lock);
|
|
|
|
bp->regs = ioremap(mem_base, mem_size);
|
|
if (!bp->regs) {
|
|
dev_err(&pdev->dev, "failed to map registers, aborting.\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_dev;
|
|
}
|
|
|
|
dev->irq = irq;
|
|
err = request_irq(dev->irq, dnet_interrupt, 0, DRV_NAME, dev);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "Unable to request IRQ %d (error %d)\n",
|
|
irq, err);
|
|
goto err_out_iounmap;
|
|
}
|
|
|
|
dev->netdev_ops = &dnet_netdev_ops;
|
|
netif_napi_add(dev, &bp->napi, dnet_poll, 64);
|
|
dev->ethtool_ops = &dnet_ethtool_ops;
|
|
|
|
dev->base_addr = (unsigned long)bp->regs;
|
|
|
|
bp->capabilities = dnet_readl(bp, VERCAPS) & DNET_CAPS_MASK;
|
|
|
|
dnet_get_hwaddr(bp);
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
|
/* choose a random ethernet address */
|
|
random_ether_addr(dev->dev_addr);
|
|
__dnet_set_hwaddr(bp);
|
|
}
|
|
|
|
err = register_netdev(dev);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "Cannot register net device, aborting.\n");
|
|
goto err_out_free_irq;
|
|
}
|
|
|
|
/* register the PHY board fixup (for Marvell 88E1111) */
|
|
err = phy_register_fixup_for_uid(0x01410cc0, 0xfffffff0,
|
|
dnet_phy_marvell_fixup);
|
|
/* we can live without it, so just issue a warning */
|
|
if (err)
|
|
dev_warn(&pdev->dev, "Cannot register PHY board fixup.\n");
|
|
|
|
err = dnet_mii_init(bp);
|
|
if (err)
|
|
goto err_out_unregister_netdev;
|
|
|
|
dev_info(&pdev->dev, "Dave DNET at 0x%p (0x%08x) irq %d %pM\n",
|
|
bp->regs, mem_base, dev->irq, dev->dev_addr);
|
|
dev_info(&pdev->dev, "has %smdio, %sirq, %sgigabit, %sdma\n",
|
|
(bp->capabilities & DNET_HAS_MDIO) ? "" : "no ",
|
|
(bp->capabilities & DNET_HAS_IRQ) ? "" : "no ",
|
|
(bp->capabilities & DNET_HAS_GIGABIT) ? "" : "no ",
|
|
(bp->capabilities & DNET_HAS_DMA) ? "" : "no ");
|
|
phydev = bp->phy_dev;
|
|
dev_info(&pdev->dev, "attached PHY driver [%s] "
|
|
"(mii_bus:phy_addr=%s, irq=%d)\n",
|
|
phydev->drv->name, dev_name(&phydev->dev), phydev->irq);
|
|
|
|
return 0;
|
|
|
|
err_out_unregister_netdev:
|
|
unregister_netdev(dev);
|
|
err_out_free_irq:
|
|
free_irq(dev->irq, dev);
|
|
err_out_iounmap:
|
|
iounmap(bp->regs);
|
|
err_out_free_dev:
|
|
free_netdev(dev);
|
|
err_out_release_mem:
|
|
release_mem_region(mem_base, mem_size);
|
|
err_out:
|
|
return err;
|
|
}
|
|
|
|
static int __devexit dnet_remove(struct platform_device *pdev)
|
|
{
|
|
|
|
struct net_device *dev;
|
|
struct dnet *bp;
|
|
|
|
dev = platform_get_drvdata(pdev);
|
|
|
|
if (dev) {
|
|
bp = netdev_priv(dev);
|
|
if (bp->phy_dev)
|
|
phy_disconnect(bp->phy_dev);
|
|
mdiobus_unregister(bp->mii_bus);
|
|
kfree(bp->mii_bus->irq);
|
|
mdiobus_free(bp->mii_bus);
|
|
unregister_netdev(dev);
|
|
free_irq(dev->irq, dev);
|
|
iounmap(bp->regs);
|
|
free_netdev(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver dnet_driver = {
|
|
.probe = dnet_probe,
|
|
.remove = __devexit_p(dnet_remove),
|
|
.driver = {
|
|
.name = "dnet",
|
|
},
|
|
};
|
|
|
|
static int __init dnet_init(void)
|
|
{
|
|
return platform_driver_register(&dnet_driver);
|
|
}
|
|
|
|
static void __exit dnet_exit(void)
|
|
{
|
|
platform_driver_unregister(&dnet_driver);
|
|
}
|
|
|
|
module_init(dnet_init);
|
|
module_exit(dnet_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Dave DNET Ethernet driver");
|
|
MODULE_AUTHOR("Ilya Yanok <yanok@emcraft.com>, "
|
|
"Matteo Vit <matteo.vit@dave.eu>");
|