From 0a4afffd67bccf9fb9c771cf2579c6e7c4451dec Mon Sep 17 00:00:00 2001 From: Narendra Damahe Date: Mon, 7 Jun 2010 16:51:50 -0700 Subject: USBNET:adding smsc power(suspend/resume)aware driver Bug 693439 Change-Id: Id251d6be11e1b251c45e811173ba770683f11c48 Reviewed-by: Narendra Damahe Tested-by: Narendra Damahe Reviewed-by: Gary King --- drivers/net/usb/Makefile | 7 +- drivers/net/usb/ioctl_9500.h | 189 ++ drivers/net/usb/smsc9500.c | 5078 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/usb/smsc9500.h | 775 +++++++ drivers/net/usb/smscusbnet.c | 2075 +++++++++++++++++ drivers/net/usb/smscusbnet.h | 318 +++ drivers/net/usb/version.h | 6 + 7 files changed, 8447 insertions(+), 1 deletion(-) create mode 100644 drivers/net/usb/ioctl_9500.h create mode 100644 drivers/net/usb/smsc9500.c create mode 100644 drivers/net/usb/smsc9500.h create mode 100644 drivers/net/usb/smscusbnet.c create mode 100644 drivers/net/usb/smscusbnet.h create mode 100644 drivers/net/usb/version.h diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index e17afb78f372..35403cd87532 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -11,7 +11,12 @@ obj-$(CONFIG_USB_NET_AX8817X) += asix.o obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o obj-$(CONFIG_USB_NET_DM9601) += dm9601.o -obj-$(CONFIG_USB_NET_SMSC95XX) += smsc95xx.o +ifeq ($(CONFIG_PM),y) +obj-$(CONFIG_USB_NET_SMSC95XX) += smsc9500.o +obj-$(CONFIG_USB_NET_SMSC95XX) += smscusbnet.o +else +obj-$(CONFIG_USB_NET_SMSC95XX) += smsc95xx.o +endif obj-$(CONFIG_USB_NET_GL620A) += gl620a.o obj-$(CONFIG_USB_NET_NET1080) += net1080.o obj-$(CONFIG_USB_NET_PLUSB) += plusb.o diff --git a/drivers/net/usb/ioctl_9500.h b/drivers/net/usb/ioctl_9500.h new file mode 100644 index 000000000000..5f0ba965ad58 --- /dev/null +++ b/drivers/net/usb/ioctl_9500.h @@ -0,0 +1,189 @@ +#ifndef IOCTL_9500_H_ +#define IOCTL_9500_H_ + +/*************************************************************************** + + * + + * Copyright (C) 2008-2009 SMSC + + * + + * 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. + + * + + *************************************************************************** + + * File: ioctl_500.h + + */ + + +#define SMSC9500_DRIVER_SIGNATURE (0x82745BACUL+DRIVER_VERSION) + +#define SMSC9500_APP_SIGNATURE (0x987BEF28UL+DRIVER_VERSION) + + + +#define SMSC9500_IOCTL (SIOCDEVPRIVATE + 0xB) + + +enum{ + COMMAND_BASE = 0x974FB832UL, + COMMAND_GET_SIGNATURE, + COMMAND_LAN_GET_REG, + COMMAND_LAN_SET_REG, + COMMAND_MAC_GET_REG, + COMMAND_MAC_SET_REG, + COMMAND_PHY_GET_REG, + COMMAND_PHY_SET_REG, + COMMAND_DUMP_LAN_REGS, + COMMAND_DUMP_MAC_REGS, + COMMAND_DUMP_PHY_REGS, + COMMAND_DUMP_EEPROM, + COMMAND_GET_MAC_ADDRESS, + COMMAND_SET_MAC_ADDRESS, + COMMAND_LOAD_MAC_ADDRESS, + COMMAND_SAVE_MAC_ADDRESS, + COMMAND_SET_DEBUG_MODE, + COMMAND_SET_POWER_MODE, + COMMAND_GET_POWER_MODE, + COMMAND_SET_LINK_MODE, + COMMAND_GET_LINK_MODE, + COMMAND_GET_CONFIGURATION, + COMMAND_DUMP_TEMP, + COMMAND_READ_BYTE, + COMMAND_READ_WORD, + COMMAND_READ_DWORD, + COMMAND_WRITE_BYTE, + COMMAND_WRITE_WORD, + COMMAND_WRITE_DWORD, + COMMAND_CHECK_LINK, + COMMAND_GET_ERRORS, + COMMAND_SET_EEPROM, + COMMAND_GET_EEPROM, + COMMAND_WRITE_EEPROM_FROM_FILE, + COMMAND_WRITE_EEPROM_TO_FILE, + COMMAND_VERIFY_EEPROM_WITH_FILE, + +//the following codes are intended for cmd9500 only +// they are not intended to have any use in the driver + + COMMAND_RUN_SERVER, + COMMAND_RUN_TUNER, + COMMAND_GET_FLOW_PARAMS, + COMMAND_SET_FLOW_PARAMS, + COMMAND_SET_AMDIX_STS, + COMMAND_GET_AMDIX_STS +}; + +enum{ + LAN_REG_ID_REV, + LAN_REG_FPGA_REV, + LAN_REG_INT_STS, + LAN_REG_RX_CFG, + LAN_REG_TX_CFG, + LAN_REG_HW_CFG, + LAN_REG_RX_FIFO_INF, + LAN_REG_TX_FIFO_INF, + LAN_REG_PMT_CTRL, + LAN_REG_LED_GPIO_CFG, + LAN_REG_GPIO_CFG, + LAN_REG_AFC_CFG, + LAN_REG_E2P_CMD, + LAN_REG_E2P_DATA, + LAN_REG_BURST_CAP, + LAN_REG_STRAP_DBG, + LAN_REG_DP_SEL, + LAN_REG_DP_CMD, + LAN_REG_DP_ADDR, + LAN_REG_DP_DATA0, + LAN_REG_DP_DATA1, + LAN_REG_GPIO_WAKE, + LAN_REG_INT_EP_CTL, + LAN_REG_BULK_IN_DLY, + MAX_LAN_REG_NUM +}; + +enum{ + MAC_REG_MAC_CR, + MAC_REG_ADDRH, + MAC_REG_ADDRL, + MAC_REG_HASHH, + MAC_REG_HASHL, + MAC_REG_MII_ADDR, + MAC_REG_MII_DATA, + MAC_REG_FLOW, + MAC_REG_VLAN1, + MAC_REG_VLAN2, + MAC_REG_WUFF, + MAC_REG_WUCSR, + MAC_REG_COE_CR, + MAX_MAC_REG_NUM +}; + +enum{ + PHY_REG_BCR, + PHY_REG_BSR, + PHY_REG_ID1, + PHY_REG_ID2, + PHY_REG_ANEG_ADV, + PHY_REG_ANEG_LPA, + PHY_REG_ANEG_ER, + PHY_REG_SILICON_REV, + PHY_REG_MODE_CTRL_STS, + PHY_REG_SPECIAL_MODES, + PHY_REG_TSTCNTL, + PHY_REG_TSTREAD1, + PHY_REG_TSTREAD2, + PHY_REG_TSTWRITE, + PHY_REG_SPECIAL_CTRL_STS, + PHY_REG_SITC, + PHY_REG_INT_SRC, + PHY_REG_INT_MASK, + PHY_REG_SPECIAL, + MAX_PHY_REG_NUM +}; + + +typedef struct _SMSC9500_IOCTL_DATA { + + unsigned long dwSignature; + + unsigned long dwCommand; + + unsigned long Data[0x90]; + + char Strng1[30]; + + char Strng2[10]; + +} SMSC9500_IOCTL_DATA, *PSMSC9500_IOCTL_DATA; + + +#endif /*IOCTL_9500_H_*/ diff --git a/drivers/net/usb/smsc9500.c b/drivers/net/usb/smsc9500.c new file mode 100644 index 000000000000..2e2e2f5e70e5 --- /dev/null +++ b/drivers/net/usb/smsc9500.c @@ -0,0 +1,5078 @@ + + /*************************************************************************** + * + * Copyright (C) 2007-2008 SMSC + * + * 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. + * + *************************************************************************** + * File: smsc9500.c + *************************************************************************** + * History: + * Vlad Lyalikov, 10/20/2009 + * Added bulkin_delay parameter for testing pusposes + * Vlad Lyalikov, 01/26/2010 + * Support for ndo framework + *****************************************************************************/ +#ifndef __KERNEL__ +# define __KERNEL__ +#endif + +#include + +#define TX_SKB_FORCE_COPY + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)) +#include +#endif + +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "smscusbnet.h" +#include "smsc9500.h" +#include "ioctl_9500.h" + +#define CHECK_RETURN_STATUS(A) { if((A) < 0){ goto DONE;} } + +unsigned int debug_mode = DBG_WARNING | DBG_INIT | DBG_LINK_CHANGE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(debug_mode, uint, 0); +#else + MODULE_PARM(debug_mode,"i"); +#endif +MODULE_PARM_DESC(debug_mode,"bit 0 enables trace points, bit 1 enables warning points, bit 2 enables eth gpios, bit 3 enables gen gpios"); + +u32 link_mode=0x7fUL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(link_mode, uint, 0); +#else + MODULE_PARM(link_mode,"i"); +#endif +MODULE_PARM_DESC(link_mode,"Set Link speed and Duplex, 1=10HD,2=10FD,4=100HD,8=100FD,default=0xF"); + +u32 auto_mdix=0x3U; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(auto_mdix, uint, 0); +#else + MODULE_PARM(auto_mdix,"i"); +#endif +MODULE_PARM_DESC(auto_mdix,"Set Auto-MDIX state, 0=StraightCable,1=CrossOver,2=Enable AMDIX,3=controlled by Strap"); + +u32 mac_addr_hi16=0xFFFFFFFFUL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(mac_addr_hi16, uint, 0); +#else + MODULE_PARM(mac_addr_hi16,"i"); +#endif +MODULE_PARM_DESC(mac_addr_hi16,"Specifies the high 16 bits of the mac address"); + +u32 mac_addr_lo32=0xFFFFFFFFUL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(mac_addr_lo32, uint, 0); +#else + MODULE_PARM(mac_addr_lo32,"i"); +#endif +MODULE_PARM_DESC(mac_addr_lo32,"Specifies the low 32 bits of the mac address"); + +u32 phy_addr=0xFFFFFFFFUL; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(phy_addr, uint, 0); +#else + MODULE_PARM(phy_addr,"i"); +#endif +MODULE_PARM_DESC(phy_addr,"phy_addr, only valid if it is external phy set by strap; 0-31=external phy with specified address, else autodetect external phy addr"); + +int scatter_gather=FALSE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(scatter_gather,bool, 0); +#else +MODULE_PARM(scatter_gather,"bool"); +#endif +MODULE_PARM_DESC(scatter_gather,"Enable Scatter Gather"); + +int tx_Csum=FALSE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(tx_Csum,bool, 0); +#else + MODULE_PARM(tx_Csum,"bool"); +#endif +MODULE_PARM_DESC(tx_Csum,"Enable Tx Hardware Checksum Offload"); + +int rx_Csum=FALSE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(rx_Csum,bool, 0); +#else + MODULE_PARM(rx_Csum,"bool"); +#endif +MODULE_PARM_DESC(tx_Csum,"Enable Rx Hardware Checksum Offload"); + +u32 bulkin_delay=DEFAULT_BULK_IN_DELAY; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(bulkin_delay,uint, 0); +#else + MODULE_PARM(bulkin_delay,"i"); +#endif +MODULE_PARM_DESC(bulkin_delay,"16 bit value in units of 16ns to delay UTX sending data"); + +int TurboMode=TRUE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(TurboMode,bool, 0); +#else + MODULE_PARM(TurboMode,"bool"); +#endif +MODULE_PARM_DESC(TurboMode,"Enable Turbo Mode"); + +int LinkActLedCfg=FALSE; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(LinkActLedCfg,bool, 0); +#else + MODULE_PARM(LinkActLedCfg,"bool"); +#endif +MODULE_PARM_DESC(LinkActLedCfg,"Enables separate Link and Activity LEDs in LAN9500A"); + +u32 LinkLedOnGpio=11; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(LinkLedOnGpio, uint, 0); +#else + MODULE_PARM(LinkLedOnGpio,"i"); +#endif +MODULE_PARM_DESC(LinkLedOnGpio,"Enable separate Link and Activity LEDs in LAN9500 and specifies gpio port for link status"); + +u32 LinkLedBufType=0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(LinkLedBufType, bool, 0); +#else + MODULE_PARM(LinkLedBufType,"bool"); +#endif +MODULE_PARM_DESC(LinkLedBufType,"Specifies gpio buffer type for link led"); + +u32 LinkLedPolarity = 0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(LinkLedPolarity, bool, 0); +#else + MODULE_PARM(LinkLedPolarity,"bool"); +#endif +MODULE_PARM_DESC(LinkLedPolarity,"Specifies active level on gpio port"); + +/* +linkdownsuspend = 0----> Disabled +linkdownsuspend = 1----> Enabled, wake up on auto-negotiation complete, device is in suspend0. +linkdownsuspend = 2----> Enabled, wake up on energy detection, device is in suspend1. +*/ +static int linkdownsuspend=2; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + module_param(linkdownsuspend, uint, 0); +#else + MODULE_PARM(linkdownsuspend,"i"); +#endif +MODULE_PARM_DESC(linkdownsuspend,"Suspend device when link is down"); + +static int dynamicsuspend=0; +module_param(dynamicsuspend,bool, 0); +MODULE_PARM_DESC(dynamicsuspend,"Enable dynamic autosuspend mode"); + +static int smartdetach=0; +module_param(smartdetach,bool, 0); +MODULE_PARM_DESC(smartdetach,"Enable smart detach mode, for LAN9500A only"); + +/********static function and variable declartion****************/ +static int smsc9500_reset(struct usbnet *dev); +static int smsc9500_get_stats(struct usbnet *dev, void *data); +static int smsc9500_private_ioctl(PADAPTER_DATA privateData, struct usbnet *dev, PSMSC9500_IOCTL_DATA ioctlData); +static int smsc9500_device_recovery(struct usbnet *dev); +static int SetGpo(struct usbnet * dev, u32 Gpo, u32 State); +static int Smsc9500SystemSuspend (struct usb_interface *intf, pm_message_t state); +static int Smsc9500SystemResume(struct usb_interface *intf); +static u16 CalculateCrc16(const BYTE * bpData,const u32 dwLen, const BOOLEAN fBitReverse); +static int SetLinkDownWakeupEvents(struct usbnet *dev, int wakeUpMode); +static int ResetLinkDownWakeupEvents(struct usbnet *dev); +static int Smsc9500AutoSuspend (struct usb_interface *intf, pm_message_t state); +static int Smsc9500AutoResume(struct usb_interface *intf); +static int EnablePHYWakeupInterrupt(struct usbnet *dev, u32 interrupt); +static int DisablePHYWakeupInterrupt(struct usbnet *dev, u32 interrupt); + +static u32 LanRegMap[MAX_LAN_REG_NUM]; +static u32 MacRegMap[MAX_MAC_REG_NUM]; +static u32 PhyRegMap[MAX_PHY_REG_NUM]; +/***************************************************************/ + +enum{ + SMSC9500_FAIL = -1, + SMSC9500_SUCCESS = 0 +}; + +/***************************************************************/ +static int smsc9500_read_reg(struct usbnet *dev, u32 index, u32 *data) +{ + int ret = 0; + u32 *buf = NULL; + u16 retry_count = 0; + + BUG_ON(!dev); + +//The heap buffer should be used for usb_control_msg, because the stack might not be DMA-mappable +//Control message is very slow so it really isn't big deal to dynamically allocate the data + buf = kmalloc (sizeof(u32), GFP_KERNEL); + if(buf == NULL){ + return SMSC9500_FAIL; + } + + do{ + ret=usb_control_msg( + dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_READ_REGISTER, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, + index, + (void*)buf, + sizeof(u32), + USB_CTRL_GET_TIMEOUT); + }while((ret < 0) && (retry_count++ < 3)); + + if (ret<0){ + SMSC_WARNING("Failed to read register index 0x%08x, set flag to recover", index); + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + }else{ + le32_to_cpus(buf); + *data = *buf; + } + + kfree(buf); + + return ret; +} + +static int smsc9500_write_reg(struct usbnet *dev, u32 index, u32 data) +{ + int ret = 0; + u32* buf = NULL; + u16 retry_count = 0; + + BUG_ON(!dev); + +//The heap buffer should be used for usb_control_msg, because the stack might not be DMA-mappable +//Control message is very slow so it really isn't big deal to dynamically allocate the data + buf = kmalloc (sizeof(u32), GFP_KERNEL); + if(buf == NULL){ + return SMSC9500_FAIL; + } + *buf = data; + + cpu_to_le32s(buf); + + do{ + ret=usb_control_msg( + dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_WRITE_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, + index, + buf, + sizeof(u32), + USB_CTRL_SET_TIMEOUT); + }while((ret < 0) && (retry_count++ < 3)); + + if (ret<0){ + SMSC_WARNING("Failed to write register index 0x%08x, set flag to recover", index); + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + } + + kfree(buf); + + return ret; +} + +static int smsc9500_set_feature(struct usbnet *dev, u32 feature) +{ + BUG_ON(!dev); + + cpu_to_le32s((u32*)&feature); + + return usb_control_msg( + dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_REQ_SET_FEATURE, + USB_RECIP_DEVICE, + feature, + 0, + NULL, + 0, + USB_CTRL_SET_TIMEOUT); + +} + +static int smsc9500_clear_feature(struct usbnet *dev, u32 feature) +{ + BUG_ON(!dev); + + cpu_to_le32s((u32*)&feature); + + return usb_control_msg( + dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + feature, + 0, + NULL, + 0, + USB_CTRL_SET_TIMEOUT); +} + + +static int smsc9500_read_phy(struct usbnet *dev, u32 Register, u32 *pValue32 ) + +{ + int ret = SMSC9500_FAIL; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + u32 dwValue,dwAddr; + int Count; + + BUG_ON(!dev); + + if(down_interruptible(&adapterData->phy_mutex)){ + return -EINTR; + } + // confirm MII not busy + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MII_ADDR, &dwValue)); + + if ((dwValue & MII_BUSY_) != 0UL) + { + SMSC_WARNING("MII is busy in smsc9500_read_phy\n"); + goto DONE; + } + + // set the address, index & direction (read from PHY) + dwAddr = ((adapterData->dwPhyAddress & 0x1FUL)<<11) | ((Register & 0x1FUL)<<6)|MII_READ_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MII_ADDR, dwAddr)); + + // Loop until the read is completed w/timeout + for(Count=1;Count<100;Count++) + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MII_ADDR, &dwValue)); + + if(!(dwValue & MII_BUSY_)) + break; + udelay(1); + + } + + if (Count < 100) + { + ret = smsc9500_read_reg(dev, MII_DATA, pValue32); + } + else + { + SMSC_WARNING ("Timed out reading MII register %08X\n",Register & 0x1f); + + } +DONE: + up(&adapterData->phy_mutex); + return ret; + +} /* smsc9500_read_phy */ + + + +static int smsc9500_write_phy(struct usbnet *dev, u32 Register, u32 pValue32) +{ + + int ret = SMSC9500_FAIL; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + u32 dwValue,dwAddr; + int Count; + + BUG_ON(!dev); + + if(down_interruptible(&adapterData->phy_mutex)){ + return -EINTR; + } + + if(Register==0) { + if(((LOWORD(pValue32))&0x1200)==0x1200) { + adapterData->wLastADVatRestart=adapterData->wLastADV; + } + } + if(Register==4) { + adapterData->wLastADV=LOWORD(pValue32); + } + + // confirm MII not busy + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MII_ADDR, &dwValue)); + + if ((dwValue & MII_BUSY_) != 0UL) + { + SMSC_WARNING ("MII is busy in smsc9500_read_phy\n"); + goto DONE; + } + + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MII_DATA, pValue32)); + + // set the address, index & direction (read from PHY) + dwAddr = ((adapterData->dwPhyAddress & 0x1FUL)<<11) | ((Register & 0x1FUL)<<6)|MII_WRITE_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MII_ADDR, dwAddr)); + + // Loop until the read is completed w/timeout + for(Count=1;Count<100;Count++) + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MII_ADDR, &dwValue)); + if(!(dwValue & MII_BUSY_)) + break; + udelay(1); + + } + + if (Count < 100) + { + ret=0; + + } + else + { + SMSC_WARNING("Timed out writing MII register %08X\n",Register & 0x1f); + + } +DONE: + up(&adapterData->phy_mutex); + return ret; + +} /* smsc9500_write_phy */ + +static int smsc9500_eeprom_IsBusy(struct usbnet *dev) +{ + int retVal = 0; + u32 dwValue; + int Count; + + BUG_ON(!dev); + + for(Count=0;Count<1000;Count++) //40ms + { + if(smsc9500_read_reg(dev, E2P_CMD, &dwValue) < 0){ + return SMSC9500_FAIL; + } + if (!(dwValue & E2P_CMD_BUSY_) || (dwValue & E2P_CMD_TIMEOUT_)) + { + break; + } + udelay(40); + } + if ((dwValue & E2P_CMD_TIMEOUT_) || (dwValue & E2P_CMD_BUSY_)){ + SMSC_WARNING("EEPROM read operation timeout"); + retVal = SMSC9500_FAIL; + } + + return retVal; +} + +/* Read EEPROM data + * pbValue: buffer pointer for data + * */ +static int smsc9500_read_eeprom(struct usbnet *dev, u32 dwOffset, u32 dwLength, BYTE* pbValue) +{ + + int ret = SMSC9500_FAIL; + u32 dwValue,dwAddr; + int Count, i; + PADAPTER_DATA adapterData; + + BUG_ON(!dev); + BUG_ON(!pbValue); + + adapterData=(PADAPTER_DATA)(dev->data[0]); + + if(dwOffset + dwLength > adapterData->eepromSize){ + SMSC_WARNING("EEPROM: out of eeprom space range, offset = %d, dwLength = %d", dwOffset, dwLength); + } + + if(down_interruptible(&adapterData->eeprom_mutex)){ + return -EINTR; + } + + // confirm eeprom not busy + for(Count=0;Count<100;Count++) + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, E2P_CMD, &dwValue)); + if (!(dwValue & E2P_CMD_BUSY_) || !(dwValue & E2P_CMD_LOADED_)) + { + break; + } + udelay(40); + } + if (!(dwValue & E2P_CMD_LOADED_)) + { + SMSC_WARNING("No EEPROM present"); + goto DONE; + } + if ((dwValue & E2P_CMD_BUSY_) != 0UL) + { + SMSC_WARNING("EEPROM is busy "); + goto DONE; + } + dwAddr = dwOffset; + for(i=0; ieeprom_mutex); + return ret; + +} /* smsc9500_read_eeprom */ + +static int smsc9500_write_eeprom(struct usbnet *dev, u32 dwOffset, u32 dwLength, BYTE* pbValue) +{ + int ret = SMSC9500_FAIL; + u32 dwValue,dwAddr; + int Count, i; + PADAPTER_DATA adapterData; + + BUG_ON(!dev); + BUG_ON(!pbValue); + + adapterData=(PADAPTER_DATA)(dev->data[0]); + + if(dwOffset + dwLength > adapterData->eepromSize){ + SMSC_WARNING("EEPROM: out of eeprom space range"); + } + + if(down_interruptible(&adapterData->eeprom_mutex)){ + return -EINTR; + } + + // confirm eeprom not busy + for(Count=0;Count<100;Count++) + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, E2P_CMD, &dwValue)); + + if (!(dwValue & E2P_CMD_BUSY_)) + { + break; + } + udelay(40); + } + + if ((dwValue & E2P_CMD_BUSY_) != 0UL) + { + SMSC_WARNING("EEPROM is busy "); + goto DONE; + } + + //Iuuse write/erase enable command + dwValue = E2P_CMD_BUSY_ | E2P_CMD_EWEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, E2P_CMD, dwValue)); + CHECK_RETURN_STATUS(smsc9500_eeprom_IsBusy(dev)); + + dwAddr = dwOffset; + for(i=0; ieeprom_mutex); + return ret; + +} /* smsc9500_write_eeprom */ + +static int IsDataPortReady(struct usbnet *dev){ + int ret = FALSE; + int count = 0; + u32 dwValue; + +// confirm data port is not busy + for(count=0; count<100; count++) + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, DP_SEL, &dwValue)); + if (dwValue & DP_SEL_DPRDY){ + ret = TRUE; + break; + } + udelay(40); + } + + if (ret == FALSE) + { + SMSC_WARNING("Data port is busy "); + } +DONE: + return ret; +} + +/* Read data from internal RAM + * ramSel: Choose which internal RAM to access. + * startAddr: The first offset to access. + * length: Data length in DWORD. + * valLow: Low 32 bits buffer pointer. + * valHigh: High 5 bits buffer pointer. If null, will ignore. + * */ +static int ReadDataPort(struct usbnet *dev, int ramSel, u32 startAddr, u32 length, u32 *valLow, u32 *valHigh) +{ + u32 dwValue; + int ret = SMSC9500_FAIL; + int i; + PADAPTER_DATA adapterData; + + BUG_ON(!dev); + adapterData = (PADAPTER_DATA)(dev->data[0]); + BUG_ON(!adapterData); + + if(down_interruptible(&adapterData->internal_ram_mutex)){ + return -EINTR; + } + + // confirm data port not busy + if(!IsDataPortReady(dev))goto DONE; + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, DP_SEL, &dwValue)); + dwValue &= ~DP_SEL_RSEL; + switch(ramSel){ + case RAMSEL_FCT: dwValue |= DP_SEL_RSEL_FCT; break; + case RAMSEL_EEPROM: dwValue |= DP_SEL_RSEL_EEPROM; break; + case RAMSEL_TXTLI: dwValue |= DP_SEL_RSEL_TXTLI; break; + case RAMSEL_RXTLI: dwValue |= DP_SEL_RSEL_RXTLI; break; + } + dwValue |= DP_SEL_TESTEN; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, DP_SEL, dwValue)); + + for(i=0; iinternal_ram_mutex); + + return ret; +} + +static void smsc9500_status(struct usbnet *dev, struct urb *urb) +{ + struct smsc9500_int_data *event; + int hasFrame; + + BUG_ON(!dev); + BUG_ON(!urb); + + SMSC_TRACE(DBG_INTR,"---->in smsc9500_status\n"); + + if (urb->actual_length < 4) { + SMSC_WARNING("urb->actual_length= %d",urb->actual_length); + return; + } + + event = urb->transfer_buffer; + + le32_to_cpus((u32*)&event->IntEndPoint); + + SMSC_TRACE(DBG_INTR, "event->IntEndPoint= 0x%08x\n", event->IntEndPoint); + hasFrame = event->IntEndPoint &INT_END_RXFIFO_HAS_FRAME_; + + if (hasFrame) { + dev->StopSummitUrb=0; + tasklet_schedule (&dev->bh); + } + + SMSC_TRACE(DBG_INTR,"<----out of smsc9500_status\n"); +} + + +static int smsc9500_get_stats(struct usbnet *dev, void *data) +{ + int ret = 0; + void* buf; + u16 retry_count = 0; + + BUG_ON(!dev); + BUG_ON(!data); + + SMSC_TRACE(DBG_RX, "in smsc9500_get_stats\n"); + + buf = kmalloc (sizeof(SMSC9500_RX_STATS), GFP_KERNEL); + if(buf == NULL){ + return SMSC9500_FAIL; + } + + do{ + ret=usb_control_msg( + dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_GET_STATS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, + 0, + buf, + sizeof(SMSC9500_RX_STATS), + USB_CTRL_SET_TIMEOUT); + }while((ret < 0) && (retry_count++ < 3)); + + if (ret < 0){ + SMSC_WARNING("Failed to get status, set flag to recover"); + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + }else{ + memcpy(data, buf, sizeof(SMSC9500_RX_STATS)); + } + + kfree(buf); + + return ret; +} + +#if 0 /* commenting as it was causing panic on harmony platform */ + +static void smsc9500_async_cmd_callback(struct urb *urb, struct pt_regs *regs) +{ + struct USB_CONTEXT * usb_context = (struct USB_CONTEXT *)urb->context; + + if (urb->status < 0) + SMSC_WARNING("smsc9500_async_cmd_callback() failed with %d\n",urb->status); + + complete((struct completion *)&usb_context->notify); + + kfree(&usb_context->req); + usb_free_urb(urb); +} + +static int +smsc9500_read_reg_async(struct usbnet *dev, u32 index, void *data, int wait) +{ + int ret = ASYNC_RW_SUCCESS, expire; + struct USB_CONTEXT * usb_context; + int status; + struct urb *urb; + u32 size=4; + + BUG_ON(!dev); + BUG_ON(!data); + + if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) { + SMSC_WARNING("Error allocating URB in write_cmd_async!"); + return ASYNC_RW_FAIL; + } + + if ((usb_context = kmalloc(sizeof(struct USB_CONTEXT), GFP_ATOMIC)) == NULL) { + SMSC_WARNING( "Failed to allocate memory for control request"); + usb_free_urb(urb); + return ASYNC_RW_FAIL; + } + + usb_context->req.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + usb_context->req.bRequest = USB_VENDOR_REQUEST_READ_REGISTER; + usb_context->req.wValue = 00; + usb_context->req.wIndex = cpu_to_le32(index); + usb_context->req.wLength = cpu_to_le32(size); + init_completion(&usb_context->notify); + + usb_fill_control_urb(urb, dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + (void *)&usb_context->req, data, size, + (usb_complete_t)smsc9500_async_cmd_callback, (void*)usb_context); + + if((status = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + SMSC_WARNING( "Error submitting the control message: status=%d", status); + kfree(usb_context); + usb_free_urb(urb); + } + + if(wait){ +//wait_for_completion_timeout only implemented in 2.6.11 and higher kernel version +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)) + expire = msecs_to_jiffies(USB_CTRL_SET_TIMEOUT); + if (!wait_for_completion_timeout(&usb_context->notify, expire)) { + + ret = ASYNC_RW_TIMEOUT; + SMSC_TRACE(DBG_WARNING,"urb timeout \n"); + kfree(usb_context); + usb_free_urb(urb); + } +#endif + } + + return ret; + +} +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) +static void CalculateTxChecksumOffset( + struct sk_buff *skb, + int *csum_start_offset + ) +{ + unsigned int skbFragCnt; + int i; + u32 offset; + + skbFragCnt = skb_shinfo(skb)->nr_frags + 1; + + // Initialize csum offset locations as if it was single frag. + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) + SMSC_ASSERT(skb->h.raw); + #else + SMSC_ASSERT(skb->transport_header); // Should never happen for a CHECKSUM_HW packet. + #endif + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) + *csum_start_offset = skb->h.raw - skb->data; + #else + *csum_start_offset = (unsigned long)skb->transport_header - (unsigned long)skb->data; + #endif + + offset = (skbFragCnt == 1) ? skb->len : (skb->len - skb->data_len); + + // Process all fragments + for(i=0;i<(skbFragCnt-1);i++) + { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + unsigned char *frag_addr = (unsigned char *) (page_address(frag->page) + frag->page_offset); + + // Find if transport header start belongs to this fragment and if so calculate offset from start of packet. + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) + if((frag_addr <= skb->h.raw) && ((frag_addr + frag->size) >=skb->h.raw)) + { + *csum_start_offset = offset + ((u32)skb->h.raw) - ((u32)frag_addr); + } + #else + if((frag_addr <= (unsigned char *)((unsigned long)skb->transport_header)) && + ((frag_addr + frag->size) >= (unsigned char *)((unsigned long)skb->transport_header))) + { + *csum_start_offset = offset + ((unsigned long)skb->transport_header) - ((unsigned long)frag_addr); + } + #endif + + SMSC_ASSERT((offset + frag->size) <= skb->len); + + offset += frag->size; + } + +} +#endif + +static void Tx_StopQueue( + struct usbnet *dev,u32 dwSource) +{ + unsigned long intFlags=0; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + spin_lock_irqsave(&(adapterData->TxQueueLock),intFlags); + if(adapterData->dwTxQueueDisableMask==0) { + netif_stop_queue(dev->net); + } + adapterData->dwTxQueueDisableMask|=dwSource; + spin_unlock_irqrestore(&(adapterData->TxQueueLock),intFlags); +} + +static void Tx_WakeQueue( + struct usbnet *dev,u32 dwSource) +{ + unsigned long intFlags=0; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + spin_lock_irqsave(&(adapterData->TxQueueLock),intFlags); + adapterData->dwTxQueueDisableMask&=(~dwSource); + if(adapterData->dwTxQueueDisableMask==0) { + netif_wake_queue(dev->net); + } + spin_unlock_irqrestore(&(adapterData->TxQueueLock),intFlags); +} + + +//returns hash bit number for given MAC address +//example: +// 01 00 5E 00 00 01 -> returns bit number 31 +static u32 Rx_Hash(BYTE addr[6]) +{ + int i; + u32 crc=0xFFFFFFFFUL; + u32 poly=0xEDB88320UL; + u32 result=0; + for(i=0;i<6;i++) + { + int bit; + u32 data=((u32)addr[i]); + for(bit=0;bit<8;bit++) + { + u32 p = (crc^((u32)data))&1UL; + crc >>= 1; + if(p!=0) crc ^= poly; + data >>=1; + } + } + result=((crc&0x01UL)<<5)| + ((crc&0x02UL)<<3)| + ((crc&0x04UL)<<1)| + ((crc&0x08UL)>>1)| + ((crc&0x10UL)>>3)| + ((crc&0x20UL)>>5); + return result; +} + +static int smsc9500_rx_setmulticastlist(struct usbnet *dev) +{ + + u32 local_MACCR, dwHashHi,dwHashLo; + u32 ret = SMSC9500_FAIL; + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + if (dev->suspendFlag) { + return 0; + } + if(down_interruptible(&adapterData->RxFilterLock)){ + return -EINTR; + } + + SMSC_TRACE(DBG_MCAST, "---------->in smsc9500_set_multicast\n"); + + if(dev->net->flags & IFF_PROMISC) { + SMSC_TRACE(DBG_MCAST,"Promiscuous Mode Enabled"); + adapterData->set_bits_mask = MAC_CR_PRMS_; + adapterData->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + + adapterData->HashHi = 0UL; + adapterData->HashLo = 0UL; + goto PREPARE; + } + + if(dev->net->flags & IFF_ALLMULTI) { + SMSC_TRACE(DBG_MCAST, "Receive all Multicast Enabled"); + adapterData->set_bits_mask = MAC_CR_MCPAS_; + adapterData->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_); + + adapterData->HashHi = 0UL; + adapterData->HashLo = 0UL; + goto PREPARE; + } + + + if(dev->net->mc_count>0) { + u32 dwHashH=0; + u32 dwHashL=0; + u32 dwCount=0; + struct dev_mc_list *mc_list=dev->net->mc_list; + + adapterData->set_bits_mask = MAC_CR_HPFILT_; + adapterData->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + while(mc_list!=NULL) { + dwCount++; + if((mc_list->dmi_addrlen)==6) { + u32 dwMask=0x01UL; + u32 dwBitNum=Rx_Hash(mc_list->dmi_addr); + + dwMask<<=(dwBitNum&0x1FUL); + if(dwBitNum&0x20UL) { + dwHashH|=dwMask; + } else { + dwHashL|=dwMask; + } + } else { + SMSC_WARNING("dmi_addrlen!=6"); + } + mc_list=mc_list->next; + } + if(dwCount!=((u32)(dev->net->mc_count))) { + SMSC_WARNING("dwCount!=dev->net->mc_count"); + } + SMSC_TRACE(DBG_MCAST, "Multicast: HASHH=0x%08X,HASHL=0x%08X",dwHashH,dwHashL); + adapterData->HashHi = dwHashH; + adapterData->HashLo = dwHashL; + } + else + { + adapterData->set_bits_mask = 0L; + adapterData->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + + SMSC_TRACE(DBG_MCAST, "Receive own packets only."); + adapterData->HashHi = 0UL; + adapterData->HashLo = 0UL; + } + + +PREPARE: + up(&adapterData->RxFilterLock); + + dwHashHi=adapterData->HashHi; + dwHashLo=adapterData->HashLo; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,HASHH,dwHashHi)); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,HASHL,dwHashLo)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,MAC_CR,&local_MACCR)); + + local_MACCR |= adapterData->set_bits_mask; + local_MACCR &= ~(adapterData->clear_bits_mask); + + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,MAC_CR,local_MACCR)); + + + SMSC_TRACE(DBG_MCAST, "<---------out of smsc9500_set_multicast"); + ret = 0; +DONE: + return ret; +} + +static void smsc9500_set_multicast(struct net_device *netdev) +{ + struct usbnet *dev=netdev_priv(netdev); + smscusbnet_defer_myevent(dev, EVENT_SET_MULTICAST); + +} + +static int Phy_GetLinkMode(struct usbnet *dev) +{ + u32 dwTemp, dwValue, result=LINK_OFF; + u16 wRegBcr=0; + u16 wRegBSR,wRegLPA; + int ret = SMSC9500_FAIL; + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + SMSC_TRACE(DBG_LINK, "---------->in Phy_GetLinkMode"); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_BSR,&dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_BSR,&dwTemp)); + + wRegBSR=LOWORD(dwTemp); + + adapterData->dwLinkSettings=LINK_OFF; + + if(wRegBSR&PHY_BSR_LINK_STATUS_) { + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_BCR,&dwValue)); + wRegBcr=LOWORD(dwValue); + + if(wRegBcr & PHY_BCR_AUTO_NEG_ENABLE_) { + u32 linkSettings=LINK_AUTO_NEGOTIATE; + u16 wRegADV=adapterData->wLastADVatRestart; + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_ANEG_LPA,&dwTemp)); + wRegLPA=LOWORD(dwTemp); + + if(wRegADV & PHY_ANEG_ADV_ASYMP_) { + linkSettings |= LINK_ASYMMETRIC_PAUSE; + } + if(wRegADV & PHY_ANEG_ADV_SYMP_) { + linkSettings |= LINK_SYMMETRIC_PAUSE; + } + if(wRegADV & PHY_ANEG_LPA_100FDX_) { + linkSettings |= LINK_SPEED_100FD; + } + if(wRegADV & PHY_ANEG_LPA_100HDX_) { + linkSettings |= LINK_SPEED_100HD; + } + if(wRegADV & PHY_ANEG_LPA_10FDX_) { + linkSettings |= LINK_SPEED_10FD; + } + if(wRegADV & PHY_ANEG_LPA_10HDX_) { + linkSettings |= LINK_SPEED_10HD; + } + adapterData->dwLinkSettings=linkSettings; + + wRegLPA &= wRegADV; + if(wRegLPA & PHY_ANEG_LPA_100FDX_) { + result = LINK_SPEED_100FD; + } else if(wRegLPA & PHY_ANEG_LPA_100HDX_) { + result = LINK_SPEED_100HD; + } else if(wRegLPA & PHY_ANEG_LPA_10FDX_) { + result = LINK_SPEED_10FD; + } else if(wRegLPA & PHY_ANEG_LPA_10HDX_) { + result = LINK_SPEED_10HD; + } + } else { + if(wRegBcr & PHY_BCR_SPEED_SELECT_) { + if(wRegBcr & PHY_BCR_DUPLEX_MODE_) { + adapterData->dwLinkSettings=result=LINK_SPEED_100FD; + } else { + adapterData->dwLinkSettings=result=LINK_SPEED_100HD; + } + } else { + if(wRegBcr & PHY_BCR_DUPLEX_MODE_) { + adapterData->dwLinkSettings=result=LINK_SPEED_10FD; + } else { + adapterData->dwLinkSettings=result=LINK_SPEED_10HD; + } + } + } + } + adapterData->dwLinkSpeed=result; + SMSC_TRACE(DBG_LINK,"<----------out of Phy_GetLinkMode"); + + ret = 0; +DONE: + return ret; +} + +static int Phy_UpdateLinkMode(struct usbnet *dev) +{ + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + int ret = SMSC9500_FAIL; + u32 dwOldLinkSpeed=adapterData->dwLinkSpeed; + u32 dwTemp,dwValue; + + SMSC_TRACE(DBG_LINK,"---------->in Phy_UpdateLinkMode"); + + Phy_GetLinkMode(dev); + + if(dwOldLinkSpeed!=(adapterData->dwLinkSpeed)) { + if(adapterData->dwLinkSpeed!=LINK_OFF) { + u32 dwRegVal=0; + switch(adapterData->dwLinkSpeed) { + case LINK_SPEED_10HD: + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now UP at 10Mbps HD"); + break; + case LINK_SPEED_10FD: + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now UP at 10Mbps FD"); + break; + case LINK_SPEED_100HD: + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now UP at 100Mbps HD"); + break; + case LINK_SPEED_100FD: + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now UP at 100Mbps FD"); + break; + default: + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now UP at Unknown Link Speed, dwLinkSpeed=0x%08X", + adapterData->dwLinkSpeed); + break; + } + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR,&dwRegVal)); + dwRegVal&=~(MAC_CR_FDPX_|MAC_CR_RCVOWN_); + switch(adapterData->dwLinkSpeed) { + case LINK_SPEED_10HD: + case LINK_SPEED_100HD: + dwRegVal|=MAC_CR_RCVOWN_; + break; + case LINK_SPEED_10FD: + case LINK_SPEED_100FD: + dwRegVal|=MAC_CR_FDPX_; + break; + default:break; + } + + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, dwRegVal)); + + if(adapterData->dwLinkSettings&LINK_AUTO_NEGOTIATE) { + u16 linkPartner=0; + u16 localLink=0; + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_ANEG_ADV, &dwTemp)); + localLink=LOWORD(dwTemp); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_ANEG_LPA, &dwTemp)); + linkPartner=LOWORD(dwTemp); + switch(adapterData->dwLinkSpeed) { + case LINK_SPEED_10FD: + case LINK_SPEED_100FD: + if(((localLink&linkPartner)&((u16)PHY_ANEG_ADV_SYMP_)) != ((u16)0U)) { + //Enable PAUSE receive and transmit + dwTemp=0xFFFF0002UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,FLOW, dwTemp)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,AFC_CFG,&dwValue)); + dwValue=dwValue|0x0000000FUL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG,dwValue)); + + } else if(((localLink&((u16)0x0C00U))==((u16)0x0C00U)) && + ((linkPartner&((u16)0x0C00U))==((u16)0x0800U))) + { + //Enable PAUSE receive, disable PAUSE transmit + dwTemp=0xFFFF0002UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue&(~0x0000000FUL); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + + } else { + //Disable PAUSE receive and transmit + dwTemp=0x0UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue&(~0x0000000FUL); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + + };break; + case LINK_SPEED_10HD: + case LINK_SPEED_100HD: + + dwTemp=0x0UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue|0x0000000FUL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + + break; + default:break; + } + SMSC_TRACE(DBG_LINK_CHANGE,"LAN9500: %s,%s,%s,%s,%s,%s", + (localLink&PHY_ANEG_ADV_ASYMP_)?"ASYMP":" ", + (localLink&PHY_ANEG_ADV_SYMP_)?"SYMP ":" ", + (localLink&PHY_ANEG_ADV_100F_)?"100FD":" ", + (localLink&PHY_ANEG_ADV_100H_)?"100HD":" ", + (localLink&PHY_ANEG_ADV_10F_)?"10FD ":" ", + (localLink&PHY_ANEG_ADV_10H_)?"10HD ":" "); + + SMSC_TRACE(DBG_LINK_CHANGE,"Partner: %s,%s,%s,%s,%s,%s", + (linkPartner&PHY_ANEG_LPA_ASYMP_)?"ASYMP":" ", + (linkPartner&PHY_ANEG_LPA_SYMP_)?"SYMP ":" ", + (linkPartner&PHY_ANEG_LPA_100FDX_)?"100FD":" ", + (linkPartner&PHY_ANEG_LPA_100HDX_)?"100HD":" ", + (linkPartner&PHY_ANEG_LPA_10FDX_)?"10FD ":" ", + (linkPartner&PHY_ANEG_LPA_10HDX_)?"10HD ":" "); + } else { + switch(adapterData->dwLinkSpeed) { + case LINK_SPEED_10HD: + case LINK_SPEED_100HD: + + dwTemp=0x0UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue|0x0000000FUL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + break; + default: + + dwTemp=0x0UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue&(~0x0000000FUL); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + break; + } + } + netif_carrier_on(dev->net); + Tx_WakeQueue(dev,0x01); + SetGpo(dev, adapterData->LinkLedOnGpio, !adapterData->LinkLedOnGpioPolarity); + + } else { + SMSC_TRACE(DBG_LINK_CHANGE,"Link is now DOWN"); + Tx_StopQueue(dev,0x01); + netif_carrier_off(dev->net); + SetGpo(dev, adapterData->LinkLedOnGpio, adapterData->LinkLedOnGpioPolarity); + + dwTemp=0x0UL; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, FLOW, dwTemp)); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, AFC_CFG,&dwValue)); + dwValue=dwValue& (~0x0000000FUL); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, AFC_CFG, dwValue)); + } + } + SMSC_TRACE(DBG_LINK,"<----------out of Phy_UpdateLinkMode"); + + ret = 0; +DONE: + return ret; +} + +static int Phy_CheckLink(void * ptr) +{ + struct usbnet *dev = ptr; + SMSC9500_RX_STATS rx_stats; + u32 dwValue, droppedFrame = 0; + int ret = SMSC9500_FAIL; + + BUG_ON(!dev); + SMSC_TRACE(DBG_LINK,"-------->in Phy_CheckLink"); + + if(Phy_UpdateLinkMode(dev) < 0)return ret; + + if(dev->suspendFlag & AUTOSUSPEND_DETACH){ + if(!netif_carrier_ok(dev->net)){//Link is down, detach device + //Set wakeup event + SetLinkDownWakeupEvents(dev, WAKEPHY_ENERGY); + + //Enable smart detach + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, HW_CFG,&dwValue)); + dwValue |= HW_CFG_SMDET_EN; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, HW_CFG, dwValue)); + dev->suspendFlag &= ~AUTOSUSPEND_DETACH; + ret = SMSC9500_SUCCESS; + goto DONE; + } + } + + if(smsc9500_get_stats(dev, (void*)&rx_stats) > 0){ + le32_to_cpus((u32*)&rx_stats.RxFifoDroppedFrames); + rx_stats.RxFifoDroppedFrames &= 0xFFFFF; //This counter has 20 bits. + if(dev->chipDependFeatures[FEATURE_NEWSTATIS_CNT]){//Statistics counters are rollover ones in LAN9500A + if(rx_stats.RxFifoDroppedFrames >= dev->preRxFifoDroppedFrame){ + droppedFrame = rx_stats.RxFifoDroppedFrames - dev->preRxFifoDroppedFrame; + }else{//Rollover + droppedFrame = 0x100000 - dev->preRxFifoDroppedFrame + rx_stats.RxFifoDroppedFrames; + } + dev->preRxFifoDroppedFrame = rx_stats.RxFifoDroppedFrames; + }else{ + droppedFrame = rx_stats.RxFifoDroppedFrames; + } + dev->stats.rx_dropped += droppedFrame; + } + + if( (!(dev->StopLinkPolling)) && (!timer_pending(&dev->LinkPollingTimer))) { + dev->LinkPollingTimer.expires=jiffies+HZ; + add_timer(&(dev->LinkPollingTimer)); + } + SMSC_TRACE(DBG_LINK,"<---------out of Phy_CheckLink"); + ret = SMSC9500_SUCCESS; +DONE: + return ret; +} + + +static int phy_SetLink(struct usbnet *dev, u32 dwLinkRequest) +{ + u32 dwValue; + u16 wTemp=0; + int ret = SMSC9500_FAIL; + + SMSC_TRACE(DBG_LINK,"--------->in phy_SetLink"); + + if(dwLinkRequest&LINK_AUTO_NEGOTIATE) { + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_ANEG_ADV,&dwValue)); + wTemp=LOWORD(dwValue); + wTemp&=~PHY_ANEG_ADV_PAUSE_; + if(dwLinkRequest&LINK_ASYMMETRIC_PAUSE) { + wTemp|=PHY_ANEG_ADV_ASYMP_; + } + if(dwLinkRequest&LINK_SYMMETRIC_PAUSE) { + wTemp|=PHY_ANEG_ADV_SYMP_; + } + wTemp&=~PHY_ANEG_ADV_SPEED_; + if(dwLinkRequest&LINK_SPEED_10HD) { + wTemp|=PHY_ANEG_ADV_10H_; + } + if(dwLinkRequest&LINK_SPEED_10FD) { + wTemp|=PHY_ANEG_ADV_10F_; + } + + if((dwLinkRequest&LINK_SPEED_100HD) && (dev->udev->speed == USB_SPEED_HIGH)) { + wTemp|=PHY_ANEG_ADV_100H_; + } + if((dwLinkRequest&LINK_SPEED_100FD) && (dev->udev->speed == USB_SPEED_HIGH)) { + wTemp|=PHY_ANEG_ADV_100F_; + } + + dwValue=(u32)(wTemp) &0x0000FFFFUL; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_ANEG_ADV, dwValue)); + + // begin to establish link + wTemp=PHY_BCR_AUTO_NEG_ENABLE_|PHY_BCR_RESTART_AUTO_NEG_; + dwValue=(u32)(wTemp) &0x0000FFFFUL; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_BCR, dwValue)); + } else { + + if(dwLinkRequest&(LINK_SPEED_100FD)) { + dwLinkRequest=LINK_SPEED_100FD; + } else if(dwLinkRequest&(LINK_SPEED_100HD)) { + dwLinkRequest=LINK_SPEED_100HD; + } else if(dwLinkRequest&(LINK_SPEED_10FD)) { + dwLinkRequest=LINK_SPEED_10FD; + } else if(dwLinkRequest&(LINK_SPEED_10HD)) { + dwLinkRequest=LINK_SPEED_10HD; + } + if(dwLinkRequest&(LINK_SPEED_10FD|LINK_SPEED_100FD)) { + wTemp|=PHY_BCR_DUPLEX_MODE_; + } + if(dwLinkRequest&(LINK_SPEED_100HD|LINK_SPEED_100FD)) { + wTemp|=PHY_BCR_SPEED_SELECT_; + } + dwValue=(u32)(wTemp) &0x0000FFFFUL; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_BCR, dwValue)); + } + SMSC_TRACE(DBG_LINK,"<---------out of phy_SetLink"); + ret = 0; +DONE: + return ret; +} + +static int Phy_SetAutoMdix( + struct usbnet *dev, + u16 wAutoMdix + ) +{ + u32 SpecialCtrlSts=0U; + int ret = SMSC9500_FAIL; + + if (wAutoMdix > 2) + { + SMSC_WARNING("LAN9500 Auto MDIX feature controlled by hardware strap\n"); + } + else + { + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_SPECIAL_CTRL_STS, &SpecialCtrlSts)); + + SpecialCtrlSts = (((wAutoMdix+4) << 13) | (SpecialCtrlSts&0x1FFF)); + CHECK_RETURN_STATUS(smsc9500_write_phy(dev, PHY_SPECIAL_CTRL_STS, SpecialCtrlSts)); + + if (wAutoMdix & AMDIX_ENABLE) + { + SMSC_WARNING("LAN9500 Auto MDIX hardware strap was overiden by driver. AutoMdix is enabled"); + } + else if (wAutoMdix & AMDIX_DISABLE_CROSSOVER) + { + SMSC_WARNING("LAN9500 Auto MDIX hardware strap was overiden by driver. AutoMdix is disabled, use crossover cable."); + } + else + { + SMSC_WARNING("LAN9500 Auto MDIX hardware strap was overiden by driver. AutoMdix is disabled, use straight cable."); + } + } + + ret = 0; +DONE: + return ret; +} + +static BOOLEAN Phy_Initialize( + struct usbnet *dev, + u32 dwPhyAddr, + u32 dwLinkRequest) +{ + BOOLEAN result=FALSE, bConfigureAutoMdix = FALSE; + u32 dwTemp=0,dwValue, address; + u32 dwLoopCount=0; + u32 phy_id_1, phy_id_2; + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + SMSC_TRACE(DBG_INIT,"-->Phy_Initialize"); + SMSC_ASSERT(dwLinkRequest<=0x7FUL); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, HW_CFG, &dwValue)); + + if(dwValue & HW_CFG_PSEL_){ + SMSC_TRACE(DBG_INIT,"using external PHY "); + + if(dwPhyAddr <= 31){ + // Using external PHY board on MII connector + // First isolate all PHYs we can find... + + for (address=0;address<=31;address++) + { + adapterData->dwPhyAddress = address; + dwValue=(u32)PHY_BCR_ISOLATE; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev, PHY_BCR, dwValue)); + } + + // Now put the PHY at the registry parsed address out of isolation. + adapterData->dwPhyAddress = dwPhyAddr; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev, PHY_BCR, 0x0UL)); + }else{//Auto detect PHY + + phy_id_1 = 0xFFFFU; + phy_id_2 = 0xFFFFU; + for (address=0; address<=31; address++) + { + adapterData->dwPhyAddress = address; + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_ID_1, &phy_id_1)); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_ID_2, &phy_id_2)); + if((phy_id_1 != 0x7FFFU) && (phy_id_1 != 0xFFFFU) && (phy_id_1 != 0x0000U)){ + SMSC_TRACE(DBG_INIT,"Deteced Phy at address = 0x%02X", address); + break; + } + } + } + SMSC_TRACE(DBG_INIT,"using external PHY "); + + } else { +//USE_INTERNAL_PHY + SMSC_TRACE(DBG_INIT,"using internal PHY "); + adapterData->dwPhyAddress=1; + + bConfigureAutoMdix = TRUE; + } + + { + u32 dwPhyBcr; + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_ID_2,&dwTemp)); + adapterData->bPhyRev=((BYTE)(dwTemp&(0x0FUL))); + adapterData->bPhyModel=((BYTE)((dwTemp>>4)&(0x3FUL))); + adapterData->dwPhyId=((dwTemp&(0xFC00UL))<<8); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_ID_1,&dwTemp)); + adapterData->dwPhyId|=((dwTemp&(0x0000FFFFUL))<<2); + + + SMSC_TRACE(DBG_INIT,"dwPhyId==0x%08X,bPhyModel==0x%02X,bPhyRev==0x%02X", + adapterData->dwPhyId, + adapterData->bPhyModel, + adapterData->bPhyRev); + + adapterData->dwLinkSpeed = LINK_INIT; + adapterData->dwLinkSettings=LINK_INIT; + //reset the PHY + dwPhyBcr=(u32)PHY_BCR_RESET_ ; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_BCR, dwPhyBcr)); + + dwLoopCount = 20; + do { + + mdelay(100); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_BCR,&dwPhyBcr)); + + dwLoopCount--; + } while((dwLoopCount>0) && ((u16)dwPhyBcr&PHY_BCR_RESET_)); + + + if((u16)dwPhyBcr&PHY_BCR_RESET_) { + SMSC_WARNING("PHY reset failed to complete."); + goto DONE; + } + else + SMSC_TRACE(DBG_INIT,"PHY reset!!!"); + } + + if (bConfigureAutoMdix) + { + Phy_SetAutoMdix(dev, (u16)auto_mdix); + } + phy_SetLink(dev,dwLinkRequest); + + result=TRUE; +DONE: + SMSC_TRACE(DBG_INIT,"<--Phy_Initialize, result=%s\n",result?"TRUE":"FALSE"); + return result; +} + + + +static int smsc9500_get_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + cmd->supported= + SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_MII; + cmd->advertising=ADVERTISED_MII; + + if(adapterData->dwLinkSettings & LINK_SPEED_10HD) + cmd->advertising|=ADVERTISED_10baseT_Half; + if(adapterData->dwLinkSettings & LINK_SPEED_10FD) + cmd->advertising|=ADVERTISED_10baseT_Full; + if(adapterData->dwLinkSettings & LINK_SPEED_100HD) + cmd->advertising|=ADVERTISED_100baseT_Half; + if(adapterData->dwLinkSettings & LINK_SPEED_100FD) + cmd->advertising|=ADVERTISED_100baseT_Full; + if(adapterData->dwLinkSettings & LINK_AUTO_NEGOTIATE) { + cmd->advertising|=ADVERTISED_Autoneg; + cmd->autoneg=AUTONEG_ENABLE; + } else cmd->autoneg=AUTONEG_DISABLE; + if(adapterData->dwLinkSpeed & (LINK_SPEED_100HD|LINK_SPEED_100FD)) + cmd->speed=SPEED_100; + else cmd->speed=SPEED_10; + if(adapterData->dwLinkSpeed & (LINK_SPEED_10FD|LINK_SPEED_100FD)) + cmd->duplex=DUPLEX_FULL; + else cmd->duplex=DUPLEX_HALF; + cmd->port=PORT_MII; + cmd->phy_address=(u8)adapterData->dwPhyAddress; + cmd->transceiver=XCVR_INTERNAL; + cmd->maxtxpkt=0; + cmd->maxrxpkt=0; + + + return 0; + +} + + +static int smsc9500_set_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + int result=-EFAULT; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + + u16 speed=0; + u8 duplex=0; + u8 autoneg=0; + + if(adapterData->dwLinkSettings&LINK_AUTO_NEGOTIATE) { + autoneg=AUTONEG_ENABLE; + } else { + autoneg=AUTONEG_DISABLE; + } + if(adapterData->dwLinkSpeed&(LINK_SPEED_100HD|LINK_SPEED_100FD)) + { + speed=SPEED_100; + } else { + speed=SPEED_10; + } + if(adapterData->dwLinkSpeed&(LINK_SPEED_10FD|LINK_SPEED_100FD)) + { + duplex=DUPLEX_FULL; + } else { + duplex=DUPLEX_HALF; + } + if((cmd->speed!=100)&&(cmd->speed!=10)) { + result=-EOPNOTSUPP; + goto DONE; + } + if((cmd->duplex!=DUPLEX_FULL)&&(cmd->duplex!=DUPLEX_HALF)) { + result=-EOPNOTSUPP; + goto DONE; + } + if((cmd->autoneg!=AUTONEG_ENABLE)&&(cmd->autoneg!=AUTONEG_DISABLE)) { + result=-EOPNOTSUPP; + goto DONE; + } + if((cmd->autoneg!=autoneg)|| + (cmd->speed!=speed)|| + (cmd->duplex!=duplex)) + { + if(cmd->autoneg==AUTONEG_ENABLE) { + u32 dwBcrValue; + + dwBcrValue=PHY_BCR_AUTO_NEG_ENABLE_|PHY_BCR_RESTART_AUTO_NEG_; + if(smsc9500_write_phy(dev,PHY_BCR, dwBcrValue) < 0)goto DONE; + + } else { + u32 dwBcrValue; + u16 wBcr; + if(smsc9500_read_phy(dev,PHY_BCR,&dwBcrValue) < 0)goto DONE; + wBcr=(u16)dwBcrValue; + if(cmd->speed==SPEED_100) { + wBcr|=PHY_BCR_SPEED_SELECT_; + } else { + wBcr&=(~PHY_BCR_SPEED_SELECT_); + } + if(cmd->duplex==DUPLEX_FULL) { + wBcr|=PHY_BCR_DUPLEX_MODE_; + } else { + wBcr&=(~PHY_BCR_DUPLEX_MODE_); + } + wBcr &= ~PHY_BCR_AUTO_NEG_ENABLE_; + if(smsc9500_write_phy(dev,PHY_BCR,wBcr) < 0)goto DONE; + } + } + result=0; + +DONE: + return result; +} + +void smsc9500_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info) +{ + struct usbnet *dev = netdev_priv(net); + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + strcpy(info->driver,"Smsc9500"); + memset(&info->version,0,sizeof(info->version)); + sprintf(info->version,"%lX.%02lX.%02lX", + (DRIVER_VERSION>>16),(DRIVER_VERSION>>8)&0xFF,(DRIVER_VERSION&0xFFUL)); + memset(&info->fw_version,0,sizeof(info->fw_version)); + sprintf(info->fw_version,"%lu",(adapterData->dwIdRev)&0xFFFFUL); + memset(&info->bus_info,0,sizeof(info->bus_info)); + memset(&info->reserved1,0,sizeof(info->reserved1)); + memset(&info->reserved2,0,sizeof(info->reserved2)); + info->n_stats=0; + info->testinfo_len=0; + info->eedump_len=0; + info->regdump_len=0; + +} + +static u32 smsc9500_get_link (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + + if(adapterData->dwLinkSpeed!=LINK_OFF) + return 1; + else + return 0; + +} + +static u32 smsc9500_get_msglevel (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + return dev->msg_enable; +} + + +static void smsc9500_set_msglevel (struct net_device *net, u32 level) +{ + struct usbnet *dev = netdev_priv(net); + + dev->msg_enable = level; +} + + +static void +smsc9500_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + wolinfo->supported=(WAKE_PHY | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_MAGIC); + wolinfo->wolopts= adapterData->WolWakeupOpts; +} + +static int +smsc9500_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + int result=-EFAULT; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + adapterData->WolWakeupOpts = wolinfo->wolopts; + result=0; + + return result; +} + +static int smsc9500_get_eeprom_len(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + return adapterData->eepromSize; +} + +static int smsc9500_get_eeprom(struct net_device *netdev, struct ethtool_eeprom *ee, u8 *data) +{ + struct usbnet *dev=netdev_priv(netdev); + int offset = ee->offset; + int len = ee->len; + int result = 0; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + ee->magic = LAN9500_EEPROM_MAGIC; + + if(len == 0){ + return 0; + } + + if(offset + len > adapterData->eepromSize){ + SMSC_WARNING("EEPROM address is out of range"); + result = -EINVAL; + }else{ + if(smsc9500_read_eeprom(dev, offset, len, data) < 0){ + result = -EFAULT; + } + } + + return result; +} + +static int smsc9500_set_eeprom(struct net_device *netdev, struct ethtool_eeprom *ee, u8 *data) +{ + struct usbnet *dev=netdev_priv(netdev); + int offset = ee->offset; + int len = ee->len; + int result = 0; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + if(len == 0){ + return 0; + } + + if(offset + len > adapterData->eepromSize){ + SMSC_WARNING("EEPROM address is out of range"); + result = -EINVAL; + return result; + } + + if(ee->magic != LAN9500_EEPROM_MAGIC){ + SMSC_WARNING("EEPROM: magic value mismatch, writing fail, magic = 0x%x", ee->magic); + result = -EFAULT; + return result; + } + + if(smsc9500_write_eeprom(dev, offset, len, data) < 0){ + result=-EFAULT; + } + + return result; +} + +static int smsc9500_eeprom_size(struct usbnet *dev) +{ +#define CHECK_SIZE 4 + u32 dwValue; + int size = 0; + int i; + char save[CHECK_SIZE+1]; + char saveEach[CHECK_SIZE+1]; + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, E2P_CMD, &dwValue)); + if (!(dwValue & E2P_CMD_LOADED_)) + { + size = 0; + goto DONE; + } + + if(smsc9500_read_eeprom(dev, 0, CHECK_SIZE, save) < 0){//Save first 4 bytes + goto DONE; + } + + for(i=128; i<=MAX_EEPROM_SIZE; i+=128){ + if(smsc9500_read_eeprom(dev, i, CHECK_SIZE, saveEach) < 0){ + goto DONE; + } + if(!strncmp(save, saveEach, CHECK_SIZE)){ + size = i; + break; + } + } + +DONE: + return size; + +} + +/* We need to override some ethtool_ops so we require our + own structure so we don't interfere with other usbnet + devices that may be connected at the same time. */ +static struct ethtool_ops smsc9500_ethtool_ops = { + .get_drvinfo = smsc9500_get_drvinfo, + .get_link = smsc9500_get_link, + .get_msglevel = smsc9500_get_msglevel, + .set_msglevel = smsc9500_set_msglevel, + .get_wol = smsc9500_get_wol, + .set_wol = smsc9500_set_wol, + .get_settings = smsc9500_get_settings, + .set_settings = smsc9500_set_settings, + .get_eeprom_len = smsc9500_get_eeprom_len, + .get_eeprom = smsc9500_get_eeprom, + .set_eeprom = smsc9500_set_eeprom, +}; + +static int Smsc9500_do_ioctl( + struct net_device *netdev, + struct ifreq *ifr, + int cmd) +{ + int result=0; + struct usbnet *dev=netdev_priv(netdev); + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + void __user *userAddr=NULL; + + SMSC_TRACE(DBG_IOCTL,"---->Smsc9500_do_ioctl"); + if(netdev==NULL) { + SMSC_WARNING("netdev==NULL"); + result=-EFAULT; + goto DONE; + } + + if(ifr==NULL) { + SMSC_WARNING("ifr==NULL"); + result=-EFAULT; + goto DONE; + } + userAddr=ifr->ifr_data; + + switch(cmd) { + + case SIOCGMIIPHY: + case SIOCDEVPRIVATE: + SMSC_TRACE(DBG_IOCTL,"SIOCGMIIPHY"); + if(adapterData->LanInitialized) { + struct mii_ioctl_data *miiData= + (struct mii_ioctl_data *)&(ifr->ifr_data); + miiData->phy_id=1; + }; + break; + + case SIOCGMIIREG: + case SIOCDEVPRIVATE+1: + SMSC_TRACE(DBG_IOCTL,"SIOCGMIIREG"); + if(adapterData->LanInitialized) { + struct mii_ioctl_data *miiData= + (struct mii_ioctl_data *)&(ifr->ifr_data); + { + u32 dwValue; + if(smsc9500_read_phy(dev,miiData->reg_num,&dwValue) < 0){ + result = -EFAULT; + } + miiData->val_out=(u16)dwValue; + } + };break; + + case SIOCSMIIREG: + case SIOCDEVPRIVATE+2: + SMSC_TRACE(DBG_IOCTL,"SIOCSMIIREG"); + if(adapterData->LanInitialized) { + struct mii_ioctl_data *miiData= + (struct mii_ioctl_data *)&(ifr->ifr_data); + { + u32 dwValue; + dwValue=miiData->val_in; + if(smsc9500_write_phy(dev,miiData->reg_num, dwValue) < 0){ + result = -EFAULT; + } + } + };break; + + case SMSC9500_IOCTL: + result=smsc9500_private_ioctl(adapterData, dev, (PSMSC9500_IOCTL_DATA)userAddr); + break; + + default: + SMSC_WARNING("unknown cmd = 0x%08X",cmd); + result=-EOPNOTSUPP; + break; + } + +DONE: + + SMSC_TRACE(DBG_IOCTL,"<--Smsc9500_do_ioctl"); + return result; +} + +static int smsc9500_private_ioctl(PADAPTER_DATA privateData, struct usbnet *dev, PSMSC9500_IOCTL_DATA ioctlData) +{ + BOOLEAN success=FALSE; + int i; + u32 dwBuf; + u32 offset; + if(ioctlData->dwSignature!=SMSC9500_APP_SIGNATURE) { + goto DONE; + } + + switch(ioctlData->dwCommand) { + case COMMAND_GET_SIGNATURE: + success=TRUE; + break; + case COMMAND_GET_CONFIGURATION: + ioctlData->Data[0]=DRIVER_VERSION; + ioctlData->Data[1]=link_mode; + ioctlData->Data[2] = privateData->macAddrHi16; + ioctlData->Data[3] = privateData->MmacAddrLo32; + ioctlData->Data[4]=debug_mode; + ioctlData->Data[5]=privateData->dwIdRev; + ioctlData->Data[6]=privateData->dwFpgaRev; + ioctlData->Data[7] = 1; + ioctlData->Data[8]=privateData->dwPhyId; + ioctlData->Data[9]=privateData->bPhyModel; + ioctlData->Data[10]=privateData->bPhyRev; + ioctlData->Data[11]=privateData->dwLinkSpeed; + ioctlData->Data[12] = privateData->eepromSize / 128; //Unit is 128B + sprintf(ioctlData->Strng1,"%s, %s",__DATE__,__TIME__); + + success=TRUE; + break; + case COMMAND_LAN_GET_REG: + offset = ioctlData->Data[0]; + + if((ioctlData->Data[0] <= LAN_REGISTER_RANGE) && ((ioctlData->Data[0]&0x3UL)==0)) + { + if(smsc9500_read_reg(dev, offset, &dwBuf) >= 0){ + ioctlData->Data[1] = dwBuf; + success=TRUE; + } + } else { + SMSC_WARNING("Reading LAN9500 Mem Map Failed"); + goto MEM_MAP_ACCESS_FAILED; + } + break; + case COMMAND_LAN_SET_REG: + if((ioctlData->Data[0] <= LAN_REGISTER_RANGE) && ((ioctlData->Data[0]&0x3UL)==0)) + { + offset = ioctlData->Data[0]; + dwBuf = ioctlData->Data[1]; + if(smsc9500_write_reg(dev, offset, dwBuf) >= 0){ + success=TRUE; + } + } else { + SMSC_WARNING("Writing LAN9500 Mem Map Failed"); +MEM_MAP_ACCESS_FAILED: + SMSC_WARNING(" Invalid offset == 0x%08lX",ioctlData->Data[0]); + if(ioctlData->Data[0] > LAN_REGISTER_RANGE) { + SMSC_WARNING(" Out of range"); + } + if(ioctlData->Data[0]&0x3UL) { + SMSC_WARNING(" Not u32 aligned"); + } + } + break; + case COMMAND_MAC_GET_REG: + if((ioctlData->Data[0] >= MAC_REGISTER_RANGE_MIN)&& (ioctlData->Data[0] <= MAC_REGISTER_RANGE_MAX) && ((ioctlData->Data[0]&0x3UL)==0)) { + offset = ioctlData->Data[0]; + if(smsc9500_read_reg(dev, offset, &dwBuf) >= 0){ + ioctlData->Data[1] = dwBuf; + success=TRUE; + } + } else { + SMSC_WARNING("Reading Mac Register Failed"); + goto MAC_ACCESS_FAILURE; + } + break; + case COMMAND_MAC_SET_REG: + if((ioctlData->Data[0] >= MAC_REGISTER_RANGE_MIN)&& (ioctlData->Data[0] <= MAC_REGISTER_RANGE_MAX) && ((ioctlData->Data[0]&0x3UL)==0)) { + offset = ioctlData->Data[0]; + dwBuf = ioctlData->Data[1]; + if(smsc9500_write_reg(dev, offset, dwBuf) >= 0){ + ioctlData->Data[1] = dwBuf; + success=TRUE; + } + } else { + SMSC_WARNING("Writing Mac Register Failed"); +MAC_ACCESS_FAILURE: + if(!(privateData->LanInitialized)) { + + SMSC_WARNING(" LAN Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + } + if(!((ioctlData->Data[0] >= MAC_REGISTER_RANGE_MIN)&& (ioctlData->Data[0] <= MAC_REGISTER_RANGE_MAX))) { + SMSC_WARNING(" Invalid index == 0x%08lX",ioctlData->Data[0]); + } + } + break; + case COMMAND_PHY_GET_REG: + if((ioctlData->Data[0]<32)&&(privateData->LanInitialized)) { + offset = ioctlData->Data[0]; + if(smsc9500_read_phy(dev,offset, &dwBuf) >= 0){ + success=TRUE; + ioctlData->Data[1] = dwBuf; + } + } else { + SMSC_WARNING("Reading Phy Register Failed"); + goto PHY_ACCESS_FAILURE; + } + break; + case COMMAND_PHY_SET_REG: + if((ioctlData->Data[0]<32)&&(privateData->LanInitialized)) { + offset = ioctlData->Data[0]; + dwBuf = ioctlData->Data[1]; + if(smsc9500_write_phy(dev,offset, dwBuf) >= 0){ + success=TRUE; + } + } else { + SMSC_WARNING("Writing Phy Register Failed"); +PHY_ACCESS_FAILURE: + if(!(privateData->LanInitialized)) { + SMSC_WARNING(" Lan Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + } + if(!(ioctlData->Data[0]<32)) { + SMSC_WARNING(" Invalid index == 0x%ld",ioctlData->Data[0]); + } + } + break; + case COMMAND_GET_EEPROM: + { + BYTE cBuf; + offset = ioctlData->Data[0]; + if(offset < privateData->eepromSize) { + if(smsc9500_read_eeprom(dev,offset, 1, &cBuf) >= 0){ + success=TRUE; + ioctlData->Data[1] = cBuf; + } + } else { + SMSC_WARNING("Reading EEPROM Failed"); + goto PHY_ACCESS_FAILURE; + } + } + break; + case COMMAND_SET_EEPROM: + { + BYTE cBuf; + offset = ioctlData->Data[0]; + cBuf = (BYTE)ioctlData->Data[1]; + if(offset < privateData->eepromSize) { + if(smsc9500_write_eeprom(dev, offset, 1, &cBuf) >= 0){ + success=TRUE; + } + } else { + SMSC_WARNING("Writing EEPROM Failed"); + if(!(offset < privateData->eepromSize)) { + SMSC_WARNING(" Invalid eeprom offset == 0x%d",offset); + } + } + } + break; + + case COMMAND_DUMP_LAN_REGS: + + success=TRUE; + for(i=0; iData[i] = dwBuf; + } + } + break; + case COMMAND_DUMP_MAC_REGS: + if(privateData->LanInitialized) { + success=TRUE; + for(i=0; iData[i] = dwBuf; + } + } + } else { + SMSC_WARNING("Mac Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + } + break; + case COMMAND_DUMP_PHY_REGS: + if(privateData->LanInitialized) { + success=TRUE; + for(i=0; iData[i] = dwBuf; + } + } + } else { + SMSC_WARNING("Phy Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + } + break; + case COMMAND_DUMP_EEPROM: + { + success=TRUE; + + if(smsc9500_read_eeprom(dev, 0, privateData->eepromSize, (BYTE*)ioctlData->Data) < 0){ + success=FALSE; + } + };break; + case COMMAND_GET_MAC_ADDRESS: + + if(privateData->LanInitialized) { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, ADDRH, &dwBuf)); + ioctlData->Data[0] = dwBuf; + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, ADDRL, &dwBuf)); + ioctlData->Data[1] = dwBuf; + success=TRUE; + } else { + SMSC_WARNING("Lan Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + } + break; + + case COMMAND_SET_MAC_ADDRESS: + if(privateData->LanInitialized) + { + u32 dwLow32=ioctlData->Data[1]; + u32 dwHigh16=ioctlData->Data[0]; + + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, ADDRH, dwHigh16)); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, ADDRL, dwLow32)); + + dev->net->dev_addr[0]=LOBYTE(LOWORD(dwLow32)); + dev->net->dev_addr[1]=HIBYTE(LOWORD(dwLow32)); + dev->net->dev_addr[2]=LOBYTE(HIWORD(dwLow32)); + dev->net->dev_addr[3]=HIBYTE(HIWORD(dwLow32)); + dev->net->dev_addr[4]=LOBYTE(LOWORD(dwHigh16)); + dev->net->dev_addr[5]=HIBYTE(LOWORD(dwHigh16)); + + success=TRUE; + } else { + SMSC_WARNING("Lan Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + };break; + + case COMMAND_LOAD_MAC_ADDRESS: + if(privateData->LanInitialized) { + if(smsc9500_read_eeprom(dev, EEPROM_MAC_OFFSET, 6, dev->net->dev_addr) == 0){ + dwBuf = dev->net->dev_addr[0] | dev->net->dev_addr[1] << 8 | dev->net->dev_addr[2] << 16 | dev->net->dev_addr[3] << 24; + ioctlData->Data[1] = dwBuf; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, ADDRL, ioctlData->Data[1])); + dwBuf = dev->net->dev_addr[4] | dev->net->dev_addr[5] << 8; + ioctlData->Data[0] = dwBuf; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, ADDRH, ioctlData->Data[0])); + + success=TRUE; + } else { + SMSC_WARNING("Failed to Load Mac Address"); + } + } else { + SMSC_WARNING("Lan Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + };break; + case COMMAND_SAVE_MAC_ADDRESS: + if(privateData->LanInitialized) { + u32 dwLow32 = ioctlData->Data[1]; + u32 dwHigh16 = ioctlData->Data[0]; + + cpu_to_le32s((u32*)&dwLow32); + cpu_to_le32s((u32*)&dwHigh16); + + if((smsc9500_write_eeprom(dev, EEPROM_MAC_OFFSET, 4, (BYTE*)&dwLow32) == 0) && + (smsc9500_write_eeprom(dev, EEPROM_MAC_OFFSET+4, 2, (BYTE*)&dwHigh16) == 0)){ + success=TRUE; + } + } else { + SMSC_WARNING("Lan Not Initialized,"); + SMSC_WARNING(" Use ifconfig to bring interface UP"); + };break; + + case COMMAND_SET_DEBUG_MODE: + debug_mode=ioctlData->Data[0]; + if(debug_mode&0x04UL) { + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, GPIO_CFG, 0x00670700UL)); + success=TRUE; + } else { + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, GPIO_CFG, 0x70070000)); + success=TRUE; + } + success=TRUE; + break; + case COMMAND_SET_LINK_MODE: + link_mode=(ioctlData->Data[0]&0x7FUL); + if(privateData->LanInitialized) { + phy_SetLink(dev,link_mode); + } + success=TRUE; + break; + case COMMAND_GET_LINK_MODE: + ioctlData->Data[0]=link_mode; + success=TRUE; + break; + case COMMAND_CHECK_LINK: + Phy_UpdateLinkMode(dev); + success=TRUE; + break; + + case COMMAND_GET_ERRORS: + + ioctlData->Data[0] = dev->extra_error_cnts.tx_epipe; + ioctlData->Data[1] = dev->extra_error_cnts.tx_eproto; + ioctlData->Data[2] = dev->extra_error_cnts.tx_etimeout; + ioctlData->Data[3] = dev->extra_error_cnts.tx_eilseq; + + ioctlData->Data[4] = dev->extra_error_cnts.rx_epipe; + ioctlData->Data[5] = dev->extra_error_cnts.rx_eproto; + ioctlData->Data[6] = dev->extra_error_cnts.rx_etimeout; + ioctlData->Data[7] = dev->extra_error_cnts.rx_eilseq; + ioctlData->Data[8] = dev->extra_error_cnts.rx_eoverflow; + + success = TRUE; + break; + case COMMAND_READ_BYTE: + ioctlData->Data[1]=(*((volatile BYTE *)(ioctlData->Data[0]))); + success=TRUE; + break; + case COMMAND_READ_WORD: + ioctlData->Data[1]=(*((volatile u16 *)(ioctlData->Data[0]))); + success=TRUE; + break; + case COMMAND_READ_DWORD: + ioctlData->Data[1]=(*((volatile u32 *)(ioctlData->Data[0]))); + success=TRUE; + break; + case COMMAND_WRITE_BYTE: + (*((volatile BYTE *)(ioctlData->Data[0])))= + ((BYTE)(ioctlData->Data[1])); + success=TRUE; + break; + case COMMAND_WRITE_WORD: + (*((volatile u16 *)(ioctlData->Data[0])))= + ((u16)(ioctlData->Data[1])); + success=TRUE; + break; + case COMMAND_WRITE_DWORD: + (*((volatile u32 *)(ioctlData->Data[0])))= + ((u32)(ioctlData->Data[1])); + success=TRUE; + break; + case COMMAND_SET_AMDIX_STS: + auto_mdix=(ioctlData->Data[0]); + if(privateData->LanInitialized) { + Phy_SetAutoMdix(dev, (u16)auto_mdix); + } + success=TRUE; + break; + case COMMAND_GET_AMDIX_STS: + ioctlData->Data[0]=auto_mdix; + success=TRUE; + break; + + default:break;//make lint happy + } + +DONE: + if((success)&&(ioctlData!=NULL)) { + ioctlData->dwSignature=SMSC9500_DRIVER_SIGNATURE; + return SMSC9500_SUCCESS; + } + return SMSC9500_FAIL; + +} +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +static const struct net_device_ops smsc95xx_netdev_ops = +{ + .ndo_open = smscusbnet_open, + .ndo_stop = smscusbnet_stop, + .ndo_start_xmit = smscusbnet_start_xmit, + .ndo_tx_timeout = smscusbnet_tx_timeout, + .ndo_change_mtu = smscusbnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = Smsc9500_do_ioctl, + .ndo_set_multicast_list = smsc9500_set_multicast, + .ndo_get_stats = smscusbnet_get_stats, +}; +#endif //linux 2.6.29 + +static int smsc9500_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret=0; + PADAPTER_DATA adapterData=NULL; + u32 dwBuf; + char version[15]; + + SMSC_TRACE(DBG_INIT,"---------->in smsc9500_bind\n"); + + //Init system control and status regsiter map + LanRegMap[LAN_REG_ID_REV] = ID_REV; + LanRegMap[LAN_REG_FPGA_REV] = FPGA_REV; + LanRegMap[LAN_REG_INT_STS] = INT_STS; + LanRegMap[LAN_REG_RX_CFG] = RX_CFG; + LanRegMap[LAN_REG_TX_CFG] = TX_CFG; + LanRegMap[LAN_REG_HW_CFG] = HW_CFG; + LanRegMap[LAN_REG_RX_FIFO_INF] = RX_FIFO_INF; + LanRegMap[LAN_REG_TX_FIFO_INF] = TX_FIFO_INF; + LanRegMap[LAN_REG_PMT_CTRL] = PM_CTRL; + LanRegMap[LAN_REG_LED_GPIO_CFG] = LED_GPIO_CFG; + LanRegMap[LAN_REG_GPIO_CFG] = GPIO_CFG; + LanRegMap[LAN_REG_AFC_CFG] = AFC_CFG; + LanRegMap[LAN_REG_E2P_CMD] = E2P_CMD; + LanRegMap[LAN_REG_E2P_DATA] = E2P_DATA; + LanRegMap[LAN_REG_BURST_CAP] = BURST_CAP; + LanRegMap[LAN_REG_STRAP_DBG] = STRAP_DBG; + LanRegMap[LAN_REG_DP_SEL] = DP_SEL; + LanRegMap[LAN_REG_DP_CMD] = DP_CMD; + LanRegMap[LAN_REG_DP_ADDR] = DP_ADDR; + LanRegMap[LAN_REG_DP_DATA0] = DP_DATA0; + LanRegMap[LAN_REG_DP_DATA1] = DP_DATA1; + LanRegMap[LAN_REG_GPIO_WAKE] = GPIO_WAKE; + LanRegMap[LAN_REG_INT_EP_CTL] = INT_EP_CTL; + LanRegMap[LAN_REG_BULK_IN_DLY] = BULK_IN_DLY; + + //Init MAC register map + MacRegMap[MAC_REG_MAC_CR] = MAC_CR; + MacRegMap[MAC_REG_ADDRH] = ADDRH; + MacRegMap[MAC_REG_ADDRL] = ADDRL; + MacRegMap[MAC_REG_HASHH] = HASHH; + MacRegMap[MAC_REG_HASHL] = HASHL; + MacRegMap[MAC_REG_MII_ADDR] = MII_ADDR; + MacRegMap[MAC_REG_MII_DATA] = MII_DATA; + MacRegMap[MAC_REG_FLOW] = FLOW; + MacRegMap[MAC_REG_VLAN1] = VLAN1; + MacRegMap[MAC_REG_VLAN2] = VLAN2; + MacRegMap[MAC_REG_WUFF] = WUFF; + MacRegMap[MAC_REG_WUCSR] = WUCSR; + MacRegMap[MAC_REG_COE_CR] = COE_CR; + + //Init PHY map + PhyRegMap[PHY_REG_BCR] = PHY_BCR; + PhyRegMap[PHY_REG_BSR] = PHY_BSR; + PhyRegMap[PHY_REG_ID1] = PHY_ID_1; + PhyRegMap[PHY_REG_ID2] = PHY_ID_2; + PhyRegMap[PHY_REG_ANEG_ADV] = PHY_ANEG_ADV; + PhyRegMap[PHY_REG_ANEG_LPA] = PHY_ANEG_LPA; + PhyRegMap[PHY_REG_ANEG_ER] = PHY_ANEG_REG; + PhyRegMap[PHY_REG_SILICON_REV] = PHY_SILICON_REV; + PhyRegMap[PHY_REG_MODE_CTRL_STS] = PHY_MODE_CTRL_STS; + PhyRegMap[PHY_REG_SPECIAL_MODES] = PHY_SPECIAL_MODES; + PhyRegMap[PHY_REG_TSTCNTL] = PHY_TSTCNTL; + PhyRegMap[PHY_REG_TSTREAD1] = PHY_TSTREAD1; + PhyRegMap[PHY_REG_TSTREAD2] = PHY_TSTREAD2; + PhyRegMap[PHY_REG_TSTWRITE] = PHY_TSTWRITE; + PhyRegMap[PHY_REG_SPECIAL_CTRL_STS] = PHY_SPECIAL_CTRL_STS; + PhyRegMap[PHY_REG_SITC] = PHY_SITC; + PhyRegMap[PHY_REG_INT_SRC] = PHY_INT_SRC; + PhyRegMap[PHY_REG_INT_MASK] = PHY_INT_MASK; + PhyRegMap[PHY_REG_SPECIAL] = PHY_SPECIAL; + + sprintf(version,"%lX.%02lX.%02lX", + (DRIVER_VERSION>>16),(DRIVER_VERSION>>8)&0xFF,(DRIVER_VERSION&0xFFUL)); + SMSC_TRACE(DBG_INIT,"Driver smsc9500.ko verison %s, built on %s, %s",version, __TIME__, __DATE__); + + ret=smscusbnet_get_endpoints(dev,intf); + if (ret<0) + goto out1; + + dev->data[0]=(unsigned long) kmalloc(sizeof(ADAPTER_DATA),GFP_KERNEL); + + if((PADAPTER_DATA)dev->data[0]==NULL) { + SMSC_WARNING("Unable to allocate ADAPTER_DATA"); + ret=-ENOMEM; + goto out1; + } + memset((PADAPTER_DATA)dev->data[0],0,sizeof(ADAPTER_DATA)); + adapterData=(PADAPTER_DATA)(dev->data[0]); + + init_MUTEX(&adapterData->phy_mutex); + init_MUTEX(&adapterData->eeprom_mutex); + init_MUTEX(&adapterData->internal_ram_mutex); + init_MUTEX(&adapterData->RxFilterLock); + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + if(dwBuf & HW_CFG_SMDET_STS){ + SMSC_TRACE(DBG_INIT,"Come back from smart detach"); + } + + adapterData->macAddrHi16 = mac_addr_hi16; + adapterData->MmacAddrLo32 = mac_addr_lo32; + + adapterData->eepromSize = MAX_EEPROM_SIZE + 128; //Set a initial value + adapterData->eepromSize = smsc9500_eeprom_size(dev); + SMSC_TRACE(DBG_INIT,"EEPROM size: %d bytes", adapterData->eepromSize); + + //Init all registers + ret = smsc9500_reset(dev); + if(ret < 0)goto out1; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) + dev->net->do_ioctl = Smsc9500_do_ioctl; + dev->net->set_multicast_list = smsc9500_set_multicast; +#else + dev->net->netdev_ops = &smsc95xx_netdev_ops; +#endif //2.6.29 + dev->net->ethtool_ops = &smsc9500_ethtool_ops; + dev->net->flags|=IFF_MULTICAST; + + dev->linkDownSuspend = linkdownsuspend; + dev->dynamicSuspend = dynamicsuspend; +#ifndef CONFIG_PM + if(dev->dynamicSuspend || dev->linkDownSuspend){ + SMSC_WARNING("Power management has to be enabled in the kernel configuration to support dynamicsuspend and linkdownsuspend"); + dev->dynamicSuspend = dev->linkDownSuspend = 0; + } +#endif //CONFIG_PM +#ifndef CONFIG_USB_SUSPEND + if(dev->dynamicSuspend || dev->linkDownSuspend){ + SMSC_WARNING("Usb suspend has to be enabled in the kernel configuration to support dynamicsuspend and linkdownsuspend"); + dev->dynamicSuspend = dev->linkDownSuspend = 0; + } +#endif //CONFIG_USB_SUSPEND + + if(dev->chipDependFeatures[FEATURE_SMARTDETACH]){ + dev->smartDetach = smartdetach; + //If smart detach is enabled, link down suspend should be disabled + if(dev->smartDetach)dev->linkDownSuspend = 0; + } +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) +#ifdef CONFIG_PM + if(dev->dynamicSuspend || dev->linkDownSuspend){ + if(dev->udev->autosuspend_disabled){ + SMSC_WARNING("Autosuspend should be enabled by shell cmd \"echo auto > /sys/bus/usb/devices/X-XX/power/level\""); + } + } +#endif //CONFIG_PM +#endif + adapterData->UseScatterGather=scatter_gather; + adapterData->UseTxCsum=tx_Csum; + adapterData->UseRxCsum=rx_Csum; + if (scatter_gather) + SMSC_TRACE(DBG_INIT,"Tx Scatter-Gather"); + if (tx_Csum) + SMSC_TRACE(DBG_INIT,"Tx HW Checksum"); + if (rx_Csum) + SMSC_TRACE(DBG_INIT,"Rx HW Checksum"); + + + if(adapterData->UseScatterGather) { + + if(adapterData->UseTxCsum) + dev->net->features = (NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_FRAGLIST); + else + dev->net->features = (NETIF_F_SG | NETIF_F_FRAGLIST); // Kernel will turn off SG in this case. + } + + else { + + if(adapterData->UseTxCsum) + dev->net->features = (NETIF_F_HW_CSUM); + else + dev->net->features = 0; + } + + adapterData->dwTxQueueDisableMask=0; + spin_lock_init(&(adapterData->TxQueueLock)); + adapterData->TxInitialized=TRUE; + + adapterData->WolWakeupOpts= 0; + + adapterData->LinkActLedCfg = LinkActLedCfg; + adapterData->LinkLedOnGpio = LinkLedOnGpio; + adapterData->LinkLedOnGpioBufType = LinkLedBufType; + adapterData->LinkLedOnGpioPolarity = LinkLedPolarity; + + adapterData->LanInitialized=TRUE; + + SMSC_TRACE(DBG_INIT,"<--------out of bind, return 0\n"); + return 0; + + if (adapterData != NULL){ + kfree(adapterData); + adapterData=NULL; + } +out1: + SMSC_TRACE(DBG_INIT,"<--------bind out1, return %d\n",ret); + return ret; +} + + +static void smsc9500_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + SMSC_TRACE(DBG_CLOSE,"------->in smsc9500_unbind\n"); + + if (adapterData != NULL){ + SMSC_TRACE(DBG_CLOSE,"free adapterData\n"); + kfree(adapterData); + adapterData=NULL; + } + + SMSC_TRACE(DBG_CLOSE,"<-------out of smsc9500_unbind\n"); +} + +static int smsc9500_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + u8 *head; + u16 size; + u32 header,AlignCount=0; + char *packet; + struct sk_buff *ax_skb; + int ret = RX_FIXUP_VALID_SKB; + u16 *vlan_tag; + + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + SMSC_TRACE(DBG_RX,"------->in smsc9500_rx_fixup\n"); + + head = (u8 *) skb->data; + memcpy(&header, head, sizeof(header)); + le32_to_cpus(&header); +#ifdef RX_OFFSET + skb_pull(skb, 4 + NET_IP_ALIGN); //two extra for ip header alignment + packet = skb->data; +#else + packet = head + sizeof(header); + skb_pull(skb, 4); +#endif //RX_OFFSET + + while (skb->len > 0) { + /* get the packet length */ + size = (u16) ((header & RX_STS_FL_)>>16); + +#ifdef RX_OFFSET + AlignCount = (STATUS_WORD_LEN - ((size + NET_IP_ALIGN) % STATUS_WORD_LEN)) % STATUS_WORD_LEN; +#else + AlignCount = (STATUS_WORD_LEN - (size % STATUS_WORD_LEN)) % STATUS_WORD_LEN; +#endif + + if(header & RX_STS_ES_){ + dev->stats.rx_errors++; + dev->stats.rx_dropped++; + if(header & RX_STS_CRC_){ + dev->stats.rx_crc_errors++; + }else{ + if(header & (RX_STS_TL_ | RX_STS_RF_)){ + dev->stats.rx_frame_errors++; + } + if(((header & RX_STS_LE_) != 0L) && ((header & RX_STS_FT_)==0L)){ + dev->stats.rx_length_errors++; + } + } + + if(size == skb->len){//last packet + return RX_FIXUP_INVALID_SKB; + }else{ + + skb_pull(skb, size+AlignCount); + + if (skb->len == 0) { + ret = RX_FIXUP_INVALID_SKB; + + return ret; + } + + goto NEXT_PACKET; + } + + } + + if ((size == skb->len)){ + + if (adapterData->UseRxCsum) { + + u16 wHwCsum; +#ifdef NET_SKBUFF_DATA_USES_OFFSET + wHwCsum = *(u16*)(skb_tail_pointer(skb) - 2); +#else + wHwCsum = *(u16*)(skb->tail - 2); +#endif + skb->csum = wHwCsum; + + } + + + if (adapterData->UseRxCsum) + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) + skb->ip_summed = CHECKSUM_HW; + #else + skb->ip_summed = CHECKSUM_COMPLETE; + #endif + + else + skb->ip_summed = CHECKSUM_NONE; + + if (adapterData->UseRxCsum) + { + skb_trim(skb,size-2-4); + + } + else + { + skb_trim(skb,size-4); + + } + +#ifdef RX_SKB_COPY +//FIXME: Kernel calculate received size based on skb->truesize, which holds total buffer size. +//If we allocate a big skb buffer, but only part of buffer hold valid data like turbo mode did, +//Kernel accumulate received data with skb->truesize, so total received data might be over limit. But +//actual data size isn't, then kernel may drop the subsequent packets. + if(TurboMode){ + ax_skb = alloc_skb (skb->len + NET_IP_ALIGN, GFP_ATOMIC); + skb_reserve (ax_skb, NET_IP_ALIGN); + skb_put(ax_skb, skb->len); + memcpy(ax_skb->data, skb->data, skb->len); + + vlan_tag = (u16*)&ax_skb->cb[0]; + *vlan_tag = VLAN_DUMMY; //Reserved value + smscusbnet_skb_return(dev, ax_skb); + ret = RX_FIXUP_INVALID_SKB; + }else{ + ret = RX_FIXUP_VALID_SKB; + vlan_tag = (u16*)&skb->cb[0]; + *vlan_tag = VLAN_DUMMY; //Reserved value + } +#else +//FIXME: We are not supposed to change truesize, but this is the easy way to cheat kernel without memory copy + skb->truesize = skb->len + sizeof(struct sk_buff); + vlan_tag = (u16*)&skb->cb[0]; + *vlan_tag = VLAN_DUMMY; //Reserved value +#endif + return ret; + } + + + if (size > (ETH_FRAME_LEN+12)) { // ETH_FRAME_LEN+4(CRC)+2(COE)+4(Vlan) + SMSC_TRACE(DBG_RX,"size > (ETH_FRAME_LEN+12), hearder= 0x%08x\n", header); + return RX_FIXUP_ERROR; + } + +#ifndef RX_SKB_COPY + ax_skb = skb_clone(skb, GFP_ATOMIC); +#else + ax_skb = alloc_skb (size + NET_IP_ALIGN, GFP_ATOMIC); + skb_reserve (ax_skb, NET_IP_ALIGN); +#endif + if (ax_skb) { +#ifndef RX_SKB_COPY + ax_skb->len = size; + ax_skb->data = packet; + + skb_trim(ax_skb, 0); + skb_put(ax_skb, size); + +#else + skb_put(ax_skb, size); + memcpy(ax_skb->data, packet, size); +#endif + + if (adapterData->UseRxCsum) { + + u16 wHwCsum; +#ifdef NET_SKBUFF_DATA_USES_OFFSET + wHwCsum = *(u16*)(skb_tail_pointer(ax_skb) - 2); +#else + wHwCsum = *(u16*)(ax_skb->tail - 2); +#endif + ax_skb->csum = wHwCsum; + + } + + + if (adapterData->UseRxCsum) + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) + ax_skb->ip_summed = CHECKSUM_HW; + #else + ax_skb->ip_summed = CHECKSUM_COMPLETE; + #endif + + else + ax_skb->ip_summed = CHECKSUM_NONE; + + if (adapterData->UseRxCsum) + { + skb_trim(ax_skb,size-2-4); + + } + else + { + skb_trim(ax_skb,size-4); + + } + +#ifndef RX_SKB_COPY +//FIXME: We are not supposed to change truesize, but this is the easy way to cheat kernel without memory copy + ax_skb->truesize = ax_skb->len + sizeof(struct sk_buff); +#endif + + vlan_tag = (u16*)&ax_skb->cb[0]; + *vlan_tag = VLAN_DUMMY; //Reserved value + smscusbnet_skb_return(dev, ax_skb); + } else { + SMSC_TRACE(DBG_RX,"no ax_skb\n"); + return RX_FIXUP_ERROR; + } + + skb_pull(skb, size+AlignCount); + + if (skb->len == 0) { + SMSC_TRACE(DBG_RX,"skb->len==0 left\n"); + break; + } +NEXT_PACKET: + head = (u8 *) skb->data; + memcpy(&header, head, sizeof(header)); + le32_to_cpus(&header); +#ifdef RX_OFFSET + skb_pull(skb, 4 + NET_IP_ALIGN); //two extra for ip header alignment + packet = skb->data; +#else + packet = head + sizeof(header); + skb_pull(skb, 4); +#endif //RX_OFFSET + } + + if (skb->len < 0) { + SMSC_WARNING("invalid rx length<0 %d", skb->len); + return RX_FIXUP_ERROR; + } + + + SMSC_TRACE(DBG_RX,"<-------out of smsc9500_rx_fixup\n"); + return ret; +} + +static struct sk_buff *smsc9500_tx_fixup(struct usbnet *dev, struct sk_buff *skb, + int flags) +{ + +#ifndef TX_SKB_FORCE_COPY + int headroom = skb_headroom(skb); + int tailroom = skb_tailroom(skb); +#endif /* TX_SKB_FORCE_COPY */ + int SkbSize,CopySize,AlignmentSize; + u8 * prt; + int i; + + unsigned skbFragCnt = skb_shinfo(skb)->nr_frags + 1; + u32 TxCommandA,TxCommandB; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + SMSC_TRACE(DBG_TX,"in smsc9500_tx_fixup\n"); + + if (adapterData->UseTxCsum) { + + + u32 dwTxCsumPreamble=0; + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) + + if (skb->ip_summed == CHECKSUM_HW) + { + int Chsum_start_offset=0; + CalculateTxChecksumOffset( + skb, + &Chsum_start_offset); + + /* tx checksum problem workaround */ + if (skb->len <= 45) { + u32 csum; + csum = csum_partial(skb->data + Chsum_start_offset, skb->len - (skb->data + Chsum_start_offset - skb->data), 0); + *((u16 *)(skb->data + Chsum_start_offset + skb->csum)) = csum_fold(csum); + goto Non_CheckSumOffLoad; + } + + dwTxCsumPreamble=(((u16) (Chsum_start_offset + skb->csum)) << 16) | ((u16) Chsum_start_offset); + + } + #else + + if (skb->ip_summed == CHECKSUM_PARTIAL) + { + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) + int Chsum_start_offset=0; + CalculateTxChecksumOffset( + skb, + &Chsum_start_offset); + + /* tx checksum problem workaround */ + if (skb->len <= 45) { + u32 csum; + csum = csum_partial(skb->data + Chsum_start_offset, skb->len - (skb->data + Chsum_start_offset - skb->data), 0); + *((u16 *)(skb->data + Chsum_start_offset + skb->csum)) = csum_fold(csum); + goto Non_CheckSumOffLoad; + } + + dwTxCsumPreamble=(((u16) (Chsum_start_offset + skb->csum)) << 16) | ((u16) Chsum_start_offset); + + #else + + /* tx checksum problem workaround */ + if (skb->len <= 45) { + u32 csum; + csum = csum_partial(skb->head + skb->csum_start, skb->len - (skb->head + skb->csum_start - skb->data), 0); + *((u16 *)(skb->head + (skb->csum_start + skb->csum_offset))) = csum_fold(csum); + goto Non_CheckSumOffLoad; + } + + dwTxCsumPreamble=(((u16) (skb->csum_offset+skb->csum_start-(skb->data - skb->head))) << 16) + | ((u16) (skb->csum_start-(skb->data - skb->head))); + + #endif + + + } + #endif + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) + if(skb->ip_summed == CHECKSUM_HW) + #else + if(skb->ip_summed == CHECKSUM_PARTIAL) + #endif + { + if (skbFragCnt ==1) { +// ip_summed, one Fragament + SMSC_TRACE(DBG_TX,"ip_summed, Onefrag\n"); + + AlignmentSize = skb->len % STATUS_WORD_LEN; + if(AlignmentSize)AlignmentSize = STATUS_WORD_LEN - AlignmentSize; + +#ifndef TX_SKB_FORCE_COPY + if ((!skb_cloned(skb)) + && ((headroom + tailroom) >= (12 + AlignmentSize))) { + if( (headroom < 12 ) || (tailroom < AlignmentSize) ){ + skb->data = memmove(skb->head +12, skb->data, skb->len); + SkbSize = skb->len; + skb_trim(skb, 0); + skb_put(skb, SkbSize); + + + } + } else +#endif /* TX_SKB_FORCE_COPY */ + { + struct sk_buff *skb2; + skb2 = skb_copy_expand(skb, 12, AlignmentSize, flags); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return NULL; + } + + skb_push(skb, STATUS_WORD_LEN); + memcpy(skb->data, &dwTxCsumPreamble, STATUS_WORD_LEN); + + skb_push(skb, STATUS_WORD_LEN); + TxCommandB=(u32)(skb->len-STATUS_WORD_LEN)|TX_CMD_B_CSUM_ENABLE; + cpu_to_le32s((u32*)&TxCommandB); + memcpy(skb->data, &TxCommandB, STATUS_WORD_LEN); + skb_push(skb, STATUS_WORD_LEN); + TxCommandA= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_ | (u32)(skb->len-8); + cpu_to_le32s((u32*)&TxCommandA); + memcpy(skb->data, &TxCommandA, STATUS_WORD_LEN); + skb_put(skb, AlignmentSize); + return skb; + } + else { +// ip_summed, Multi Fragament + + struct sk_buff *skb2; + SkbSize=skb->len+4*4*(skbFragCnt+1); + skb2 = dev_alloc_skb(SkbSize); + + SMSC_TRACE(DBG_TX,"ip_summed, Multifrags\n"); + if (!skb2) + return NULL; + + skb_put(skb2, SkbSize); + + + { + + TxCommandA=TX_CMD_A_FIRST_SEG_ |((u32)sizeof(u32)); + + TxCommandB=TX_CMD_B_CSUM_ENABLE |((u32)(skb->len+STATUS_WORD_LEN)) ; + cpu_to_le32s((u32*)&TxCommandA); + cpu_to_le32s((u32*)&TxCommandB); + + memcpy(skb2->data, &TxCommandA, STATUS_WORD_LEN); + memcpy(skb2->data+STATUS_WORD_LEN, &TxCommandB, STATUS_WORD_LEN); + memcpy(skb2->data+8, &dwTxCsumPreamble, STATUS_WORD_LEN); + } + + { + + + TxCommandA = + ((((unsigned long)(skb->data))&0x03UL)<<16) | //u32 alignment adjustment + ((u32)((skb->len)-(skb->data_len))); + TxCommandB= + ((u32)(skb->len+STATUS_WORD_LEN)); + cpu_to_le32s((u32*)&TxCommandA); + cpu_to_le32s((u32*)&TxCommandB); + CopySize=((((u32)(skb->len - skb->data_len))+3+(((unsigned long)(skb->data))&0x03UL))>>2)*4; + memcpy(skb2->data + 12, &TxCommandA, STATUS_WORD_LEN); + memcpy(skb2->data + 12 + STATUS_WORD_LEN, &TxCommandB, STATUS_WORD_LEN); + memcpy(skb2->data + 12 + STATUS_WORD_LEN * 2, (u32 *)(((unsigned long)(skb->data))&0xFFFFFFFCUL),CopySize); + } + + prt=(u8 *)skb2->data+20+CopySize; + + for(i=1;ifrags[i - 1]; + void *frag_addr = page_address(frag->page) + frag->page_offset; + + + TxCommandA= + ((((unsigned long)(frag_addr))&0x03UL)<<16) | //alignment adjustment + ((u32)(frag->size)); + + + if (i==(skbFragCnt-1)){ + TxCommandA |= TX_CMD_A_LAST_SEG_ ; + } + + + TxCommandB= ((u32)(skb->len+STATUS_WORD_LEN)); + cpu_to_le32s((u32*)&TxCommandA); + cpu_to_le32s((u32*)&TxCommandB); + memcpy(prt, &TxCommandA, STATUS_WORD_LEN); + prt=prt+STATUS_WORD_LEN; + memcpy(prt, &TxCommandB, STATUS_WORD_LEN); + prt=prt+STATUS_WORD_LEN; + CopySize=((((unsigned long)(frag->size))+3+(((unsigned long)(frag_addr))&0x03UL))>>2)*4; + memcpy(prt, (u32 *)(((unsigned long)(frag_addr))&0xFFFFFFFCUL),CopySize); + prt=prt+CopySize; + + } + + skb_trim(skb2,prt-skb2->data); + dev_kfree_skb_any(skb); + return skb2; + } + + } + else { + if (skbFragCnt >1) { +//Non ip_summed, Multifrags + + struct sk_buff *skb2; + SkbSize=skb->len+4*4*skbFragCnt; + skb2 = dev_alloc_skb(SkbSize); + SMSC_TRACE(DBG_TX,"Non ip_summed, Multifrags\n"); + + if (!skb2) + return NULL; + + skb_put(skb2, SkbSize); + + { + + TxCommandA =((((unsigned long)(skb->data))&0x03UL)<<16) | //u32 alignment adjustment + TX_CMD_A_FIRST_SEG_ | + (u32)((skb->len)-(skb->data_len)); + + TxCommandB=TX_CMD_B_CSUM_ENABLE |((u32)(skb->len)); + cpu_to_le32s((u32*)&TxCommandA); + cpu_to_le32s((u32*)&TxCommandB); + SMSC_TRACE(DBG_TX,"first frag. \n"); + CopySize=((((unsigned long)((skb->len)-(skb->data_len)))+3+(((unsigned long)(skb->data))&0x03UL))>>2)*4; + memcpy(skb2->data, &TxCommandA, STATUS_WORD_LEN); + memcpy(skb2->data+STATUS_WORD_LEN, &TxCommandB, STATUS_WORD_LEN); + memcpy(skb2->data+STATUS_WORD_LEN*2, (void *)(((unsigned long)(skb->data))&0xFFFFFFFCUL),CopySize); + } + + prt=(u8 *)skb2->data+8+CopySize; + + for(i=1;ifrags[i - 1]; + void *frag_addr = page_address(frag->page) + frag->page_offset; + + + TxCommandA= + ((((unsigned long)(frag_addr))&0x03UL)<<16) | //u32 alignment adjustment + ((u32)(frag->size)); + + + if (i==(skbFragCnt-1)){ + TxCommandA |= TX_CMD_A_LAST_SEG_ ; + } + + + TxCommandB=((u32)(skb->len)); + cpu_to_le32s((u32*)&TxCommandA); + cpu_to_le32s((u32*)&TxCommandB); + memcpy(prt, &TxCommandA, STATUS_WORD_LEN); + prt=prt+STATUS_WORD_LEN; + memcpy(prt, &TxCommandB, STATUS_WORD_LEN); + prt=prt+STATUS_WORD_LEN; + + CopySize=((((unsigned long)(frag->size))+3+(((unsigned long)(frag_addr))&0x03UL))>>2)*4; + memcpy(prt, (void *)(((unsigned long)(frag_addr))&0xFFFFFFFCUL),CopySize); + prt=prt+CopySize; + + } + + skb_trim(skb2,prt-skb2->data); + dev_kfree_skb_any(skb); + SMSC_TRACE(DBG_TX,"return from Nonip_summed, Multifrags\n"); + return skb2; + } else { + goto Non_CheckSumOffLoad; + } + } + + } + else { +Non_CheckSumOffLoad: +//Non ip_summed, onefrag + SMSC_TRACE(DBG_TX,"Non ip_summed, onefrag\n"); + AlignmentSize = skb->len % STATUS_WORD_LEN; + if(AlignmentSize)AlignmentSize = STATUS_WORD_LEN - AlignmentSize; + +#ifndef TX_SKB_FORCE_COPY + if ((!skb_cloned(skb)) + && ((headroom + tailroom) >= (2*STATUS_WORD_LEN + AlignmentSize))) { + if ((headroom < (2*STATUS_WORD_LEN)) || (tailroom < AlignmentSize)){ + skb->data = memmove(skb->head + 2*STATUS_WORD_LEN, skb->data, skb->len); + SkbSize = skb->len; + skb_trim(skb, 0); + skb_put(skb, SkbSize); + } + } else +#endif /* TX_SKB_FORCE_COPY */ + { + struct sk_buff *skb2; + skb2 = skb_copy_expand(skb, 2*STATUS_WORD_LEN, AlignmentSize, flags); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return NULL; + } + + skb_push(skb, STATUS_WORD_LEN); + TxCommandB=(u32)(skb->len - STATUS_WORD_LEN); + cpu_to_le32s((u32*)&TxCommandB); + + memcpy(skb->data, &TxCommandB, STATUS_WORD_LEN); + skb_push(skb, STATUS_WORD_LEN); + TxCommandA= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_ | (u32)(skb->len - 2*STATUS_WORD_LEN); + cpu_to_le32s((u32*)&TxCommandA); + memcpy(skb->data, &TxCommandA, STATUS_WORD_LEN); + + skb_put(skb, AlignmentSize); + return skb; + } + +} + +static int smsc9500_reset(struct usbnet *dev) +{ + int ret=0,Timeout; + u32 dwReadBuf, dwAddrH, dwAddrL, dwWriteBuf,dwMacCr,DwTemp, dwBurstCap; + SMSC9500_RX_STATS rx_stats; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + SMSC_TRACE(DBG_INIT,"---------->smsc9500_reset\n"); + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + dwReadBuf |= HW_CFG_LRST_; + if ((ret = smsc9500_write_reg(dev, HW_CFG, dwReadBuf))<0) + { + SMSC_WARNING("Failed to write HW_CFG_LRST_ bit in HW_CFG register, ret = %d \n",ret); + return ret; + } + + Timeout = 0; + do { + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + msleep(100); /* wait for 100 us before trying again */ + Timeout++; + } while ( (dwReadBuf & HW_CFG_LRST_) && (Timeout < 100)); + + if(Timeout >= 100) + { + SMSC_WARNING("Timeout waiting for completion of Lite Reset\n"); + return ret; + } + + if((ret = smsc9500_read_reg(dev, ID_REV, &dev->chipID)) < 0){ + SMSC_WARNING("Failed to read GPIO_CFG: %d", ret); + return ret; + } + dev->chipID = dev->chipID >> 16; + + /*******Enable new features**************/ + if(dev->chipID == ID_REV_9500A_CHIPID){ + int i; + for(i=0; ichipDependFeatures[i] = TRUE; + } + } + if(dev->chipID == ID_REV_9512_CHIPID)dev->chipDependFeatures[FEATURE_WUFF_8] = TRUE; + /*******Enable new features**************/ + + if ((ret = smsc9500_read_reg(dev,PM_CTRL,&DwTemp)< 0)) { + SMSC_WARNING("Failed to read PM_CTRL: %d", ret); + return ret; + } + + if ((ret = smsc9500_write_reg(dev, PM_CTRL, (DwTemp | PM_CTL_PHY_RST_))< 0)) { + SMSC_WARNING("Failed to write PM_CTRL: %d", ret); + return ret; + } + + Timeout = 0; + do { + if ((ret = smsc9500_read_reg(dev,PM_CTRL,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read PM_CTRL: %d", ret); + return ret; + } + msleep(100); + Timeout++; + } while ( (dwReadBuf & PM_CTL_PHY_RST_) && (Timeout < 100)); + + if(Timeout >= 100) + { + SMSC_WARNING("Timeout waiting for PHY Reset\n"); + return ret; + } + dwAddrH = 0x0000FFFFUL; + dwAddrL = 0xFFFFFFFF; + + if(adapterData->macAddrHi16 != 0xFFFFFFFF || adapterData->MmacAddrLo32 != 0xFFFFFFFF){ + dwAddrH = adapterData->macAddrHi16 & 0xFFFF; + dwAddrL = adapterData->MmacAddrLo32; + }else{ + if(adapterData->eepromSize && smsc9500_read_eeprom(dev, EEPROM_MAC_OFFSET, 6, dev->net->dev_addr) == 0){ + dwAddrL = dev->net->dev_addr[0] | dev->net->dev_addr[1] << 8 | dev->net->dev_addr[2] << 16 | dev->net->dev_addr[3] << 24; + dwAddrH = dev->net->dev_addr[4] | dev->net->dev_addr[5] << 8; + }else{//LAN9500's descriptor RAM may provide Mac address + MAC_ADDR_IN_RAM macRam; + if(ReadDataPort(dev, RAMSEL_EEPROM, 0, sizeof(MAC_ADDR_IN_RAM)/4, (u32*)&macRam, NULL) == SMSC9500_SUCCESS){ + cpu_to_le32s(&macRam.signature); + cpu_to_le32s(&macRam.MacAddrL); + cpu_to_le32s(&macRam.MacAddrH); + cpu_to_le16s(&macRam.crc); + cpu_to_le16s(&macRam.crcComplement); + if(macRam.signature == 0x736D7363){//Signature "smsc" + u16 crc = CalculateCrc16((char*)&macRam, 12, FALSE); + if((crc == macRam.crc) && (crc == (u16)~macRam.crcComplement)){ + dwAddrL = macRam.MacAddrL; + dwAddrH = macRam.MacAddrH; + } + } + } + } + } + + //Mac address could be initialized by system firmware. Lan9500A will implement this way. + if((dwAddrH==0x0000FFFFUL)&&(dwAddrL==0xFFFFFFFF)){ + if ((ret = smsc9500_read_reg(dev,ADDRL, &dwAddrL)< 0)) { + SMSC_WARNING("Failed to read ADDRL: %d", ret); + return ret; + } + if ((ret = smsc9500_read_reg(dev,ADDRH, &dwAddrH)< 0)) { + SMSC_WARNING("Failed to read ADDRH: %d", ret); + return ret; + } + } + + if(((dwAddrH & 0xFFFF) == 0x0000FFFFUL) && (dwAddrL == 0xFFFFFFFF)) + { + dwAddrH=0x00000070UL; + dwAddrL=0x110F8000UL; + + SMSC_TRACE(DBG_INIT,"Mac Address is set by default to 0x%04X%08X\n", + dwAddrH,dwAddrL); + } + adapterData->macAddrHi16 = dwAddrH; + adapterData->MmacAddrLo32 = dwAddrL; + + if ((ret = smsc9500_write_reg(dev,ADDRL, dwAddrL)< 0)) { + SMSC_WARNING("Failed to write ADDRL: %d", ret); + return ret; + } + if ((ret = smsc9500_write_reg(dev,ADDRH, dwAddrH)< 0)) { + SMSC_WARNING("Failed to write ADDRH: %d", ret); + return ret; + } + + dev->net->dev_addr[0]=LOBYTE(LOWORD(dwAddrL)); + dev->net->dev_addr[1]=HIBYTE(LOWORD(dwAddrL)); + dev->net->dev_addr[2]=LOBYTE(HIWORD(dwAddrL)); + dev->net->dev_addr[3]=HIBYTE(HIWORD(dwAddrL)); + dev->net->dev_addr[4]=LOBYTE(LOWORD(dwAddrH)); + dev->net->dev_addr[5]=HIBYTE(LOWORD(dwAddrH)); + + SMSC_TRACE(DBG_INIT,"dev->net->dev_addr %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->net->dev_addr [0], dev->net->dev_addr [1], + dev->net->dev_addr [2], dev->net->dev_addr [3], + dev->net->dev_addr [4], dev->net->dev_addr [5]); + + if (!(smscusbnet_IsOperationalMode(dev))) { + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from HW_CFG : 0x%08x\n",dwReadBuf); + + dwReadBuf |=HW_CFG_BIR_; + if ((ret = smsc9500_write_reg(dev, HW_CFG, dwReadBuf))<0) + { + SMSC_WARNING("Failed to write HW_CFG_BIR_ bit in HW_CFG register, ret = %d ",ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from HW_CFG after writing HW_CFG_BIR_: 0x%08x\n",dwReadBuf); + } + + if (TurboMode) { + if(dev->udev->speed == USB_SPEED_HIGH){ + dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE; + dwBurstCap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE; + }else{ + dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; + dwBurstCap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE; + } + }else{ + dwBurstCap = 0; + dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE; + } + SMSC_TRACE(DBG_INIT,"rx_urb_size= %d\n", (int)dev->rx_urb_size); + + if ((ret = smsc9500_write_reg(dev, BURST_CAP, dwBurstCap))<0) + { + SMSC_WARNING("Failed to write BURST_CAP"); + return ret; + } + if ((ret = smsc9500_read_reg(dev,BURST_CAP,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read BURST_CAP: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from BURST_CAP after writing: 0x%08x\n",dwReadBuf); + + if ((ret = smsc9500_write_reg(dev, BULK_IN_DLY, bulkin_delay))<0) + { + SMSC_WARNING("Failed to write BULK_IN_DLY"); + return ret; + } + if ((ret = smsc9500_read_reg(dev,BULK_IN_DLY,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read BULK_IN_DLY: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from BULK_IN_DLY after writing: 0x%08x\n",dwReadBuf); + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from HW_CFG: 0x%08x\n",dwReadBuf); + + if (TurboMode) { + dwReadBuf |= (HW_CFG_MEF_|HW_CFG_BCE_); + } +#ifdef RX_OFFSET + dwReadBuf &= ~HW_CFG_RXDOFF_; + dwReadBuf |= NET_IP_ALIGN << 9; //set Rx data offset=2, Make IP header aligns on word boundary. +#endif + if ((ret = smsc9500_write_reg(dev, HW_CFG, dwReadBuf))<0) + { + SMSC_WARNING("Failed to write HW_CFG_BIR_ bit in HW_CFG register, ret = %d \n",ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev,HW_CFG,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from HW_CFG after writing: 0x%08x\n",dwReadBuf); + + if ((ret = smsc9500_write_reg(dev, INT_STS, 0xFFFFFFFFUL))<0) + { + SMSC_WARNING("Failed to write INT_STS register, ret = %d \n",ret); + return ret; + } + + + if ((ret = smsc9500_read_reg(dev,ID_REV,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read ID_REV: %d", ret); + return ret; + } + adapterData->dwIdRev = dwReadBuf; + SMSC_TRACE(DBG_INIT,"ID_REV = 0x%08x\n",dwReadBuf); + + + if ((ret = smsc9500_read_reg(dev,FPGA_REV,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read FPGA_REV: %d", ret); + return ret; + } + adapterData->dwFpgaRev = dwReadBuf; + SMSC_TRACE(DBG_INIT,"FPGA_REV = 0x%08x\n",dwReadBuf); + + if (smscusbnet_IsOperationalMode(dev)) { + + if ((ret = smsc9500_read_reg(dev,INT_EP_CTL,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read INT_EP_CTL: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from INT_EP_CTL: 0x%08x\n",dwReadBuf); + + dwReadBuf|=INT_EP_CTL_RX_FIFO_EN_; + if ((ret = smsc9500_write_reg(dev, INT_EP_CTL, dwReadBuf))<0) + { + SMSC_WARNING("Failed to write INT_EP_CTL register,ret = %d \n",ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev,INT_EP_CTL,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read INT_EP_CTL: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"Read Value from INT_EP_CTLafter writing INT_EP_CTL_RX_FIFO_EN_: 0x%08x\n",dwReadBuf); + } + +//Init Tx + + if(adapterData->UseTxCsum) + + //Set TX COE + { + if ((ret = smsc9500_read_reg(dev, COE_CR,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read COE_CR: %d", ret); + return ret; + } + dwReadBuf |= Tx_COE_EN_; + + if ((ret = smsc9500_write_reg(dev, COE_CR, dwReadBuf)< 0)) { + SMSC_WARNING("Failed to write COE_CR: %d", ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev, COE_CR,&DwTemp)< 0)) { + SMSC_WARNING("Failed to read COE_CR: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"COE_CR = 0x%08x\n", DwTemp); + } + + if ((ret = smsc9500_write_reg(dev, FLOW, 0x0UL)< 0)) { + SMSC_WARNING("Failed to write FLOW: %d", ret); + return ret; + } + + if ((ret = smsc9500_write_reg(dev, AFC_CFG, AFC_CFG_DEFAULT)< 0)) { + SMSC_WARNING("Failed to write AFC_CFG: %d", ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev, MAC_CR,&dwMacCr)< 0)) { + SMSC_WARNING("Failed to read MAC_CR: %d", ret); + return ret; + } + dwMacCr|=(MAC_CR_TXEN_); + if ((ret = smsc9500_write_reg(dev, MAC_CR, dwMacCr)< 0)) { + SMSC_WARNING("Failed to read MAC_CR: %d", ret); + return ret; + } + dwWriteBuf=TX_CFG_ON_; + if ((ret = smsc9500_write_reg(dev,TX_CFG, dwWriteBuf)< 0)) { + SMSC_WARNING("Failed to write TX_CFG: %d", ret); + return ret; + } + +//Init Rx + + //Set Vlan + { + dwWriteBuf=(u32)ETH_P_8021Q; + if ((ret = smsc9500_write_reg(dev,VLAN1, dwWriteBuf)< 0)) { + SMSC_WARNING("Failed to write VAN1: %d", ret); + return ret; + } + + } + + //Set Rx COE + if (adapterData->UseRxCsum) { + + if ((ret = smsc9500_read_reg(dev, COE_CR,&dwReadBuf)< 0)) { + SMSC_WARNING("Failed to read COE_CR: %d", ret); + return ret; + } + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15)) + dwReadBuf|=(Rx_COE_EN_ | Rx_COE_MODE_); + #else + dwReadBuf|=Rx_COE_EN_; + #endif + + if ((ret = smsc9500_write_reg(dev, COE_CR, dwReadBuf)< 0)) { + SMSC_WARNING("Failed to write COE_CR: %d", ret); + return ret; + } + + if ((ret = smsc9500_read_reg(dev, COE_CR,&DwTemp)< 0)) { + SMSC_WARNING("Failed to read COE_CR: %d", ret); + return ret; + } + SMSC_TRACE(DBG_INIT,"COE_CR = 0x%08x\n", DwTemp); + + } + + if ((ret = smsc9500_read_reg(dev, MAC_CR,&dwMacCr)< 0)) { + SMSC_WARNING("Failed to read MAC_CR: %d", ret); + return ret; + } + + dwMacCr|=MAC_CR_RXEN_; + + if ((ret = smsc9500_write_reg(dev, MAC_CR, dwMacCr)< 0)) { + SMSC_WARNING("Failed to read MAC_CR: %d", ret); + return ret; + } + + // Enable the LEDs by default + if ((ret = smsc9500_read_reg(dev, LED_GPIO_CFG,&DwTemp)< 0)) { + SMSC_WARNING("Failed to read LED_GPIO_CFG: %d", ret); + return ret; + } + DwTemp &= ~LED_GPIO_CFG_GPCTL_10_ | ~LED_GPIO_CFG_GPCTL_09_ | ~LED_GPIO_CFG_GPCTL_08_; + + if((ret = smsc9500_write_reg( dev, LED_GPIO_CFG, + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_10_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_09_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_08_SH))) < 0) + { + SMSC_WARNING("Failed to write LED_GPIO_CFG: %d", ret); + return ret; + } + + if (adapterData->LinkActLedCfg){ // Driver parameter enables separate Link and Activity LEDs in Thera 130 + // Make sure that the device is Thera 130 + if ((ret = smsc9500_read_reg(dev, ID_REV,&DwTemp)< 0)) { + SMSC_WARNING("Failed to read ID_REV: %d", ret); + return ret; + } + if(dev->chipDependFeatures[FEATURE_SEP_LEDS]){ + u32 dwLedGpioCfg = (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_10_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_09_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_08_SH); + + // Enable separate Link/Act LEDs + dwLedGpioCfg |= LED_GPIO_CFG_LED_SEL_; + + // Set LED GPIO Config + if((ret = smsc9500_write_reg(dev, LED_GPIO_CFG, dwLedGpioCfg)) < 0){ + SMSC_WARNING("Failed to write LED_GPIO_CFG: %d", ret); + return ret; + } + }else{ + // Reset to default value + adapterData->LinkActLedCfg = 0; + // Disable Software control LinkLed GPIO in case it is set + adapterData->LinkLedOnGpio = 11; + } + }else if (adapterData->LinkLedOnGpio <= 10){ // Software control LinkLed GPIO is enable + // If selected GPIO is GPIO0-GPIO7, make sure external PHY is not enable. + // GPIO0-GPIO7 pins are multiplexed with MII signals. + if (adapterData->LinkLedOnGpio < 8) + { + // Check PHY Strap + if((ret = smsc9500_read_reg(dev, HW_CFG, &DwTemp)) < 0){ + SMSC_WARNING("Failed to read HW_CFG: %d", ret); + return ret; + } + + if (DwTemp & HW_CFG_PSEL_) // External PHY is enable + { + SMSC_WARNING("External PHY Enable::GPIO%d can not be set as Link Up/Down Led\n", adapterData->LinkLedOnGpio); + // Disable Software control LinkLed GPIO + adapterData->LinkLedOnGpio = 11; + } + else + { + u32 dwGpioCfg;// = ~GPIO_CFG_GPO0_EN_ | GPIO_CFG_GPO0_DIR_; + + if((ret = smsc9500_read_reg(dev, GPIO_CFG, &dwGpioCfg)) < 0){ + SMSC_WARNING("Failed to read GPIO_CFG: %d", ret); + return ret; + } + + dwGpioCfg &= ~(GPIO_CFG_GPO0_EN_ << adapterData->LinkLedOnGpio); + dwGpioCfg |= GPIO_CFG_GPO0_DIR_ << adapterData->LinkLedOnGpio; + + // Check GPIO buffer type + if (!adapterData->LinkLedOnGpioBufType) // Push-Pull output + { + dwGpioCfg |= GPIO_CFG_GPO0_TYPE << adapterData->LinkLedOnGpio; + } + + // Check GPIO Polarity + if (adapterData->LinkLedOnGpioPolarity) // Active low + { + dwGpioCfg |= GPIO_CFG_GPO0_DATA_ << adapterData->LinkLedOnGpio; + } + + // Set GPIO Config register + if((ret = smsc9500_write_reg(dev, GPIO_CFG, dwGpioCfg)) < 0){ + SMSC_WARNING("Failed to write GPIO_CFG: %d", ret); + return ret; + } + } + } + else + { + u32 dwLedGpioCfg = (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_10_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_09_SH) | + (LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_08_SH); + + switch (adapterData->LinkLedOnGpio) + { + case 8: + // Enable GPIO 8 + dwLedGpioCfg &= (~(LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_08_SH)); + // Set Output Direction + dwLedGpioCfg |= LED_GPIO_CFG_GPDIR_08_; + // Set Buffer Type + if (!adapterData->LinkLedOnGpioBufType) // Push-Pull Output + { + dwLedGpioCfg |= LED_GPIO_CFG_GPBUF_08_; + } + + // Check GPIO Polarity + if (adapterData->LinkLedOnGpioPolarity) // Active low + { + dwLedGpioCfg |= LED_GPIO_CFG_GPDATA_08_; + } + break; + + case 9: + // Enable GPIO 9 + dwLedGpioCfg &= (~(LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_09_SH)); + // Set Output Direction + dwLedGpioCfg |= LED_GPIO_CFG_GPDIR_09_; + // Set Buffer Type + if (!adapterData->LinkLedOnGpioBufType) // Push-Pull Output + { + dwLedGpioCfg |= LED_GPIO_CFG_GPBUF_09_; + } + + // Check GPIO Polarity + if (adapterData->LinkLedOnGpioPolarity) // Active low + { + dwLedGpioCfg |= LED_GPIO_CFG_GPDATA_09_; + } + break; + + case 10: + // Enable GPIO 10 + dwLedGpioCfg &= (~(LED_GPIO_CFG_GPCTL_LED_ << LED_GPIO_CFG_GPCTL_10_SH)); + // Set Output Direction + dwLedGpioCfg |= LED_GPIO_CFG_GPDIR_10_; + + // Set Buffer Type + if (!adapterData->LinkLedOnGpioBufType) // Push-Pull Output + { + dwLedGpioCfg |= LED_GPIO_CFG_GPBUF_10_; + } + + // Check GPIO Polarity + if (adapterData->LinkLedOnGpioPolarity) // Active low + { + dwLedGpioCfg |= LED_GPIO_CFG_GPDATA_10_; + } + break; + } + + // Set LED GPIO Config + if((ret = smsc9500_write_reg( dev, LED_GPIO_CFG, dwLedGpioCfg)) < 0){ + SMSC_WARNING("Failed to write LED_GPIO_CFG: %d", ret); + return ret; + } + } + } + + if(dev->chipDependFeatures[FEATURE_NEWSTATIS_CNT]){//Statistics counters are rollover ones in LAN9500A + if(smsc9500_get_stats(dev, (void*)&rx_stats) > 0){//Save initial value + le32_to_cpus((u32*)&rx_stats.RxFifoDroppedFrames); + rx_stats.RxFifoDroppedFrames &= 0xFFFFF; //This counter has 20 bits. + dev->preRxFifoDroppedFrame = rx_stats.RxFifoDroppedFrames; + } + } + + smsc9500_rx_setmulticastlist(dev); + + if (!Phy_Initialize(dev, phy_addr, link_mode)) + return SMSC9500_FAIL; + + SMSC_TRACE(DBG_INIT,"<--------out of smsc9500_reset, return 0\n"); + + return 0; +} + +static int smsc9500_link_reset(struct usbnet *dev) +{ + int ret = 0; + SMSC_TRACE(DBG_LINK,"---->in smsc9500_link_reset\n"); + ret = Phy_CheckLink(dev); + + if(dev->StopLinkPolling){ + clear_bit (EVENT_DEV_RECOVERY, &dev->flags); + } + + if (test_bit (EVENT_DEV_RECOVERY, &dev->flags)) { + ret = smsc9500_device_recovery(dev); + clear_bit (EVENT_DEV_RECOVERY, &dev->flags); + } + + SMSC_TRACE(DBG_LINK,"<----out of smsc9500_link_reset\n"); + return ret; +} + + +static int smsc9500_stopTxPath(struct usbnet * dev) +/*++ + +Routine Description: + + This routine Stops the Tx Path at both Mac and USB + +Arguments: + +Return Value: + +--*/ +{ + u32 Value32; + int ret = SMSC9500_FAIL; + int Count = 0; + + SMSC_TRACE(DBG_TX,"--> smsc9500_stopTxPath\n"); + + // Stop the Transmit path at SCSRs + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, TX_CFG, TX_CFG_STOP_)); + // The bit should self clear as soon as the packet makes it out of the Mac, + // worst case is 10 Mbps HD and frame deferred to the maximum. This will + // be ~100ms tops. Assuming one register read per (micro)frame the case of + // high speed USB - 125us register read cycle time - is the worse and + // would need up to 800 reads. Let's just round up to 1000. + do + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, TX_CFG, &Value32)); + // Let it try to do the 1000 reads even if the reg reads are failing + // If the previous write did go thru at least this way we have a better + // chance of making sure the transmit path did stop. + } + while ( (++Count<1000) && ((Value32 & (TX_CFG_STOP_ | TX_CFG_ON_)) != 0) ); + + // Disable Mac TX + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR, &Value32)); + Value32 &= ~MAC_CR_TXEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, Value32 )); + + SMSC_TRACE(DBG_TX,"<-- smsc9500_stopTxPath\n"); + ret = SMSC9500_SUCCESS; +DONE: + return ret; +} + +static int smsc9500_stopAndFlushTxPath(struct usbnet *dev) +/*++ + +Routine Description: + + This routine Stops the Tx Path at both Mac and USB and then flushes + the Tx Fifo pointers. + +Arguments: + + +Return Value: + +--*/ +{ + + u32 Value32; + int ret = SMSC9500_FAIL; + SMSC_TRACE(DBG_TX,"--> smsc9500_stopAndFlushTxPath\n"); + + + if(smsc9500_stopTxPath(dev) < 0)return ret; + + // Flush the transmit path + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, TX_CFG, TX_CFG_FIFO_FLUSH_)); + + // Should self clear way before the read. + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, TX_CFG, &Value32)); + + if (Value32 & TX_CFG_FIFO_FLUSH_) + { + // Flush did not self clear! + goto DONE; + } + SMSC_TRACE(DBG_TX,"<-- smsc9500_stopAndFlushTxPath\n"); + ret = SMSC9500_SUCCESS; +DONE: + return ret; +} + + +static int smsc9500_stopRxPath(struct usbnet * dev) +/*++ + +Routine Description: + + This routine Stops the Rx Path at both Mac and USB + +Arguments: + +Return Value: + +--*/ +{ + u32 Value32; + int ret = SMSC9500_FAIL; + int Count = 0; + + SMSC_TRACE(DBG_RX,"--> smsc9500_stopRxPath\n"); + + // Clr the Rx Stop bit if not already + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, INT_STS, INT_STS_RX_STOP_)); + + // Disable the receiver at the Mac + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR, &Value32)); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, Value32 & (~MAC_CR_RXEN_))); + + // The Rx Stop bit should assert as soon as the packet "in flight" makes + // it into the Mac, worst case is 10 Mbps HD. This will be ~2ms tops + // Assuming one register read per (micro)frame the case of high speed USB + // - 125us register read cycle time - is the worse and would need up to + // 16 reads. Let's just round up to 20. + do + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, INT_STS, &Value32)); + // Let it try to do the 20 reads even if the reg reads are failing + // If the previous write did go thru at least this way we have a better + // chance of making sure the receiver did stop. + } + while ( (++Count<20) && ((Value32 & INT_STS_RX_STOP_) == 0) ); + + + + // Disable Mac RX + //CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR, &Value32)); + //Value32 &= ~MAC_CR_RXEN_; + //CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, Value32)); + + SMSC_TRACE(DBG_RX,"<-- smsc9500_stopRxPath\n"); + ret = SMSC9500_SUCCESS; +DONE: + return ret; +} + +static int smsc9500_stopAndFlushRxPath(struct usbnet *dev) +/*++ + +Routine Description: + + This routine Stops the Rx Path at both Mac and USB and then flushes + the Rx Fifo pointers. + +Arguments: + + +Return Value: + +--*/ +{ + u32 Value32; + int ret = SMSC9500_FAIL; + SMSC_TRACE(DBG_RX,"--> smsc9500_stopAndFlushRxPath\n"); + + + if(smsc9500_stopRxPath(dev) < 0)goto DONE; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, RX_CFG, RX_FIFO_FLUSH_)); + + // Should self clear way before the read. + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, RX_CFG, &Value32)); + + if (Value32 & RX_FIFO_FLUSH_) + { + // Flush did not self clear! + goto DONE; + } + + SMSC_TRACE(DBG_RX,"<-- smsc9500_stopAndFlushRxPath\n"); + ret = SMSC9500_SUCCESS; +DONE: + return ret; +} + +static u16 CalculateCrc16(const BYTE * bpData,const u32 dwLen, const BOOLEAN fBitReverse) +{ + const u16 wCrc16Poly = 0x8005U; // s/b international standard for CRC-16 + // x^16 + x^15 + x^2 + 1 + //u16 wCrc16Poly = 0xA001; // reverse + u16 i, j, bit; + u16 wCrc = 0xFFFFU; + u16 wMsb; + BYTE bCurrentByte; + u16 wNumOfBits = 16U; + u16 wCrcOut=0; + + wNumOfBits = wNumOfBits; // to avoid lint warning + + for (i=0; i<(u16)dwLen; i++) + { + bCurrentByte = *bpData++; + + for (bit=(u16)0U; bit<(u16)8U; bit++) + { + wMsb = wCrc >> 15; + wCrc <<= 1; + + if (wMsb ^ (u16)(bCurrentByte & 1)) + { + wCrc ^= wCrc16Poly; + wCrc |= (u16)0x0001U; + } + bCurrentByte >>= 1; + } + } + //bit reverse if needed + // so far we do not need this for 117 + // but the standard CRC-16 seems to require this. + if (fBitReverse) + { + j = 1; + for (i=(u16)(1<<(wNumOfBits-(u16)1U)); i; i = i>>1) { + if (wCrc & i) + { + wCrcOut |= j; + } + j <<= 1; + } + wCrc = wCrcOut; + } + + return wCrc; +} + +/*++ + +Routine Description: + + This routine set/reset General Purpose Output. + +Arguments: + + Adapter - pointer to our Adapter + Gpo - Gpo [0:10] + State - 1 = ON or 0 = OFF + +Return Value: + +--*/ + +static int SetGpo(struct usbnet * dev, u32 Gpo, u32 State) +{ + int ret = SMSC9500_FAIL; + u32 Value32, Status; + + if ((Gpo > 10) || (State > 1)) + { + if (State > 1) + { + SMSC_WARNING("Gpo%d state (%d) is out of range [0:1] in NICSetGpo\n", Gpo, State); + } + goto Exit_NICSetGpo; + } + + if (Gpo < 8) + { + if(smsc9500_read_reg( dev, GPIO_CFG, &Value32 ) < 0){ + SMSC_WARNING("Failed to read GPIO_CFG\n"); + goto Exit_NICSetGpo; + } + + Value32 &= (~(GPIO_CFG_GPO0_DATA_ << Gpo)); + Value32 |= (State << Gpo); + + Status = smsc9500_write_reg(dev, GPIO_CFG, Value32); + if (Status < 0) + goto Exit_NICSetGpo; + } + else + { + Status = smsc9500_read_reg(dev, LED_GPIO_CFG, &Value32 ); + if (Status < 0) + goto Exit_NICSetGpo; + + Value32 &= (~(LED_GPIO_CFG_GPDATA_08_ << (Gpo-8))); + Value32 |= (State << (Gpo-8)); + + Status = smsc9500_write_reg(dev, LED_GPIO_CFG, Value32 ); + if (Status < 0) + goto Exit_NICSetGpo; + } + + ret = SMSC9500_SUCCESS; +Exit_NICSetGpo: + return ret; +} + +/*++ + +Routine Description: + + This routine set device at suspend3 state + +Arguments: + + netdev - pointer to our device + +Return Value: + +--*/ +static int SetWakeupOnSuspend3(struct net_device *netdev) +{ + struct usbnet * dev=netdev_priv(netdev); + int ret = SMSC9500_FAIL; + u32 Value32; + BUG_ON(!dev); + + SMSC_TRACE(DBG_PWR,"Setting Suspend3 mode\n"); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, RX_FIFO_INF, &Value32)); + + if((Value32 & 0xFFFF) != 0){ + SMSC_TRACE(DBG_PWR,"Rx FIFO is not empty, abort suspend\n"); + goto DONE; + }else{ + SMSC_TRACE(DBG_PWR,"Rx FIFO is empty, continue suspend\n"); + } + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + Value32 |= PM_CTL_SUS_MODE_3 | PM_CTL_RES_CLR_WKP_STS; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + Value32 &= ~PM_CTL_WUPS_; + Value32 |= PM_CTL_WUPS_WOL_; //Clear wol status + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + + ret = SMSC9500_SUCCESS; + +DONE: + return ret; +} + +static int SetWakeupEvents(struct net_device *netdev) +{ + WAKEUP_FILTER sFilter; + u32 dwValue,dwTemp,Value32; + u16 wValue; + int i=0, ret = SMSC9500_FAIL; + int filterMaskCnt=0, filterCmdCnt=0, filterOffsetCnt=0, filterCrcCnt=0; + + struct usbnet * dev=netdev_priv(netdev); + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + + u32 opts = adapterData->WolWakeupOpts; + + BYTE bBcast[BCAST_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + BYTE bMcast[MCAST_LEN] = {0x01, 0x00, 0x5E}; + BYTE bArp[ARP_LEN] = {0x08, 0x06}; + + BUG_ON(!netdev); + + SMSC_TRACE(DBG_PWR,"In SetWakeupEvents. \n"); + + if(opts & (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_MAGIC)) + { + if(opts & WAKE_PHY) { + + // Clear any pending Phy interrupt and enable the mask for it + adapterData->systemSuspendPHYEvent = PHY_INT_MASK_LINK_DOWN_; + if(adapterData->dwLinkSettings&LINK_AUTO_NEGOTIATE) + adapterData->systemSuspendPHYEvent |= PHY_INT_MASK_ANEG_COMP_; + + EnablePHYWakeupInterrupt(dev, adapterData->systemSuspendPHYEvent); + + // If there's currently no link we can use Suspend1 + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_BSR, &Value32)); + CHECK_RETURN_STATUS(smsc9500_read_phy(dev, PHY_BSR, &Value32)); + wValue=(u16)Value32; + if (!(wValue & PHY_BSR_LINK_STATUS_)) + { + SMSC_TRACE(DBG_PWR,"Setting PHY in PD/ED Mode.\n"); + + //Enable the energy detect power-down mode + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_MODE_CTRL_STS,&dwValue)); + wValue=(u16)dwValue; + wValue |= MODE_CTRL_STS_EDPWRDOWN_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_MODE_CTRL_STS, wValue)); + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwTemp)); //Read to clear + //Enable ENERGYON interrupt source + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + wValue=(u16)dwValue; + wValue |= PHY_INT_MASK_ENERGY_ON_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK,wValue)); + + // Put Lan9500 in Suspend1 + SMSC_TRACE(DBG_PWR,"Setting Suspend1 mode\n"); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + Value32 |= PM_CTL_SUS_MODE_1; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + Value32 &= ~PM_CTL_WUPS_; + Value32 |= (PM_CTL_WUPS_ED_ | PM_CTL_ED_EN_); //Clear wol status, enable energy detection + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + } + else + { + // Put Lan9500 in Suspend0 + SMSC_TRACE(DBG_PWR,"Setting Suspend0 mode\n"); + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + Value32 |=PM_CTL_SUS_MODE_0; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + Value32 &= ~PM_CTL_WUPS_; + Value32 |= PM_CTL_WUPS_ED_; //Clear wol status + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + } + } + else + { + // Clear any pending Phy interrupt and disable the mask for it + Value32 = PHY_INT_MASK_LINK_DOWN_; + if(adapterData->dwLinkSettings&LINK_AUTO_NEGOTIATE) + Value32 |= PHY_INT_MASK_ANEG_COMP_; + DisablePHYWakeupInterrupt(dev, Value32); + } + + + if(opts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST|WAKE_MAGIC)) + { + if(opts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)){ + + int filter = 0; + + memset(&sFilter,0,sizeof(sFilter)); + + if(opts & WAKE_BCAST) { + SMSC_TRACE(DBG_PWR,"Set broadicast detection\n"); + sFilter.dwFilterMask[filter * 4] = 0x003F; + sFilter.dwFilterMask[filter * 4 + 1] = 0x00; + sFilter.dwFilterMask[filter * 4 + 2] = 0x00; + sFilter.dwFilterMask[filter * 4 + 3] = 0x00; + sFilter.dwCommad[filter/4] |= 0x05UL << ((filter % 4) * 8); //set command + sFilter.dwOffset[filter/4] |= 0x00 << ((filter % 4) * 8); + sFilter.dwCRC[filter/2] |= CalculateCrc16(bBcast, BCAST_LEN, FALSE) << ((filter % 2) * 16); + filter++; + } + if(opts & WAKE_MCAST) { + SMSC_TRACE(DBG_PWR,"Set multicast detection\n"); + sFilter.dwFilterMask[filter * 4] = 0x0007; + sFilter.dwFilterMask[filter * 4 + 1] = 0x00; + sFilter.dwFilterMask[filter * 4 + 2] = 0x00; + sFilter.dwFilterMask[filter * 4 + 3] = 0x00; + sFilter.dwCommad[filter/4] |= 0x09UL << ((filter % 4) * 8); //set command + sFilter.dwOffset[filter/4] |= 0x00 << ((filter % 4) * 8); + sFilter.dwCRC[filter/2] |= (CalculateCrc16(bMcast, MCAST_LEN, FALSE) << ((filter % 2) * 16)); + filter++; + } + if(opts & WAKE_ARP) { + SMSC_TRACE(DBG_PWR,"Set ARP detection\n"); + sFilter.dwFilterMask[filter * 4] = 0x0003; //Check two bytes for ARP + sFilter.dwFilterMask[filter * 4 + 1] = 0x00; + sFilter.dwFilterMask[filter * 4 + 2] = 0x00; + sFilter.dwFilterMask[filter * 4 + 3] = 0x00; + sFilter.dwCommad[filter/4] |= 0x05UL << ((filter % 4) * 8); //set command + sFilter.dwOffset[filter/4] |= 0x0C << ((filter % 4) * 8); + sFilter.dwCRC[filter/2]= CalculateCrc16(bArp, ARP_LEN, FALSE) << ((filter % 2) * 16); //This is ARP type + filter++; + } + if(opts & WAKE_UCAST){ + SMSC_TRACE(DBG_PWR,"Set UCAST detection\n"); + sFilter.dwFilterMask[filter * 4] = 0x003F; //Check nothing + sFilter.dwFilterMask[filter * 4 + 1] = 0x00; + sFilter.dwFilterMask[filter * 4 + 2] = 0x00; + sFilter.dwFilterMask[filter * 4 + 3] = 0x00; + sFilter.dwCommad[filter/4] |= 0x01UL << ((filter % 4) * 8); //set command + sFilter.dwOffset[filter/4] |= 0x00 << ((filter % 4) * 8); + sFilter.dwCRC[filter/2] |= CalculateCrc16(&netdev->dev_addr[0], 6, FALSE) << ((filter % 2) * 16); + filter++; + } + + if(!dev->chipDependFeatures[FEATURE_WUFF_8]){ + filterMaskCnt = LAN9500_WUFF_NUM * 4; + filterCmdCnt = LAN9500_WUFF_NUM / 4; + filterOffsetCnt = LAN9500_WUFF_NUM / 4; + filterCrcCnt = LAN9500_WUFF_NUM / 2; + }else{ + filterMaskCnt = LAN9500A_WUFF_NUM * 4; + filterCmdCnt = LAN9500A_WUFF_NUM / 4; + filterOffsetCnt = LAN9500A_WUFF_NUM / 4; + filterCrcCnt = LAN9500A_WUFF_NUM / 2; + } + + for(i=0; idata[0]); + + BUG_ON(!adapterData); + SMSC_TRACE(DBG_PWR,"In ResetWakeupEvents. \n"); + + if(!(adapterData->WolWakeupOpts & (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_MAGIC))) + return ret; + + smsc9500_clear_feature(dev,USB_DEVICE_REMOTE_WAKEUP); + if(adapterData->WolWakeupOpts & WAKE_PHY) { + //Disable the energy detect power-down mode + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_MODE_CTRL_STS,&dwValue)); + wValue=(u16)dwValue; + wValue &= ~MODE_CTRL_STS_EDPWRDOWN_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_MODE_CTRL_STS,wValue)); + + //Disable energy-detect wake-up + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,PM_CTRL,&dwValue)); + dwValue &= ~PM_CTL_PHY_RST_; + dwValue |= PM_CTL_WUPS_; //Clear wake-up status + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,PM_CTRL, dwValue)); + + DisablePHYWakeupInterrupt(dev, adapterData->systemSuspendPHYEvent); + }else{ + + if(adapterData->WolWakeupOpts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)){ + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,WUCSR,&dwValue)); + dwValue &= ~WUCSR_WAKE_EN_; //Disable Wake-up frame detection + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUCSR, dwValue)); + + } + if(adapterData->WolWakeupOpts & WAKE_MAGIC){//Set Magic packet detection + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,WUCSR,&dwValue)); + dwValue &= ~WUCSR_MPEN_; //Disable magic frame detection + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUCSR, dwValue)); + } + //Disable wake-up frame interrupt + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,PM_CTRL,&dwValue)); + dwValue &= ~PM_CTL_WOL_EN_; + dwValue |= PM_CTL_WUPS_; //Clear wake-up status + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,PM_CTRL, dwValue)); + } + + ret = 0; +DONE: + return ret; +} + +static int SetLinkDownWakeupEvents(struct usbnet *dev, int wakeUpMode) +{ + u32 dwValue; + u16 wValue; + int ret = SMSC9500_FAIL; + + BUG_ON(!dev); + + SMSC_TRACE(DBG_PWR,"In SetLinkDownWakeupEvents. \n"); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &dwValue)); + dwValue |= PM_CTL_ED_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, dwValue)); + + SMSC_TRACE(DBG_PWR,"Setting PHY in PD/ED Mode\n"); + + if(wakeUpMode == WAKEPHY_ENERGY){ + //Enable the energy detect power-down mode + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_MODE_CTRL_STS,&dwValue)); + wValue=(u16)dwValue; + wValue |= MODE_CTRL_STS_EDPWRDOWN_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_MODE_CTRL_STS, wValue)); + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read to clear + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read two times + + //Enable interrupt source + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + wValue |= (PHY_INT_MASK_ENERGY_ON_ | PHY_INT_MASK_ANEG_COMP_); + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK,wValue)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &dwValue)); + dwValue &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + dwValue |= PM_CTL_SUS_MODE_1; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, dwValue)); + + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + + SMSC_TRACE(DBG_PWR,"Setting Suspend1 mode\n"); + + }else if (wakeUpMode == WAKEPHY_NEGO_COMPLETE){ + //Disable the energy detect power-down mode + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_MODE_CTRL_STS,&dwValue)); + dwValue &= ~MODE_CTRL_STS_EDPWRDOWN_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_MODE_CTRL_STS, dwValue)); + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read to clear + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read two times + //Enable interrupt source + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + dwValue |= PHY_INT_MASK_ANEG_COMP_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK,dwValue)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &dwValue)); + dwValue &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + dwValue |= PM_CTL_SUS_MODE_0; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, dwValue)); + + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + + SMSC_TRACE(DBG_PWR,"Setting Suspend0 mode\n"); + + } + + ret = 0; +DONE: + return ret; +} + +static int ResetLinkDownWakeupEvents(struct usbnet *dev) +{ + u32 dwValue; + u16 wValue; + int ret = SMSC9500_FAIL; + + SMSC_TRACE(DBG_PWR,"In ResetLinkDownWakeupEvents. \n"); + + smsc9500_clear_feature(dev,USB_DEVICE_REMOTE_WAKEUP); + + //Disable the energy detect power-down mode + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_MODE_CTRL_STS,&dwValue)); + wValue=(u16)dwValue; + wValue &= ~MODE_CTRL_STS_EDPWRDOWN_; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_MODE_CTRL_STS,wValue)); + + //Disable ENERGYON interrupt source + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + wValue=(u16)dwValue; + wValue &= ~PHY_INT_MASK_ENERGY_ON_; + if(dev->linkDownSuspend == WAKEPHY_ENERGY){ + wValue &= ~(PHY_INT_MASK_ENERGY_ON_ | PHY_INT_MASK_ANEG_COMP_); + }else if (dev->linkDownSuspend == WAKEPHY_NEGO_COMPLETE){ + wValue &= ~PHY_INT_MASK_ANEG_COMP_; + } + + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK, wValue)); + + //Disable energy-detect wake-up + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,PM_CTRL,&dwValue)); + dwValue &= ~PM_CTL_ED_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,PM_CTRL, dwValue)); + + ret = 0; +DONE: + return ret; +} + +/*++ + +Routine Description: + + This routine enables PHY interrupt + +Arguments: + interrupt: Bit mask for PHY interrupt +Return Value: + +--*/ + +static int EnablePHYWakeupInterrupt(struct usbnet *dev, u32 interrupt) +{ + u32 dwValue; + int ret = SMSC9500_FAIL; + + BUG_ON(!dev); + + SMSC_TRACE(DBG_PWR,"In EnablePHYWakeupInterrupt. \n"); + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read to clear + + //Enable interrupt source + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + dwValue |= interrupt; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK,dwValue)); + + if(dwValue){ + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &dwValue)); + dwValue &= ~PM_CTL_PHY_RST_; + dwValue |= PM_CTL_ED_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, dwValue)); + } + + ret = SMSC9500_SUCCESS; +DONE: + return ret; + +} + +/*++ + +Routine Description: + + This routine Disables linkdown interrupt in the PHY + +Arguments: + +Return Value: + +--*/ + +static int DisablePHYWakeupInterrupt(struct usbnet *dev, u32 interrupt) +{ + u32 dwValue; + int ret = SMSC9500_FAIL; + + BUG_ON(!dev); + + SMSC_TRACE(DBG_PWR,"In DisablePHYWakeupInterrupt. \n"); + + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_SRC,&dwValue)); //Read to clear + + //Disable interrupt source + CHECK_RETURN_STATUS(smsc9500_read_phy(dev,PHY_INT_MASK,&dwValue)); + dwValue &= PHY_INT_MASK_ALL; + dwValue &= ~interrupt; + CHECK_RETURN_STATUS(smsc9500_write_phy(dev,PHY_INT_MASK,dwValue)); + + if(dwValue == 0){ //All interrupt sources are disabled + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &dwValue)); + dwValue &= ~(PM_CTL_PHY_RST_ | PM_CTL_ED_EN_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, dwValue)); + } + + ret = SMSC9500_SUCCESS; + +DONE: + return ret; + +} + +/*++ + +Routine Description: + + This routine Starts the Tx path at the MAC and sets the flag to accept + bulk out requests + +--*/ +static int StartTxPath(struct usbnet * dev ) +{ + u32 Value32; + int ret = SMSC9500_FAIL; + + // Enable Tx at MAC + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR, &Value32)); + Value32 |= MAC_CR_TXEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, Value32)); + + // Enable Tx at SCSRs + Value32 = TX_CFG_ON_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, TX_CFG, Value32)); + + ret = 0; +DONE: + return ret; +} + +/*++ + +Routine Description: + + Starts the Receive path. + + Note that if we are operating in USB bandwidth friendly mode we defer + starting the bulk in requests for now (will start when operational mode pipe signals + receive data available). + +--*/ +static int StartRxPath(struct usbnet *dev ) +{ + u32 Value32; + int ret = SMSC9500_FAIL; + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, MAC_CR, &Value32)); + Value32 |= MAC_CR_RXEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, MAC_CR, Value32)); + + ret = 0; +DONE: + return ret; +} + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11)) +static int Smsc9500_suspend (struct usb_interface *intf, u32 state) +#else +static int Smsc9500_suspend (struct usb_interface *intf, pm_message_t state) +#endif +{ + + struct usbnet *dev = usb_get_intfdata(intf); + int ret = SMSC9500_SUCCESS; + u32 dwValue; + + SMSC_TRACE(DBG_PWR,"---->Smsc9500_suspend\n"); + BUG_ON(!dev); + BUG_ON(!dev->udev); + + smsc9500_read_reg(dev,WUCSR,&dwValue); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) +#ifdef CONFIG_PM + if(dev->udev->auto_pm) //Internal pm event, autosuspend +#else + if(0) +#endif //CONFIG_PM +#else + if(0) +#endif + { + ret = Smsc9500AutoSuspend(intf, state); + }else + {//It is system suspend + ret = Smsc9500SystemSuspend(intf, state); + } + + SMSC_TRACE(DBG_PWR,"<----Smsc9500_suspend\n"); + + return ret; +} + + +static int Smsc9500AutoSuspend (struct usb_interface *intf, pm_message_t state) +{ + + struct usbnet *dev = usb_get_intfdata(intf); + u32 Value32; + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + int suspendFlag = dev->suspendFlag; + int ret = SMSC9500_SUCCESS; + + SMSC_TRACE(DBG_PWR,"---->Smsc9500AutoSuspend, suspendFlag = 0x%x\n", suspendFlag); + + dev->suspendFlag = 0; + + if (netif_running (dev->net)) + { + adapterData->wakeupOptsBackup = adapterData->WolWakeupOpts; + + smsc9500_read_phy(dev, PHY_BSR, &Value32); + if(smsc9500_read_phy(dev, PHY_BSR, &Value32) < 0){ + return SMSC9500_FAIL; + } + + if (!(Value32 & PHY_BSR_LINK_STATUS_))//last minute link check + {//Link is down + if(dev->smartDetach){//Highest priority + if(!dev->pmLock){//Resume immediately, then detach device from USB bus. + smscusbnet_defer_myevent(dev, EVENT_IDLE_RESUME); + } + ret = SMSC9500_FAIL; + goto _SUSPEND_EXIT; + }else if(dev->linkDownSuspend || (suspendFlag & AUTOSUSPEND_LINKDOWN)){//Always check it to save more power + dev->suspendFlag |= AUTOSUSPEND_LINKDOWN; + ret = SetLinkDownWakeupEvents(dev, dev->linkDownSuspend); + }else if(suspendFlag & AUTOSUSPEND_DYNAMIC){//suspend on s0, work for lan9500 and lan9500a + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC; + adapterData->WolWakeupOpts = (WAKE_UCAST | WAKE_BCAST | WAKE_MCAST | WAKE_ARP); + ret = SetWakeupEvents(dev->net); + + //Save PHY interrupt event, so we can clear it after waking up. + adapterData->dynamicSuspendPHYEvent = PHY_INT_MASK_ENERGY_ON_ | PHY_INT_MASK_ANEG_COMP_; + ret = EnablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + + }else if(suspendFlag & AUTOSUSPEND_DYNAMIC_S3){//suspend on s3, only for lan9500a + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC_S3; + ret = SetWakeupOnSuspend3(dev->net); + if(ret != SMSC9500_FAIL){ + //Save PHY interrupt event, so we can clear it after waking up. + adapterData->dynamicSuspendPHYEvent = PHY_INT_MASK_ENERGY_ON_ | PHY_INT_MASK_ANEG_COMP_; + ret = EnablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + } + + }else{ + SMSC_WARNING("auto suspend event is null\n"); + ret = SMSC9500_FAIL; + } + + }else{//link is up + + if(suspendFlag & AUTOSUSPEND_DYNAMIC){//suspend on s0, work for lan9500 and lan9500a + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC; + adapterData->WolWakeupOpts = (WAKE_UCAST | WAKE_BCAST | WAKE_MCAST | WAKE_ARP); + ret = SetWakeupEvents(dev->net); + + //Save PHY interrupt event, so we can clear it after waking up. + adapterData->dynamicSuspendPHYEvent = PHY_INT_MASK_LINK_DOWN_; + ret = EnablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + }else if(suspendFlag & AUTOSUSPEND_DYNAMIC_S3){//suspend on s3, only for lan9500a + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC_S3; + ret = SetWakeupOnSuspend3(dev->net); + if(ret != SMSC9500_FAIL){ + //Save PHY interrupt event, so we can clear it after waking up. + adapterData->dynamicSuspendPHYEvent = PHY_INT_MASK_LINK_DOWN_; + ret = EnablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + } + }else{ + ret = SMSC9500_FAIL; + } + } + + if(ret != SMSC9500_SUCCESS){ + //SMSC_WARNING("Failed to suspend device\n"); + dev->suspendFlag = 0; + if(!dev->pmLock){//Resume immediately + smscusbnet_defer_myevent(dev, EVENT_IDLE_RESUME); + Tx_WakeQueue(dev,0x04UL); + tasklet_schedule (&dev->bh); + } + goto _SUSPEND_EXIT; + }else{ + smscusbnet_FreeQueue(dev); + del_timer_sync(&dev->LinkPollingTimer); + } + + }else{//Interface down + u32 Value32; + SMSC_TRACE(DBG_PWR,"Interface is down, set suspend2 mode\n"); + smsc9500_read_reg(dev, PM_CTRL, &Value32); + Value32 &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + Value32 |= PM_CTL_SUS_MODE_2; + smsc9500_write_reg(dev, PM_CTRL, Value32); + + dev->suspendFlag |= AUTOSUSPEND_INTFDOWN; + } + +_SUSPEND_EXIT: + SMSC_TRACE(DBG_PWR,"<----Smsc9500AutoSuspend\n"); + return ret; +} + +static int Smsc9500SystemSuspend (struct usb_interface *intf, pm_message_t state) +{ + + struct usbnet *dev = usb_get_intfdata(intf); + + SMSC_TRACE(DBG_PWR,"---->Smsc9500SystemSuspend\n"); + + dev->idleCount = 0; + dev->suspendFlag = 0; + + if (dev->net && netif_running (dev->net) && netif_device_present (dev->net)) + { + netif_device_detach (dev->net); + smscusbnet_FreeQueue(dev); + + dev->StopLinkPolling=TRUE; + del_timer_sync(&dev->LinkPollingTimer); + + Tx_StopQueue(dev,0x04UL); + smsc9500_stopAndFlushTxPath(dev); + smsc9500_stopAndFlushRxPath(dev); + + SetWakeupEvents(dev->net); + + }else{ + // Put Lan9500 in Suspend2 + u32 Value32; + SMSC_TRACE(DBG_PWR,"Setting Suspend2 mode\n"); + smsc9500_read_reg(dev, PM_CTRL, &Value32); + Value32 &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + Value32 |= PM_CTL_SUS_MODE_2; + smsc9500_write_reg(dev, PM_CTRL, Value32); + + dev->suspendFlag |= AUTOSUSPEND_INTFDOWN; + } + + SMSC_TRACE(DBG_PWR,"<----Smsc9500SystemSuspend\n"); + + return SMSC9500_SUCCESS; +} + +static int Smsc9500_resume(struct usb_interface *intf) +{ + int ret = SMSC9500_SUCCESS; + + struct usbnet *dev = usb_get_intfdata(intf); + + SMSC_TRACE(DBG_PWR,"--->in Smsc9500_resume\n"); + BUG_ON(!dev); + BUG_ON(!dev->udev); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) + //autoresume + if(dev->suspendFlag) +#else + if(0) +#endif + { + ret = Smsc9500AutoResume(intf); + }else + { + ret = Smsc9500SystemResume(intf); + } + SMSC_TRACE(DBG_PWR,"------->out of in Smsc9500_resume\n"); + return ret; +} + +static int Smsc9500AutoResume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[0]); + u32 dwValue; + + SMSC_TRACE(DBG_PWR,"--->in Smsc9500AutoResume\n"); + + clear_bit(EVENT_DEV_RECOVERY, &dev->flags); + + if (dev->suspendFlag & AUTOSUSPEND_INTFDOWN){ + SMSC_TRACE(DBG_PWR,"Resume from interface down\n"); + goto _EXIT_AUTORESUME; + } + + if(!dev->pmLock){ + smscusbnet_defer_myevent(dev, EVENT_IDLE_RESUME); + } + + if(dev->suspendFlag & AUTOSUSPEND_LINKDOWN){ + ResetLinkDownWakeupEvents(dev); + }else if(dev->suspendFlag & AUTOSUSPEND_DYNAMIC){ + DisablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + ResetWakeupEvents(dev->net); + }else if(dev->suspendFlag & AUTOSUSPEND_DYNAMIC_S3){//Resume from suspend3 + smsc9500_clear_feature(dev,USB_DEVICE_REMOTE_WAKEUP); + + DisablePHYWakeupInterrupt(dev, adapterData->dynamicSuspendPHYEvent); + + if(smsc9500_read_reg(dev, PM_CTRL, &dwValue) < 0){ + SMSC_WARNING("Failed to read PM_CTRL"); + } + dwValue &= (~(PM_CTL_SUS_MODE_ | PM_CTL_PHY_RST_)); + dwValue |= PM_CTL_SUS_MODE_2; + + if(smsc9500_write_reg(dev, PM_CTRL, dwValue) < 0){ + SMSC_WARNING("Failed to write PM_CTRL"); + } + dwValue &= ~PM_CTL_WUPS_; + dwValue |= PM_CTL_WUPS_WOL_; + + if(smsc9500_write_reg(dev, PM_CTRL, dwValue) < 0){ //Should not change suspend_mode while clearing WUPS_sts[1] + SMSC_WARNING("Failed to write PM_CTRL"); + } + + } + //if(dev->delay.function)(dev->delay.function)((unsigned long)dev); + tasklet_schedule (&dev->bh); + + adapterData->WolWakeupOpts = adapterData->wakeupOptsBackup; + adapterData->wakeupOptsBackup = 0; + + Tx_WakeQueue(dev,0x04UL); + +_EXIT_AUTORESUME: + dev->idleCount = 0; + dev->suspendFlag = 0; + + SMSC_TRACE(DBG_PWR,"------->out of in Smsc9500AutoResume\n"); + return SMSC9500_SUCCESS; +} + +static int Smsc9500SystemResume(struct usb_interface *intf) +{ + int ret=0; +#if 0 /* commenting as it was causing panic on harmony platform */ + u32 dwValue; +#endif + + struct usbnet *dev = usb_get_intfdata(intf); + + SMSC_TRACE(DBG_PWR,"--->in Smsc9500SystemResume\n"); + + if(netif_running (dev->net) && !netif_device_present (dev->net)){ + netif_device_attach (dev->net); + } + +#if 0 /* commenting as it was causing panic on harmony platform */ + //test if hcd is still alive + + if(smsc9500_read_reg_async(dev, MAC_CR, &dwValue, TRUE) == ASYNC_RW_SUCCESS){ + SMSC_TRACE(DBG_PWR,"hcd is alive\n"); +#endif + ret=smsc9500_reset(dev); + StartTxPath(dev); + StartRxPath(dev); + + ResetWakeupEvents(dev->net); + +#if 0 /* commenting as it was causing panic on harmony platform */ + }else{//This will happen on suspend-to-disk, if we access usb bus, will hang on usb_kill_urb + SMSC_TRACE(DBG_PWR,"no hcd\n"); + } +#endif + + Tx_WakeQueue(dev,0x04UL); + + init_timer(&(dev->LinkPollingTimer)); + dev->StopLinkPolling=FALSE; + dev->LinkPollingTimer.function=smscusbnet_linkpolling; + dev->LinkPollingTimer.data=(unsigned long) dev; + dev->LinkPollingTimer.expires=jiffies+HZ; + add_timer(&(dev->LinkPollingTimer)); + tasklet_schedule (&dev->bh); + + dev->idleCount = 0; + + SMSC_TRACE(DBG_PWR,"------->out of in Smsc9500SystemResume\n"); + return SMSC9500_SUCCESS; +} + +static int smsc9500_device_recovery(struct usbnet *dev) +{ + u32 dwReadBuf; + + BUG_ON(!dev); + + if (dev->net && netif_device_present (dev->net)) + { + + SMSC_WARNING("Device recovery is in progress\n"); + + if (smsc9500_read_reg(dev,INT_STS,&dwReadBuf)< 0)return SMSC9500_FAIL; + + smscusbnet_FreeQueue(dev); + + dev->StopLinkPolling=TRUE; + del_timer_sync(&dev->LinkPollingTimer); + + Tx_StopQueue(dev,0x04UL); + + if(smsc9500_stopAndFlushTxPath(dev) < 0)return SMSC9500_FAIL; + if(smsc9500_stopAndFlushRxPath(dev) < 0)return SMSC9500_FAIL; + + if(dwReadBuf & INT_STS_TXE_){// reset only when TXE occurs + if(smsc9500_reset(dev) < 0)return SMSC9500_FAIL; + } + + if(StartTxPath(dev) < 0)return SMSC9500_FAIL; + if(StartRxPath(dev) < 0)return SMSC9500_FAIL; + + Tx_WakeQueue(dev,0x04UL); + + init_timer(&(dev->LinkPollingTimer)); + dev->StopLinkPolling=FALSE; + dev->LinkPollingTimer.function=smscusbnet_linkpolling; + dev->LinkPollingTimer.data=(unsigned long) dev; + dev->LinkPollingTimer.expires=jiffies+HZ; + add_timer(&(dev->LinkPollingTimer)); + + tasklet_schedule (&dev->bh); + + SMSC_WARNING("Device recovery is done\n"); + } + + return SMSC9500_SUCCESS; +} + +static const struct driver_info smsc9500_info = { + .description = "smsc9500 USB 2.0 Ethernet", + .bind = smsc9500_bind, + .unbind=smsc9500_unbind, + .status = smsc9500_status, + .link_reset = smsc9500_link_reset, + .reset = smsc9500_reset, + .flags = FLAG_ETHER, + .rx_fixup = smsc9500_rx_fixup, + .tx_fixup = smsc9500_tx_fixup, + .rx_setmulticastlist=smsc9500_rx_setmulticastlist, +}; + +static const struct usb_device_id products [] = { + { + // SMSC9500 USB Ethernet Device + USB_DEVICE (0x0424, PID_LAN9500), + .driver_info = (unsigned long) &smsc9500_info, + }, + { + // SMSC9512/9514 USB hub with Ethernet Device + USB_DEVICE (0x0424, PID_LAN9512), + .driver_info = (unsigned long) &smsc9500_info, + }, + { + // SMSC9500A USB Ethernet Device + USB_DEVICE (0x0424, PID_LAN9500A), + .driver_info = (unsigned long) &smsc9500_info, + }, + { }, // END +}; + +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver smsc9500_driver = { + .name = "smsc9500", + .id_table = products, + .probe = smscusbnet_probe, + .suspend = Smsc9500_suspend, + .resume = Smsc9500_resume, + .disconnect = smscusbnet_disconnect, + .reset_resume = Smsc9500SystemResume, +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) + .supports_autosuspend = 1, +#endif +}; + +static int __init smsc9500_init(void) +{ + + return usb_register(&smsc9500_driver); +} +module_init(smsc9500_init); + +static void __exit smsc9500_exit(void) +{ + usb_deregister(&smsc9500_driver); +} +module_exit(smsc9500_exit); + +MODULE_AUTHOR("Nancy Lin and Sean(Xiang) Chen"); +MODULE_DESCRIPTION("SMSC9500 USB 2.0 Ethernet Devices"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/net/usb/smsc9500.h b/drivers/net/usb/smsc9500.h new file mode 100644 index 000000000000..556744ab1f47 --- /dev/null +++ b/drivers/net/usb/smsc9500.h @@ -0,0 +1,775 @@ +/*************************************************************************** + * + * Copyright (C) 2007-2010 SMSC + * + * 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. + * + *************************************************************************** + * File: smsc9500.h + ***************************************************************************/ + + +#ifndef _LAN9500_H +#define _LAN9500_H + +typedef unsigned char BYTE; + +#define ON TRUE +#define OFF FALSE +#define STATUS_WORD_LEN 4 + +#define RX_OFFSET +#define LAN_REGISTER_RANGE 0xFF +#define MAC_REGISTER_RANGE_MIN 0x100 +#define MAC_REGISTER_RANGE_MAX 0x130 +#define MAX_EEPROM_SIZE 512 + +//product id definition +#define PID_LAN9500 0x9500 +#define PID_LAN9500A 0x9E00 +#define PID_LAN9512 0xEC00 + + +// Tx MAT Cmds +#define TX_CMD_A_DATA_OFFSET_ (0x001F0000UL) +#define TX_CMD_A_FIRST_SEG_ (0x00002000UL) +#define TX_CMD_A_LAST_SEG_ (0x00001000UL) +#define TX_CMD_A_BUF_SIZE_ (0x000007FFUL) + +#define TX_CMD_B_CSUM_ENABLE (0x00004000UL) +#define TX_CMD_B_ADD_CRC_DISABLE_ (0x00002000UL) +#define TX_CMD_B_DISABLE_PADDING_ (0x00001000UL) +#define TX_CMD_B_PKT_BYTE_LENGTH_ (0x000007FFUL) + +// Rx sts Wd +#define RX_STS_FF_ 0x40000000UL // Filter Fail +#define RX_STS_FL_ 0x3FFF0000UL // Frame Length +#define RX_STS_DL_SHIFT_ 16UL +#define RX_STS_ES_ 0x00008000UL // Error Summary +#define RX_STS_BF_ 0x00002000UL // Broadcast Frame +#define RX_STS_LE_ 0x00001000UL // Length Error +#define RX_STS_RF_ 0x00000800UL // Runt Frame +#define RX_STS_MF_ 0x00000400UL // Multicast Frame +#define RX_STS_TL_ 0x00000080UL // Frame too long +#define RX_STS_CS_ 0x00000040UL // Collision Seen +#define RX_STS_FT_ 0x00000020UL // Frame Type +#define RX_STS_RW_ 0x00000010UL // Receive Watchdog +#define RX_STS_ME_ 0x00000008UL // Mii Error +#define RX_STS_DB_ 0x00000004UL // Dribbling +#define RX_STS_CRC_ 0x00000002UL // CRC Error + +#define RX_STS_ERR_MASK_ 0x000088DEUL + + +// SCSRs +#define ID_REV (0x00UL) + #define ID_REV_CHIP_ID_MASK_ (0xffff0000UL) + #define ID_REV_CHIP_REV_MASK_ (0x0000ffffUL) + #define ID_REV_CHIP_ID_9500_ (0x9500UL) + #define GetChipIdFromID_REV(dwReg) ((dwReg & ID_REV_CHIP_ID_MASK_) >> 16) + #define GetChiprevFromID_REV(dwReg) (dwReg & ID_REV_CHIP_REV_MASK_) + +#define FPGA_REV (0x04UL) + +#define INT_STS (0x08UL) + #define INT_STS_TX_STOP_ (0x00020000UL) + #define INT_STS_RX_STOP_ (0x00010000UL) + #define INT_STS_PHY_INT_ (0x00008000UL) + #define INT_STS_TXE_ (0x00004000UL) + #define INT_STS_TDFU_ (0x00002000UL) + #define INT_STS_TDFO_ (0x00001000UL) + #define INT_STS_RXDF_ (0x00000800UL) + #define INT_STS_GPIOS_ (0x000007FFUL) + +#define RX_CFG (0x0CUL) + #define RX_FIFO_FLUSH_ (0x00000001UL) + +#define TX_CFG (0x10UL) + #define TX_CFG_ON_ (0x00000004UL) + #define TX_CFG_STOP_ (0x00000002UL) + #define TX_CFG_FIFO_FLUSH_ (0x00000001UL) + +#define HW_CFG (0x14UL) + #define HW_CFG_SMDET_STS (0x00040000UL) + #define HW_CFG_SMDET_EN (0x00020000UL) + #define HW_CFG_EEM (0x00010000UL) + #define HW_CFG_RST_PROTECT (0x00008000UL) + #define HW_CFG_PHY_BOOST (0x00006000UL) + #define HW_CFG_PHY_BOOST_NORMAL (0x00000000UL) + #define HW_CFG_PHY_BOOST_4 (0x00002000UL) + #define HW_CFG_PHY_BOOST_8 (0x00004000UL) + #define HW_CFG_PHY_BOOST_12 (0x00006000UL) + #define HW_CFG_BIR_ (0x00001000UL) + #define HW_CFG_LEDB_ (0x00000800UL) + #define HW_CFG_RXDOFF_ (0x00000600UL) + #define HW_CFG_RXDOFF_2_ (0x00000400UL) + #define HW_CFG_SBP_ (0x00000100UL) + #define HW_CFG_IME_ (0x00000080UL) + #define HW_CFG_DRP_ (0x00000040UL) + #define HW_CFG_MEF_ (0x00000020UL) + #define HW_CFG_ETC_ (0x00000010UL) + #define HW_CFG_LRST_ (0x00000008UL) + #define HW_CFG_PSEL_ (0x00000004UL) + #define HW_CFG_BCE_ (0x00000002UL) + #define HW_CFG_SRST_ (0x00000001UL) + +#define RX_FIFO_INF (0x18UL) + +#define TX_FIFO_INF (0x1CUL) + +#define PM_CTRL (0x20UL) + #define PM_CTL_RES_CLR_WKP_STS (0x00000200UL) + #define PM_CTL_RES_CLR_WKP_EN (0x00000100UL) + #define PM_CTL_DEV_RDY_ (0x00000080UL) + #define PM_CTL_SUS_MODE_ (0x00000060UL) + #define PM_CTL_SUS_MODE_0 (0x00000000UL) + #define PM_CTL_SUS_MODE_1 (0x00000020UL) + #define PM_CTL_SUS_MODE_2 (0x00000040UL) + #define PM_CTL_SUS_MODE_3 (0x00000060UL) + #define PM_CTL_PHY_RST_ (0x00000010UL) + #define PM_CTL_WOL_EN_ (0x00000008UL) + #define PM_CTL_ED_EN_ (0x00000004UL) + #define PM_CTL_WUPS_ (0x00000003UL) + #define PM_CTL_WUPS_NO_ (0x00000000UL) + #define PM_CTL_WUPS_ED_ (0x00000001UL) + #define PM_CTL_WUPS_WOL_ (0x00000002UL) + #define PM_CTL_WUPS_MULTI_ (0x00000003UL) + +#define LED_GPIO_CFG (0x24UL) + #define LED_GPIO_CFG_LED_SEL_ (0x80000000UL) + #define LED_GPIO_CFG_GPCTL_10_ (0x03000000UL) + #define LED_GPIO_CFG_GPCTL_10_SH 24 + #define LED_GPIO_CFG_GPCTL_09_ (0x00300000UL) + #define LED_GPIO_CFG_GPCTL_09_SH 20 + #define LED_GPIO_CFG_GPCTL_08_ (0x00030000UL) + #define LED_GPIO_CFG_GPCTL_08_SH 16 + + #define LED_GPIO_CFG_GPCTL_DIAG2_ (0x3UL) + #define LED_GPIO_CFG_GPCTL_DIAG1_ (0x2UL) + #define LED_GPIO_CFG_GPCTL_LED_ (0x1UL) + #define LED_GPIO_CFG_GPCTL_GPIO_ (0x0UL) + + #define LED_GPIO_CFG_GPBUF_ (0x00000700UL) + #define LED_GPIO_CFG_GPBUF_10_ (0x00000400UL) + #define LED_GPIO_CFG_GPBUF_09_ (0x00000200UL) + #define LED_GPIO_CFG_GPBUF_08_ (0x00000100UL) + #define LED_GPIO_CFG_GPDIR_ (0x00000070UL) + #define LED_GPIO_CFG_GPDIR_10_ (0x00000040UL) + #define LED_GPIO_CFG_GPDIR_09_ (0x00000020UL) + #define LED_GPIO_CFG_GPDIR_08_ (0x00000010UL) + #define LED_GPIO_CFG_GPDATA_ (0x00000007UL) + #define LED_GPIO_CFG_GPDATA_10_ (0x00000004UL) + #define LED_GPIO_CFG_GPDATA_09_ (0x00000002UL) + #define LED_GPIO_CFG_GPDATA_08_ (0x00000001UL) + +#define GPIO_CFG (0x28UL) +#define GPIO_CFG_GPEN_ (0xFF000000UL) + #define GPIO_CFG_GPO0_EN_ (0x01000000UL) +#define GPIO_CFG_GPTYPE (0x00FF0000UL) + #define GPIO_CFG_GPO0_TYPE (0x00010000UL) +#define GPIO_CFG_GPDIR_ (0x0000FF00UL) + #define GPIO_CFG_GPO0_DIR_ (0x00000100UL) +#define GPIO_CFG_GPDATA_ (0x000000FFUL) + #define GPIO_CFG_GPO0_DATA_ (0x00000001UL) + +#define AFC_CFG (0x2CUL) + #define AFC_CFG_DEFAULT (0x00f830A1UL) // Hi watermark = 15.5Kb (~10 mtu pkts) + // low watermark = 3k (~2 mtu pkts) + // backpressure duration = ~ 350us + // Apply FC on any frame. + +#define E2P_CMD (0x30UL) + #define E2P_CMD_BUSY_ 0x80000000UL + #define E2P_CMD_MASK_ 0x70000000UL + #define E2P_CMD_READ_ 0x00000000UL + #define E2P_CMD_EWDS_ 0x10000000UL + #define E2P_CMD_EWEN_ 0x20000000UL + #define E2P_CMD_WRITE_ 0x30000000UL + #define E2P_CMD_WRAL_ 0x40000000UL + #define E2P_CMD_ERASE_ 0x50000000UL + #define E2P_CMD_ERAL_ 0x60000000UL + #define E2P_CMD_RELOAD_ 0x70000000UL + #define E2P_CMD_TIMEOUT_ 0x00000400UL + #define E2P_CMD_LOADED_ 0x00000200UL + #define E2P_CMD_ADDR_ 0x000001FFUL + #define MAX_EEPROM_SIZE 512 +#define E2P_DATA (0x34UL) + #define E2P_DATA_MASK_ 0x000000FFUL + +#define BURST_CAP (0x38UL) +#define STRAP_DBG (0x3CUL) +#define DP_SEL (0x40UL) + #define DP_SEL_DPRDY (0x80000000UL) + #define DP_SEL_RSEL (0x00000006UL) + #define DP_SEL_RSEL_FCT (0x00000000UL) + #define DP_SEL_RSEL_EEPROM (0x00000002UL) + #define DP_SEL_RSEL_TXTLI (0x00000004UL) + #define DP_SEL_RSEL_RXTLI (0x00000006UL) + #define DP_SEL_TESTEN (0x00000001UL) +#define DP_CMD (0x44UL) + #define DP_CMD_READ (0x0) + #define DP_CMD_WRITE (0x1) +#define DP_ADDR (0x48UL) +#define DP_DATA0 (0x4CUL) +#define DP_DATA1 (0x50UL) +#define RAM_BIST0 (0x54UL) +#define RAM_BIST1 (0x58UL) +#define RAM_BIST2 (0x5CUL) +#define RAM_BIST3 (0x60UL) +#define GPIO_WAKE (0x64UL) +#define INT_EP_CTL (0x68UL) +#define BULK_IN_DLY (0x6CUL) + +#define INT_EP_CTL_INTEP_ (0x80000000UL) +#define INT_EP_CTL_MACRTO_ (0x00080000UL) +#define INT_EP_CTL_RX_FIFO_EN_ (0x00040000UL) +#define INT_EP_CTL_TX_STOP_ (0x00020000UL) +#define INT_EP_CTL_RX_STOP_ (0x00010000UL) +#define INT_EP_CTL_PHY_INT_ (0x00008000UL) +#define INT_EP_CTL_TXE_ (0x00004000UL) +#define INT_EP_CTL_TDFU_ (0x00002000UL) +#define INT_EP_CTL_TDFO_ (0x00001000UL) +#define INT_EP_CTL_RXDF_ (0x00000800UL) +#define INT_EP_CTL_GPIOS_ (0x000007FFUL) + +// MAC CSRs +#define MAC_CR (0x100UL) + #define MAC_CR_RXALL_ 0x80000000UL + #define MAC_CR_RCVOWN_ 0x00800000UL + #define MAC_CR_LOOPBK_ 0x00200000UL + #define MAC_CR_FDPX_ 0x00100000UL + #define MAC_CR_MCPAS_ 0x00080000UL + #define MAC_CR_PRMS_ 0x00040000UL + #define MAC_CR_INVFILT_ 0x00020000UL + #define MAC_CR_PASSBAD_ 0x00010000UL + #define MAC_CR_HFILT_ 0x00008000UL + #define MAC_CR_HPFILT_ 0x00002000UL + #define MAC_CR_LCOLL_ 0x00001000UL + #define MAC_CR_BCAST_ 0x00000800UL + #define MAC_CR_DISRTY_ 0x00000400UL + #define MAC_CR_PADSTR_ 0x00000100UL + #define MAC_CR_BOLMT_MASK 0x000000C0UL + #define MAC_CR_DFCHK_ 0x00000020UL + #define MAC_CR_TXEN_ 0x00000008UL + #define MAC_CR_RXEN_ 0x00000004UL +#define ADDRH (0x104UL) +#define ADDRL (0x108UL) +#define HASHH (0x10CUL) +#define HASHL (0x110UL) + +#define MII_ADDR (0x114UL) + #define MII_WRITE_ (0x02UL) + #define MII_BUSY_ (0x01UL) + #define MII_READ_ (0x00UL) // ~of MII Write bit + + #define MII_DATA (0x118UL) + #define FLOW (0x11CUL) + #define FLOW_FCPT_ (0xFFFF0000UL) + #define FLOW_FCPASS_ (0x00000004UL) + #define FLOW_FCEN_ (0x00000002UL) + #define FLOW_FCBSY_ (0x00000001UL) + +#define VLAN1 (0x120UL) +#define VLAN2 (0x124UL) +#define WUFF (0x128UL) +#define WUCSR (0x12CUL) + #define WUCSR_GUE_ (0x00000200UL) + #define WUCSR_WUFR_ (0x00000040UL) + #define WUCSR_MPR_ (0x00000020UL) + #define WUCSR_WAKE_EN_ (0x00000004UL) + #define WUCSR_MPEN_ (0x00000002UL) + +#define COE_CR (0x130UL) + #define Tx_COE_EN_ (0x00010000UL) + #define Rx_COE_MODE_ (0x00000002UL) + #define Rx_COE_EN_ (0x00000001UL) + +//////////////////////////////////// +// PHY Definitions +//////////////////////////////////// +#define LAN_PHY_ADDR (1UL) + +#define PHY_BCR ((u32)0U) +#define PHY_BCR_RESET_ ((u16)0x8000U) +#define PHY_BCR_LOOPBACK_ ((u16)0x4000U) +#define PHY_BCR_SPEED_SELECT_ ((u16)0x2000U) +#define PHY_BCR_AUTO_NEG_ENABLE_ ((u16)0x1000U) +#define PHY_BCR_POWER_DOWN ((u16)0x0800U) +#define PHY_BCR_ISOLATE ((u16)0x0400U) +#define PHY_BCR_RESTART_AUTO_NEG_ ((u16)0x0200U) +#define PHY_BCR_DUPLEX_MODE_ ((u16)0x0100U) + +#define PHY_BSR ((u32)1U) + #define PHY_BSR_AUTO_NEG_COMP_ ((u16)0x0020U) + #define PHY_BSR_REMOTE_FAULT_ ((u16)0x0010U) + #define PHY_BSR_LINK_STATUS_ ((u16)0x0004U) + +#define PHY_ID_1 ((u32)2U) +#define PHY_ID_2 ((u32)3U) + +#define PHY_ANEG_ADV ((u32)4U) + +#define PHY_ANEG_ADV_PAUSE_ ((u16)0x0C00) +#define PHY_ANEG_ADV_ASYMP_ ((u16)0x0800) +#define PHY_ANEG_ADV_SYMP_ ((u16)0x0400) +#define PHY_ANEG_ADV_SPEED_ ((u16)0x1E0) +#define PHY_ANEG_ADV_100F_ ((u16)0x100) +#define PHY_ANEG_ADV_100H_ ((u16)0x80) +#define PHY_ANEG_ADV_10F_ ((u16)0x40) +#define PHY_ANEG_ADV_10H_ ((u16)0x20) + + +#define PHY_ANEG_LPA ((u32)5U) +#define PHY_ANEG_LPA_ASYMP_ ((u16)0x0800) +#define PHY_ANEG_LPA_SYMP_ ((u16)0x0400) +#define PHY_ANEG_LPA_T4_ ((u16)0x0200) +#define PHY_ANEG_LPA_100FDX_ ((u16)0x0100) +#define PHY_ANEG_LPA_100HDX_ ((u16)0x0080) +#define PHY_ANEG_LPA_10FDX_ ((u16)0x0040) +#define PHY_ANEG_LPA_10HDX_ ((u16)0x0020) + +#define PHY_ANEG_REG ((u32)6U) /* Auto-Negotiation Expansion Reg */ +#define PHY_LP_AN_ABLE ((u16)0x0001) + +#define PHY_SILICON_REV ((u32)16U) + + +#define PHY_MODE_CTRL_STS ((u32)17) // Mode Control/Status Register + #define MODE_CTRL_STS_FASTRIP_ ((u16)0x4000U) + #define MODE_CTRL_STS_EDPWRDOWN_ ((u16)0x2000U) + #define MODE_CTRL_STS_LOWSQEN_ ((u16)0x0800U) + #define MODE_CTRL_STS_MDPREBP_ ((u16)0x0400U) + #define MODE_CTRL_STS_FARLOOPBACK_ ((u16)0x0200U) + #define MODE_CTRL_STS_FASTEST_ ((u16)0x0100U) + #define MODE_CTRL_STS_REFCLKEN_ ((u16)0x0010U) + #define MODE_CTRL_STS_PHYADBP_ ((u16)0x0008U) + #define MODE_CTRL_STS_FORCE_G_LINK_ ((u16)0x0004U) + #define MODE_CTRL_STS_ENERGYON_ ((u16)0x0002U) + +#define PHY_SPECIAL_MODES ((u32)18) // Special Modes + +#define PHY_TSTCNTL ((u32)20) // Testability/Configuration Control +#define PHY_TSTREAD1 ((u32)21) // Testability data Read for LSB +#define PHY_TSTREAD2 ((u32)22) // Testability data Read for MSB +#define PHY_TSTWRITE ((u32)23) // Testability/Configuration data Write + +#define PHY_SPECIAL_CTRL_STS ((u32)27) + #define SPECIAL_CTRL_STS_OVRRD_AMDIX_ ((u16)0x8000U) + #define SPECIAL_CTRL_STS_AMDIX_ENABLE_ ((u16)0x4000U) + #define SPECIAL_CTRL_STS_AMDIX_STATE_ ((u16)0x2000U) + +#define PHY_SITC ((u32)28) //Special internal testability controls + +#define PHY_INT_SRC ((u32)29) +#define PHY_INT_SRC_ENERGY_ON_ ((u16)0x0080U) +#define PHY_INT_SRC_ANEG_COMP_ ((u16)0x0040U) +#define PHY_INT_SRC_REMOTE_FAULT_ ((u16)0x0020U) +#define PHY_INT_SRC_LINK_DOWN_ ((u16)0x0010U) + +#define PHY_INT_MASK ((u32)30) +#define PHY_INT_MASK_ALL ((u16)0x00FFU) +#define PHY_INT_MASK_ENERGY_ON_ ((u16)0x0080U) +#define PHY_INT_MASK_ANEG_COMP_ ((u16)0x0040U) +#define PHY_INT_MASK_REMOTE_FAULT_ ((u16)0x0020U) +#define PHY_INT_MASK_LINK_DOWN_ ((u16)0x0010U) + +#define PHY_SPECIAL ((u32)31) +#define PHY_SPECIAL_SPD_ ((u16)0x001CU) +#define PHY_SPECIAL_SPD_10HALF_ ((u16)0x0004U) +#define PHY_SPECIAL_SPD_10FULL_ ((u16)0x0014U) +#define PHY_SPECIAL_SPD_100HALF_ ((u16)0x0008U) +#define PHY_SPECIAL_SPD_100FULL_ ((u16)0x0018U) + +#define AMDIX_DISABLE_STRAIGHT ((u16)0x0U) +#define AMDIX_DISABLE_CROSSOVER ((u16)0x01U) +#define AMDIX_ENABLE ((u16)0x02U) + + + +#define PHY_LAN83C180_INT_ACK_REG 0x15 +#define PHY_LAN83C180_OPTIONS 0x18 + +#define PHY_LAN83C183_STATUS_OUTPUT 0x12 +#define PHY_LAN83C183_INT_MASK 0x13 +#define PHY_LAN83C183_MASK_BASE 0xFFC0 +#define PHY_LAN83C183_MASK_INT ~0x8000 +#define PHY_LAN83C183_MASK_LINK ~0x4000 + +#define PHY_FOX_INT_SOURCE 0x1D +#define PHY_FOX_INT_ENERGY_ON BIT_7 +#define PHY_FOX_INT_NWAY_DONE BIT_6 +#define PHY_FOX_INT_REMOTE_FAULT BIT_5 +#define PHY_FOX_INT_LINK_DOWN BIT_4 +#define PHY_FOX_INT_NWAY_LP_ACK BIT_3 +#define PHY_FOX_INT_PARALLEL_DET BIT_2 +#define PHY_FOX_INT_NWAY_PAGE_RX BIT_1 +#define PHY_FOX_INT_RESERVED BIT_0 + +#define PHY_FOX_INT_MASK 0x1E + +#define PHY_QSI_INT_SOURCE_REG 0x1D /* Interrupt Source Register for QSI */ +#define PHY_QSI_AN_COMPLETE_INT BIT_6 +#define PHY_QSI_REM_FAULT_INT BIT_5 +#define PHY_QSI_LINK_DOWN_INT BIT_4 +#define PHY_QSI_AN_LP_ACK_INT BIT_3 +#define PHY_QSI_PAR_DET_INT BIT_2 +#define PHY_QSI_AN_PAGE_RCVD_INT BIT_1 +#define PHY_QSI_RCV_ERR_CNT_INT BIT_0 + +#define PHY_QSI_INT_MASK_REG 0x1E /* Interrupt Mask Register for QSI */ +#define PHY_QSI_INT_MODE BIT_15 + +/******************************************************************************/ +/* Bit Mask definitions */ +/******************************************************************************/ +#define BIT_NONE 0x00000000 +#define BIT_0 0x0001 +#define BIT_1 0x0002 +#define BIT_2 0x0004 +#define BIT_3 0x0008 +#define BIT_4 0x0010 +#define BIT_5 0x0020 +#define BIT_6 0x0040 +#define BIT_7 0x0080 +#define BIT_8 0x0100 +#define BIT_9 0x0200 +#define BIT_10 0x0400 +#define BIT_11 0x0800 +#define BIT_12 0x1000 +#define BIT_13 0x2000 +#define BIT_14 0x4000 +#define BIT_15 0x8000 +#define BIT_16 0x10000 +#define BIT_17 0x20000 +#define BIT_18 0x40000 +#define BIT_19 0x80000 +#define BIT_20 0x100000 +#define BIT_21 0x200000 +#define BIT_22 0x400000 +#define BIT_23 0x800000 +#define BIT_24 0x1000000 +#define BIT_25 0x2000000 +#define BIT_26 0x4000000 +#define BIT_27 0x8000000 +#define BIT_28 0x10000000 +#define BIT_29 0x20000000 +#define BIT_30 0x40000000 +#define BIT_31 0x80000000 + +// Vendor Requests +#define USB_VENDOR_REQUEST_WRITE_REGISTER 0xA0 +#define USB_VENDOR_REQUEST_READ_REGISTER 0xA1 +#define USB_VENDOR_REQUEST_GET_STATS 0xA2 + +// Stats block structures +typedef struct _SMSC9500_RX_STATS { + u32 RxGoodFrames; + u32 RxCrcErrors; + u32 RxRuntFrameErrors; + u32 RxAlignmentErrors; + u32 RxFrameTooLongError; + u32 RxLaterCollisionError; + u32 RxBadFrames; + u32 RxFifoDroppedFrames; +}SMSC9500_RX_STATS, *PSMSC9500_RX_STATS; + +typedef struct _SMSC9500_TX_STATS { + u32 TxGoodFrames; + u32 TxPauseFrames; + u32 TxSingleCollisions; + u32 TxMultipleCollisions; + u32 TxExcessiveCollisionErrors; + u32 TxLateCollisionErrors; + u32 TxBufferUnderrunErrors; + u32 TxExcessiveDeferralErrors; + u32 TxCarrierErrors; + u32 TxBadFrames; +} SMSC9500_TX_STATS, *PSMSC9500_TX_STATS; + +// Interrupt Endpoint status word bitfields +#define INT_ENP_RFHF_ (0x00040000UL) +#define INT_ENP_TX_STOP_ ETH_INT_STS_TX_STOP_ +#define INT_ENP_RX_STOP_ ETH_INT_STS_RX_STOP_ +#define INT_ENP_PHY_INT_ ETH_INT_STS_PHY_INT_ +#define INT_ENP_TXE_ ETH_INT_STS_TXE_ +#define INT_ENP_TDFU_ ETH_INT_STS_TDFU_ +#define INT_ENP_TDFO_ ETH_INT_STS_TDFO_ +#define INT_ENP_RXDF_ ETH_INT_STS_RXDF_ +#define INT_ENP_GPIOS_ ETH_INT_STS_GPIOS_ + +struct USB_CONTEXT{ + struct usb_ctrlrequest req; + struct completion notify; +}; + +enum{ + ASYNC_RW_SUCCESS, + ASYNC_RW_FAIL, + ASYNC_RW_TIMEOUT, +}; + +#define USE_DEBUG +#ifdef USE_DEBUG +#define USE_WARNING +#define USE_TRACE +#define USE_ASSERT +#endif //USE_DEBUG + + +#define HIBYTE(word) ((BYTE)(((u16)(word))>>8)) +#define LOBYTE(word) ((BYTE)(((u16)(word))&0x00FFU)) +#define HIWORD(dWord) ((u16)(((u32)(dWord))>>16)) +#define LOWORD(dWord) ((u16)(((u32)(dWord))&0x0000FFFFUL)) + +/******************************************************* +* Macro: SMSC_TRACE +* Description: +* This macro is used like printf. +* It can be used anywhere you want to display information +* For any release version it should not be left in +* performance sensitive Tx and Rx code paths. +* To use this macro define USE_TRACE and set bit 0 of debug_mode +*******************************************************/ +#ifdef USING_LINT +extern void SMSC_TRACE(unsigned long dbgBit, const char * a, ...); +#else //not USING_LINT +#ifdef USE_TRACE +extern u32 debug_mode; +#ifndef USE_WARNING +#define USE_WARNING +#endif +# define SMSC_TRACE(dbgBit,msg,args...) \ + if(debug_mode&dbgBit) { \ + printk("SMSC_9500: " msg "\n", ## args); \ + } +#else +# define SMSC_TRACE(dbgBit,msg,args...) +#endif +#endif //end of not USING_LINT + +/******************************************************* +* Macro: SMSC_WARNING +* Description: +* This macro is used like printf. +* It can be used anywhere you want to display warning information +* For any release version it should not be left in +* performance sensitive Tx and Rx code paths. +* To use this macro define USE_TRACE or +* USE_WARNING and set bit 1 of debug_mode +*******************************************************/ + + +#ifdef USING_LINT +extern void SMSC_WARNING(const char * a, ...); +#else //not USING_LINT +#ifdef USE_WARNING +extern u32 debug_mode; +#ifndef USE_ASSERT +#define USE_ASSERT +#endif +# define SMSC_WARNING(msg, args...) \ + if(debug_mode&DBG_WARNING) { \ + printk("SMSC_9500_WARNING: ");\ + printk(__FUNCTION__);\ + printk(": " msg "\n",## args);\ + } +#else +# define SMSC_WARNING(msg, args...) +#endif +#endif //end of not USING_LINT + + +/******************************************************* +* Macro: SMSC_ASSERT +* Description: +* This macro is used to test assumptions made when coding. +* It can be used anywhere, but is intended only for situations +* where a failure is fatal. +* If code execution where allowed to continue it is assumed that +* only further unrecoverable errors would occur and so this macro +* includes an infinite loop to prevent further corruption. +* Assertions are only intended for use during developement to +* insure consistency of logic through out the driver. +* A driver should not be released if assertion failures are +* still occuring. +* To use this macro define USE_TRACE or USE_WARNING or +* USE_ASSERT +*******************************************************/ +#ifdef USING_LINT +extern void SMSC_ASSERT(BOOLEAN condition); +#else //not USING_LINT +#ifdef USE_ASSERT + #define SMSC_ASSERT(condition) \ + if(!(condition)) { \ + printk("SMSC_9500_ASSERTION_FAILURE: \n");\ + printk(" Condition = " #condition "\n");\ + printk(" Function = ");printk(__FUNCTION__);printk("\n");\ + printk(" File = " __FILE__ "\n");\ + printk(" Line = %d\n",__LINE__); \ + while(1);\ + } +#else +#define SMSC_ASSERT(condition) +#endif +#endif //end of not USING_LINT + + +#define LINK_INIT (0xFFFF) +#define LINK_OFF (0x00UL) +#define LINK_SPEED_10HD (0x01UL) +#define LINK_SPEED_10FD (0x02UL) +#define LINK_SPEED_100HD (0x04UL) +#define LINK_SPEED_100FD (0x08UL) +#define LINK_SYMMETRIC_PAUSE (0x10UL) +#define LINK_ASYMMETRIC_PAUSE (0x20UL) +#define LINK_AUTO_NEGOTIATE (0x40UL) + + +#define INT_END_MACRTO_INT_ (0x00080000UL) +#define INT_END_RXFIFO_HAS_FRAME_ (0x00040000UL) +#define INT_END_TXSTOP_INT_ (0x00020000UL) +#define INT_END_RXSTOP_INT_ (0x00010000UL) +#define INT_END_PHY_INT_ (0x00008000UL) +#define INT_END_TXE_ (0x00004000UL) +#define INT_END_TDFU_ (0x00002000UL) +#define INT_END_TDFO_ (0x00001000UL) +#define INT_END_RXDF_INT_ (0x00000800UL) +#define INT_END_GPIO_INT_ (0x000007FFUL) + +#define DBG_TRACE (0x01UL) +#define DBG_WARNING (0x02UL) +#define DBG_INIT (0x80000000UL) +#define DBG_CLOSE (0x40000000UL) +#define DBG_INTR (0x20000000UL) +#define DBG_PWR (0x10000000UL) +#define DBG_IOCTL (0x08000000UL) +#define DBG_LINK (0x04000000UL) +#define DBG_RX (0x02000000UL) +#define DBG_TX (0x01000000UL) +#define DBG_MCAST (0x00800000UL) +#define DBG_HOST (0x00400000UL) +#define DBG_LINK_CHANGE (0x00200000UL) + +#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 (0x00002000UL) +#define ETH_HEADER_SIZE 14 +#define ETH_MAX_DATA_SIZE 1500 +#define ETH_MAX_PACKET_SIZE (ETH_MAX_DATA_SIZE + ETH_HEADER_SIZE) +#define MAX_SINGLE_PACKET_SIZE 2048 +#define LAN9500_EEPROM_MAGIC 0x9500UL +#define EEPROM_MAC_OFFSET 0x01 + +struct smsc9500_int_data { + u32 IntEndPoint; +} __attribute__ ((packed)); + +#define MAX_WUFF_NUM 8 +#define LAN9500_WUFF_NUM 4 +#define LAN9500A_WUFF_NUM 8 + +typedef struct _WAKEUP_FILTER { + u32 dwFilterMask[MAX_WUFF_NUM*4]; + u32 dwCommad[MAX_WUFF_NUM/4]; + u32 dwOffset[MAX_WUFF_NUM/4]; + u32 dwCRC[MAX_WUFF_NUM/2]; +} WAKEUP_FILTER, *PWAKEUP_FILTER; + +typedef struct _ADAPTER_DATA { + u32 dwIdRev; + u32 dwPhyAddress; + u32 dwPhyId; + u32 dwFpgaRev; + u32 macAddrHi16; //Mac address high 16 bits + u32 MmacAddrLo32; //Mac address Low 32 bits + + BOOLEAN UseScatterGather; + BOOLEAN UseTxCsum; + BOOLEAN UseRxCsum; + + BOOLEAN LanInitialized; + BYTE bPhyModel; + BYTE bPhyRev; + u32 dwLinkSpeed; + u32 dwLinkSettings; + struct semaphore phy_mutex; // Mutex for PHY access + struct semaphore eeprom_mutex; //Mutex for eeprom access + struct semaphore internal_ram_mutex; //Mutex for internal ram operation + struct semaphore RxFilterLock; + + u16 wLastADV; + u16 wLastADVatRestart; + + spinlock_t TxQueueLock; + BOOLEAN TxInitialized; + u32 dwTxQueueDisableMask; + BOOLEAN TxQueueDisabled; + + u32 WolWakeupOpts; + u32 wakeupOptsBackup; + u32 dwWUFF[20]; + + /* for Rx Multicast work around */ + volatile u32 HashLo; + volatile u32 HashHi; + volatile BOOLEAN MulticastUpdatePending; + volatile u32 set_bits_mask; + volatile u32 clear_bits_mask; + + u32 LinkLedOnGpio; //Gpio port number for link led + u32 LinkLedOnGpioPolarity; //Gpio Output polarity + u32 LinkActLedCfg; // + u32 LinkLedOnGpioBufType; + + u32 dynamicSuspendPHYEvent; //Store PHY interrupt source for dynamic suspend + u32 systemSuspendPHYEvent; //Store PHY interrupt source for system suspend + + u16 eepromSize; +} ADAPTER_DATA, *PADAPTER_DATA; + +typedef struct _MAC_ADDR_IN_RAM{ + u32 signature; + u32 MacAddrL; + u32 MacAddrH; + u16 crc; + u16 crcComplement; +}MAC_ADDR_IN_RAM; + +enum{ + RAMSEL_FCT, + RAMSEL_EEPROM, + RAMSEL_TXTLI, + RAMSEL_RXTLI +}; + +#ifndef min +#define min(_a, _b) (((_a) < (_b)) ? (_a) : (_b)) +#endif + +#ifndef max +#define max(_a, _b) (((_a) > (_b)) ? (_a) : (_b)) +#endif + +#define BCAST_LEN 6 +#define MCAST_LEN 3 +#define ARP_LEN 2 + +#endif // _LAN9500_H + + + diff --git a/drivers/net/usb/smscusbnet.c b/drivers/net/usb/smscusbnet.c new file mode 100644 index 000000000000..ab685fc33d83 --- /dev/null +++ b/drivers/net/usb/smscusbnet.c @@ -0,0 +1,2075 @@ +/* + * USB Network driver infrastructure + * Copyright (C) 2000-2005 by David Brownell + * Copyright (C) 2003-2005 David Hollis + * + * 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 + */ + +/* + * This is a generic "USB networking" framework that works with several + * kinds of full and high speed networking devices: host-to-host cables, + * smart usb peripherals, and actual Ethernet adapters. + * + * These devices usually differ in terms of control protocols (if they + * even have one!) and sometimes they define new framing to wrap or batch + * Ethernet packets. Otherwise, they talk to USB pretty much the same, + * so interface (un)binding, endpoint I/O queues, fault handling, and other + * issues can usefully be addressed by this framework. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)) +#include +#endif + +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smscusbnet.h" +#include "version.h" +/*-------------------------------------------------------------------------*/ + +/* + * Nineteen USB 1.1 max size bulk transactions per frame (ms), max. + * Several dozen bytes of IPv4 data can fit in two such transactions. + * One maximum size Ethernet packet takes twenty four of them. + * For high speed, each frame comfortably fits almost 36 max size + * Ethernet packets (so queues should be bigger). + * + * REVISIT qlens should be members of 'struct usbnet'; the goal is to + * let the USB host controller be busy for 5msec or more before an irq + * is required, under load. Jumbograms change the equation. + */ +#define RX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? rx_queue_size : 4) +#define TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? tx_queue_size : 4) + +// reawaken network queue this soon after stopping; else watchdog barks +#define TX_TIMEOUT_JIFFIES (5*HZ) + +#define Link_Check_Delay (1*HZ) + +// throttle rx/tx briefly after some faults, so khubd might disconnect() +// us (it polls at HZ/4 usually) before we report too many false errors. +#define THROTTLE_JIFFIES (HZ/8) + +// between wakeups +#define UNLINK_TIMEOUT_MS 3 + +#define TX_PENDING_LEN 30 +/*-------------------------------------------------------------------------*/ + +// randomly generated ethernet address +static u8 node_id [ETH_ALEN]; + +static const char driver_name [] = "smscusbnet"; + +/* use ethtool to change the level for any given device */ +static int msg_level = -1; +module_param (msg_level, int, 0); +MODULE_PARM_DESC (msg_level, "Override default message level"); + +//operational_mode = 0----> low latency +//operational_mode = 1----> low power +static int operational_mode = 0; +module_param(operational_mode,int, 0); +MODULE_PARM_DESC(operational_mode,"Enable operational mode"); + +static unsigned long rx_queue_size = 60UL; +module_param(rx_queue_size, ulong, 0); +MODULE_PARM_DESC(rx_queue_size,"Specifies the size of the rx queue lenght"); + +static unsigned long tx_queue_size = 60UL; +module_param(tx_queue_size, ulong, 0); +MODULE_PARM_DESC(tx_queue_size,"Specifies the size of the tx queue lenght"); + +int tx_hold_on_completion = TRUE; +module_param(tx_hold_on_completion, bool, 0); +MODULE_PARM_DESC(tx_hold_on_completion,"Hold tx until USB completion if Enable"); + + +static int smscusbnet_xmit (struct sk_buff *skb, struct net_device *net); +static int smscusbnet_bundle_skb_ximt(struct usbnet *dev, struct sk_buff_head *q); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static struct net_device_stats *smscusbnet_get_stats (struct net_device *net); +static int smscusbnet_stop (struct net_device *net); +static int smscusbnet_open (struct net_device *net); +static int smscusbnet_start_xmit (struct sk_buff *skb, struct net_device *net); +static void smscusbnet_tx_timeout (struct net_device *net); +static int smscusbnet_change_mtu (struct net_device *net, int new_mtu); +#endif + +static inline struct urb *smscusbnet_get_rx_urb(struct usbnet *dev) +{ + unsigned long lockflags; + struct urb * urb = NULL; + + spin_lock_irqsave (&dev->rx_urblist_lock, lockflags); + if(dev->rx_urb_pool_head != dev->rx_urb_pool_tail){ + urb = dev->rx_urb_pool[dev->rx_urb_pool_head]; + dev->rx_urb_pool[dev->rx_urb_pool_head] = NULL; + dev->rx_urb_pool_head = (dev->rx_urb_pool_head + 1) % dev->rx_urb_pool_size; + } + spin_unlock_irqrestore (&dev->rx_urblist_lock, lockflags); + + return urb; +} + +static inline int smscusbnet_return_rx_urb(struct usbnet *dev, struct urb *urb) +{ + unsigned long lockflags; + + //return this urb to rx urb pool + if(urb == NULL){ + return 0; + } + + spin_lock_irqsave(&dev->rx_urblist_lock, lockflags); + dev->rx_urb_pool[dev->rx_urb_pool_tail] = urb; + dev->rx_urb_pool_tail = (dev->rx_urb_pool_tail + 1) % dev->rx_urb_pool_size; + + spin_unlock_irqrestore (&dev->rx_urblist_lock, lockflags); + + return 0; +} + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +static const struct net_device_ops smscusbnet_netdev_ops = +{ + .ndo_open = smscusbnet_open, + .ndo_stop = smscusbnet_stop, + .ndo_start_xmit = smscusbnet_start_xmit, + .ndo_tx_timeout = smscusbnet_tx_timeout, + .ndo_change_mtu = smscusbnet_change_mtu, + .ndo_get_stats = smscusbnet_get_stats, + .ndo_validate_addr = eth_validate_addr, +}; +#endif //linux 2.6.29 + +/*-------------------------------------------------------------------------*/ + +int smscusbnet_IsOperationalMode(struct usbnet *dev) +{ + + return operational_mode; +} +/*-------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------*/ + +/* handles CDC Ethernet and many other network "bulk data" interfaces */ +int smscusbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt = NULL; + struct usb_host_endpoint *in = NULL, *out = NULL; + struct usb_host_endpoint *status = NULL; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = status = NULL; + alt = intf->altsetting + tmp; + + /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; + * ignore other endpoints and altsetttings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int intr = 0; + + e = alt->endpoint + ep; + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!(e->desc.bEndpointAddress & USB_DIR_IN)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: + continue; + } + if (e->desc.bEndpointAddress & USB_DIR_IN) { + if (!intr && !in) + in = e; + else if (intr && !status) + status = e; + } else { + if (!out) + out = e; + } + } + if (in && out) + break; + } + if (!alt || !in || !out) + { + devdbg (dev, "return EINVAL = %d", EINVAL); + return -EINVAL; + } + if (alt->desc.bAlternateSetting != 0 + || !(dev->driver_info->flags & FLAG_NO_SETINT)) { + tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + { + devdbg (dev, "return tmp = %d", tmp); + return tmp; + + } + } + + dev->in = usb_rcvbulkpipe (dev->udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out = usb_sndbulkpipe (dev->udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->status = status; + + return 0; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +static void intr_complete (struct urb *urb, struct pt_regs *regs); +#else +static void intr_complete (struct urb *urb); +#endif + +static int init_status (struct usbnet *dev, struct usb_interface *intf) +{ + unsigned pipe = 0; + unsigned maxp; + unsigned period; + int i; + + if (!dev->driver_info->status) + return 0; + + if(dev->chipID == ID_REV_7500_CHIPID){ + dev->device_id = DEV_SMSC7500; + dev->tx_hold_on_completion = 0; //LAN7500 doesn't need this feature + }else{ + dev->device_id = DEV_SMSC9500; + dev->tx_hold_on_completion = tx_hold_on_completion; + } + + pipe = usb_rcvintpipe (dev->udev, + dev->status->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + maxp = usb_maxpacket (dev->udev, pipe, 0); + + + period = dev->status->desc.bInterval; + dev->interrupt_urb_buffer = NULL; + if(operational_mode){ + + dev->interrupt_urb_buffer = kmalloc (maxp, GFP_KERNEL); + if (dev->interrupt_urb_buffer) { + dev->interrupt = usb_alloc_urb (0, GFP_KERNEL); + if (!dev->interrupt) { + devdbg (dev, "failed to alloc interrupt urb\n!!!"); + kfree (dev->interrupt_urb_buffer); + dev->interrupt_urb_buffer = NULL; + return -ENOMEM; + } else { + usb_fill_int_urb(dev->interrupt, dev->udev, pipe, + dev->interrupt_urb_buffer, maxp, intr_complete, dev, period); + devdbg (dev,"status ep%din, %d bytes period %d\n", + usb_pipeendpoint(pipe), maxp, period); + } + } + } + + dev->rx_urb_pool_head = 0; + dev->rx_urb_pool_tail = 0; + dev->rx_urb_pool_size = RX_QLEN (dev); + //Allocate extra one to distinguish between empty and full list + dev->rx_urb_pool_size += 1; + dev->rx_urb_pool = kmalloc(sizeof(struct urb*) * dev->rx_urb_pool_size, GFP_KERNEL); + memset(dev->rx_urb_pool, 0, sizeof(struct urb*) * dev->rx_urb_pool_size); + + if(!dev->rx_urb_pool){ + devwarn(dev, "unable to allocate memory for rx_urb_pool"); + return -ENOMEM; + } + + for(i=0; i<(dev->rx_urb_pool_size-1); i++){ + dev->rx_urb_pool[i] = usb_alloc_urb (0, GFP_KERNEL); + if(!dev->rx_urb_pool[i]){ + devwarn(dev, "failed to alloc rx urb"); + return -ENOMEM; + } + //URB_ASYNC_UNLINK: usb_unlink_urb will return immediately without waiting for complete handler + // which might result in deadlock problem on muitl-core cpu +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)) + dev->rx_urb_pool[i]->transfer_flags |= URB_ASYNC_UNLINK; +#endif + + } + dev->rx_urb_pool_tail = dev->rx_urb_pool_size - 1; + spin_lock_init(&(dev->rx_urblist_lock)); + devdbg (dev, "init_status, rx_urb_pool_head = %d, rx_urb_pool_tail = %d", dev->rx_urb_pool_head, dev->rx_urb_pool_tail); + + return 0; +} + +/* Passes this packet up the stack, updating its accounting. + * Some link protocols batch packets, so their rx_fixup paths + * can return clones as well as just modify the original skb. + */ +void smscusbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) +{ + int status; + u16 vlan_tag = *((u16 *)&skb->cb[0]); + + skb->dev = dev->net; + skb->protocol = eth_type_trans (skb, dev->net); + + if (netif_msg_rx_status (dev)) + devdbg (dev, "< rx, len %zu, type 0x%x", + skb->len + sizeof (struct ethhdr), skb->protocol); + memset (skb->cb, 0, sizeof (struct skb_data)); + + if(vlan_tag == VLAN_DUMMY){//No vlan tag acceleration + status = netif_rx (skb); + }else{//Vlan tag acceleration + status = vlan_hwaccel_rx(skb, dev->vlgrp, vlan_tag); + } + + if(status == NET_RX_DROP){ + dev->stats.rx_dropped++; + }else{ + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + } + + if (status != NET_RX_SUCCESS && netif_msg_rx_err (dev)) + devdbg (dev, "netif_rx status %d", status); +} + +/*------------------------------------------------------------------------- + * + * Network Device Driver (peer link to "Host Device", from USB host) + * + *-------------------------------------------------------------------------*/ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static +#endif +int smscusbnet_change_mtu (struct net_device *net, int new_mtu) +{ + struct usbnet *dev = netdev_priv(net); + int ll_mtu = new_mtu + net->hard_header_len; + +#ifdef JUMBO_FRAME + if (new_mtu <= 0) + return -EINVAL; + // no second zero-length packet read wanted after mtu-sized packets + if ((ll_mtu % dev->maxpacket) == 0) + return -EDOM; + if(dev->driver_info->set_max_frame_size(dev, net->hard_header_len + new_mtu - EXTRA_HEADER_LEN) < 0){ + return -EINVAL; + } + net->mtu = new_mtu; + dev->hard_mtu = net->mtu + net->hard_header_len; +#else + if (new_mtu <= 0 || ll_mtu > dev->hard_mtu) + return -EINVAL; + // no second zero-length packet read wanted after mtu-sized packets + if ((ll_mtu % dev->maxpacket) == 0) + return -EDOM; + net->mtu = new_mtu; +#endif + return 0; +} + +/*-------------------------------------------------------------------------*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static +#endif +struct net_device_stats *smscusbnet_get_stats (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + return &dev->stats; +} + +/*-------------------------------------------------------------------------*/ + +/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from + * completion callbacks. 2.5 should have fixed those bugs... + */ + +static void defer_bh(struct usbnet *dev, struct sk_buff *skb, struct sk_buff_head *list) +{ + unsigned long flags; + + spin_lock_irqsave(&list->lock, flags); + __skb_unlink(skb, list); + spin_unlock(&list->lock); + spin_lock(&dev->done.lock); + __skb_queue_tail(&dev->done, skb); + if (dev->done.qlen == 1) + tasklet_schedule(&dev->bh); + spin_unlock_irqrestore(&dev->done.lock, flags); +} + +/* some work can't be done in tasklets, so we use keventd + * + * NOTE: annoying asymmetry: if it's active, schedule_work() fails, + * but tasklet_schedule() doesn't. hope the failure is rare. + */ +void smscusbnet_defer_kevent (struct usbnet *dev, int work) +{ + set_bit (work, &dev->flags); + if (!schedule_work (&dev->kevent)){ + devdbg (dev, "kevent %d may have been dropped", work); + } +// else +// devdbg (dev, "kevent %d scheduled", work); +} + +/* some work can't be done in tasklets, so we use keventd + * + * We use myevent to schedule Rx Bulk in + * + */ + +void smscusbnet_defer_myevent (struct usbnet *dev, int work) +{ + set_bit (work, &dev->flags); + if (!queue_work(dev->MyWorkQueue,&dev->myevent)){ + //deverr (dev, "myevent %d may have been dropped", work); + } + //else + // devdbg (dev,"myevent %d scheduled", work); +} + +void smscusbnet_linkpolling(unsigned long ptr) +{ + + struct usbnet * dev= (struct usbnet *) ptr; + smscusbnet_defer_myevent(dev, EVENT_LINK_RESET); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if(dev->dynamicSuspend){ + smscusbnet_defer_myevent(dev, EVENT_IDLE_CHECK); + } +#endif //(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if(dev->linkDownSuspend || dev->smartDetach){ +#else + if(dev->smartDetach){ +#endif //(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if(!netif_carrier_ok(dev->net) && dev->linkcheck++ > 2){//Check 2 times in case of conflict with resume + smscusbnet_defer_myevent(dev, EVENT_LINK_DOWN); + dev->linkcheck = 0; + } + } +} + + + +/*-------------------------------------------------------------------------*/ + +//static void rx_complete (struct urb *urb, struct pt_regs *regs); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +static void rx_complete (struct urb *urb, struct pt_regs *regs ); +#else +static void rx_complete (struct urb *urb); +#endif +static void rx_submit (struct usbnet *dev, struct urb *urb, int flags) +{ + struct sk_buff *skb; + struct skb_data *entry; + int retval = 0; + unsigned long lockflags; + size_t size = dev->rx_urb_size; + +#ifndef RX_OFFSET + if ((skb = alloc_skb (size + NET_IP_ALIGN , flags)) == NULL) { +#else + if ((skb = alloc_skb (size, flags)) == NULL) { +#endif //RX_OFFSET + if (netif_msg_rx_err (dev)) + devdbg (dev,"no rx skb\n"); + smscusbnet_defer_kevent (dev, EVENT_RX_MEMORY); + smscusbnet_return_rx_urb(dev, urb); + + return; + } + +#ifndef RX_OFFSET + skb_reserve (skb, NET_IP_ALIGN); +#endif + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->state = rx_start; + entry->length = 0; + + usb_fill_bulk_urb (urb, dev->udev, dev->in, + skb->data, size, rx_complete, skb); + + spin_lock_irqsave (&dev->rxq.lock, lockflags); + + if (netif_running (dev->net) + && netif_device_present (dev->net) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + switch (retval = usb_submit_urb (urb, GFP_ATOMIC)){ + case -EPIPE: + smscusbnet_defer_kevent (dev, EVENT_RX_HALT); + break; + case -ENOMEM: + smscusbnet_defer_kevent (dev, EVENT_RX_MEMORY); + break; + case -ENODEV: + if (netif_msg_ifdown (dev)) + devdbg (dev, "device gone"); + netif_device_detach (dev->net); + break; + default: + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx submit, %d", retval); + tasklet_schedule (&dev->bh); + break; + case 0: + __skb_queue_tail (&dev->rxq, skb); + } + } else { + if (netif_msg_ifdown (dev)) + devdbg (dev, "rx: stopped"); + retval = -ENOLINK; + } + spin_unlock_irqrestore (&dev->rxq.lock, lockflags); + if (retval) { + dev_kfree_skb_any (skb); + smscusbnet_return_rx_urb(dev, urb); + } +} + +//------------------------------------------------------------------------- + +static inline void rx_process (struct usbnet *dev, struct sk_buff *skb) +{ + int ret; + + if (!dev->driver_info->rx_fixup)return; + + ret = dev->driver_info->rx_fixup (dev, skb); + + if(ret == RX_FIXUP_INVALID_SKB)skb_queue_tail(&dev->done, skb); + else if(ret == RX_FIXUP_ERROR){ + dev->stats.rx_errors++; + skb_queue_tail (&dev->done, skb); + } + else{ + if (skb->len) { + smscusbnet_skb_return (dev, skb); + + } else { + if (netif_msg_rx_err (dev)) + devdbg (dev, "drop"); + dev->stats.rx_errors++; + skb_queue_tail (&dev->done, skb); + } + } + +} + +/*-------------------------------------------------------------------------*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +static void rx_complete (struct urb *urb, struct pt_regs *regs) +#else +static void rx_complete (struct urb *urb) +#endif + +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + int urb_status = urb->status; + + dev->idleCount = 0; + + skb_put (skb, urb->actual_length); + entry->state = rx_done; + entry->urb = NULL; + + switch (urb_status) { + // success + case 0: + if (operational_mode) { + if ((skb->len < dev->net->hard_header_len) && (skb->len != 0)) { + entry->state = rx_cleanup; + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx length %d", skb->len); + } + + if (skb->len == 0) { + entry->state = rx_cleanup; + dev->StopSummitUrb=1; + } + else { + dev->StopSummitUrb=0; + } + + } else { + if (skb->len < dev->net->hard_header_len) { + entry->state = rx_cleanup; + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx length %d", skb->len); + } + + } + break; + + // stalls need manual reset. this is rare ... except that + // when going through USB 2.0 TTs, unplug appears this way. + // we avoid the highspeed version of the ETIMEOUT/EILSEQ + // storm, recovering as needed. + case -EPIPE: + + dev->extra_error_cnts.rx_epipe++; + dev->stats.rx_errors++; + devdbg (dev,"in rx_complete,case -EPIPE, dev->stats.rx_errors=0x%08lx\n", dev->stats.rx_errors); + smscusbnet_defer_kevent (dev, EVENT_RX_HALT); + // FALLTHROUGH + + // software-driven interface shutdown + case -ECONNRESET: // async unlink + case -ESHUTDOWN: // hardware gone + if (netif_msg_ifdown (dev)) + devdbg (dev, "rx shutdown, code %d", urb_status); + goto block; + + // we get controller i/o faults during khubd disconnect() delays. + // throttle down resubmits, to avoid log floods; just temporarily, + // so we still recover when the fault isn't a khubd delay. + case -EPROTO: // ehci + dev->extra_error_cnts.rx_eproto++; + goto _HANDLE; + case -ETIMEDOUT: // ohci + dev->extra_error_cnts.rx_etimeout++; + goto _HANDLE; + case -EILSEQ: // uhci + dev->extra_error_cnts.rx_eilseq++; +_HANDLE: + + dev->stats.rx_errors++; + devdbg (dev,"in rx_complete,case -EPROTO, dev->stats.rx_errors=0x%08lx\n", dev->stats.rx_errors); + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, jiffies + THROTTLE_JIFFIES); + if (netif_msg_link (dev)) + devdbg (dev, "rx throttle %d", urb_status); + } + //Set recovery flag + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + +block: + entry->state = rx_cleanup; + entry->urb = urb; + urb = NULL; + break; + + // data overrun ... flush fifo? + case -EOVERFLOW: + dev->extra_error_cnts.rx_eoverflow++; + dev->stats.rx_over_errors++; + // FALLTHROUGH + //Set recovery flag + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + + default: + entry->state = rx_cleanup; + dev->stats.rx_errors++; + + devdbg (dev,"in rx_complete,case default dev->stats.rx_errors=0x%08lx\n", dev->stats.rx_errors); + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx status %d", urb_status); + break; + } + + defer_bh(dev, skb, &dev->rxq); + + if (urb) { + if (operational_mode) { + if (netif_running (dev->net) + && !test_bit (EVENT_RX_HALT, &dev->flags) + && !dev->StopSummitUrb) { + rx_submit (dev, urb, GFP_ATOMIC); + return; + } + + } else { + + if (netif_running (dev->net) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + rx_submit (dev, urb, GFP_ATOMIC); + return; + } + } + //return this urb to rx urb pool + smscusbnet_return_rx_urb(dev, urb); + } + if (netif_msg_rx_err (dev)) + devdbg (dev, "no read resubmitted"); +} +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +static void intr_complete (struct urb *urb, struct pt_regs *regs) +#else +static void intr_complete (struct urb *urb) +#endif + +{ + struct usbnet *dev = urb->context; + int status = urb->status; + + switch (status) { + /* success */ + case 0: + dev->driver_info->status(dev, urb); + break; + + /* software-driven interface shutdown */ + case -ENOENT: // urb killed + case -ESHUTDOWN: // hardware gone + if (netif_msg_ifdown (dev)) + devdbg (dev,"intr shutdown, code %d", status); + return; + + // we get controller i/o faults during khubd disconnect() delays. + // throttle down resubmits, to avoid log floods; just temporarily, + // so we still recover when the fault isn't a khubd delay. + /* We need throttling here like RX/TX, because flood events occur on slow ppc platform + * when the device is being disconnected. + */ + case -EPROTO: // ehci + case -ETIMEDOUT: // ohci + case -EILSEQ: // uhci + + devdbg (dev,"in intr_complete,case -EPROTO\n"); + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, jiffies + THROTTLE_JIFFIES); + if (netif_msg_link (dev)) + devdbg (dev, "intr throttle %d", status); + } + dev->intr_urb_delay_submit = TRUE; + return; + + default: + devdbg (dev,"intr status %d", status); + break; + } + + if (!netif_running (dev->net)){ + return; + } + + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status != 0 && netif_msg_timer (dev)) + deverr(dev, "intr resubmit --> %d", status); +} + +/*-------------------------------------------------------------------------*/ + +// unlink pending rx/tx; completion handlers do all other cleanup + +static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) +{ + unsigned long flags; + struct sk_buff *skb, *skbnext; + int count = 0; + + spin_lock_irqsave (&q->lock, flags); + for (skb = q->next; skb != (struct sk_buff *) q; skb = skbnext) { + struct skb_data *entry; + struct urb *urb; + int retval; + + entry = (struct skb_data *) skb->cb; + urb = entry->urb; + skbnext = skb->next; + + // during some PM-driven resume scenarios, + // these (async) unlinks complete immediately + retval = usb_unlink_urb (urb); + if (retval != -EINPROGRESS && retval != 0) + devdbg (dev, "unlink urb err, %d", retval); + else + count++; + } + spin_unlock_irqrestore (&q->lock, flags); + return count; +} + +/*-------------------------------------------------------------------------*/ + +// tasklet (work deferred from completions, in_irq) or timer + +static void smscusbnet_bh (unsigned long param) +{ + struct usbnet *dev = (struct usbnet *) param; + struct sk_buff *skb = NULL; + struct skb_data *entry = NULL; + + while ((skb = skb_dequeue (&dev->done))) { + entry = (struct skb_data *) skb->cb; + switch (entry->state) { + case rx_done: + entry->state = rx_cleanup; + rx_process (dev, skb); + continue; + case tx_done: + usb_free_urb (entry->urb); + dev_kfree_skb (skb); + + if(dev->device_id == DEV_SMSC7500){ + if(!dev->txq.qlen){ + skb = skb_dequeue(&dev->tx_pending_q); + if(skb){ + smscusbnet_xmit(skb, dev->net); + } + } + }else{//DEV_SMSC9500 + unsigned long flags; + spin_lock_irqsave (&dev->txq.lock, flags); + if(!dev->txq.qlen) + smscusbnet_bundle_skb_ximt(dev, &dev->tx_pending_q); + spin_unlock_irqrestore (&dev->txq.lock, flags); + } + + continue; + case rx_cleanup: + if(entry->urb)smscusbnet_return_rx_urb(dev, entry->urb); + dev_kfree_skb (skb); + continue; + default: + devdbg (dev, "bogus skb state %d", entry->state); + } + } + + if (netif_running (dev->net) && netif_device_present (dev->net) && !timer_pending (&dev->delay)) { + //submit delayed interrupt urb + if(operational_mode && dev->interrupt && dev->intr_urb_delay_submit){ + int status = usb_submit_urb (dev->interrupt, GFP_ATOMIC); + dev->intr_urb_delay_submit = FALSE; + if (status != 0 && netif_msg_timer (dev)) + deverr(dev, "intr resubmit --> %d", status); + + } + } + + // waiting for all pending urbs to complete? + if (dev->wait) { + if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) { + wake_up (dev->wait); + } + + // or are we maybe short a few urbs? + } else if (netif_running (dev->net) + && netif_device_present (dev->net) + && !timer_pending (&dev->delay) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + + if (((operational_mode)&&(!dev->StopSummitUrb)) ||(!(operational_mode) )) { + int temp = dev->rxq.qlen; + int qlen = RX_QLEN (dev); + + if (temp < qlen) { + struct urb *urb; + int i; + + // don't refill the queue all at once + for (i = 0; i < 10 && dev->rxq.qlen < qlen; i++) { + urb = smscusbnet_get_rx_urb(dev); + if (urb != NULL) { + rx_submit (dev, urb, GFP_ATOMIC); + } + } + + //if (temp != dev->rxq.qlen && netif_msg_link (dev)) + //devdbg (dev, "in dev->bh, rxqlen %d --> %d", + // temp, dev->rxq.qlen); + if (dev->rxq.qlen < qlen) + tasklet_schedule (&dev->bh); + } + + } + if(!dev->tx_hold_on_completion){ + if (dev->txq.qlen < TX_QLEN (dev)) + netif_wake_queue (dev->net); + } + } +} + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +int smscusbnet_stop (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int temp; + + DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); + DECLARE_WAITQUEUE (wait, current); + + netif_stop_queue (net); + if (netif_msg_ifdown (dev)) + devinfo (dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld", + dev->stats.rx_packets, dev->stats.tx_packets, + dev->stats.rx_errors, dev->stats.tx_errors + ); + + if(!skb_queue_empty(&dev->tx_pending_q)){ + skb_queue_purge(&dev->tx_pending_q); + } + // ensure there are no more active urbs + add_wait_queue (&unlink_wakeup, &wait); + dev->wait = &unlink_wakeup; + temp = unlink_urbs (dev, &dev->txq) + unlink_urbs (dev, &dev->rxq); + // maybe wait for deletions to finish. + while (!skb_queue_empty(&dev->rxq) && + !skb_queue_empty(&dev->txq) && + !skb_queue_empty(&dev->done)) { + msleep(UNLINK_TIMEOUT_MS); + if (netif_msg_ifdown (dev)) + devdbg (dev, "waited for %d urb completions", temp); + } + dev->wait = NULL; + remove_wait_queue (&unlink_wakeup, &wait); + if(operational_mode){ + usb_kill_urb(dev->interrupt); + } + devdbg (dev, "smscusbnet_stop, rx_urb_pool_head = %d, rx_urb_pool_tail = %d", dev->rx_urb_pool_head, dev->rx_urb_pool_tail); + + /* deferred work (task, timer, softirq) must also stop. + * can't flush_scheduled_work() until we drop rtnl (later), + * else workers could deadlock; so make workers a NOP. + */ + dev->flags = 0; + //Clean up the counters + memset(&dev->stats, 0, sizeof(dev->stats)); + memset(&dev->extra_error_cnts, 0, sizeof(dev->extra_error_cnts)); + + dev->StopLinkPolling=TRUE; + del_timer_sync (&dev->LinkPollingTimer); + del_timer_sync (&dev->delay); + tasklet_kill (&dev->bh); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) + //if thera is in suspend0 status already, resume it first. So we can set suspend2 in Smsc9500_suspend(); + down(&dev->pm_mutex); + if(!dev->pmLock){ + dev->pmLock = TRUE; + usb_autopm_get_interface(dev->uintf); + } + up(&dev->pm_mutex); + + msleep(100); + + down(&dev->pm_mutex); + if(dev->pmLock){ + usb_autopm_put_interface(dev->uintf); + } + dev->pmLock = FALSE; + up(&dev->pm_mutex); +#endif + return 0; +} + +/*-------------------------------------------------------------------------*/ + +// posts reads, and enables write queuing + +// precondition: never called in_interrupt + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static +#endif +int smscusbnet_open (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int retval = 0; + struct driver_info *info = dev->driver_info; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) + down(&dev->pm_mutex); + dev->pmLock = TRUE; + usb_autopm_get_interface(dev->uintf); + up(&dev->pm_mutex); +#endif + + dev->delay.function = smscusbnet_bh; + dev->delay.data = (unsigned long) dev; + init_timer (&dev->delay); + + dev->suspendFlag = 0; + + // put into "known safe" state + if (info->reset && (retval = info->reset (dev)) < 0) { + devdbg (dev,"info->reset && (retval = info->reset (dev)) \n"); + if (netif_msg_ifup (dev)) + devinfo (dev, + "open reset fail (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + goto done; + } + + dev->intr_urb_delay_submit = FALSE; + init_timer(&(dev->LinkPollingTimer)); + dev->StopLinkPolling=FALSE; + dev->LinkPollingTimer.function=smscusbnet_linkpolling; + dev->LinkPollingTimer.data=(unsigned long) dev; + dev->LinkPollingTimer.expires=jiffies+HZ; + add_timer(&(dev->LinkPollingTimer)); + + // insist peer be connected + if (info->check_connect && (retval = info->check_connect (dev)) < 0) { + if (netif_msg_ifup (dev)) + devdbg (dev, "can't open; %d", retval); + goto done; + } + + /* start any status interrupt transfer */ + if(operational_mode){ + if (dev->interrupt) { + devdbg (dev,"dev->interrupt \n"); + retval = usb_submit_urb (dev->interrupt, GFP_KERNEL); + if (retval < 0) { + if (netif_msg_ifup (dev)) + deverr (dev, "intr submit %d", retval); + goto done; + } + } + } + + netif_start_queue (net); + if (netif_msg_ifup (dev)) { + char *framing; + devdbg (dev,"netif_msg_ifup \n"); + if (dev->driver_info->flags & FLAG_FRAMING_NC) + framing = "NetChip"; + else if (dev->driver_info->flags & FLAG_FRAMING_GL) + framing = "GeneSys"; + else if (dev->driver_info->flags & FLAG_FRAMING_Z) + framing = "Zaurus"; + else if (dev->driver_info->flags & FLAG_FRAMING_RN) + framing = "RNDIS"; + else if (dev->driver_info->flags & FLAG_FRAMING_AX) + framing = "ASIX"; + else + framing = "simple"; + + devinfo (dev, "open: enable queueing " + "(rx %lx, tx %lx) mtu %d %s framing", + RX_QLEN (dev), TX_QLEN (dev), dev->net->mtu, + framing); + } + + // delay posting reads until we're fully open + tasklet_schedule (&dev->bh); +done: + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* work that cannot be done in interrupt context uses keventd. + * + * NOTE: with 2.5 we could do more of this using completion callbacks, + * especially now that control transfers can be queued. + */ + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) +static void kevent (void *data) +{ + struct usbnet *dev = data; +#else +static void kevent(struct work_struct *work) +{ + struct usbnet *dev = container_of(work,struct usbnet,kevent); +#endif + + int status; + + /* usb_clear_halt() needs a thread context */ + if (test_bit (EVENT_TX_HALT, &dev->flags)) { + unlink_urbs (dev, &dev->txq); + status = usb_clear_halt (dev->udev, dev->out); + + //Set recovery flag, the device will be reset in the smsc9500.c + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + + if (status < 0 + && status != -EPIPE + && status != -ESHUTDOWN) { + if (netif_msg_tx_err (dev)) + deverr (dev, "can't clear tx halt, status %d", + status); + } else { + clear_bit (EVENT_TX_HALT, &dev->flags); + if (status != -ESHUTDOWN){ + netif_wake_queue (dev->net); + } + } + } + if (test_bit (EVENT_RX_HALT, &dev->flags)) { + unlink_urbs (dev, &dev->rxq); + status = usb_clear_halt (dev->udev, dev->in); + + //Set recovery flag. + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + + if (status < 0 + && status != -EPIPE + && status != -ESHUTDOWN) { + if (netif_msg_rx_err (dev)) + deverr (dev, "can't clear rx halt, status %d", + status); + } else { + clear_bit (EVENT_RX_HALT, &dev->flags); + tasklet_schedule (&dev->bh); + } + } + + /* tasklet could resubmit itself forever if memory is tight */ + if (test_bit (EVENT_RX_MEMORY, &dev->flags)) { + struct urb *urb = NULL; + + if (netif_running (dev->net)) + urb = smscusbnet_get_rx_urb(dev); + else + clear_bit (EVENT_RX_MEMORY, &dev->flags); + if (urb != NULL) { + clear_bit (EVENT_RX_MEMORY, &dev->flags); + rx_submit (dev, urb, GFP_KERNEL); + tasklet_schedule (&dev->bh); + } + } + + if (dev->flags) + devdbg (dev, "kevent done, flags = 0x%lx \n", + dev->flags); +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) +/*-------------------------------------------------------------------------*/ + +/* Stop all traffic so kernel will suspend our device. + * + */ +#ifdef CONFIG_PM +static int suspendDevice(struct usbnet *dev) +{ + if(operational_mode){ + usb_kill_urb(dev->interrupt); //stop interrupt urb + }else{ + DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); + DECLARE_WAITQUEUE (wait, current); + + add_wait_queue (&unlink_wakeup, &wait); + dev->wait = &unlink_wakeup; + unlink_urbs (dev, &dev->txq); + unlink_urbs (dev, &dev->rxq); + // maybe wait for deletions to finish. + while (!skb_queue_empty(&dev->rxq) && + !skb_queue_empty(&dev->txq) && + !skb_queue_empty(&dev->done)) { + msleep(UNLINK_TIMEOUT_MS); + } + dev->wait = NULL; + remove_wait_queue (&unlink_wakeup, &wait); + } + + dev->StopLinkPolling = TRUE; //stop accessing registers + down(&dev->pm_mutex); + if(dev->pmLock){ + dev->pmLock = FALSE; + usb_autopm_put_interface(dev->uintf); + } + up(&dev->pm_mutex); + + return 0; +} +#endif //CONFIG_PM +#endif + +/*-------------------------------------------------------------------------*/ + +/* work that cannot be done in interrupt context uses keventd. + * + * NOTE: with 2.5 we could do more of this using completion callbacks, + * especially now that control transfers can be queued. + */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) +static void myevent(void *data) +{ + struct usbnet *dev = data; + +#else + +static void myevent(struct work_struct *work) +{ + + struct usbnet *dev = container_of(work,struct usbnet,myevent); +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if (test_bit (EVENT_IDLE_CHECK, &dev->flags)) { + clear_bit (EVENT_IDLE_CHECK, &dev->flags); + //autosuspend_disabled should be enabled by shell cmd "echo auto > /sys/bus/usb/devices/X-XX/power/level" + if(dev->dynamicSuspend && !dev->udev->autosuspend_disabled){ + if((dev->idleCount >= PM_IDLE_DELAY)/* && (dev->uintf->pm_usage_cnt > 0)*/){ + + if(dev->device_id == DEV_SMSC7500){ + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC_S3; + }else{ + if(dev->chipDependFeatures[FEATURE_SUSPEND3]){ + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC_S3; + }else{ + dev->suspendFlag |= AUTOSUSPEND_DYNAMIC; + } + } + suspendDevice(dev); + } + if(dev->idleCount < PM_IDLE_DELAY)dev->idleCount++; + } + } +#endif //(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if (test_bit (EVENT_LINK_DOWN, &dev->flags)) { + clear_bit (EVENT_LINK_DOWN, &dev->flags); + if(dev->smartDetach){ + dev->suspendFlag |= AUTOSUSPEND_DETACH; + } +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + else if(dev->linkDownSuspend && !dev->udev->autosuspend_disabled){ + dev->suspendFlag |= AUTOSUSPEND_LINKDOWN; + suspendDevice(dev); + } +#endif //(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + } +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if (test_bit (EVENT_IDLE_RESUME, &dev->flags)) { + clear_bit (EVENT_IDLE_RESUME, &dev->flags); + + if(dev->dynamicSuspend || dev->linkDownSuspend){ + down(&dev->pm_mutex); + if(!dev->pmLock){ + dev->pmLock = TRUE; + usb_autopm_get_interface(dev->uintf); + } + up(&dev->pm_mutex); + + if(operational_mode){ + if (dev->interrupt) { + if(usb_submit_urb (dev->interrupt, GFP_KERNEL) < 0){ + deverr(dev, "intr submit "); + } + } + } + if( (!timer_pending(&dev->LinkPollingTimer))){ + dev->LinkPollingTimer.expires=jiffies+HZ; + add_timer(&(dev->LinkPollingTimer)); + } + dev->StopLinkPolling = FALSE; + netif_wake_queue (dev->net); + } + } + +#endif //(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) && defined(CONFIG_PM) + if (test_bit (EVENT_LINK_RESET, &dev->flags)) { + struct driver_info *info = dev->driver_info; + int retval = 0; + + clear_bit (EVENT_LINK_RESET, &dev->flags); + + if(info->link_reset && (retval = info->link_reset(dev)) < 0) { + devinfo(dev, "link reset failed (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + } + + } + + if (test_bit (EVENT_SET_MULTICAST, &dev->flags)) { + struct driver_info *info = dev->driver_info; + int retval = 0; + + clear_bit (EVENT_SET_MULTICAST, &dev->flags); + if(info->rx_setmulticastlist&& (retval = info->rx_setmulticastlist(dev)) < 0) { + devinfo(dev, "Set Multicast failed (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + } + } + + if (dev->flags) + devdbg (dev, "myevent done, flags = 0x%lx", dev->flags); +} + +/*-------------------------------------------------------------------------*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +static void tx_complete (struct urb *urb, struct pt_regs *regs) +#else +static void tx_complete (struct urb *urb) +#endif + +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + + dev->idleCount = 0; + + if (urb->status == 0) { + if(dev->tx_hold_on_completion){ + if (dev->tx_pending_q.qlen < TX_PENDING_LEN) + netif_wake_queue (dev->net); + + dev->stats.tx_packets += entry->pkt_cnt; + }else{ + dev->stats.tx_packets++; + } + dev->stats.tx_bytes += entry->length; + } else { + + dev->stats.tx_errors++; + + switch (urb->status) { + case -EPIPE: + smscusbnet_defer_kevent (dev, EVENT_TX_HALT); + dev->extra_error_cnts.tx_epipe++; + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: // async unlink + case -ESHUTDOWN: // hardware gone + break; + + // like rx, tx gets controller i/o faults during khubd delays + // and so it uses the same throttling mechanism. + case -EPROTO: // ehci + dev->extra_error_cnts.tx_eproto++; + goto _HANDLE; + case -ETIMEDOUT: // ohci + dev->extra_error_cnts.tx_etimeout++; + goto _HANDLE; + case -EILSEQ: // uhci + dev->extra_error_cnts.tx_eilseq++; +_HANDLE: + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, + jiffies + THROTTLE_JIFFIES); + if (netif_msg_link (dev)) + devdbg (dev, "tx throttle %d", + urb->status); + } + netif_stop_queue (dev->net); + //Set recovery flag + set_bit (EVENT_DEV_RECOVERY, &dev->flags); + + break; + default: + if (netif_msg_tx_err (dev)) + devdbg (dev, "tx err %d", entry->urb->status); + break; + } + } + + urb->dev = NULL; + entry->state = tx_done; + defer_bh(dev, skb, &dev->txq); +} + +/*-------------------------------------------------------------------------*/ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static +#endif +void smscusbnet_tx_timeout (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + unlink_urbs (dev, &dev->txq); + tasklet_schedule (&dev->bh); + + // FIXME: device recovery -- reset? +} + +/*-------------------------------------------------------------------------*/ +/*txq.lock will be acquired by caller*/ +static int smscusbnet_bundle_skb_ximt(struct usbnet *dev, struct sk_buff_head *q) +{ + unsigned long flags; + struct sk_buff *skb = NULL, *skbnext = NULL; + int count = 0, pkt_cnt = 0; + int SkbSize = 0; + struct skb_data *entry; + int retval = NET_XMIT_SUCCESS; + struct urb *urb = NULL; + + if((struct sk_buff *)q == q->next)return retval; + + spin_lock_irqsave(&q->lock, flags); + + for(skbnext = q->next; skbnext != (struct sk_buff *) q; skbnext = skbnext->next){ + SkbSize += skbnext->len; + pkt_cnt++; + } + if(SkbSize == 0){ + spin_unlock_irqrestore(&q->lock, flags); + return retval; + } + + if(pkt_cnt > 1){ + skb = dev_alloc_skb(SkbSize); + + if (!skb){ + devdbg (dev, "smscusbnet_merge_skb, skb is NULL, CX_SkbSize = %d", SkbSize); + spin_unlock_irqrestore(&q->lock, flags); + return retval; + } + skb_put(skb, SkbSize); + count = 0; + + for(skbnext = q->next; skbnext != (struct sk_buff *) q; skbnext = skbnext->next){ + memcpy(skb->data + count, skbnext->data, skbnext->len); + count += skbnext->len; + } + while((skbnext = __skb_dequeue(q)) != NULL){ + kfree_skb(skbnext); + } + + }else{ //one packet + skb = __skb_dequeue(q); + } + spin_unlock_irqrestore(&q->lock, flags); + + netif_wake_queue (dev->net); + + if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "no urb"); + retval = NET_XMIT_SUCCESS; + dev->stats.tx_dropped += pkt_cnt; + if (skb){ + dev_kfree_skb_any (skb); + skb = NULL; + } + + goto drop; + }else{ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)) + urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->state = tx_start; + entry->length = SkbSize; + entry->pkt_cnt = pkt_cnt; + + usb_fill_bulk_urb (urb, dev->udev, dev->out, + skb->data, skb->len, tx_complete, skb); + } + + switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { + case -EPIPE: + devdbg (dev," in smscusbnet_bundle_skb_ximt,-EPIPE\n"); + netif_stop_queue (dev->net); + smscusbnet_defer_kevent (dev, EVENT_TX_HALT); + break; + default: + if (netif_msg_tx_err (dev)) + devdbg (dev, "tx: submit urb err %d", retval); + break; + case 0: + dev->net->trans_start = jiffies; + __skb_queue_tail (&dev->txq, skb); + if (dev->txq.qlen >= TX_QLEN (dev)) + netif_stop_queue (dev->net); + } + + if (retval) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "drop, code %d", retval); +drop: + retval = NET_XMIT_SUCCESS; + + dev->stats.tx_dropped += pkt_cnt; + if (skb) + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } else if (netif_msg_tx_queued (dev)) { + devdbg (dev, "> tx, len %d, type 0x%x", + SkbSize, skb->protocol); + } + + return retval; +} + +/*-------------------------------------------------------------------------*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +static +#endif +int smscusbnet_start_xmit (struct sk_buff *skb, struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int retval = NET_XMIT_SUCCESS; + struct driver_info *info = dev->driver_info; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) +#if defined(CONFIG_PM) && defined(CONFIG_USB_SUSPEND) + if(dev->uintf->pm_usage_cnt <= 0){ + netif_stop_queue (net); + smscusbnet_defer_myevent(dev, EVENT_IDLE_RESUME); + return NET_XMIT_DROP; + } +#endif //CONFIG_PM && CONFIG_USB_SUSPEND +#endif + + // some devices want funky USB-level framing, for + // win32 driver (usually) and/or hardware quirks + if (info->tx_fixup) { + skb = info->tx_fixup (dev, skb, GFP_ATOMIC); + if (!skb) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "can't tx_fixup skb"); + retval = NET_XMIT_SUCCESS; + dev->stats.tx_dropped++; + return NET_XMIT_DROP; + } + } + + if(dev->tx_hold_on_completion){ + + if(dev->device_id == DEV_SMSC7500){ + if(dev->txq.qlen != 0){ + + skb_queue_tail (&dev->tx_pending_q, skb); + if (dev->tx_pending_q.qlen >= TX_PENDING_LEN){ + netif_stop_queue (net); + } + return retval; + }else{ + + if(dev->tx_pending_q.qlen != 0){ + skb_queue_tail (&dev->tx_pending_q, skb); + skb = skb_dequeue(&dev->tx_pending_q); + if(!skb)return retval; + } + } + retval = smscusbnet_xmit(skb, net); + }else{ + unsigned long flags; + skb_queue_tail (&dev->tx_pending_q, skb); + + spin_lock_irqsave (&dev->txq.lock, flags); + if(dev->txq.qlen != 0){ + if (dev->tx_pending_q.qlen >= TX_PENDING_LEN){ + netif_stop_queue (net); + } + }else{ + retval = smscusbnet_bundle_skb_ximt(dev, &dev->tx_pending_q); + } + spin_unlock_irqrestore (&dev->txq.lock, flags); + } + }else{ + + retval = smscusbnet_xmit(skb, net); + } + + return retval; +} + +static int smscusbnet_xmit (struct sk_buff *skb, struct net_device *net) +{ + int retval = NET_XMIT_SUCCESS; + struct usbnet *dev = netdev_priv(net); + struct urb *urb = NULL; + struct skb_data *entry; + unsigned long flags; + + if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "no urb"); + goto drop; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)) + urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->state = tx_start; + entry->length = skb->len; + + usb_fill_bulk_urb (urb, dev->udev, dev->out, + skb->data, skb->len, tx_complete, skb); + + /* don't assume the hardware handles USB_ZERO_PACKET + * NOTE: strictly conforming cdc-ether devices should expect + * the ZLP here, but ignore the one-byte packet. + * + * FIXME zero that byte, if it doesn't require a new skb. + */ + if ((skb->len % dev->maxpacket) == 0) { + urb->transfer_buffer_length++; + if (skb_tailroom(skb)) { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + } + } + spin_lock_irqsave (&dev->txq.lock, flags); + + switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { + case -EPIPE: + devdbg (dev," in tx_fixup,-EPIPE\n"); + netif_stop_queue (net); + smscusbnet_defer_kevent (dev, EVENT_TX_HALT); + break; + default: + if (netif_msg_tx_err (dev)) + devdbg (dev, "tx: submit urb err %d", retval); + break; + case 0: + net->trans_start = jiffies; + __skb_queue_tail (&dev->txq, skb); + + if (dev->txq.qlen >= TX_QLEN (dev)) + netif_stop_queue (net); + } + spin_unlock_irqrestore (&dev->txq.lock, flags); + + if (retval) { + devdbg (dev," in tx_fixup,drop\n"); + if (netif_msg_tx_err (dev)) + devdbg (dev, "drop, code %d", retval); +drop: + retval = NET_XMIT_SUCCESS; + dev->stats.tx_dropped++; + if (skb) + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } else if (netif_msg_tx_queued (dev)) { + devdbg (dev, "> tx, len %d, type 0x%x", + skb->len, skb->protocol); + } + + return retval; +} + +/*------------------------------------------------------------------------- + * + * USB Device Driver support + * + *-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +void smscusbnet_disconnect (struct usb_interface *intf) +{ + struct usbnet *dev = NULL; + struct usb_device *xdev = NULL; + struct net_device *net = NULL; + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + if (!dev) + return; + + xdev = interface_to_usbdev (intf); + + if (netif_msg_probe (dev)) + devinfo (dev, "unregister '%s' usb-%s-%s, %s", + intf->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description); + + net = dev->net; + unregister_netdev (net); + + devdbg (dev, "smscusbnet_disconnect, rx_urb_pool_head = %d, rx_urb_pool_tail = %d", dev->rx_urb_pool_head, dev->rx_urb_pool_tail); + + if(dev->rx_urb_pool){ + while(dev->rx_urb_pool_head != dev->rx_urb_pool_tail){ + if(dev->rx_urb_pool[dev->rx_urb_pool_head])usb_free_urb(dev->rx_urb_pool[dev->rx_urb_pool_head]); + dev->rx_urb_pool[dev->rx_urb_pool_head] = NULL; + dev->rx_urb_pool_head = (dev->rx_urb_pool_head + 1) % dev->rx_urb_pool_size; + } + if(dev->rx_urb_pool[dev->rx_urb_pool_head]){ + usb_free_urb(dev->rx_urb_pool[dev->rx_urb_pool_head]); + dev->rx_urb_pool[dev->rx_urb_pool_head] = NULL; + } + kfree(dev->rx_urb_pool); + } + + if(operational_mode && dev->interrupt){ + usb_free_urb(dev->interrupt); + } + if(dev->interrupt_urb_buffer){ + kfree(dev->interrupt_urb_buffer); + dev->interrupt_urb_buffer = NULL; + } + + if (dev->driver_info->unbind) { + devinfo (dev,"unbind usb\n"); + dev->driver_info->unbind (dev, intf); + } + + /* we don't hold rtnl here ... */ + flush_scheduled_work (); + flush_workqueue(dev->MyWorkQueue); + destroy_workqueue(dev->MyWorkQueue); + + free_netdev(net); + usb_put_dev (xdev); + +} + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +int +smscusbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) +{ + struct usbnet *dev = NULL; + struct net_device *net = NULL; + struct usb_host_interface *interface = NULL; + struct driver_info *info = NULL; + struct usb_device *xdev = NULL; + int status = 0; + char version[15]; + + info = (struct driver_info *) prod->driver_info; + if (!info) { + return -ENODEV; + } + xdev = interface_to_usbdev (udev); + interface = udev->cur_altsetting; + + usb_get_dev (xdev); + + status = -ENOMEM; + + sprintf(version,"%lX.%02lX.%02lX", (DRIVER_VERSION>>16),(DRIVER_VERSION>>8)&0xFF,(DRIVER_VERSION&0xFFUL)); + printk("Driver smscusbnet.ko verison %s, built on %s, %s\n",version, __TIME__, __DATE__); + + // set up our own records + net = alloc_etherdev(sizeof(*dev)); + if (!net) { + devdbg (dev,"can't kmalloc dev"); + goto out; + } + dev = netdev_priv(net); + + init_MUTEX(&dev->pm_mutex); + + dev->udev = xdev; + dev->uintf = udev; + dev->driver_info = info; + dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV + | NETIF_MSG_PROBE | NETIF_MSG_LINK); + skb_queue_head_init (&dev->rxq); + skb_queue_head_init (&dev->txq); + skb_queue_head_init (&dev->tx_pending_q); + skb_queue_head_init (&dev->done); + dev->bh.func = smscusbnet_bh; + dev->bh.data = (unsigned long) dev; + + dev->idVendor = prod->idVendor; + dev->idProduct = prod->idProduct; + + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) + INIT_WORK (&dev->kevent, kevent,dev); + #else + INIT_WORK (&dev->kevent, kevent); + #endif + + if(operational_mode)devdbg (dev,"Operational mode enabled\n"); + + dev->MyWorkQueue=create_singlethread_workqueue("intr_work"); + if (!dev->MyWorkQueue) { + devdbg (dev,"can't create MyWorkQueue!\n"); + goto out1; + } + #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) + INIT_WORK (&dev->myevent, myevent,dev); + #else + INIT_WORK (&dev->myevent, myevent); + #endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)) + SET_MODULE_OWNER (net); +#endif + dev->net = net; + strcpy (net->name, "usb%d"); + memcpy (net->dev_addr, node_id, sizeof node_id); + + /* rx and tx sides can use different message sizes; + * bind() should set rx_urb_size in that case. + */ + net->hard_header_len += EXTRA_HEADER_LEN; //Reserve extra 8 bytes for control word to eliminate memcpy in tx_fixup() + devdbg (dev, "hard_header_len = %d\n", (int)net->hard_header_len); + dev->hard_mtu = net->mtu + net->hard_header_len; +#if 0 +// dma_supported() is deeply broken on almost all architectures + // possible with some EHCI controllers + if (dma_supported (&udev->dev, DMA_64BIT_MASK)) + net->features |= NETIF_F_HIGHDMA; +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) + net->change_mtu = smscusbnet_change_mtu; + net->get_stats = smscusbnet_get_stats; + net->hard_start_xmit = smscusbnet_start_xmit; + net->open = smscusbnet_open; + net->stop = smscusbnet_stop; + net->tx_timeout = smscusbnet_tx_timeout; +#else + net->netdev_ops = &smscusbnet_netdev_ops; +#endif //linux 2.6.29 + net->watchdog_timeo = TX_TIMEOUT_JIFFIES; + + // allow device-specific bind/init procedures + // NOTE net->name still not usable ... + if (info->bind) { + status = info->bind (dev, udev); + + if (status < 0) + { + devdbg (dev,"info->bind,status<0 \n"); + goto out2; + } + // heuristic: "usb%d" for links we know are two-host, + // else "eth%d" when there's reasonable doubt. userspace + // can rename the link if it knows better. + if ((dev->driver_info->flags & FLAG_ETHER) != 0 + && (net->dev_addr [0] & 0x02) == 0) + { + strcpy (net->name, "eth%d"); + } + + /* maybe the remote can't receive an Ethernet MTU */ + if (net->mtu > (dev->hard_mtu - net->hard_header_len)) + net->mtu = dev->hard_mtu - net->hard_header_len; + } else if (!info->in || !info->out) + { + status = smscusbnet_get_endpoints (dev, udev); + } + else { + dev->in = usb_rcvbulkpipe (xdev, info->in); + dev->out = usb_sndbulkpipe (xdev, info->out); + if (!(info->flags & FLAG_NO_SETINT)) + status = usb_set_interface (xdev, + interface->desc.bInterfaceNumber, + interface->desc.bAlternateSetting); + else + status = 0; + + } + if (status >= 0 && dev->status) + { + devdbg (dev,"status==0 \n"); + status = init_status (dev, udev); + } + if (status < 0) + { + devdbg (dev,"status<0 \n"); + goto out3; + } + + if (!dev->rx_urb_size) + dev->rx_urb_size = dev->hard_mtu; + + devdbg (dev, "rx_urb_size = %d\n", (int)dev->rx_urb_size); + dev->StopSummitUrb=1; + + dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1); + SET_NETDEV_DEV(net, &udev->dev); + status = register_netdev (net); + + if (status) + { + goto out3; + } + if (netif_msg_probe (dev)) + devinfo (dev, "register '%s' at usb-%s-%s, %s, " + "%02x:%02x:%02x:%02x:%02x:%02x", + udev->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description, + net->dev_addr [0], net->dev_addr [1], + net->dev_addr [2], net->dev_addr [3], + net->dev_addr [4], net->dev_addr [5]); + + // ok, it's ready to go. + usb_set_intfdata (udev, dev); + + // start as if the link is up + netif_device_attach (net); + + return 0; + +out3: + if (info->unbind) + info->unbind (dev, udev); +out2: + if (dev->MyWorkQueue) { + destroy_workqueue(dev->MyWorkQueue); + } +out1: + free_netdev(net); +out: + usb_put_dev(xdev); + + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* FIXME these suspend/resume methods assume non-CDC style + * devices, with only one interface. + */ + +int smscusbnet_FreeQueue (struct usbnet *dev) +{ + DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); + DECLARE_WAITQUEUE (wait, current); + + /* accelerate emptying of the rx and queues, to avoid + * having everything error out. + */ + // ensure there are no more active urbs + add_wait_queue (&unlink_wakeup, &wait); + dev->wait = &unlink_wakeup; + (void) unlink_urbs (dev, &dev->rxq); + (void) unlink_urbs (dev, &dev->txq); + + // maybe wait for deletions to finish. + while (!skb_queue_empty(&dev->rxq) && + !skb_queue_empty(&dev->txq) && + !skb_queue_empty(&dev->done)) { + msleep(UNLINK_TIMEOUT_MS); + } + dev->wait = NULL; + remove_wait_queue (&unlink_wakeup, &wait); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* FIXME these suspend/resume methods assume non-CDC style + * devices, with only one interface. + */ + #ifndef pm_message_t +#define pm_message_t u32 +#endif +int smscusbnet_suspend (struct usb_interface *intf, pm_message_t state) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + /* accelerate emptying of the rx and queues, to avoid + * having everything error out. + */ + netif_device_detach (dev->net); + + (void) unlink_urbs (dev, &dev->txq); + (void) unlink_urbs (dev, &dev->rxq); + + return 0; +} + +int smscusbnet_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + netif_device_attach (dev->net); + tasklet_schedule (&dev->bh); + return 0; +} + +EXPORT_SYMBOL_GPL(smscusbnet_IsOperationalMode); +EXPORT_SYMBOL_GPL(smscusbnet_get_endpoints); +EXPORT_SYMBOL_GPL(smscusbnet_skb_return); +EXPORT_SYMBOL_GPL(smscusbnet_defer_kevent); +EXPORT_SYMBOL_GPL(smscusbnet_defer_myevent); +EXPORT_SYMBOL_GPL(smscusbnet_disconnect); +EXPORT_SYMBOL_GPL(smscusbnet_probe); +EXPORT_SYMBOL_GPL(smscusbnet_FreeQueue); +EXPORT_SYMBOL_GPL(smscusbnet_linkpolling); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +EXPORT_SYMBOL(smscusbnet_stop); +EXPORT_SYMBOL(smscusbnet_get_stats); +EXPORT_SYMBOL(smscusbnet_open); +EXPORT_SYMBOL(smscusbnet_start_xmit); +EXPORT_SYMBOL(smscusbnet_tx_timeout); +EXPORT_SYMBOL(smscusbnet_change_mtu); +#endif +/*-------------------------------------------------------------------------*/ + +static int __init smscusbnet_init(void) +{ + /* compiler should optimize this out */ + BUG_ON (sizeof (((struct sk_buff *)0)->cb) + < sizeof (struct skb_data)); + + random_ether_addr(node_id); + return 0; +} +module_init(smscusbnet_init); + +static void __exit smscusbnet_exit(void) +{ +} +module_exit(smscusbnet_exit); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB network driver framework"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/smscusbnet.h b/drivers/net/usb/smscusbnet.h new file mode 100644 index 000000000000..bef2fb96886d --- /dev/null +++ b/drivers/net/usb/smscusbnet.h @@ -0,0 +1,318 @@ +/* + * USB Networking Link Interface + * + * Copyright (C) 2000-2005 by David Brownell + * Copyright (C) 2003-2005 David Hollis + * + * 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 + */ + + +#ifndef __SMSCUSBNET_H +#define __SMSCUSBNET_H + +#ifndef DEBUG +#define DEBUG +#endif + +typedef unsigned char BOOLEAN; +#define TRUE ((BOOLEAN)1) +#define FALSE ((BOOLEAN)0) + +#define ID_REV_9500_CHIPID 0x9500 +#define ID_REV_9500A_CHIPID 0x9E00 +#define ID_REV_9512_CHIPID 0xEC00 +#define ID_REV_7500_CHIPID 0x7500 + +#define EXTRA_HEADER_LEN 8 +#define VLAN_DUMMY 0xFFFF + +enum{ + DEV_SMSC9500, + DEV_SMSC7500 +}; + +//#define RX_SKB_COPY +enum{ + RX_FIXUP_VALID_SKB, + RX_FIXUP_INVALID_SKB, + RX_FIXUP_ERROR, +}; + +struct ExtraErrorCounter +{ + u32 tx_epipe; + u32 tx_eproto; + u32 tx_etimeout; + u32 tx_eilseq; + u32 tx_eoverflow; + + u32 rx_epipe; + u32 rx_eproto; + u32 rx_etimeout; + u32 rx_eilseq; + u32 rx_eoverflow; + +}; + +#define AUTOSUSPEND_DYNAMIC 0x01 //Suspend on s0, work for lan9500 and lan9500a +#define AUTOSUSPEND_DYNAMIC_S3 0x02 //Suspend on s3, only for lan9500a +#define AUTOSUSPEND_LINKDOWN 0x04 //Suspend when link is down +#define AUTOSUSPEND_INTFDOWN 0x08 //Suspend when interface is down +#define AUTOSUSPEND_DETACH 0x10 //Enable smart detach + +enum{ + FEATURE_SUSPEND3, + FEATURE_SMARTDETACH, + FEATURE_NEWSTATIS_CNT, + FEATURE_SEP_LEDS, + FEATURE_WUFF_8, + FEATURE_MAX_NO +}; + +enum{ + EVENT_TX_HALT, + EVENT_RX_HALT, + EVENT_RX_MEMORY, + EVENT_STS_SPLIT, + EVENT_LINK_RESET, + EVENT_SET_MULTICAST, + EVENT_HAS_FRAME, + EVENT_DEV_RECOVERY, + EVENT_IDLE_CHECK, + EVENT_IDLE_RESUME, + EVENT_LINK_DOWN, +}; + +/* interface from usbnet core to each USB networking link we handle */ +struct usbnet { + /* housekeeping */ + struct usb_device *udev; + struct usb_interface *uintf; + struct driver_info *driver_info; + wait_queue_head_t *wait; + int StopSummitUrb; + + /* i/o info: pipes etc */ + unsigned in, out; + struct usb_host_endpoint *status; + unsigned maxpacket; + struct timer_list delay; + struct timer_list LinkPollingTimer; + BOOLEAN StopLinkPolling; + u16 idProduct; //Product id from device descriptor + u16 idVendor; //Vendor id from device descriptor + u32 linkDownSuspend; //1. auto-negotiation complete; 2. energy detection; + u32 dynamicSuspend; //Enable dynamic suspend when there are no traffic + u32 smartDetach; //Enable smart detach, only for LAN9500A + + /* protocol/interface state */ + struct net_device *net; + struct net_device_stats stats; + struct ExtraErrorCounter extra_error_cnts; + int msg_enable; + unsigned long data [5]; + u32 xid; + u32 hard_mtu; /* count any extra framing */ + size_t rx_urb_size; /* size for rx urbs */ + struct mii_if_info mii; + + /* various kinds of pending driver work */ + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head tx_pending_q; + struct sk_buff_head done; + + struct urb **rx_urb_pool; //idle urb pool + unsigned int rx_urb_pool_head; + unsigned int rx_urb_pool_tail; + unsigned int rx_urb_pool_size; + spinlock_t rx_urblist_lock; + + struct urb *tx_urb; + + struct urb *interrupt; + void *interrupt_urb_buffer; + struct tasklet_struct bh; + + struct work_struct kevent; + struct work_struct myevent; + struct workqueue_struct * MyWorkQueue; + unsigned long flags; + BOOLEAN intr_urb_delay_submit; + + int idleCount; + int linkcheck; + BOOLEAN pmLock; + struct semaphore pm_mutex; + int suspendFlag; //Flag indicates link down suspend and select suspend + u32 chipID; + u32 preRxFifoDroppedFrame; //Previous counter value + + int chipDependFeatures[FEATURE_MAX_NO]; //Flag for chip-depend feratures + struct vlan_group *vlgrp; //vlan support + + int device_id; //DEV_SMSC9500 or DEV_SMSC7500 + int tx_hold_on_completion; +}; +#define PM_IDLE_DELAY 3 //Time before auto-suspend +enum{ + WAKEPHY_OFF, + WAKEPHY_NEGO_COMPLETE, + WAKEPHY_ENERGY +}; + +static inline struct usb_driver *driver_of(struct usb_interface *intf) +{ + return to_usb_driver(intf->dev.driver); +} +/* interface from the device/framing level "minidriver" to core */ +struct driver_info { + char *description; + + int flags; +/* framing is CDC Ethernet, not writing ZLPs (hw issues), or optionally: */ +#define FLAG_FRAMING_NC 0x0001 /* guard against device dropouts */ +#define FLAG_FRAMING_GL 0x0002 /* genelink batches packets */ +#define FLAG_FRAMING_Z 0x0004 /* zaurus adds a trailer */ +#define FLAG_FRAMING_RN 0x0008 /* RNDIS batches, plus huge header */ + +#define FLAG_NO_SETINT 0x0010 /* device can't set_interface() */ +#define FLAG_ETHER 0x0020 /* maybe use "eth%d" names */ + +#define FLAG_FRAMING_AX 0x0040 /* AX88772/178 packets */ + + /* init device ... can sleep, or cause probe() failure */ + int (*bind)(struct usbnet *, struct usb_interface *); + + /* cleanup device ... can sleep, but can't fail */ + void (*unbind)(struct usbnet *, struct usb_interface *); + + /* reset device ... can sleep */ + int (*reset)(struct usbnet *); + + /* see if peer is connected ... can sleep */ + int (*check_connect)(struct usbnet *); + + /* for status polling */ + void (*status)(struct usbnet *, struct urb *); + /* Set max frame size*/ + int (*set_max_frame_size)(struct usbnet *, int size); + /* link reset handling, called from defer_kevent */ + int (*link_reset)(struct usbnet *); + + /* fixup rx packet (strip framing) */ + int (*rx_fixup)(struct usbnet *dev, struct sk_buff *skb); + + /*set multicast list */ + int (*rx_setmulticastlist) (struct usbnet *dev); + + /* fixup tx packet (add framing) */ + struct sk_buff *(*tx_fixup)(struct usbnet *dev, + struct sk_buff *skb, int flags); + + /* for new devices, use the descriptor-reading code instead */ + int in; /* rx endpoint */ + int out; /* tx endpoint */ + + unsigned long data; /* Misc driver specific data */ + +}; + +/* Minidrivers are just drivers using the "usbnet" core as a powerful + * network-specific subroutine library ... that happens to do pretty + * much everything except custom framing and chip-specific stuff. + */ +extern int smscusbnet_probe(struct usb_interface *, const struct usb_device_id *); +extern void smscusbnet_disconnect(struct usb_interface *); +extern void smscusbnet_linkpolling(unsigned long ptr); + +/* Drivers that reuse some of the standard USB CDC infrastructure + * (notably, using multiple interfaces according to the the CDC + * union descriptor) get some helper code. + */ +struct cdc_state { + struct usb_cdc_header_desc *header; + struct usb_cdc_union_desc *u; + struct usb_cdc_ether_desc *ether; + struct usb_interface *control; + struct usb_interface *data; +}; + +extern int usbnet_generic_cdc_bind (struct usbnet *, struct usb_interface *); +extern void usbnet_cdc_unbind (struct usbnet *, struct usb_interface *); + +/* CDC and RNDIS support the same host-chosen packet filters for IN transfers */ +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + |USB_CDC_PACKET_TYPE_PROMISCUOUS \ + |USB_CDC_PACKET_TYPE_DIRECTED) + + +/* we record the state for each of our queued skbs */ +enum skb_state { + illegal = 0, + tx_start, tx_done, + rx_start, rx_done, rx_cleanup +}; + +struct skb_data { /* skb->cb is one of these */ + struct urb *urb; + struct usbnet *dev; + enum skb_state state; + size_t length; + size_t pkt_cnt; +}; + +extern int smscusbnet_IsOperationalMode(struct usbnet *); +extern int smscusbnet_get_endpoints(struct usbnet *, struct usb_interface *); +extern void smscusbnet_defer_kevent (struct usbnet *, int); +extern void smscusbnet_defer_myevent (struct usbnet *, int); +extern void smscusbnet_skb_return (struct usbnet *, struct sk_buff *); +extern int summit_IntrUrb (struct usbnet *dev); + +extern u32 smscusbnet_get_msglevel (struct net_device *); +extern void smscusbnet_set_msglevel (struct net_device *, u32); +extern void smscusbnet_get_drvinfo (struct net_device *, struct ethtool_drvinfo *); +extern int smscusbnet_FreeQueue (struct usbnet *dev); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +extern int smscusbnet_stop (struct net_device *net); +extern struct net_device_stats *smscusbnet_get_stats (struct net_device *net); +extern int smscusbnet_open (struct net_device *net); +extern int smscusbnet_start_xmit (struct sk_buff *skb, struct net_device *net); +extern int smscusbnet_change_mtu (struct net_device *net, int new_mtu); +extern void smscusbnet_tx_timeout (struct net_device *net); +#endif +/* messaging support includes the interface name, so it must not be + * used before it has one ... notably, in minidriver bind() calls. + */ +#ifdef DEBUG +#define devdbg(usbnet, fmt, arg...) \ + printk(KERN_DEBUG "%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#else +#define devdbg(usbnet, fmt, arg...) do {} while(0) +#endif + +#define deverr(usbnet, fmt, arg...) \ + printk(KERN_ERR "%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#define devwarn(usbnet, fmt, arg...) \ + printk(KERN_WARNING "%s: " fmt "\n" , (usbnet)->net->name , ## arg) + +#define devinfo(usbnet, fmt, arg...) \ + printk(KERN_INFO "%s: " fmt "\n" , (usbnet)->net->name , ## arg); \ + + +#endif /* __smscusbnet_H */ diff --git a/drivers/net/usb/version.h b/drivers/net/usb/version.h new file mode 100644 index 000000000000..f085e4759ce1 --- /dev/null +++ b/drivers/net/usb/version.h @@ -0,0 +1,6 @@ +#ifndef SMSC9500_VERSION_H_ +#define SMSC9500_VERSION_H_ + +#define DRIVER_VERSION (0x01010604UL) + +#endif /*SMSC9500_VERSION_H_*/ -- cgit v1.2.3