/* * Copyright (C) 2017 Michael Brown . * * 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 #include #include #include #include #include #include #include "smscusb.h" /** @file * * SMSC USB Ethernet drivers * */ /** Interrupt completion profiler */ static struct profiler smscusb_intr_profiler __profiler = { .name = "smscusb.intr" }; /****************************************************************************** * * Register access * ****************************************************************************** */ /** * Write register (without byte-swapping) * * @v smscusb Smscusb device * @v address Register address * @v value Register value * @ret rc Return status code */ 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; } /** * Read register (without byte-swapping) * * @v smscusb SMSC USB device * @v address Register address * @ret value Register value * @ret rc Return status code */ 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; } /****************************************************************************** * * 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 MAC (%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; } /****************************************************************************** * * OTP access * ****************************************************************************** */ /** * Power up OTP * * @v smscusb SMSC USB device * @v otp_base OTP register base * @ret rc Return status code */ static int smscusb_otp_power_up ( struct smscusb_device *smscusb, unsigned int otp_base ) { uint32_t otp_power; unsigned int i; int rc; /* Power up OTP */ if ( ( rc = smscusb_writel ( smscusb, ( otp_base + SMSCUSB_OTP_POWER ), 0 ) ) != 0 ) return rc; /* Wait for OTP_POWER_DOWN to become clear */ for ( i = 0 ; i < SMSCUSB_OTP_MAX_WAIT_MS ; i++ ) { /* Read OTP_POWER and check OTP_POWER_DOWN */ if ( ( rc = smscusb_readl ( smscusb, ( otp_base + SMSCUSB_OTP_POWER ), &otp_power ) ) != 0 ) return rc; if ( ! ( otp_power & SMSCUSB_OTP_POWER_DOWN ) ) return 0; /* Delay */ mdelay ( 1 ); } DBGC ( smscusb, "SMSCUSB %p timed out waiting for OTP power up\n", smscusb ); return -ETIMEDOUT; } /** * Wait for OTP to become idle * * @v smscusb SMSC USB device * @v otp_base OTP register base * @ret rc Return status code */ static int smscusb_otp_wait ( struct smscusb_device *smscusb, unsigned int otp_base ) { uint32_t otp_status; unsigned int i; int rc; /* Wait for OTP_STATUS_BUSY to become clear */ for ( i = 0 ; i < SMSCUSB_OTP_MAX_WAIT_MS ; i++ ) { /* Read OTP_STATUS and check OTP_STATUS_BUSY */ if ( ( rc = smscusb_readl ( smscusb, ( otp_base + SMSCUSB_OTP_STATUS ), &otp_status ) ) != 0 ) return rc; if ( ! ( otp_status & SMSCUSB_OTP_STATUS_BUSY ) ) return 0; /* Delay */ mdelay ( 1 ); } DBGC ( smscusb, "SMSCUSB %p timed out waiting for OTP\n", smscusb ); return -ETIMEDOUT; } /** * Read byte from OTP * * @v smscusb SMSC USB device * @v otp_base OTP register base * @v address OTP address * @ret byte Byte read, or negative error */ static int smscusb_otp_read_byte ( struct smscusb_device *smscusb, unsigned int otp_base, unsigned int address ) { uint8_t addrh = ( address >> 8 ); uint8_t addrl = ( address >> 0 ); uint32_t otp_data; int rc; /* Wait for OTP to become idle */ if ( ( rc = smscusb_otp_wait ( smscusb, otp_base ) ) != 0 ) return rc; /* Initiate read command */ if ( ( rc = smscusb_writel ( smscusb, ( otp_base + SMSCUSB_OTP_ADDRH ), addrh ) ) != 0 ) return rc; if ( ( rc = smscusb_writel ( smscusb, ( otp_base + SMSCUSB_OTP_ADDRL ), addrl ) ) != 0 ) return rc; if ( ( rc = smscusb_writel ( smscusb, ( otp_base + SMSCUSB_OTP_CMD ), SMSCUSB_OTP_CMD_READ ) ) != 0 ) return rc; if ( ( rc = smscusb_writel ( smscusb, ( otp_base + SMSCUSB_OTP_GO ), SMSCUSB_OTP_GO_GO ) ) != 0 ) return rc; /* Wait for command to complete */ if ( ( rc = smscusb_otp_wait ( smscusb, otp_base ) ) != 0 ) return rc; /* Read OTP data */ if ( ( rc = smscusb_readl ( smscusb, ( otp_base + SMSCUSB_OTP_DATA ), &otp_data ) ) != 0 ) return rc; return SMSCUSB_OTP_DATA_GET ( otp_data ); } /** * Read data from OTP * * @v smscusb SMSC USB device * @v otp_base OTP register base * @v address OTP address * @v data Data buffer * @v len Length of data * @ret rc Return status code */ static int smscusb_otp_read ( struct smscusb_device *smscusb, unsigned int otp_base, unsigned int address, void *data, size_t len ) { uint8_t *bytes; int byte; int rc; /* Power up OTP */ if ( ( rc = smscusb_otp_power_up ( smscusb, otp_base ) ) != 0 ) return rc; /* Read bytes */ for ( bytes = data ; len-- ; address++, bytes++ ) { byte = smscusb_otp_read_byte ( smscusb, otp_base, address ); if ( byte < 0 ) return byte; *bytes = byte; } return 0; } /** * Fetch MAC address from OTP * * @v smscusb SMSC USB device * @v otp_base OTP register base * @ret rc Return status code */ int smscusb_otp_fetch_mac ( struct smscusb_device *smscusb, unsigned int otp_base ) { struct net_device *netdev = smscusb->netdev; uint8_t signature; unsigned int address; int rc; /* Read OTP signature byte */ if ( ( rc = smscusb_otp_read ( smscusb, otp_base, 0, &signature, sizeof ( signature ) ) ) != 0 ) return rc; /* Determine location of MAC address */ switch ( signature ) { case SMSCUSB_OTP_1_SIG: address = SMSCUSB_OTP_1_MAC; break; case SMSCUSB_OTP_2_SIG: address = SMSCUSB_OTP_2_MAC; break; default: DBGC ( smscusb, "SMSCUSB %p unknown OTP signature %#02x\n", smscusb, signature ); return -ENOTSUP; } /* Read MAC address from OTP */ if ( ( rc = smscusb_otp_read ( smscusb, otp_base, address, netdev->hw_addr, ETH_ALEN ) ) != 0 ) return rc; /* Check that OTP is valid */ if ( ! is_valid_ether_addr ( netdev->hw_addr ) ) { DBGC ( smscusb, "SMSCUSB %p has no layout %#02x OTP MAC (%s)\n", smscusb, signature, eth_ntoa ( netdev->hw_addr ) ); return -ENODEV; } DBGC ( smscusb, "SMSCUSB %p using layout %#02x OTP MAC %s\n", smscusb, signature, 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->phy_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->phy_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 * @v phy_mask PHY interrupt mask register * @v intrs PHY interrupts to enable * @ret rc Return status code */ int smscusb_mii_open ( struct smscusb_device *smscusb, unsigned int phy_mask, unsigned int intrs ) { int rc; /* Enable PHY interrupts */ if ( ( rc = mii_write ( &smscusb->mii, phy_mask, intrs ) ) != 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, };