]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[smscusb] Abstract out common SMSC USB device functionality
authorMichael Brown <mcb30@ipxe.org>
Thu, 6 Jul 2017 15:58:22 +0000 (16:58 +0100)
committerMichael Brown <mcb30@ipxe.org>
Fri, 7 Jul 2017 15:44:28 +0000 (16:44 +0100)
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>
src/drivers/net/smscusb.c [new file with mode: 0644]
src/drivers/net/smscusb.h [new file with mode: 0644]
src/include/ipxe/errfile.h

diff --git a/src/drivers/net/smscusb.c b/src/drivers/net/smscusb.c
new file mode 100644 (file)
index 0000000..d4f3af0
--- /dev/null
@@ -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 (file)
index 0000000..1523381
--- /dev/null
@@ -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 */
index dc3de0518962230c6a31779873d34084fcd533c5..6da1a4505fd016bb4dfde04d3943376a7dc75871 100644 (file)
@@ -200,6 +200,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_sfc_hunt            ( ERRFILE_DRIVER | 0x00c40000 )
 #define ERRFILE_efx_hunt            ( ERRFILE_DRIVER | 0x00c50000 )
 #define ERRFILE_exanic              ( ERRFILE_DRIVER | 0x00c60000 )
+#define ERRFILE_smscusb                     ( ERRFILE_DRIVER | 0x00c70000 )
 
 #define ERRFILE_aoe                    ( ERRFILE_NET | 0x00000000 )
 #define ERRFILE_arp                    ( ERRFILE_NET | 0x00010000 )