From patchwork Thu Apr 14 02:29:32 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 91176 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 8F0A7B70D3 for ; Thu, 14 Apr 2011 12:30:55 +1000 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 488EB28307; Thu, 14 Apr 2011 04:30:49 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id zvFqSzBtBRSc; Thu, 14 Apr 2011 04:30:49 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 340AF28315; Thu, 14 Apr 2011 04:30:45 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id CBE9F28300 for ; Thu, 14 Apr 2011 04:30:39 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id siM8yRNy96kT for ; Thu, 14 Apr 2011 04:30:35 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from smtp-out.google.com (smtp-out.google.com [216.239.44.51]) by theia.denx.de (Postfix) with ESMTPS id 62C1E282F4 for ; Thu, 14 Apr 2011 04:30:34 +0200 (CEST) Received: from hpaq12.eem.corp.google.com (hpaq12.eem.corp.google.com [172.25.149.12]) by smtp-out.google.com with ESMTP id p3E2TpcH005598; Wed, 13 Apr 2011 19:29:54 -0700 Received: from sglass.mtv.corp.google.com (sglass.mtv.corp.google.com [172.22.72.144]) by hpaq12.eem.corp.google.com with ESMTP id p3E2Tl86009088; Wed, 13 Apr 2011 19:29:48 -0700 Received: by sglass.mtv.corp.google.com (Postfix, from userid 121222) id 358E61409E5; Wed, 13 Apr 2011 19:29:47 -0700 (PDT) From: Simon Glass To: u-boot@lists.denx.de Date: Wed, 13 Apr 2011 19:29:32 -0700 Message-Id: <1302748176-4324-1-git-send-email-sjg@chromium.org> X-Mailer: git-send-email 1.7.3.1 X-System-Of-Record: true Cc: Andy Fleming Subject: [U-Boot] [PATCH v4 1/5] Add support for SMSC95XX USB 2.0 10/100MBit Ethernet Adapter X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.9 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de The SMSC95XX is a USB hub with a built-in Ethernet adapter. This adds support for this, using the USB host network framework. TEST=usb start; bootp; tftp ... Signed-off-by: Simon Glass --- Changes for v2: - Coding style cleanup - Changed some comments as suggested - eth_set_hwaddr -> eth_write_hwaddr - tided up other users of eth_getenv_enetaddr_by_index() Changes for v3: - Drop tfpserverip patch - Change turbo_mode to #define - Fix tfpserverip patch bleed Changes for v4: - Dropped Tegra2 specific bit - Added patch in place of tftpserverip patch, to speed up successive network commands on asix - Fixed a few broken bits in SMSC from my testing --- drivers/usb/eth/Makefile | 1 + drivers/usb/eth/smsc95xx.c | 921 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/eth/usb_ether.c | 7 + include/usb_ether.h | 13 + 4 files changed, 942 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/eth/smsc95xx.c diff --git a/drivers/usb/eth/Makefile b/drivers/usb/eth/Makefile index 6a5f25a..e28793d 100644 --- a/drivers/usb/eth/Makefile +++ b/drivers/usb/eth/Makefile @@ -28,6 +28,7 @@ COBJS-$(CONFIG_USB_HOST_ETHER) += usb_ether.o ifdef CONFIG_USB_ETHER_ASIX COBJS-y += asix.o endif +COBJS-$(CONFIG_USB_ETHER_SMSC95XX) += smsc95xx.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/usb/eth/smsc95xx.c b/drivers/usb/eth/smsc95xx.c new file mode 100644 index 0000000..6a2290f --- /dev/null +++ b/drivers/usb/eth/smsc95xx.c @@ -0,0 +1,921 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * Copyright (C) 2009 NVIDIA, Corporation + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include "usb_ether.h" + +/* SMSC LAN95xx based USB 2.0 Ethernet Devices */ + +/* Tx command words */ +#define TX_CMD_A_FIRST_SEG_ (0x00002000) +#define TX_CMD_A_LAST_SEG_ (0x00001000) + +/* Rx status word */ +#define RX_STS_FL_ (0x3FFF0000) /* Frame Length */ +#define RX_STS_ES_ (0x00008000) /* Error Summary */ + +/* SCSRs */ +#define ID_REV (0x00) + +#define INT_STS (0x08) + +#define TX_CFG (0x10) +#define TX_CFG_ON_ (0x00000004) + +#define HW_CFG (0x14) +#define HW_CFG_BIR_ (0x00001000) +#define HW_CFG_RXDOFF_ (0x00000600) +#define HW_CFG_MEF_ (0x00000020) +#define HW_CFG_BCE_ (0x00000002) +#define HW_CFG_LRST_ (0x00000008) + +#define PM_CTRL (0x20) +#define PM_CTL_PHY_RST_ (0x00000010) + +#define AFC_CFG (0x2C) + +/* + * Hi watermark = 15.5Kb (~10 mtu pkts) + * low watermark = 3k (~2 mtu pkts) + * backpressure duration = ~ 350us + * Apply FC on any frame. + */ +#define AFC_CFG_DEFAULT (0x00F830A1) + +#define E2P_CMD (0x30) +#define E2P_CMD_BUSY_ (0x80000000) +#define E2P_CMD_READ_ (0x00000000) +#define E2P_CMD_TIMEOUT_ (0x00000400) +#define E2P_CMD_LOADED_ (0x00000200) +#define E2P_CMD_ADDR_ (0x000001FF) + +#define E2P_DATA (0x34) + +#define BURST_CAP (0x38) + +#define INT_EP_CTL (0x68) +#define INT_EP_CTL_PHY_INT_ (0x00008000) + +#define BULK_IN_DLY (0x6C) + +/* MAC CSRs */ +#define MAC_CR (0x100) +#define MAC_CR_MCPAS_ (0x00080000) +#define MAC_CR_PRMS_ (0x00040000) +#define MAC_CR_HPFILT_ (0x00002000) +#define MAC_CR_TXEN_ (0x00000008) +#define MAC_CR_RXEN_ (0x00000004) + +#define ADDRH (0x104) + +#define ADDRL (0x108) + +#define MII_ADDR (0x114) +#define MII_WRITE_ (0x02) +#define MII_BUSY_ (0x01) +#define MII_READ_ (0x00) /* ~of MII Write bit */ + +#define MII_DATA (0x118) + +#define FLOW (0x11C) + +#define VLAN1 (0x120) + +#define COE_CR (0x130) +#define Tx_COE_EN_ (0x00010000) +#define Rx_COE_EN_ (0x00000001) + +/* Vendor-specific PHY Definitions */ +#define PHY_INT_SRC (29) + +#define PHY_INT_MASK (30) +#define PHY_INT_MASK_ANEG_COMP_ ((u16)0x0040) +#define PHY_INT_MASK_LINK_DOWN_ ((u16)0x0010) +#define PHY_INT_MASK_DEFAULT_ (PHY_INT_MASK_ANEG_COMP_ | \ + PHY_INT_MASK_LINK_DOWN_) + +/* USB Vendor Requests */ +#define USB_VENDOR_REQUEST_WRITE_REGISTER 0xA0 +#define USB_VENDOR_REQUEST_READ_REGISTER 0xA1 + +/* Some extra defines */ +#define HS_USB_PKT_SIZE (512) +#define FS_USB_PKT_SIZE (64) +#define DEFAULT_HS_BURST_CAP_SIZE (16 * 1024 + 5 * HS_USB_PKT_SIZE) +#define DEFAULT_FS_BURST_CAP_SIZE (6 * 1024 + 33 * FS_USB_PKT_SIZE) +#define DEFAULT_BULK_IN_DELAY (0x00002000) +#define MAX_SINGLE_PACKET_SIZE (2048) +#define EEPROM_MAC_OFFSET (0x01) +#define SMSC95XX_INTERNAL_PHY_ID (1) +#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ + +/* local defines */ +#define SMSC95XX_BASE_NAME "sms" +#define USB_CTRL_SET_TIMEOUT 5000 +#define USB_CTRL_GET_TIMEOUT 5000 +#define USB_BULK_SEND_TIMEOUT 5000 +#define USB_BULK_RECV_TIMEOUT 5000 + +#define AX_RX_URB_SIZE 2048 +#define PHY_CONNECT_TIMEOUT 5000 + +#define TURBO_MODE + +/* local vars */ +static int curr_eth_dev; /* index for name of next device detected */ + + +/* + * Smsc95xx infrastructure commands + */ +static int smsc95xx_write_reg(struct ueth_data *dev, u32 index, u32 data) +{ + int len; + + cpu_to_le32s(&data); + + len = usb_control_msg(dev->pusb_dev, usb_sndctrlpipe(dev->pusb_dev, 0), + USB_VENDOR_REQUEST_WRITE_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, &data, sizeof(data), USB_CTRL_SET_TIMEOUT); + + return len == sizeof(data) ? 0 : -1; +} + +static int smsc95xx_read_reg(struct ueth_data *dev, u32 index, u32 *data) +{ + int len; + + len = usb_control_msg(dev->pusb_dev, usb_rcvctrlpipe(dev->pusb_dev, 0), + USB_VENDOR_REQUEST_READ_REGISTER, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, data, sizeof(data), USB_CTRL_GET_TIMEOUT); + + le32_to_cpus(data); + return len == sizeof(data) ? 0 : -1; +} + +/* Loop until the read is completed with timeout */ +static int smsc95xx_phy_wait_not_busy(struct ueth_data *dev) +{ + unsigned long start_time = get_timer(0); + u32 val; + + do { + smsc95xx_read_reg(dev, MII_ADDR, &val); + if (!(val & MII_BUSY_)) + return 0; + } while (get_timer(start_time) < 1 * 1000 * 1000); + + return -1; +} + +static int smsc95xx_mdio_read(struct ueth_data *dev, int phy_id, int idx) +{ + u32 val, addr; + + /* confirm MII not busy */ + if (smsc95xx_phy_wait_not_busy(dev)) { + debug("MII is busy in smsc95xx_mdio_read\n"); + return -1; + } + + /* set the address, index & direction (read from PHY) */ + addr = (phy_id << 11) | (idx << 6) | MII_READ_; + smsc95xx_write_reg(dev, MII_ADDR, addr); + + if (smsc95xx_phy_wait_not_busy(dev)) { + debug("Timed out reading MII reg %02X\n", idx); + return -1; + } + + smsc95xx_read_reg(dev, MII_DATA, &val); + + return (u16)(val & 0xFFFF); +} + +static void smsc95xx_mdio_write(struct ueth_data *dev, int phy_id, int idx, + int regval) +{ + u32 val, addr; + + /* confirm MII not busy */ + if (smsc95xx_phy_wait_not_busy(dev)) { + debug("MII is busy in smsc95xx_mdio_write\n"); + return; + } + + val = regval; + smsc95xx_write_reg(dev, MII_DATA, val); + + /* set the address, index & direction (write to PHY) */ + addr = (phy_id << 11) | (idx << 6) | MII_WRITE_; + smsc95xx_write_reg(dev, MII_ADDR, addr); + + if (smsc95xx_phy_wait_not_busy(dev)) + debug("Timed out writing MII reg %02X\n", idx); +} + +static int smsc95xx_eeprom_confirm_not_busy(struct ueth_data *dev) +{ + unsigned long start_time = get_timer(0); + u32 val; + + do { + smsc95xx_read_reg(dev, E2P_CMD, &val); + if (!(val & E2P_CMD_LOADED_)) { + debug("No EEPROM present\n"); + return -1; + } + if (!(val & E2P_CMD_BUSY_)) + return 0; + udelay(40); + } while (get_timer(start_time) < 1 * 1000 * 1000); + + debug("EEPROM is busy\n"); + return -1; +} + +static int smsc95xx_wait_eeprom(struct ueth_data *dev) +{ + unsigned long start_time = get_timer(0); + u32 val; + + do { + smsc95xx_read_reg(dev, E2P_CMD, &val); + if (!(val & E2P_CMD_BUSY_) || (val & E2P_CMD_TIMEOUT_)) + break; + udelay(40); + } while (get_timer(start_time) < 1 * 1000 * 1000); + + if (val & (E2P_CMD_TIMEOUT_ | E2P_CMD_BUSY_)) { + debug("EEPROM read operation timeout\n"); + return -1; + } + return 0; +} + +static int smsc95xx_read_eeprom(struct ueth_data *dev, u32 offset, u32 length, + u8 *data) +{ + u32 val; + int i, ret; + + ret = smsc95xx_eeprom_confirm_not_busy(dev); + if (ret) + return ret; + + for (i = 0; i < length; i++) { + val = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (offset & E2P_CMD_ADDR_); + smsc95xx_write_reg(dev, E2P_CMD, val); + + ret = smsc95xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + smsc95xx_read_reg(dev, E2P_DATA, &val); + data[i] = val & 0xFF; + offset++; + } + return 0; +} + +/* + * mii_nway_restart - restart NWay (autonegotiation) for this interface + * + * Returns 0 on success, negative on error. + */ +static int mii_nway_restart(struct ueth_data *dev) +{ + int bmcr; + int r = -1; + + /* if autoneg is off, it's an error */ + bmcr = smsc95xx_mdio_read(dev, dev->phy_id, MII_BMCR); + + if (bmcr & BMCR_ANENABLE) { + bmcr |= BMCR_ANRESTART; + smsc95xx_mdio_write(dev, dev->phy_id, MII_BMCR, bmcr); + r = 0; + } + return r; +} + +static int smsc95xx_phy_initialize(struct ueth_data *dev) +{ + smsc95xx_mdio_write(dev, dev->phy_id, MII_BMCR, BMCR_RESET); + smsc95xx_mdio_write(dev, dev->phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + + /* read to clear */ + smsc95xx_mdio_read(dev, dev->phy_id, PHY_INT_SRC); + + smsc95xx_mdio_write(dev, dev->phy_id, PHY_INT_MASK, + PHY_INT_MASK_DEFAULT_); + mii_nway_restart(dev); + + debug("phy initialised succesfully\n"); + return 0; +} + +static int smsc95xx_init_mac_address(struct eth_device *eth, + struct ueth_data *dev) +{ + /* try reading mac address from EEPROM */ + if (smsc95xx_read_eeprom(dev, EEPROM_MAC_OFFSET, ETH_ALEN, + eth->enetaddr) == 0) { + if (is_valid_ether_addr(eth->enetaddr)) { + /* eeprom values are valid so use them */ + debug("MAC address read from EEPROM\n"); + return 0; + } + } + + /* + * No eeprom, or eeprom values are invalid. Generating a random MAC + * address is not safe. Just return an error. + */ + return -1; +} + +static int smsc95xx_write_hwaddr(struct eth_device *eth) +{ + struct ueth_data *dev = (struct ueth_data *)eth->priv; + u32 addr_lo, addr_hi; + int ret; + + /* set hardware address */ + debug("** %s()\n", __func__); + addr_lo = cpu_to_le32(*((u32 *)eth->enetaddr)); + addr_hi = cpu_to_le16(*((u16 *)(eth->enetaddr + 4))); + ret = smsc95xx_write_reg(dev, ADDRL, addr_lo); + if (ret < 0) { + debug("Failed to write ADDRL: %d\n", ret); + return ret; + } + + ret = smsc95xx_write_reg(dev, ADDRH, addr_hi); + if (ret < 0) { + debug("Failed to write ADDRH: %d\n", ret); + return ret; + } + debug("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + eth->enetaddr[0], eth->enetaddr[1], + eth->enetaddr[2], eth->enetaddr[3], + eth->enetaddr[4], eth->enetaddr[5]); + dev->have_hwaddr = 1; + return 0; +} + +/* Enable or disable Tx & Rx checksum offload engines */ +static int smsc95xx_set_csums(struct ueth_data *dev, + int use_tx_csum, int use_rx_csum) +{ + u32 read_buf; + int ret = smsc95xx_read_reg(dev, COE_CR, &read_buf); + if (ret < 0) { + debug("Failed to read COE_CR: %d\n", ret); + return ret; + } + + if (use_tx_csum) + read_buf |= Tx_COE_EN_; + else + read_buf &= ~Tx_COE_EN_; + + if (use_rx_csum) + read_buf |= Rx_COE_EN_; + else + read_buf &= ~Rx_COE_EN_; + + ret = smsc95xx_write_reg(dev, COE_CR, read_buf); + if (ret < 0) { + debug("Failed to write COE_CR: %d\n", ret); + return ret; + } + + debug("COE_CR = 0x%08x\n", read_buf); + return 0; +} + +static void smsc95xx_set_multicast(struct ueth_data *dev) +{ + /* No multicast in u-boot */ + dev->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); +} + +/* starts the TX path */ +static void smsc95xx_start_tx_path(struct ueth_data *dev) +{ + u32 reg_val; + + /* Enable Tx at MAC */ + dev->mac_cr |= MAC_CR_TXEN_; + + smsc95xx_write_reg(dev, MAC_CR, dev->mac_cr); + + /* Enable Tx at SCSRs */ + reg_val = TX_CFG_ON_; + smsc95xx_write_reg(dev, TX_CFG, reg_val); +} + +/* Starts the Receive path */ +static void smsc95xx_start_rx_path(struct ueth_data *dev) +{ + dev->mac_cr |= MAC_CR_RXEN_; + smsc95xx_write_reg(dev, MAC_CR, dev->mac_cr); +} + +/* + * Smsc95xx callbacks + */ +static int smsc95xx_init(struct eth_device *eth, bd_t *bd) +{ + int ret; + u32 write_buf; + u32 read_buf; + u32 burst_cap; + int timeout; + struct ueth_data *dev = (struct ueth_data *)eth->priv; +#define TIMEOUT_RESOLUTION 50 /* ms */ + int link_detected; + + debug("** %s()\n", __func__); + dev->phy_id = SMSC95XX_INTERNAL_PHY_ID; /* fixed phy id */ + + write_buf = HW_CFG_LRST_; + ret = smsc95xx_write_reg(dev, HW_CFG, write_buf); + if (ret < 0) { + debug("Failed to write HW_CFG_LRST_ bit in HW_CFG " + "register, ret = %d\n", ret); + return ret; + } + + timeout = 0; + do { + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + debug("Failed to read HW_CFG: %d\n", ret); + return ret; + } + udelay(10 * 1000); + timeout++; + } while ((read_buf & HW_CFG_LRST_) && (timeout < 100)); + + if (timeout >= 100) { + debug("timeout waiting for completion of Lite Reset\n"); + return -1; + } + + write_buf = PM_CTL_PHY_RST_; + ret = smsc95xx_write_reg(dev, PM_CTRL, write_buf); + if (ret < 0) { + debug("Failed to write PM_CTRL: %d\n", ret); + return ret; + } + + timeout = 0; + do { + ret = smsc95xx_read_reg(dev, PM_CTRL, &read_buf); + if (ret < 0) { + debug("Failed to read PM_CTRL: %d\n", ret); + return ret; + } + udelay(10 * 1000); + timeout++; + } while ((read_buf & PM_CTL_PHY_RST_) && (timeout < 100)); + if (timeout >= 100) { + debug("timeout waiting for PHY Reset\n"); + return -1; + } + if (!dev->have_hwaddr && smsc95xx_init_mac_address(eth, dev) == 0) + dev->have_hwaddr = 1; + if (!dev->have_hwaddr) { + puts("Error: SMSC95xx: No MAC address set - set usbethaddr\n"); + return -1; + } + if (smsc95xx_write_hwaddr(eth) < 0) + return -1; + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + debug("Failed to read HW_CFG: %d\n", ret); + return ret; + } + debug("Read Value from HW_CFG : 0x%08x\n", read_buf); + + read_buf |= HW_CFG_BIR_; + ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); + if (ret < 0) { + debug("Failed to write HW_CFG_BIR_ bit in HW_CFG " + "register, ret = %d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + debug("Failed to read HW_CFG: %d\n", ret); + return ret; + } + debug("Read Value from HW_CFG after writing " + "HW_CFG_BIR_: 0x%08x\n", read_buf); + +#ifdef TURBO_MODE + if (dev->pusb_dev->speed == USB_SPEED_HIGH) { + burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE; + dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE; + } else { + burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE; + dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; + } +#else + burst_cap = 0; + dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE; +#endif + debug("rx_urb_size=%ld\n", (ulong)dev->rx_urb_size); + + ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap); + if (ret < 0) { + debug("Failed to write BURST_CAP: %d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, BURST_CAP, &read_buf); + if (ret < 0) { + debug("Failed to read BURST_CAP: %d\n", ret); + return ret; + } + debug("Read Value from BURST_CAP after writing: 0x%08x\n", read_buf); + + read_buf = DEFAULT_BULK_IN_DELAY; + ret = smsc95xx_write_reg(dev, BULK_IN_DLY, read_buf); + if (ret < 0) { + debug("ret = %d", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, BULK_IN_DLY, &read_buf); + if (ret < 0) { + debug("Failed to read BULK_IN_DLY: %d\n", ret); + return ret; + } + debug("Read Value from BULK_IN_DLY after writing: " + "0x%08x\n", read_buf); + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + debug("Failed to read HW_CFG: %d\n", ret); + return ret; + } + debug("Read Value from HW_CFG: 0x%08x\n", read_buf); + +#ifdef TURBO_MODE + read_buf |= (HW_CFG_MEF_ | HW_CFG_BCE_); +#endif + read_buf &= ~HW_CFG_RXDOFF_; + + /* set Rx data offset=2, Make IP header aligns on word boundary. */ +#define NET_IP_ALIGN 2 + read_buf |= NET_IP_ALIGN << 9; + + ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); + if (ret < 0) { + debug("Failed to write HW_CFG register, ret=%d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + debug("Failed to read HW_CFG: %d\n", ret); + return ret; + } + debug("Read Value from HW_CFG after writing: 0x%08x\n", read_buf); + + write_buf = 0xFFFFFFFF; + ret = smsc95xx_write_reg(dev, INT_STS, write_buf); + if (ret < 0) { + debug("Failed to write INT_STS register, ret=%d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, ID_REV, &read_buf); + if (ret < 0) { + debug("Failed to read ID_REV: %d\n", ret); + return ret; + } + debug("ID_REV = 0x%08x\n", read_buf); + + /* Init Tx */ + write_buf = 0; + ret = smsc95xx_write_reg(dev, FLOW, write_buf); + if (ret < 0) { + debug("Failed to write FLOW: %d\n", ret); + return ret; + } + + read_buf = AFC_CFG_DEFAULT; + ret = smsc95xx_write_reg(dev, AFC_CFG, read_buf); + if (ret < 0) { + debug("Failed to write AFC_CFG: %d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, MAC_CR, &dev->mac_cr); + if (ret < 0) { + debug("Failed to read MAC_CR: %d\n", ret); + return ret; + } + + /* Init Rx. Set Vlan */ + write_buf = (u32)ETH_P_8021Q; + ret = smsc95xx_write_reg(dev, VLAN1, write_buf); + if (ret < 0) { + debug("Failed to write VAN1: %d\n", ret); + return ret; + } + + /* Disable checksum offload engines */ + ret = smsc95xx_set_csums(dev, 0, 0); + if (ret < 0) { + debug("Failed to set csum offload: %d\n", ret); + return ret; + } + smsc95xx_set_multicast(dev); + + if (smsc95xx_phy_initialize(dev) < 0) + return -1; + ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); + if (ret < 0) { + debug("Failed to read INT_EP_CTL: %d", ret); + return ret; + } + /* enable PHY interrupts */ + read_buf |= INT_EP_CTL_PHY_INT_; + + ret = smsc95xx_write_reg(dev, INT_EP_CTL, read_buf); + if (ret < 0) { + debug("Failed to write INT_EP_CTL: %d", ret); + return ret; + } + + smsc95xx_start_tx_path(dev); + smsc95xx_start_rx_path(dev); + + timeout = 0; + do { + link_detected = smsc95xx_mdio_read(dev, dev->phy_id, MII_BMSR) + & BMSR_LSTATUS; + if (!link_detected) { + if (timeout == 0) + printf("Waiting for Ethernet connection... "); + udelay(TIMEOUT_RESOLUTION * 1000); + timeout += TIMEOUT_RESOLUTION; + } + } while (!link_detected && timeout < PHY_CONNECT_TIMEOUT); + if (link_detected) { + if (timeout != 0) + printf("done.\n"); + } else { + printf("unable to connect.\n"); + return -1; + } + return 0; +} + +static int smsc95xx_send(struct eth_device *eth, volatile void* packet, + int length) +{ + struct ueth_data *dev = (struct ueth_data *)eth->priv; + int err; + int actual_len; + u32 tx_cmd_a; + u32 tx_cmd_b; + unsigned char msg[PKTSIZE + sizeof(tx_cmd_a) + sizeof(tx_cmd_b)]; + + debug("** %s(), len %d, buf %#x\n", __func__, length, (int)msg); + if (length > PKTSIZE) + return -1; + + tx_cmd_a = (u32)length | TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + tx_cmd_b = (u32)length; + cpu_to_le32s(&tx_cmd_a); + cpu_to_le32s(&tx_cmd_b); + + /* prepend cmd_a and cmd_b */ + memcpy(msg, &tx_cmd_a, sizeof(tx_cmd_a)); + memcpy(msg + sizeof(tx_cmd_a), &tx_cmd_b, sizeof(tx_cmd_b)); + memcpy(msg + sizeof(tx_cmd_a) + sizeof(tx_cmd_b), (void *)packet, + length); + err = usb_bulk_msg(dev->pusb_dev, + usb_sndbulkpipe(dev->pusb_dev, dev->ep_out), + (void *)msg, + length + sizeof(tx_cmd_a) + sizeof(tx_cmd_b), + &actual_len, + USB_BULK_SEND_TIMEOUT); + debug("Tx: len = %u, actual = %u, err = %d\n", + length + sizeof(tx_cmd_a) + sizeof(tx_cmd_b), + actual_len, err); + return err; +} + +static int smsc95xx_recv(struct eth_device *eth) +{ + struct ueth_data *dev = (struct ueth_data *)eth->priv; + static unsigned char recv_buf[AX_RX_URB_SIZE]; + unsigned char *buf_ptr; + int err; + int actual_len; + u32 packet_len; + int cur_buf_align; + + debug("** %s()\n", __func__); + err = usb_bulk_msg(dev->pusb_dev, + usb_rcvbulkpipe(dev->pusb_dev, dev->ep_in), + (void *)recv_buf, + AX_RX_URB_SIZE, + &actual_len, + USB_BULK_RECV_TIMEOUT); + debug("Rx: len = %u, actual = %u, err = %d\n", AX_RX_URB_SIZE, + actual_len, err); + if (err != 0) { + debug("Rx: failed to receive\n"); + return -1; + } + if (actual_len > AX_RX_URB_SIZE) { + debug("Rx: received too many bytes %d\n", actual_len); + return -1; + } + + buf_ptr = recv_buf; + while (actual_len > 0) { + /* + * 1st 4 bytes contain the length of the actual data plus error + * info. Extract data length. + */ + if (actual_len < sizeof(packet_len)) { + debug("Rx: incomplete packet length\n"); + return -1; + } + memcpy(&packet_len, buf_ptr, sizeof(packet_len)); + le32_to_cpus(&packet_len); + if (packet_len & RX_STS_ES_) { + debug("Rx: Error header=%#x", packet_len); + return -1; + } + packet_len = ((packet_len & RX_STS_FL_) >> 16); + + if (packet_len > actual_len - sizeof(packet_len)) { + debug("Rx: too large packet: %d\n", packet_len); + return -1; + } + + /* Notify net stack */ + NetReceive(buf_ptr + sizeof(packet_len), packet_len - 4); + + /* Adjust for next iteration */ + actual_len -= sizeof(packet_len) + packet_len; + buf_ptr += sizeof(packet_len) + packet_len; + cur_buf_align = (int)buf_ptr - (int)recv_buf; + + if (cur_buf_align & 0x03) { + int align = 4 - (cur_buf_align & 0x03); + + actual_len -= align; + buf_ptr += align; + } + } + return err; +} + +static void smsc95xx_halt(struct eth_device *eth) +{ + debug("** %s()\n", __func__); +} + +/* + * SMSC probing functions + */ +void smsc95xx_eth_before_probe(void) +{ + curr_eth_dev = 0; +} + +struct smsc95xx_dongle { + unsigned short vendor; + unsigned short product; +}; + +static const struct smsc95xx_dongle smsc95xx_dongles[] = { + { 0x0424, 0xec00 }, /* LAN9512/LAN9514 Ethernet */ + { 0x0424, 0x9500 }, /* LAN9500 Ethernet */ + { 0x0000, 0x0000 } /* END - Do not remove */ +}; + +/* Probe to see if a new device is actually an SMSC device */ +int smsc95xx_eth_probe(struct usb_device *dev, unsigned int ifnum, + struct ueth_data *ss) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *iface_desc; + int i; + + /* let's examine the device now */ + iface = &dev->config.if_desc[ifnum]; + iface_desc = &dev->config.if_desc[ifnum].desc; + + for (i = 0; smsc95xx_dongles[i].vendor != 0; i++) { + if (dev->descriptor.idVendor == smsc95xx_dongles[i].vendor && + dev->descriptor.idProduct == smsc95xx_dongles[i].product) + /* Found a supported dongle */ + break; + } + if (smsc95xx_dongles[i].vendor == 0) + return 0; + + /* At this point, we know we've got a live one */ + debug("\n\nUSB Ethernet device detected\n"); + memset(ss, '\0', sizeof(struct ueth_data)); + + /* Initialize the ueth_data structure with some useful info */ + ss->ifnum = ifnum; + ss->pusb_dev = dev; + ss->subclass = iface_desc->bInterfaceSubClass; + ss->protocol = iface_desc->bInterfaceProtocol; + + /* + * We are expecting a minimum of 3 endpoints - in, out (bulk), and int. + * We will ignore any others. + */ + for (i = 0; i < iface_desc->bNumEndpoints; i++) { + /* is it an BULK endpoint? */ + if ((iface->ep_desc[i].bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) { + if (iface->ep_desc[i].bEndpointAddress & USB_DIR_IN) + ss->ep_in = + iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + else + ss->ep_out = + iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + } + + /* is it an interrupt endpoint? */ + if ((iface->ep_desc[i].bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) { + ss->ep_int = iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + ss->irqinterval = iface->ep_desc[i].bInterval; + } + } + debug("Endpoints In %d Out %d Int %d\n", + ss->ep_in, ss->ep_out, ss->ep_int); + + /* Do some basic sanity checks, and bail if we find a problem */ + if (usb_set_interface(dev, iface_desc->bInterfaceNumber, 0) || + !ss->ep_in || !ss->ep_out || !ss->ep_int) { + debug("Problems with device\n"); + return 0; + } + dev->privptr = (void *)ss; + return 1; +} + +int smsc95xx_eth_get_info(struct usb_device *dev, struct ueth_data *ss, + struct eth_device *eth) +{ + debug("** %s()\n", __func__); + if (!eth) { + debug("%s: missing parameter.\n", __func__); + return 0; + } + sprintf(eth->name, "%s%d", SMSC95XX_BASE_NAME, curr_eth_dev++); + eth->init = smsc95xx_init; + eth->send = smsc95xx_send; + eth->recv = smsc95xx_recv; + eth->halt = smsc95xx_halt; + eth->priv = ss; + return 1; +} diff --git a/drivers/usb/eth/usb_ether.c b/drivers/usb/eth/usb_ether.c index 68a0883..7b55da3 100644 --- a/drivers/usb/eth/usb_ether.c +++ b/drivers/usb/eth/usb_ether.c @@ -45,6 +45,13 @@ static const struct usb_eth_prob_dev prob_dev[] = { .get_info = asix_eth_get_info, }, #endif +#ifdef CONFIG_USB_ETHER_SMSC95XX + { + .before_probe = smsc95xx_eth_before_probe, + .probe = smsc95xx_eth_probe, + .get_info = smsc95xx_eth_get_info, + }, +#endif { }, /* END */ }; diff --git a/include/usb_ether.h b/include/usb_ether.h index 825c275..a7fb26b 100644 --- a/include/usb_ether.h +++ b/include/usb_ether.h @@ -51,6 +51,11 @@ struct ueth_data { unsigned char irqinterval; /* Intervall for IRQ Pipe */ /* private fields for each driver can go here if needed */ +#ifdef CONFIG_USB_ETHER_SMSC95XX + size_t rx_urb_size; /* maximum USB URB size */ + u32 mac_cr; /* MAC control register value */ + int have_hwaddr; /* 1 if we have a hardware MAC address */ +#endif }; /* @@ -65,4 +70,12 @@ int asix_eth_get_info(struct usb_device *dev, struct ueth_data *ss, struct eth_device *eth); #endif +#ifdef CONFIG_USB_ETHER_SMSC95XX +void smsc95xx_eth_before_probe(void); +int smsc95xx_eth_probe(struct usb_device *dev, unsigned int ifnum, + struct ueth_data *ss); +int smsc95xx_eth_get_info(struct usb_device *dev, struct ueth_data *ss, + struct eth_device *eth); +#endif + #endif /* __USB_ETHER_H__ */