/*
* Network device driver for Cell Processor-Based Blade and Celleb platform
*
* (C) Copyright IBM Corp. 2005
* (C) Copyright 2006 TOSHIBA CORPORATION
*
* Authors : Utz Bacher <utz.bacher@de.ibm.com>
* Jens Osterkamp <Jens.Osterkamp@de.ibm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/compiler.h>
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/firmware.h>
#include <linux/if_vlan.h>
#include <linux/in.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/mii.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/tcp.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/bitops.h>
#include <asm/pci-bridge.h>
#include <net/checksum.h>
#include "spider_net.h"
MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com> and Jens Osterkamp " \
"<Jens.Osterkamp@de.ibm.com>");
MODULE_DESCRIPTION("Spider Southbridge Gigabit Ethernet driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
MODULE_FIRMWARE(SPIDER_NET_FIRMWARE_NAME);
static int rx_descriptors = SPIDER_NET_RX_DESCRIPTORS_DEFAULT;
static int tx_descriptors = SPIDER_NET_TX_DESCRIPTORS_DEFAULT;
module_param(rx_descriptors, int, 0444);
module_param(tx_descriptors, int, 0444);
MODULE_PARM_DESC(rx_descriptors, "number of descriptors used " \
"in rx chains");
MODULE_PARM_DESC(tx_descriptors, "number of descriptors used " \
"in tx chain");
char spider_net_driver_name[] = "spidernet";
static struct pci_device_id spider_net_pci_tbl[] = {
{ PCI_VENDOR_ID_TOSHIBA_2, PCI_DEVICE_ID_TOSHIBA_SPIDER_NET,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0UL },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, spider_net_pci_tbl);
/**
* spider_net_read_reg - reads an SMMIO register of a card
* @card: device structure
* @reg: register to read from
*
* returns the content of the specified SMMIO register.
*/
static inline u32
spider_net_read_reg(struct spider_net_card *card, u32 reg)
{
/* We use the powerpc specific variants instead of readl_be() because
* we know spidernet is not a real PCI device and we can thus avoid the
* performance hit caused by the PCI workarounds.
*/
return in_be32(card->regs + reg);
}
/**
* spider_net_write_reg - writes to an SMMIO register of a card
* @card: device structure
* @reg: register to write to
* @value: value to write into the specified SMMIO register
*/
static inline void
spider_net_write_reg(struct spider_net_card *card, u32 reg, u32 value)
{
/* We use the powerpc specific variants instead of writel_be() because
* we know spidernet is not a real PCI device and we can thus avoid the
* performance hit caused by the PCI workarounds.
*/
out_be32(card->regs + reg, value);
}
/** spider_net_write_phy - write to phy register
* @netdev: adapter to be written to
* @mii_id: id of MII
* @reg: PHY register
* @val: value to be written to phy register
*
* spider_net_write_phy_register writes to an arbitrary PHY
* register via the spider GPCWOPCMD register. We assume the queue does
* not run full (not more than 15 commands outstanding).
**/
static void
spider_net_write_phy(struct net_device *netdev, int mii_id,
int reg, int val)
{
struct spider_net_card *card = netdev_priv(netdev);
u32 writevalue;
writevalue = ((u32)mii_id << 21) |
((u32)reg << 16) | ((u32)val);
spider_net_write_reg(card, SPIDER_NET_GPCWOPCMD, writevalue);
}
/** spider_net_read_phy - read from phy register
* @netdev: network device to be read from
* @mii_id: id of MII
* @reg: PHY register
*
* Returns value read from PHY register
*
* spider_net_write_phy reads from an arbitrary PHY
* register via the spider GPCROPCMD register
**/
static int
spider_net_read_phy(struct net_device *netdev, int mii_id, int reg)
{
struct spider_net_card *card = netdev_priv(netdev);
u32 readvalue;
readvalue = ((u32)mii_id << 21) | ((u32)reg << 16);
spider_net_write_reg(card, SPIDER_NET_GPCROPCMD, readvalue);
/* we don't use semaphores to wait for an SPIDER_NET_GPROPCMPINT
* interrupt, as we poll for the completion of the read operation
* in spider_net_read_phy. Should take about 50 us */
do {
readvalue = spider_net_read_reg(card, SPIDER_NET_GPCROPCMD);
} while (readvalue & SPIDER_NET_GPREXEC);
readvalue &= SPIDER_NET_GPRDAT_MASK;
return readvalue;
}
/**
* spider_net_setup_aneg - initial auto-negotiation setup
* @card: device structure
**/
static void
spider_net_setup_aneg(struct spider_net_card *card)
{
struct mii_phy *phy = &card->phy;
u32 advertise = 0;
u16 bmsr, estat;
bmsr = spider_net_read_phy(card->netdev, phy->mii_id, MII_BMSR);
estat = spider_net_read_phy(card->netdev, phy->mii_id, MII_ESTATUS);
if (bmsr & BMSR_10HALF)
advertise |= ADVERTISED_10baseT_Half;
if (bmsr & BMSR_10FULL)
advertise |= ADVERTISED_10baseT_Full;
if (bmsr & BMSR_100HALF)
advertise |= ADVERTISED_100baseT_Half;
if (bmsr & BMSR_100FULL)
advertise |= ADVERTISED_100baseT_Full;
if ((bmsr & BMSR_ESTATEN) && (estat & ESTATUS_1000_TFULL))
advertise |= SUPPORTED_1000baseT_Full;
if ((bmsr & BMSR_ESTATEN) && (estat & ESTATUS_1000_THALF))
advertise |= SUPPORTED_1000baseT_Half;
mii_phy_probe(phy, phy->mii_id);
phy->def->ops->setup_aneg(phy, advertise);
}
/**
* spider_net_rx_irq_off - switch off rx irq on this spider card
* @card: device structure
*
* switches off rx irq by masking them out in the GHIINTnMSK register
*/
static void
spider_net_rx_irq_off(struct spider_net_card *card)
{
u32 regvalue;
regvalue = SPIDER_NET_INT0_MASK_VALUE & (~SPIDER_NET_RXINT);
spider_net_write_reg(card, SPIDER_NET_GHIINT0MSK, regvalue);
}
/**
* spider_net_rx_irq_on - switch on rx irq on this spider card
* @card: device structure
*
* switches on rx irq by enabling them in the GHIINTnMSK register
*/
static void
spider_net_rx_irq_on(struct spider_net_card *card)
{
u32 regvalue;
regvalue = SPIDER_NET_INT0_MASK_VALUE | SPIDER_NET_RXINT;
spider_net_write_reg(card, SPIDER_NET_GHIINT0MSK, regvalue);
}
/**
* spider_net_set_promisc - sets the unicast address or the promiscuous mode
* @card: card structure
*
* spider_net_set_promisc sets the unicast destination address filter and
* thus either allows for non-promisc mode or promisc mode
*/
static void
spider_net_set_promisc(struct spider_net_card *card)
{
u32 macu, macl;
struct net_device *netdev = card->netdev;
if (netdev->flags & IFF_PROMISC) {
/* clear destination entry 0 */
spider_net_write_reg(card, SPIDER_NET_GMRUAFILnR, 0);
spider_net_write_reg(card, SPIDER_NET_GMRUAFILnR + 0x04, 0);
spider_net_write_reg(card, SPIDER_NET_GMRUA0FIL15R,
SPIDER_NET_PROMISC_VALUE);
} else {
macu = netdev->dev_addr[0];
macu <<= 8;
macu |= netdev->dev_addr[1];
memcpy(&macl, &netdev->dev_addr[2], sizeof(macl));
macu |= SPIDER_NET_UA_DESCR_VALUE;
spider_net_write_reg(card, SPIDER_NET_GMRUAFILnR, macu);
spider_net_write_reg(card, SPIDER_NET_GMRUAFILnR + 0x04, macl);
spider_net_write_reg(card, SPIDER_NET_GMRUA0FIL15R,
SPIDER_NET_NONPROMISC_VALUE);
}
}
/**
* spider_net_get_mac_address - read mac address from spider card
* @card: device structure
*
* reads MAC address from GMACUNIMACU and GMACUNIMACL registers
*/
static int
spider_net_get_mac_address(struct net_device *netdev)
{
struct spider_net_card *card = netdev_priv(netdev);
u32 macl, macu;
macl = spider_net_read_reg(card, SPIDER_NET_GMACUNIMACL);
macu = spider_net_read_reg(card, SPIDER_NET_GMACUNIMACU);
netdev->dev_addr[0] = (macu >> 24) & 0xff;
netdev->dev_addr[1] = (macu >> 16) & 0xff;
netdev->dev_addr[2] = (macu >> 8) & 0xff;
netdev->dev_addr[3] = macu & 0xff;
netdev->dev_addr[4] = (macl >> 8) & 0xff;
netdev->dev_addr[5] = macl & 0xff;
if (!is_valid_ether_addr(&netdev->dev_addr[0]))
return -EINVAL;
return 0;
}
/**
* spider_net_get_descr_status -- returns the status of a descriptor
* @descr: descriptor to look at
*
* returns the status as in the dmac_cmd_status field of the descriptor
*/
static inline int
spider_net_get_descr_status(struct spider_net_hw_descr *hwdescr)
{
return hwdescr->dmac_cmd_status & SPIDER_NET_DESCR_IND_PROC_MASK;
}
/**
* spider_net_free_chain - free descriptor chain
* @card: card structure
* @chain: address of chain
*
*/
static void
spider_net_free_chain(struct spider_net_card *card,
struct spider_net_descr_chain *chain)
{
struct spider_net_descr *descr;
descr = chain->ring;
do {
descr->bus_addr = 0;
descr->hwdescr->next_descr_addr = 0;
descr = descr->next;
} while (descr != chain->ring);
dma_free_coherent(&card->pdev->dev, chain->num_desc,
chain->hwring, chain->dma_addr);
}
/**
* spider_net_init_chain - alloc and link descriptor chain
* @card: card structure
* @chain: address of chain
*
* We manage a circular list that mirrors the hardware structure,
* except that the hardware uses bus addresses.
*
* Returns 0 on success, <0 on failure
*/
static int
spider_net_init_chain(struct spider_net_card *card,
struct spider_net_descr_chain *chain)
{
int i;
struct spider_net_descr *descr;
struct spider_net_hw_descr *hwdescr;
dma_addr_t buf;
size_t alloc_size;
alloc_size = chain->num_desc * sizeof(struct spider_net_hw_descr);
chain->hwring = dma_alloc_coherent(&card->pdev->dev, alloc_size,
&chain->dma_addr, GFP_KERNEL);
if (!chain->hwring)
return -ENOMEM;
memset(chain->ring, 0, chain->num_desc * sizeof(struct spider_net_descr));
/* Set up the hardware pointers in each descriptor */
descr = chain->ring;
hwdescr = chain->hwring;
buf = chain->dma_addr;
for (i=0; i < chain->num_desc; i++, descr++, hwdescr++) {
hwdescr->dmac_cmd_status = SPIDER_NET_DESCR_NOT_IN_USE;
hwdescr->next_descr_addr = 0;
descr->hwdescr = hwdescr;
descr->bus_addr = buf;
descr->next = descr + 1;
descr->prev = descr - 1;
buf += sizeof(struct spider_net_hw_descr);
}
/* do actual circular list */
(descr-1)->next = chain->ring;
chain->ring->prev = descr-1;
spin_lock_init(&chain->lock);