diff options
Diffstat (limited to 'drivers/net/usb/smsc9500.c')
-rw-r--r-- | drivers/net/usb/smsc9500.c | 5070 |
1 files changed, 5070 insertions, 0 deletions
diff --git a/drivers/net/usb/smsc9500.c b/drivers/net/usb/smsc9500.c new file mode 100644 index 000000000000..a41cf4477d40 --- /dev/null +++ b/drivers/net/usb/smsc9500.c @@ -0,0 +1,5070 @@ + + /*************************************************************************** + * + * 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 <linux/version.h> + +#define TX_SKB_FORCE_COPY + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)) +#include <linux/config.h> +#endif + +#include <linux/module.h> + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) +#include <linux/moduleparam.h> +#endif + +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/workqueue.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/crc32.h> +#include <linux/ioport.h> +#include <asm/io.h> + +#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; i<dwLength; i++){ + //Isuue command + dwValue = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (dwAddr & E2P_CMD_ADDR_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, E2P_CMD, dwValue)); + CHECK_RETURN_STATUS(smsc9500_eeprom_IsBusy(dev)); + + //Read data when ready + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, E2P_DATA, &dwValue)); + + pbValue[i] = dwValue & 0xFF; + dwAddr++; + } + ret = 0; +DONE: + up(&adapterData->eeprom_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; i<dwLength; i++){ + + //Fill data register + dwValue = pbValue[i]; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, E2P_DATA, dwValue)); + + //Send "write" command + dwValue = E2P_CMD_BUSY_ | E2P_CMD_WRITE_ | (dwAddr & E2P_CMD_ADDR_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, E2P_CMD, dwValue)); + CHECK_RETURN_STATUS(smsc9500_eeprom_IsBusy(dev)); + + dwAddr++; + } + + ret = SMSC9500_SUCCESS; +DONE: + + up(&adapterData->eeprom_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; i<length; i++){ + //Set device ram address + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, DP_ADDR, startAddr + i)); + //Enable reading + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, DP_CMD, DP_CMD_READ)); + + if(!IsDataPortReady(dev))goto DONE; + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, DP_DATA0, valLow + i)); + if(valHigh){ + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, DP_DATA1, valHigh + i)); + } + } + + ret = SMSC9500_SUCCESS; +DONE: + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, DP_SEL, &dwValue)); + dwValue &= ~DP_SEL_TESTEN; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, DP_SEL, dwValue)); + + up(&adapterData->internal_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(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)); + + 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; i<MAX_LAN_REG_NUM; i++){ + if(smsc9500_read_reg(dev, LanRegMap[i], &dwBuf) < 0){ + SMSC_WARNING("Failed to read LAN reg 0x%x", (unsigned int)LanRegMap[i]); + success = FALSE; + }else{ + ioctlData->Data[i] = dwBuf; + } + } + break; + case COMMAND_DUMP_MAC_REGS: + if(privateData->LanInitialized) { + success=TRUE; + for(i=0; i<MAX_MAC_REG_NUM; i++){ + if(smsc9500_read_reg(dev, MacRegMap[i], &dwBuf) < 0){ + SMSC_WARNING("Failed to read MAC reg 0x%x", (unsigned int)MacRegMap[i]); + success = FALSE; + }else{ + ioctlData->Data[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; i<MAX_PHY_REG_NUM; i++){ + if(smsc9500_read_phy(dev, PhyRegMap[i], &dwBuf) < 0){ + SMSC_WARNING("Failed to read PHY reg 0x%x", (unsigned int)PhyRegMap[i]); + success = FALSE; + }else{ + ioctlData->Data[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;i<skbFragCnt;i++) + { + skb_frag_t *frag = &skb_shinfo(skb)->frags[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;i<skbFragCnt;i++) + { + skb_frag_t *frag = &skb_shinfo(skb)->frags[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; i<FEATURE_MAX_NO; i++){ + dev->chipDependFeatures[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)); + 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; i<filterMaskCnt; i++){ + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUFF, sFilter.dwFilterMask[i])); + } + for(i=0; i<filterCmdCnt; i++){ + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUFF, sFilter.dwCommad[i])); + } + for(i=0; i<filterOffsetCnt; i++){ + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUFF, sFilter.dwOffset[i])); + } + for(i=0; i<filterCrcCnt; i++){ + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,WUFF, sFilter.dwCRC[i])); + } + + // Clear any pending pattern match packet status + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 |= WUCSR_WUFR_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + + // Enable pattern match packet wake + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 |= WUCSR_WAKE_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 |= PM_CTL_WOL_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + + } + else + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 &= (~WUCSR_WAKE_EN_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + } + + if (opts & WAKE_MAGIC) + { + SMSC_TRACE(DBG_PWR,"Setting magic packet detection\n"); + // Clear any pending magic packet status + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 |= WUCSR_MPR_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + + // Enable MP wake + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 |= WUCSR_MPEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 |= PM_CTL_WOL_EN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32 )); + } + else + { + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 &= (~WUCSR_MPEN_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + } + + + //enable recevier + CHECK_RETURN_STATUS(smsc9500_read_reg(dev,MAC_CR,&dwValue)); + dwValue |= MAC_CR_RXEN_; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev,MAC_CR, dwValue)); + + 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_WOL_; //Clear wol status, should not change suspned_mode while clearing WUPS_sts[1] + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + + CHECK_RETURN_STATUS(smsc9500_set_feature(dev,USB_DEVICE_REMOTE_WAKEUP)); + + + } + + }else { + + SMSC_TRACE(DBG_PWR,"Disabling Wake events in ESS Regs. "); + + // Disable Energy detect (Link up) & Wake up events to do USB wake + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, WUCSR, &Value32)); + Value32 |=~(WUCSR_MPEN_ | WUCSR_WAKE_EN_ ); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, WUCSR, Value32)); + + CHECK_RETURN_STATUS(smsc9500_read_reg(dev, PM_CTRL, &Value32)); + Value32 &= ~(PM_CTL_ED_EN_ | PM_CTL_WOL_EN_); + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + + // Put Lan9500 in Suspend2 + SMSC_TRACE(DBG_PWR,"Setting Suspend2 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_2; + CHECK_RETURN_STATUS(smsc9500_write_reg(dev, PM_CTRL, Value32)); + } + + ret = 0; +DONE: + return ret; +} + +static int ResetWakeupEvents(struct net_device *netdev) +{ + u32 dwValue; + u16 wValue; + int ret = SMSC9500_FAIL; + + struct usbnet * dev=netdev_priv(netdev); + PADAPTER_DATA adapterData=(PADAPTER_DATA)(dev->data[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; + + 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{// if(suspendFlag & AUTOSUSPEND_LINKDOWN){//Resume immediately + 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); + } + 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"); + |