summaryrefslogtreecommitdiffstats
path: root/src/drivers/net
diff options
context:
space:
mode:
authorMichael Brown2017-07-06 17:58:22 +0200
committerMichael Brown2017-07-07 17:44:28 +0200
commit5a7558447ae9a795215468e5e7ad2511dc4a02fd (patch)
treeb6afa6beaa167dfce89021aae40a4d5e2f90c233 /src/drivers/net
parent[intel] Add support for I219-V in 7th Gen Intel NUC (diff)
downloadipxe-5a7558447ae9a795215468e5e7ad2511dc4a02fd.tar.gz
ipxe-5a7558447ae9a795215468e5e7ad2511dc4a02fd.tar.xz
ipxe-5a7558447ae9a795215468e5e7ad2511dc4a02fd.zip
[smscusb] Abstract out common SMSC USB device functionality
The smsc75xx and smsc95xx drivers include a substantial amount of identical functionality, varying only in the base address of register sets. Abstract out this common functionality to allow code to be shared between the drivers. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/net')
-rw-r--r--src/drivers/net/smscusb.c523
-rw-r--r--src/drivers/net/smscusb.h302
2 files changed, 825 insertions, 0 deletions
diff --git a/src/drivers/net/smscusb.c b/src/drivers/net/smscusb.c
new file mode 100644
index 00000000..d4f3af03
--- /dev/null
+++ b/src/drivers/net/smscusb.c
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2017 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ipxe/usb.h>
+#include <ipxe/usbnet.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/profile.h>
+#include "smscusb.h"
+
+/** @file
+ *
+ * SMSC USB Ethernet drivers
+ *
+ */
+
+/** Interrupt completion profiler */
+static struct profiler smscusb_intr_profiler __profiler =
+ { .name = "smscusb.intr" };
+
+/******************************************************************************
+ *
+ * EEPROM access
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Wait for EEPROM to become idle
+ *
+ * @v smscusb SMSC USB device
+ * @v e2p_base E2P register base
+ * @ret rc Return status code
+ */
+static int smscusb_eeprom_wait ( struct smscusb_device *smscusb,
+ unsigned int e2p_base ) {
+ uint32_t e2p_cmd;
+ unsigned int i;
+ int rc;
+
+ /* Wait for EPC_BSY to become clear */
+ for ( i = 0 ; i < SMSCUSB_EEPROM_MAX_WAIT_MS ; i++ ) {
+
+ /* Read E2P_CMD and check EPC_BSY */
+ if ( ( rc = smscusb_readl ( smscusb,
+ ( e2p_base + SMSCUSB_E2P_CMD ),
+ &e2p_cmd ) ) != 0 )
+ return rc;
+ if ( ! ( e2p_cmd & SMSCUSB_E2P_CMD_EPC_BSY ) )
+ return 0;
+
+ /* Delay */
+ mdelay ( 1 );
+ }
+
+ DBGC ( smscusb, "SMSCUSB %p timed out waiting for EEPROM\n",
+ smscusb );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Read byte from EEPROM
+ *
+ * @v smscusb SMSC USB device
+ * @v e2p_base E2P register base
+ * @v address EEPROM address
+ * @ret byte Byte read, or negative error
+ */
+static int smscusb_eeprom_read_byte ( struct smscusb_device *smscusb,
+ unsigned int e2p_base,
+ unsigned int address ) {
+ uint32_t e2p_cmd;
+ uint32_t e2p_data;
+ int rc;
+
+ /* Wait for EEPROM to become idle */
+ if ( ( rc = smscusb_eeprom_wait ( smscusb, e2p_base ) ) != 0 )
+ return rc;
+
+ /* Initiate read command */
+ e2p_cmd = ( SMSCUSB_E2P_CMD_EPC_BSY | SMSCUSB_E2P_CMD_EPC_CMD_READ |
+ SMSCUSB_E2P_CMD_EPC_ADDR ( address ) );
+ if ( ( rc = smscusb_writel ( smscusb, ( e2p_base + SMSCUSB_E2P_CMD ),
+ e2p_cmd ) ) != 0 )
+ return rc;
+
+ /* Wait for command to complete */
+ if ( ( rc = smscusb_eeprom_wait ( smscusb, e2p_base ) ) != 0 )
+ return rc;
+
+ /* Read EEPROM data */
+ if ( ( rc = smscusb_readl ( smscusb, ( e2p_base + SMSCUSB_E2P_DATA ),
+ &e2p_data ) ) != 0 )
+ return rc;
+
+ return SMSCUSB_E2P_DATA_GET ( e2p_data );
+}
+
+/**
+ * Read data from EEPROM
+ *
+ * @v smscusb SMSC USB device
+ * @v e2p_base E2P register base
+ * @v address EEPROM address
+ * @v data Data buffer
+ * @v len Length of data
+ * @ret rc Return status code
+ */
+static int smscusb_eeprom_read ( struct smscusb_device *smscusb,
+ unsigned int e2p_base, unsigned int address,
+ void *data, size_t len ) {
+ uint8_t *bytes;
+ int byte;
+
+ /* Read bytes */
+ for ( bytes = data ; len-- ; address++, bytes++ ) {
+ byte = smscusb_eeprom_read_byte ( smscusb, e2p_base, address );
+ if ( byte < 0 )
+ return byte;
+ *bytes = byte;
+ }
+
+ return 0;
+}
+
+/**
+ * Fetch MAC address from EEPROM
+ *
+ * @v smscusb SMSC USB device
+ * @v e2p_base E2P register base
+ * @ret rc Return status code
+ */
+int smscusb_eeprom_fetch_mac ( struct smscusb_device *smscusb,
+ unsigned int e2p_base ) {
+ struct net_device *netdev = smscusb->netdev;
+ int rc;
+
+ /* Read MAC address from EEPROM */
+ if ( ( rc = smscusb_eeprom_read ( smscusb, e2p_base, SMSCUSB_EEPROM_MAC,
+ netdev->hw_addr, ETH_ALEN ) ) != 0 )
+ return rc;
+
+ /* Check that EEPROM is physically present */
+ if ( ! is_valid_ether_addr ( netdev->hw_addr ) ) {
+ DBGC ( smscusb, "SMSCUSB %p has no EEPROM (%s)\n",
+ smscusb, eth_ntoa ( netdev->hw_addr ) );
+ return -ENODEV;
+ }
+
+ DBGC ( smscusb, "SMSCUSB %p using EEPROM MAC %s\n",
+ smscusb, eth_ntoa ( netdev->hw_addr ) );
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * MII access
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Wait for MII to become idle
+ *
+ * @v smscusb SMSC USB device
+ * @ret rc Return status code
+ */
+static int smscusb_mii_wait ( struct smscusb_device *smscusb ) {
+ unsigned int base = smscusb->mii_base;
+ uint32_t mii_access;
+ unsigned int i;
+ int rc;
+
+ /* Wait for MIIBZY to become clear */
+ for ( i = 0 ; i < SMSCUSB_MII_MAX_WAIT_MS ; i++ ) {
+
+ /* Read MII_ACCESS and check MIIBZY */
+ if ( ( rc = smscusb_readl ( smscusb,
+ ( base + SMSCUSB_MII_ACCESS ),
+ &mii_access ) ) != 0 )
+ return rc;
+ if ( ! ( mii_access & SMSCUSB_MII_ACCESS_MIIBZY ) )
+ return 0;
+
+ /* Delay */
+ mdelay ( 1 );
+ }
+
+ DBGC ( smscusb, "SMSCUSB %p timed out waiting for MII\n",
+ smscusb );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Read from MII register
+ *
+ * @v mii MII interface
+ * @v reg Register address
+ * @ret value Data read, or negative error
+ */
+static int smscusb_mii_read ( struct mii_interface *mii, unsigned int reg ) {
+ struct smscusb_device *smscusb =
+ container_of ( mii, struct smscusb_device, mii );
+ unsigned int base = smscusb->mii_base;
+ uint32_t mii_access;
+ uint32_t mii_data;
+ int rc;
+
+ /* Wait for MII to become idle */
+ if ( ( rc = smscusb_mii_wait ( smscusb ) ) != 0 )
+ return rc;
+
+ /* Initiate read command */
+ mii_access = ( SMSCUSB_MII_ACCESS_PHY_ADDRESS |
+ SMSCUSB_MII_ACCESS_MIIRINDA ( reg ) |
+ SMSCUSB_MII_ACCESS_MIIBZY );
+ if ( ( rc = smscusb_writel ( smscusb, ( base + SMSCUSB_MII_ACCESS ),
+ mii_access ) ) != 0 )
+ return rc;
+
+ /* Wait for command to complete */
+ if ( ( rc = smscusb_mii_wait ( smscusb ) ) != 0 )
+ return rc;
+
+ /* Read MII data */
+ if ( ( rc = smscusb_readl ( smscusb, ( base + SMSCUSB_MII_DATA ),
+ &mii_data ) ) != 0 )
+ return rc;
+
+ return SMSCUSB_MII_DATA_GET ( mii_data );
+}
+
+/**
+ * Write to MII register
+ *
+ * @v mii MII interface
+ * @v reg Register address
+ * @v data Data to write
+ * @ret rc Return status code
+ */
+static int smscusb_mii_write ( struct mii_interface *mii, unsigned int reg,
+ unsigned int data ) {
+ struct smscusb_device *smscusb =
+ container_of ( mii, struct smscusb_device, mii );
+ unsigned int base = smscusb->mii_base;
+ uint32_t mii_access;
+ uint32_t mii_data;
+ int rc;
+
+ /* Wait for MII to become idle */
+ if ( ( rc = smscusb_mii_wait ( smscusb ) ) != 0 )
+ return rc;
+
+ /* Write MII data */
+ mii_data = SMSCUSB_MII_DATA_SET ( data );
+ if ( ( rc = smscusb_writel ( smscusb, ( base + SMSCUSB_MII_DATA ),
+ mii_data ) ) != 0 )
+ return rc;
+
+ /* Initiate write command */
+ mii_access = ( SMSCUSB_MII_ACCESS_PHY_ADDRESS |
+ SMSCUSB_MII_ACCESS_MIIRINDA ( reg ) |
+ SMSCUSB_MII_ACCESS_MIIWNR |
+ SMSCUSB_MII_ACCESS_MIIBZY );
+ if ( ( rc = smscusb_writel ( smscusb, ( base + SMSCUSB_MII_ACCESS ),
+ mii_access ) ) != 0 )
+ return rc;
+
+ /* Wait for command to complete */
+ if ( ( rc = smscusb_mii_wait ( smscusb ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/** MII operations */
+struct mii_operations smscusb_mii_operations = {
+ .read = smscusb_mii_read,
+ .write = smscusb_mii_write,
+};
+
+/**
+ * Check link status
+ *
+ * @v smscusb SMSC USB device
+ * @ret rc Return status code
+ */
+int smscusb_mii_check_link ( struct smscusb_device *smscusb ) {
+ struct net_device *netdev = smscusb->netdev;
+ int intr;
+ int rc;
+
+ /* Read PHY interrupt source */
+ intr = mii_read ( &smscusb->mii, SMSCUSB_MII_PHY_INTR_SOURCE );
+ if ( intr < 0 ) {
+ rc = intr;
+ DBGC ( smscusb, "SMSCUSB %p could not get PHY interrupt "
+ "source: %s\n", smscusb, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Acknowledge PHY interrupt */
+ if ( ( rc = mii_write ( &smscusb->mii, SMSCUSB_MII_PHY_INTR_SOURCE,
+ intr ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not acknowledge PHY "
+ "interrupt: %s\n", smscusb, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Check link status */
+ if ( ( rc = mii_check_link ( &smscusb->mii, netdev ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not check link: %s\n",
+ smscusb, strerror ( rc ) );
+ return rc;
+ }
+
+ DBGC ( smscusb, "SMSCUSB %p link %s (intr %#04x)\n",
+ smscusb, ( netdev_link_ok ( netdev ) ? "up" : "down" ), intr );
+ return 0;
+}
+
+/**
+ * Enable PHY interrupts and update link status
+ *
+ * @v smscusb SMSC USB device
+ * @ret rc Return status code
+ */
+int smscusb_mii_open ( struct smscusb_device *smscusb ) {
+ int rc;
+
+ /* Enable PHY interrupts */
+ if ( ( rc = mii_write ( &smscusb->mii, SMSCUSB_MII_PHY_INTR_MASK,
+ ( SMSCUSB_PHY_INTR_ANEG_DONE |
+ SMSCUSB_PHY_INTR_LINK_DOWN ) ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not set PHY interrupt "
+ "mask: %s\n", smscusb, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Update link status */
+ smscusb_mii_check_link ( smscusb );
+
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * Receive filtering
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Set receive address
+ *
+ * @v smscusb SMSC USB device
+ * @v addr_base Receive address register base
+ * @ret rc Return status code
+ */
+int smscusb_set_address ( struct smscusb_device *smscusb,
+ unsigned int addr_base ) {
+ struct net_device *netdev = smscusb->netdev;
+ union smscusb_mac mac;
+ int rc;
+
+ /* Copy MAC address */
+ memset ( &mac, 0, sizeof ( mac ) );
+ memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN );
+
+ /* Write MAC address high register */
+ if ( ( rc = smscusb_raw_writel ( smscusb,
+ ( addr_base + SMSCUSB_RX_ADDRH ),
+ mac.addr.h ) ) != 0 )
+ return rc;
+
+ /* Write MAC address low register */
+ if ( ( rc = smscusb_raw_writel ( smscusb,
+ ( addr_base + SMSCUSB_RX_ADDRL ),
+ mac.addr.l ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Set receive filter
+ *
+ * @v smscusb SMSC USB device
+ * @v filt_base Receive filter register base
+ * @ret rc Return status code
+ */
+int smscusb_set_filter ( struct smscusb_device *smscusb,
+ unsigned int filt_base ) {
+ struct net_device *netdev = smscusb->netdev;
+ union smscusb_mac mac;
+ int rc;
+
+ /* Copy MAC address */
+ memset ( &mac, 0, sizeof ( mac ) );
+ memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN );
+ mac.addr.h |= cpu_to_le32 ( SMSCUSB_ADDR_FILTH_VALID );
+
+ /* Write MAC address perfect filter high register */
+ if ( ( rc = smscusb_raw_writel ( smscusb,
+ ( filt_base + SMSCUSB_ADDR_FILTH(0) ),
+ mac.addr.h ) ) != 0 )
+ return rc;
+
+ /* Write MAC address perfect filter low register */
+ if ( ( rc = smscusb_raw_writel ( smscusb,
+ ( filt_base + SMSCUSB_ADDR_FILTL(0) ),
+ mac.addr.l ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * Endpoint operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Complete interrupt transfer
+ *
+ * @v ep USB endpoint
+ * @v iobuf I/O buffer
+ * @v rc Completion status code
+ */
+static void smscusb_intr_complete ( struct usb_endpoint *ep,
+ struct io_buffer *iobuf, int rc ) {
+ struct smscusb_device *smscusb =
+ container_of ( ep, struct smscusb_device, usbnet.intr );
+ struct net_device *netdev = smscusb->netdev;
+ struct smscusb_interrupt *intr;
+
+ /* Profile completions */
+ profile_start ( &smscusb_intr_profiler );
+
+ /* Ignore packets cancelled when the endpoint closes */
+ if ( ! ep->open )
+ goto done;
+
+ /* Record USB errors against the network device */
+ if ( rc != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p interrupt failed: %s\n",
+ smscusb, strerror ( rc ) );
+ DBGC_HDA ( smscusb, 0, iobuf->data, iob_len ( iobuf ) );
+ netdev_rx_err ( netdev, NULL, rc );
+ goto done;
+ }
+
+ /* Extract interrupt data */
+ if ( iob_len ( iobuf ) != sizeof ( *intr ) ) {
+ DBGC ( smscusb, "SMSCUSB %p malformed interrupt\n",
+ smscusb );
+ DBGC_HDA ( smscusb, 0, iobuf->data, iob_len ( iobuf ) );
+ netdev_rx_err ( netdev, NULL, rc );
+ goto done;
+ }
+ intr = iobuf->data;
+
+ /* Record interrupt status */
+ smscusb->int_sts = le32_to_cpu ( intr->int_sts );
+ profile_stop ( &smscusb_intr_profiler );
+
+ done:
+ /* Free I/O buffer */
+ free_iob ( iobuf );
+}
+
+/** Interrupt endpoint operations */
+struct usb_endpoint_driver_operations smscusb_intr_operations = {
+ .complete = smscusb_intr_complete,
+};
+
+/**
+ * Complete bulk OUT transfer
+ *
+ * @v ep USB endpoint
+ * @v iobuf I/O buffer
+ * @v rc Completion status code
+ */
+static void smscusb_out_complete ( struct usb_endpoint *ep,
+ struct io_buffer *iobuf, int rc ) {
+ struct smscusb_device *smscusb =
+ container_of ( ep, struct smscusb_device, usbnet.out );
+ struct net_device *netdev = smscusb->netdev;
+
+ /* Report TX completion */
+ netdev_tx_complete_err ( netdev, iobuf, rc );
+}
+
+/** Bulk OUT endpoint operations */
+struct usb_endpoint_driver_operations smscusb_out_operations = {
+ .complete = smscusb_out_complete,
+};
diff --git a/src/drivers/net/smscusb.h b/src/drivers/net/smscusb.h
new file mode 100644
index 00000000..15233818
--- /dev/null
+++ b/src/drivers/net/smscusb.h
@@ -0,0 +1,302 @@
+#ifndef _SMSCUSB_H
+#define _SMSCUSB_H
+
+/** @file
+ *
+ * SMSC USB Ethernet drivers
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <ipxe/usb.h>
+#include <ipxe/usbnet.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/mii.h>
+#include <ipxe/if_ether.h>
+
+/** Register write command */
+#define SMSCUSB_REGISTER_WRITE \
+ ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \
+ USB_REQUEST_TYPE ( 0xa0 ) )
+
+/** Register read command */
+#define SMSCUSB_REGISTER_READ \
+ ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \
+ USB_REQUEST_TYPE ( 0xa1 ) )
+
+/** Get statistics command */
+#define SMSCUSB_GET_STATISTICS \
+ ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \
+ USB_REQUEST_TYPE ( 0xa2 ) )
+
+/** EEPROM command register offset */
+#define SMSCUSB_E2P_CMD 0x000
+#define SMSCUSB_E2P_CMD_EPC_BSY 0x80000000UL /**< EPC busy */
+#define SMSCUSB_E2P_CMD_EPC_CMD_READ 0x00000000UL /**< READ command */
+#define SMSCUSB_E2P_CMD_EPC_ADDR(addr) ( (addr) << 0 ) /**< EPC address */
+
+/** EEPROM data register offset */
+#define SMSCUSB_E2P_DATA 0x004
+#define SMSCUSB_E2P_DATA_GET(e2p_data) \
+ ( ( (e2p_data) >> 0 ) & 0xff ) /**< EEPROM data */
+
+/** MAC address EEPROM address */
+#define SMSCUSB_EEPROM_MAC 0x01
+
+/** Maximum time to wait for EEPROM (in milliseconds) */
+#define SMSCUSB_EEPROM_MAX_WAIT_MS 100
+
+/** MII access register offset */
+#define SMSCUSB_MII_ACCESS 0x000
+#define SMSCUSB_MII_ACCESS_PHY_ADDRESS 0x00000800UL /**< PHY address */
+#define SMSCUSB_MII_ACCESS_MIIRINDA(addr) ( (addr) << 6 ) /**< MII register */
+#define SMSCUSB_MII_ACCESS_MIIWNR 0x00000002UL /**< MII write */
+#define SMSCUSB_MII_ACCESS_MIIBZY 0x00000001UL /**< MII busy */
+
+/** MII data register offset */
+#define SMSCUSB_MII_DATA 0x004
+#define SMSCUSB_MII_DATA_SET(data) ( (data) << 0 ) /**< Set data */
+#define SMSCUSB_MII_DATA_GET(mii_data) \
+ ( ( (mii_data) >> 0 ) & 0xffff ) /**< Get data */
+
+/** PHY interrupt source MII register */
+#define SMSCUSB_MII_PHY_INTR_SOURCE 29
+
+/** PHY interrupt mask MII register */
+#define SMSCUSB_MII_PHY_INTR_MASK 30
+
+/** PHY interrupt: auto-negotiation complete */
+#define SMSCUSB_PHY_INTR_ANEG_DONE 0x0040
+
+/** PHY interrupt: link down */
+#define SMSCUSB_PHY_INTR_LINK_DOWN 0x0010
+
+/** Maximum time to wait for MII (in milliseconds) */
+#define SMSCUSB_MII_MAX_WAIT_MS 100
+
+/** MAC address */
+union smscusb_mac {
+ /** MAC receive address registers */
+ struct {
+ /** MAC receive address low register */
+ uint32_t l;
+ /** MAC receive address high register */
+ uint32_t h;
+ } __attribute__ (( packed )) addr;
+ /** Raw MAC address */
+ uint8_t raw[ETH_ALEN];
+};
+
+/** MAC receive address high register offset */
+#define SMSCUSB_RX_ADDRH 0x000
+
+/** MAC receive address low register offset */
+#define SMSCUSB_RX_ADDRL 0x004
+
+/** MAC address perfect filter N high register offset */
+#define SMSCUSB_ADDR_FILTH(n) ( 0x000 + ( 8 * (n) ) )
+#define SMSCUSB_ADDR_FILTH_VALID 0x80000000UL /**< Address valid */
+
+/** MAC address perfect filter N low register offset */
+#define SMSCUSB_ADDR_FILTL(n) ( 0x004 + ( 8 * (n) ) )
+
+/** Interrupt packet format */
+struct smscusb_interrupt {
+ /** Current value of INT_STS register */
+ uint32_t int_sts;
+} __attribute__ (( packed ));
+
+/** An SMSC USB device */
+struct smscusb_device {
+ /** USB device */
+ struct usb_device *usb;
+ /** USB bus */
+ struct usb_bus *bus;
+ /** Network device */
+ struct net_device *netdev;
+ /** USB network device */
+ struct usbnet_device usbnet;
+ /** MII interface */
+ struct mii_interface mii;
+ /** MII register base */
+ uint16_t mii_base;
+ /** Interrupt status */
+ uint32_t int_sts;
+};
+
+/**
+ * Write register (without byte-swapping)
+ *
+ * @v smscusb Smscusb device
+ * @v address Register address
+ * @v value Register value
+ * @ret rc Return status code
+ */
+static int smscusb_raw_writel ( struct smscusb_device *smscusb,
+ unsigned int address, uint32_t value ) {
+ int rc;
+
+ /* Write register */
+ DBGCIO ( smscusb, "SMSCUSB %p [%03x] <= %08x\n",
+ smscusb, address, le32_to_cpu ( value ) );
+ if ( ( rc = usb_control ( smscusb->usb, SMSCUSB_REGISTER_WRITE, 0,
+ address, &value, sizeof ( value ) ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not write %03x: %s\n",
+ smscusb, address, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Write register
+ *
+ * @v smscusb SMSC USB device
+ * @v address Register address
+ * @v value Register value
+ * @ret rc Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+smscusb_writel ( struct smscusb_device *smscusb, unsigned int address,
+ uint32_t value ) {
+ int rc;
+
+ /* Write register */
+ if ( ( rc = smscusb_raw_writel ( smscusb, address,
+ cpu_to_le32 ( value ) ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Read register (without byte-swapping)
+ *
+ * @v smscusb SMSC USB device
+ * @v address Register address
+ * @ret value Register value
+ * @ret rc Return status code
+ */
+static int smscusb_raw_readl ( struct smscusb_device *smscusb,
+ unsigned int address, uint32_t *value ) {
+ int rc;
+
+ /* Read register */
+ if ( ( rc = usb_control ( smscusb->usb, SMSCUSB_REGISTER_READ, 0,
+ address, value, sizeof ( *value ) ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not read %03x: %s\n",
+ smscusb, address, strerror ( rc ) );
+ return rc;
+ }
+ DBGCIO ( smscusb, "SMSCUSB %p [%03x] => %08x\n",
+ smscusb, address, le32_to_cpu ( *value ) );
+
+ return 0;
+}
+
+/**
+ * Read register
+ *
+ * @v smscusb SMSC USB device
+ * @v address Register address
+ * @ret value Register value
+ * @ret rc Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+smscusb_readl ( struct smscusb_device *smscusb, unsigned int address,
+ uint32_t *value ) {
+ int rc;
+
+ /* Read register */
+ if ( ( rc = smscusb_raw_readl ( smscusb, address, value ) ) != 0 )
+ return rc;
+ le32_to_cpus ( value );
+
+ return 0;
+}
+
+/**
+ * Get statistics
+ *
+ * @v smscusb SMSC USB device
+ * @v index Statistics set index
+ * @v data Statistics data to fill in
+ * @v len Length of statistics data
+ * @ret rc Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+smscusb_get_statistics ( struct smscusb_device *smscusb, unsigned int index,
+ void *data, size_t len ) {
+ int rc;
+
+ /* Read statistics */
+ if ( ( rc = usb_control ( smscusb->usb, SMSCUSB_GET_STATISTICS, 0,
+ index, data, len ) ) != 0 ) {
+ DBGC ( smscusb, "SMSCUSB %p could not get statistics set %d: "
+ "%s\n", smscusb, index, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/** Interrupt maximum fill level
+ *
+ * This is a policy decision.
+ */
+#define SMSCUSB_INTR_MAX_FILL 2
+
+extern struct usb_endpoint_driver_operations smscusb_intr_operations;
+extern struct usb_endpoint_driver_operations smscusb_out_operations;
+extern struct mii_operations smscusb_mii_operations;
+
+/**
+ * Initialise SMSC USB device
+ *
+ * @v smscusb SMSC USB device
+ * @v netdev Network device
+ * @v func USB function
+ * @v in Bulk IN endpoint operations
+ */
+static inline __attribute__ (( always_inline )) void
+smscusb_init ( struct smscusb_device *smscusb, struct net_device *netdev,
+ struct usb_function *func,
+ struct usb_endpoint_driver_operations *in ) {
+ struct usb_device *usb = func->usb;
+
+ smscusb->usb = usb;
+ smscusb->bus = usb->port->hub->bus;
+ smscusb->netdev = netdev;
+ usbnet_init ( &smscusb->usbnet, func, &smscusb_intr_operations, in,
+ &smscusb_out_operations );
+ usb_refill_init ( &smscusb->usbnet.intr, 0, 0, SMSCUSB_INTR_MAX_FILL );
+}
+
+/**
+ * Initialise SMSC USB device MII interface
+ *
+ * @v smscusb SMSC USB device
+ * @v mii_base MII register base
+ */
+static inline __attribute__ (( always_inline )) void
+smscusb_mii_init ( struct smscusb_device *smscusb, unsigned int mii_base ) {
+
+ mii_init ( &smscusb->mii, &smscusb_mii_operations );
+ smscusb->mii_base = mii_base;
+}
+
+extern int smscusb_eeprom_fetch_mac ( struct smscusb_device *smscusb,
+ unsigned int e2p_base );
+extern int smscusb_mii_check_link ( struct smscusb_device *smscusb );
+extern int smscusb_mii_open ( struct smscusb_device *smscusb );
+extern int smscusb_set_address ( struct smscusb_device *smscusb,
+ unsigned int addr_base );
+extern int smscusb_set_filter ( struct smscusb_device *smscusb,
+ unsigned int filt_base );
+
+#endif /* _SMSCUSB_H */