/*
* Copyright (C) 2012 Adrian Jamróz <adrian.jamroz@gmail.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 of the
* License, 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
#include <ipxe/if_ether.h>
#include <ipxe/iobuf.h>
#include <ipxe/malloc.h>
#include <ipxe/pci.h>
#include <ipxe/mii.h>
#include "velocity.h"
#define velocity_setbit(_reg, _mask) writeb ( readb ( _reg ) | _mask, _reg )
#define virt_to_le32bus(x) ( cpu_to_le32 ( virt_to_bus ( x ) ) )
/** @file
*
* VIA Velocity network driver
*
*/
/******************************************************************************
*
* MII interface
*
******************************************************************************
*/
/**
* Stop MII auto-polling
*
* @v vlc Velocity device
* @ret rc Return status code
*/
static int velocity_autopoll_stop ( struct velocity_nic *vlc ) {
int timeout = VELOCITY_TIMEOUT_US;
/* Disable MII auto polling */
writeb ( 0, vlc->regs + VELOCITY_MIICR );
/* Wait for disabling to take effect */
while ( timeout-- ) {
udelay ( 1 );
if ( readb ( vlc->regs + VELOCITY_MIISR ) &
VELOCITY_MIISR_IDLE )
return 0;
}
DBGC ( vlc, "MII autopoll stop timeout\n" );
return -ETIMEDOUT;
}
/**
* Start MII auto-polling
*
* @v vlc Velocity device
* @ret rc Return status code
*/
static int velocity_autopoll_start ( struct velocity_nic *vlc ) {
int timeout = VELOCITY_TIMEOUT_US;
/* Enable MII auto polling */
writeb ( VELOCITY_MIICR_MAUTO, vlc->regs + VELOCITY_MIICR );
/* Wait for enabling to take effect */
while ( timeout-- ) {
udelay ( 1 );
if ( ( readb ( vlc->regs + VELOCITY_MIISR ) &
VELOCITY_MIISR_IDLE ) == 0 )
return 0;
}
DBGC ( vlc, "MII autopoll start timeout\n" );
return -ETIMEDOUT;
}
/**
* Read from MII register
*
* @v mdio MII interface
* @v phy PHY address
* @v reg Register address
* @ret value Data read, or negative error
*/
static int velocity_mii_read ( struct mii_interface *mdio,
unsigned int phy __unused, unsigned int reg ) {
struct velocity_nic *vlc =
container_of ( mdio, struct velocity_nic, mdio );
int timeout = VELOCITY_TIMEOUT_US;
int result;
DBGC2 ( vlc, "VELOCITY %p MII read reg %d\n", vlc, reg );
/* Disable autopolling before we can access MII */
velocity_autopoll_stop ( vlc );
/* Send read command and address */
writeb ( reg, vlc->regs + VELOCITY_MIIADDR );
velocity_setbit ( vlc->regs + VELOCITY_MIICR, VELOCITY_MIICR_RCMD );
/* Wait for read to complete */
while ( timeout-- ) {
udelay ( 1 );
if ( ( readb ( vlc->regs + VELOCITY_MIICR ) &
VELOCITY_MIICR_RCMD ) == 0 ) {
result = readw ( vlc->regs + VELOCITY_MIIDATA );
velocity_autopoll_start ( vlc );
return result;
}
}
/* Restart autopolling */
velocity_autopoll_start ( vlc );
DBGC ( vlc, "MII read timeout\n" );
return -ETIMEDOUT;
}
/**
* Write to MII register
*
* @v mdio MII interface
* @v phy PHY address
* @v reg Register address
* @v data Data to write
* @ret rc Return status code
*/
static int velocity_mii_write ( struct mii_interface *mdio,
unsigned int phy __unused, unsigned int reg,
unsigned int data) {
struct velocity_nic *vlc =
container_of ( mdio, struct velocity_nic, mdio );
int timeout = VELOCITY_TIMEOUT_US;
DBGC2 ( vlc, "VELOCITY %p MII write reg %d data 0x%04x\n",
vlc, reg, data );
/* Disable autopolling before we can access MII */
velocity_autopoll_stop ( vlc );
/* Send write command, data and destination register */
writeb ( reg, vlc->regs + VELOCITY_MIIADDR );
writew ( data, vlc->regs + VELOCITY_MIIDATA );
velocity_setbit ( vlc->regs + VELOCITY_MIICR, VELOCITY_MIICR_WCMD );
/* Wait for write to complete */
while ( timeout-- ) {
udelay ( 1 );
if ( ( readb ( vlc->regs + VELOCITY_MIICR ) &
VELOCITY_MIICR_WCMD ) == 0 ) {
velocity_autopoll_start ( vlc );
return 0;
}
}
/* Restart autopolling */
velocity_autopoll_start ( vlc );
DBGC ( vlc, "MII write timeout\n" );
return -ETIMEDOUT;
}
/** Velocity MII operations */
static struct mii_operations velocity_mii_operations = {
.read = velocity_mii_read,
.write = velocity_mii_write,
};
/**
* Set Link speed
*
* @v vlc Velocity device
*/
static void velocity_set_link ( struct velocity_nic *vlc ) {
int tmp;
/* Advertise 1000MBit */
tmp = mii_read ( &vlc->mii, MII_CTRL1000 );
tmp |= ADVERTISE_1000FULL | ADVERTISE_1000HALF;
mii_write ( &vlc->mii, MII_CTRL1000, tmp );
/* Enable GBit operation in MII Control Register */
tmp = mii_read ( &vlc->mii, MII_BMCR );
tmp |= BMCR_SPEED1000;
mii_write ( &vlc->mii, MII_BMCR, tmp );
}
/******************************************************************************
*
* Device reset
*
******************************************************************************
*/
/**
* Reload eeprom contents
*
* @v vlc Velocity device
*/
static int velocity_reload_eeprom ( struct velocity_nic *vlc ) {
int timeout = VELOCITY_TIMEOUT_US;
/* Initiate reload */
velocity_setbit ( vlc->regs + VELOCITY_EECSR, VELOCITY_EECSR_RELOAD );
/* Wait for reload to complete */
while ( timeout-- ) {
udelay ( 1 );
if ( ( readb ( vlc->regs + VELOCITY_EECSR ) &
VELOCITY_EECSR_RELOAD ) == 0 )
return 0;
}
DBGC ( vlc, "VELOCITY %p EEPROM reload timeout\n", vlc );
return -ETIMEDOUT;
}
/**
* Reset hardware
*
* @v vlc Velocity device
* @ret rc Return status code
*/
static int velocity_reset ( struct velocity_nic *vlc ) {
int timeout = VELOCITY_TIMEOUT_US;
uint8_t tmp;
DBGC ( vlc, "VELOCITY %p reset\n", vlc );
/* clear sticky Power state bits */
tmp = readb ( vlc->regs + VELOCITY_STICKY );
tmp &= ~( VELOCITY_STICKY_DS0 | VELOCITY_STICKY_DS1 );
writeb ( tmp, vlc->regs + VELOCITY_STICKY );
/* clear PACPI, which might have been enabled by the EEPROM reload */
tmp = readb ( vlc->regs + VELOCITY_CFGA );
tmp &= ~VELOCITY_CFGA_PACPI;
writeb ( tmp, vlc->regs + VELOCITY_CFGA );
velocity_setbit ( vlc->regs + VELOCITY_CRS1, VELOCITY_CR1_SFRST );
/* Wait for reset to complete */
while ( timeout-- ) {
udelay ( 1 );
if ( ( readb ( vlc->regs + VELOCITY_CRS1 ) &
VELOCITY_CR1_SFRST ) == 0 )
return 0;
}
return -EINVAL;
}
/******************************************************************************
*
* Link state
*
******************************************************************************
*/
/**
* Check link state
*
* @v netdev Network device
*/
static void velocity_check_link ( struct net_device *netdev ) {
struct velocity_nic *vlc = netdev->priv;
if ( readb ( vlc->regs + VELOCITY_PHYSTS0 ) & VELOCITY_PHYSTS0_LINK ) {
netdev_link_up ( netdev );
DBGC ( vlc, "VELOCITY %p link up\n", vlc );
} else {
netdev_link_down ( netdev );
DBGC ( vlc, "VELOCITY %p link down\n", vlc );
}
/* The card disables auto-poll after a link change */
velocity_autopoll_start ( vlc );
}
/******************************************************************************
*
* Network device interface
*
******************************************************************************
*/
/**
* Allocate descriptor rings
*
* @v vlc Velocity device
* @ret rc Return status code
*/
static int velocity_alloc_rings ( struct velocity_nic *vlc ) {
int rc = 0;
/* Allocate RX descriptor ring */
vlc->rx_prod = 0;
vlc->rx_cons = 0;
vlc->rx_commit = 0;
vlc->rx_ring = malloc_phys ( VELOCITY_RXDESC_SIZE,
VELOCITY_RING_ALIGN );
if ( ! vlc->rx_ring )
return -ENOMEM;
memset ( vlc->rx_ring, 0, VELOCITY_RXDESC_SIZE );
DBGC2 ( vlc, "VELOCITY %p RX ring start address: %p(phys: %#08lx)\n",
vlc, vlc->rx_ring, virt_to_bus ( vlc->rx_ring ) );
/* Allocate TX descriptor ring */
vlc->tx_prod = 0;
vlc->tx_cons = 0;
vlc->tx_ring = malloc_phys ( VELOCITY_TXDESC_SIZE,
VELOCITY_RING_ALIGN );
if ( ! vlc->tx_ring ) {
rc = -ENOMEM;
goto err_tx_alloc;
}
memset ( vlc->tx_ring, 0, VELOCITY_TXDESC_SIZE );
/* Send RX ring to the card */
writel ( virt_to_bus ( vlc->rx_ring ),
vlc->regs + VELOCITY_RXDESC_ADDR_LO );
writew ( VELOCITY_RXDESC_NUM - 1, vlc->regs + VELOCITY_RXDESCNUM );
/* Send TX ring to the card */
writel ( virt_to_bus ( vlc->tx_ring ),
vlc->regs + VELOCITY_TXDESC_ADDR_LO0 );
writew ( VELOCITY_TXDESC_NUM - 1, vlc->regs + VELOCITY_TXDESCNUM );
DBGC2 ( vlc, "VELOCITY %p TX ring start address: %p(phys: %#08lx)\n",
vlc, vlc->tx_ring, virt_to_bus ( vlc->tx_ring ) );
return 0;
err_tx_alloc:
free_phys ( vlc->rx_ring, VELOCITY_RXDESC_SIZE );
return rc;
}
/**
* Refill receive descriptor ring
*
* @v vlc Velocity device
*/
static void velocity_refill_rx ( struct velocity_nic *vlc ) {
struct velocity_rx_descriptor *desc;
struct io_buffer *iobuf;
int rx_idx, i = 0;
/* Check for new packets */
while ( ( vlc->rx_prod - vlc->rx_cons ) < VELOCITY_RXDESC_NUM ) {
iobuf = alloc_iob ( VELOCITY_RX_MAX_LEN );
/* Memory pressure: try again next poll */
if ( ! iobuf )
break;
rx_idx = ( vlc->rx_prod++ % VELOCITY_RXDESC_NUM );
desc = &vlc->rx_ring[rx_idx];
/* Set descrptor fields */
desc->des1 = 0;
desc->addr = virt_to_le32bus ( iobuf-> data );
desc->des2 = cpu_to_le32 (
VELOCITY_DES2_SIZE ( VELOCITY_RX_MAX_LEN - 1 ) |
VELOCITY_DES2_IC );
vlc->rx_buffs[rx_idx] = iobuf;
i++;
/* Return RX descriptors in blocks of 4 (hw requirement) */
if ( rx_idx % 4 == 3 ) {
int j;
for (j = 0; j < 4; j++) {
desc = &vlc->rx_ring[rx_idx - j];
desc->des0 = cpu_to_le32 ( VELOCITY_DES0_OWN );
}
vlc->rx_commit += 4;
}
}
wmb();
if ( vlc->rx_commit ) {
writew ( vlc->rx_commit,
vlc->regs + VELOCITY_RXDESC_RESIDUECNT );
vlc->rx_commit = 0;
}
if ( i > 0 )
DBGC2 ( vlc, "VELOCITY %p refilled %d RX descriptors\n",
vlc, i );
}
/**
* Open network device
*
* @v netdev Network device
* @ret rc Return status code
*/
static int velocity_open ( struct net_device *netdev ) {
struct velocity_nic *vlc = netdev->priv;
int rc;
DBGC ( vlc, "VELOCITY %p open\n", vlc );
DBGC ( vlc, "VELOCITY %p regs at: %p\n", vlc, vlc->regs );
/* Allocate descriptor rings */
if ( ( rc = velocity_alloc_rings ( vlc ) ) != 0 )
return rc;
velocity_refill_rx ( vlc );
/* Enable TX/RX queue */
writew ( VELOCITY_TXQCSRS_RUN0, vlc->regs + VELOCITY_TXQCSRS );
writew ( VELOCITY_RXQCSR_RUN | VELOCITY_RXQCSR_WAK,
vlc->regs + VELOCITY_RXQCSRS );
/* Enable interrupts */
writeb ( 0xff, vlc->regs + VELOCITY_IMR0 );
writeb ( 0xff, vlc->regs + VELOCITY_IMR1 );
/* Start MAC */
writeb ( VELOCITY_CR0_STOP, vlc->regs + VELOCITY_CRC0 );
writeb ( VELOCITY_CR1_DPOLL, vlc->regs + VELOCITY_CRC0 );
writeb ( VELOCITY_CR0_START | VELOCITY_CR0_TXON | VELOCITY_CR0_RXON,
vlc->regs + VELOCITY_CRS0 );
/* Receive all packets */
writeb ( 0xff, vlc->regs + VELOCITY_RCR );
/* Set initial link state */
velocity_check_link ( netdev );
velocity_autopoll_start ( vlc );
DBGC2 ( vlc, "VELOCITY %p CR3 %02x\n",
vlc, readb ( vlc->regs + 0x0B ) );
return 0;
}
/**
* Close network device
*
* @v netdev Network device
*/
static void velocity_close ( struct net_device *netdev ) {
struct velocity_nic *vlc = netdev->priv;
int i;
/* Stop NIC */
writeb ( VELOCITY_CR0_TXON | VELOCITY_CR0_RXON,
vlc->regs + VELOCITY_CRC0 );
writeb ( VELOCITY_CR0_STOP, vlc->regs + VELOCITY_CRS0 );
/* Clear RX ring information */
writel ( 0, vlc->regs + VELOCITY_RXDESC_ADDR_LO );
writew ( 0, vlc->regs + VELOCITY_RXDESCNUM );
/* Destroy RX ring */
free_phys ( vlc->rx_ring, VELOCITY_RXDESC_SIZE );
vlc->rx_ring = NULL;
vlc->rx_prod = 0;
vlc->rx_cons = 0;
/* Discard receive buffers */
for ( i = 0 ; i < VELOCITY_RXDESC_NUM ; i++ ) {
if ( vlc->rx_buffs[i] )
free_iob ( vlc->rx_buffs[i] );
vlc->rx_buffs[i] = NULL;
}
/* Clear TX ring information */
writel ( 0, vlc->regs + VELOCITY_TXDESC_ADDR_LO0 );
writew ( 0, vlc->regs + VELOCITY_TXDESCNUM );
/* Destroy TX ring */
free_phys ( vlc->tx_ring, VELOCITY_TXDESC_SIZE );
vlc->tx_ring = NULL;
vlc->tx_prod = 0;
vlc->tx_cons = 0;
}
/**
* Transmit packet
*
* @v netdev Network device
* @v iobuf I/O buffer
* @ret rc Return status code
*/
static int velocity_transmit ( struct net_device *netdev,
struct io_buffer *iobuf ) {
struct velocity_nic *vlc = netdev->priv;
struct velocity_tx_descriptor *desc;
unsigned int tx_idx;
/* Pad packet to minimum length */
iob_pad ( iobuf, ETH_ZLEN );
tx_idx = ( vlc->tx_prod++ % VELOCITY_TXDESC_NUM );
desc = &vlc->tx_ring[tx_idx];
/* Set packet size and transfer ownership to NIC */
desc->des0 = cpu_to_le32 ( VELOCITY_DES0_OWN |
VELOCITY_DES2_SIZE ( iob_len ( iobuf ) ) );
/* Data in first desc fragment, only desc for packet, generate INT */
desc->des1 = cpu_to_le32 ( VELOCITY_DES1_FRAG ( 1 ) |
VELOCITY_DES1_TCPLS |
VELOCITY_DES1_INTR );
desc->frags[0].addr = virt_to_le32bus ( iobuf->data );
desc->frags[0].des2 = cpu_to_le32 (
VELOCITY_DES2_SIZE ( iob_len ( iobuf ) ) );
wmb();
/* Initiate TX */
velocity_setbit ( vlc->regs + VELOCITY_TXQCSRS, VELOCITY_TXQCSRS_WAK0 );
DBGC2 ( vlc, "VELOCITY %p tx_prod=%d desc=%p iobuf=%p len=%zd\n",
vlc, tx_idx, desc, iobuf->data, iob_len ( iobuf ) );
return 0;
}
/**
* Poll for received packets.
*
* @v vlc Velocity device
*/
static void velocity_poll_rx ( struct velocity_nic *vlc ) {
struct velocity_rx_descriptor *desc;
struct io_buffer *iobuf;
int rx_idx;
size_t len;
uint32_t des0;
/* Check for packets */
while ( vlc->rx_cons != vlc->rx_prod ) {
rx_idx = ( vlc->rx_cons % VELOCITY_RXDESC_NUM );
desc = &vlc->rx_ring[rx_idx];
des0 = cpu_to_le32 ( desc->des0 );
/* Return if descriptor still in use */
if ( des0 & VELOCITY_DES0_OWN )
return;
iobuf = vlc->rx_buffs[rx_idx];
/* Get length, strip CRC */
len = VELOCITY_DES0_RMBC ( des0 ) - 4;
iob_put ( iobuf, len );
DBGC2 ( vlc, "VELOCITY %p got packet on idx=%d (prod=%d), len %zd\n",
vlc, rx_idx, vlc->rx_prod % VELOCITY_RXDESC_NUM, len );
if ( des0 & VELOCITY_DES0_RX_ERR ) {
/* Report receive error */
netdev_rx_err ( vlc->netdev, iobuf, -EINVAL );
DBGC ( vlc, "VELOCITY %p receive error, status: %02x\n",
vlc, des0 );
} else if ( des0 & VELOCITY_DES0_RXOK ) {
/* Report receive success */
netdev_rx( vlc->netdev, iobuf );
} else {
/* Card indicated neither success nor failure
* Technically this shouldn't happen, but we saw it
* in debugging once. */
DBGC ( vlc, "VELOCITY %p RX neither ERR nor OK: %04x\n",
vlc, des0 );
DBGC ( vlc, "packet len: %zd\n", len );
DBGC_HD ( vlc, iobuf->data, 64 );
/* we don't know what it is, treat is as an error */
netdev_rx_err ( vlc->netdev, iobuf, -EINVAL );
}
vlc->rx_cons++;
}
}
/**
* Poll for completed packets.
*
* @v vlc Velocity device
*/
static void velocity_poll_tx ( struct velocity_nic *vlc ) {
struct velocity_tx_descriptor *desc;
int tx_idx;
/* Check for packets */
while ( vlc->tx_cons != vlc->tx_prod ) {
tx_idx = ( vlc->tx_cons % VELOCITY_TXDESC_NUM );
desc = &vlc->tx_ring[tx_idx];
/* Return if descriptor still in use */
if ( le32_to_cpu ( desc->des0 ) & VELOCITY_DES0_OWN )
return;
/* Report errors */
if ( le32_to_cpu ( desc->des0 ) & VELOCITY_DES0_TERR ) {
netdev_tx_complete_next_err ( vlc->netdev, -EINVAL );
return;
}
netdev_tx_complete_next ( vlc->netdev );
DBGC2 ( vlc, "VELOCITY %p poll_tx cons=%d prod=%d tsr=%04x\n",
vlc, tx_idx, vlc->tx_prod % VELOCITY_TXDESC_NUM,
( desc->des0 & 0xffff ) );
vlc->tx_cons++;
}
}
/**
* Poll for completed and received packets
*
* @v netdev Network device
*/
static void velocity_poll ( struct net_device *netdev ) {
struct velocity_nic *vlc = netdev->priv;
uint8_t isr1;
isr1 = readb ( vlc->regs + VELOCITY_ISR1 );
/* ACK interrupts */
writew ( 0xFFFF, vlc->regs + VELOCITY_ISR0 );
/* Check for competed packets */
velocity_poll_rx ( vlc );
velocity_poll_tx ( vlc );
if ( isr1 & VELOCITY_ISR1_SRCI ) {
/* Update linkstate */
DBGC2 ( vlc, "VELOCITY %p link status interrupt\n", vlc );
velocity_check_link ( netdev );
}
velocity_refill_rx ( vlc );
/* deal with potential RX stall caused by RX ring underrun */
writew ( VELOCITY_RXQCSR_RUN | VELOCITY_RXQCSR_WAK,
vlc->regs + VELOCITY_RXQCSRS );
}
/**
* Enable or disable interrupts
*
* @v netdev Network device
* @v enable Interrupts should be enabled
*/
static void velocity_irq ( struct net_device *netdev, int enable ) {
struct velocity_nic *vlc = netdev->priv;
DBGC ( vlc, "VELOCITY %p interrupts %s\n", vlc,
enable ? "enable" : "disable" );
if (enable) {
/* Enable interrupts */
writeb ( VELOCITY_CR3_GINTMSK1, vlc->regs + VELOCITY_CRS3 );
} else {
/* Disable interrupts */
writeb ( VELOCITY_CR3_GINTMSK1, vlc->regs + VELOCITY_CRC3 );
}
}
/** Velocity network device operations */
static struct net_device_operations velocity_operations = {
.open = velocity_open,
.close = velocity_close,
.transmit = velocity_transmit,
.poll = velocity_poll,
.irq = velocity_irq,
};
/******************************************************************************
*
* PCI interface
*
******************************************************************************
*/
/**
* Probe PCI device
*
* @v pci PCI device
* @ret rc Return status code
*/
static int velocity_probe ( struct pci_device *pci ) {
struct net_device *netdev;
struct velocity_nic *vlc;
int rc;
/* Allocate and initialise net device */
netdev = alloc_etherdev ( sizeof ( *vlc ) );
if ( ! netdev ) {
rc = -ENOMEM;
goto err_alloc;
}
netdev_init ( netdev, &velocity_operations );
vlc = netdev->priv;
pci_set_drvdata ( pci, netdev );
netdev->dev = &pci->dev;
/* Fix up PCI device */
adjust_pci_device ( pci );
/* Map registers */
vlc->regs = pci_ioremap ( pci, pci->membase, VELOCITY_BAR_SIZE );
vlc->netdev = netdev;
/* Reset the NIC */
if ( ( rc = velocity_reset ( vlc ) ) != 0 )
goto err_reset;
/* Reload EEPROM */
if ( ( rc = velocity_reload_eeprom ( vlc ) ) != 0 )
goto err_reset;
/* Get MAC address */
netdev->hw_addr[0] = readb ( vlc->regs + VELOCITY_MAC0 );
netdev->hw_addr[1] = readb ( vlc->regs + VELOCITY_MAC1 );
netdev->hw_addr[2] = readb ( vlc->regs + VELOCITY_MAC2 );
netdev->hw_addr[3] = readb ( vlc->regs + VELOCITY_MAC3 );
netdev->hw_addr[4] = readb ( vlc->regs + VELOCITY_MAC4 );
netdev->hw_addr[5] = readb ( vlc->regs + VELOCITY_MAC5 );
/* Initialise and reset MII interface */
mdio_init ( &vlc->mdio, &velocity_mii_operations );
mii_init ( &vlc->mii, &vlc->mdio, 0 );
if ( ( rc = mii_reset ( &vlc->mii ) ) != 0 ) {
DBGC ( vlc, "VELOCITY %p could not reset MII: %s\n",
vlc, strerror ( rc ) );
goto err_mii_reset;
}
/* Enable proper link advertising */
velocity_set_link ( vlc );
/* Register network device */
if ( ( rc = register_netdev ( netdev ) ) != 0 )
goto err_register_netdev;
return 0;
err_register_netdev:
err_mii_reset:
velocity_reset ( vlc );
err_reset:
netdev_nullify ( netdev );
netdev_put ( netdev );
err_alloc:
return rc;
}
/**
* Remove PCI device
*
* @v pci PCI device
*/
static void velocity_remove ( struct pci_device *pci ) {
struct net_device *netdev = pci_get_drvdata ( pci );
struct velocity_nic *vlc = netdev->priv;
/* Unregister network device */
unregister_netdev ( netdev );
/* Reset card */
velocity_reset ( vlc );
/* Free network device */
netdev_nullify ( netdev );
netdev_put ( netdev );
}
/** Velocity PCI device IDs */
static struct pci_device_id velocity_nics[] = {
PCI_ROM ( 0x1106, 0x3119, "vt6122", "VIA Velocity", 0 ),
};
/** Velocity PCI driver */
struct pci_driver velocity_driver __pci_driver = {
.ids = velocity_nics,
.id_count = ( sizeof ( velocity_nics ) / sizeof ( velocity_nics[0] ) ),
.probe = velocity_probe,
.remove = velocity_remove,
};