diff options
author | hayeswang <hayeswang@realtek.com> | 2013-05-02 16:01:25 +0000 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-05-06 16:16:52 -0400 |
commit | ac718b69301c7c07cd0d858570f76a0e1c4c8726 (patch) | |
tree | 8d70adae65fe8326403b3db73f25b9b52d24de93 /drivers/net/usb | |
parent | c81400be716aa4c76f6ebf339ba94358dbbf6da6 (diff) |
net/usb: new driver for RTL8152
Add new driver for supporting Realtek RTL8152 Based USB 2.0 Ethernet Adapters
Signed-off-by: Hayes Wang <hayeswang@realtek.com>
Cc: Realtek linux nic maintainers <nic_swsd@realtek.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/usb')
-rw-r--r-- | drivers/net/usb/Kconfig | 11 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/usb/cdc_ether.c | 10 | ||||
-rw-r--r-- | drivers/net/usb/r8152.c | 1767 |
4 files changed, 1789 insertions, 0 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 7c769d8e25a..287cc624b90 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -93,6 +93,17 @@ config USB_RTL8150 To compile this driver as a module, choose M here: the module will be called rtl8150. +config USB_RTL8152 + tristate "Realtek RTL8152 Based USB 2.0 Ethernet Adapters" + select NET_CORE + select MII + help + This option adds support for Realtek RTL8152 based USB 2.0 + 10/100 Ethernet adapters. + + To compile this driver as a module, choose M here: the + module will be called r8152. + config USB_USBNET tristate "Multi-purpose USB Networking Framework" select NET_CORE diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index 119b06c9aa1..9ab5c9d4b45 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_USB_CATC) += catc.o obj-$(CONFIG_USB_KAWETH) += kaweth.o obj-$(CONFIG_USB_PEGASUS) += pegasus.o obj-$(CONFIG_USB_RTL8150) += rtl8150.o +obj-$(CONFIG_USB_RTL8152) += r8152.o obj-$(CONFIG_USB_HSO) += hso.o obj-$(CONFIG_USB_NET_AX8817X) += asix.o asix-y := asix_devices.o asix_common.o ax88172a.o diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index 4ff71d619cd..24fbec27a22 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -479,6 +479,7 @@ static const struct driver_info wwan_info = { #define NOVATEL_VENDOR_ID 0x1410 #define ZTE_VENDOR_ID 0x19D2 #define DELL_VENDOR_ID 0x413C +#define REALTEK_VENDOR_ID 0x0bda static const struct usb_device_id products [] = { /* @@ -619,6 +620,15 @@ static const struct usb_device_id products [] = { .driver_info = 0, }, +/* Realtek RTL8152 Based USB 2.0 Ethernet Adapters */ +#if defined(CONFIG_USB_RTL8152) || defined(CONFIG_USB_RTL8152_MODULE) +{ + USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8152, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, +#endif + /* * WHITELIST!!! * diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c new file mode 100644 index 00000000000..14e51988863 --- /dev/null +++ b/drivers/net/usb/r8152.c @@ -0,0 +1,1767 @@ +/* + * Copyright (c) 2013 Realtek Semiconductor Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + */ + +#include <linux/init.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/usb.h> +#include <linux/crc32.h> +#include <linux/if_vlan.h> +#include <linux/uaccess.h> + +/* Version Information */ +#define DRIVER_VERSION "v1.0.0 (2013/05/03)" +#define DRIVER_AUTHOR "Realtek linux nic maintainers <nic_swsd@realtek.com>" +#define DRIVER_DESC "Realtek RTL8152 Based USB 2.0 Ethernet Adapters" +#define MODULENAME "r8152" + +#define R8152_PHY_ID 32 + +#define PLA_IDR 0xc000 +#define PLA_RCR 0xc010 +#define PLA_RMS 0xc016 +#define PLA_RXFIFO_CTRL0 0xc0a0 +#define PLA_RXFIFO_CTRL1 0xc0a4 +#define PLA_RXFIFO_CTRL2 0xc0a8 +#define PLA_FMC 0xc0b4 +#define PLA_CFG_WOL 0xc0b6 +#define PLA_MAR 0xcd00 +#define PAL_BDC_CR 0xd1a0 +#define PLA_LEDSEL 0xdd90 +#define PLA_LED_FEATURE 0xdd92 +#define PLA_PHYAR 0xde00 +#define PLA_GPHY_INTR_IMR 0xe022 +#define PLA_EEE_CR 0xe040 +#define PLA_EEEP_CR 0xe080 +#define PLA_MAC_PWR_CTRL 0xe0c0 +#define PLA_TCR0 0xe610 +#define PLA_TCR1 0xe612 +#define PLA_TXFIFO_CTRL 0xe618 +#define PLA_RSTTELLY 0xe800 +#define PLA_CR 0xe813 +#define PLA_CRWECR 0xe81c +#define PLA_CONFIG5 0xe822 +#define PLA_PHY_PWR 0xe84c +#define PLA_OOB_CTRL 0xe84f +#define PLA_CPCR 0xe854 +#define PLA_MISC_0 0xe858 +#define PLA_MISC_1 0xe85a +#define PLA_OCP_GPHY_BASE 0xe86c +#define PLA_TELLYCNT 0xe890 +#define PLA_SFF_STS_7 0xe8de +#define PLA_PHYSTATUS 0xe908 +#define PLA_BP_BA 0xfc26 +#define PLA_BP_0 0xfc28 +#define PLA_BP_1 0xfc2a +#define PLA_BP_2 0xfc2c +#define PLA_BP_3 0xfc2e +#define PLA_BP_4 0xfc30 +#define PLA_BP_5 0xfc32 +#define PLA_BP_6 0xfc34 +#define PLA_BP_7 0xfc36 + +#define USB_DEV_STAT 0xb808 +#define USB_USB_CTRL 0xd406 +#define USB_PHY_CTRL 0xd408 +#define USB_TX_AGG 0xd40a +#define USB_RX_BUF_TH 0xd40c +#define USB_USB_TIMER 0xd428 +#define USB_PM_CTRL_STATUS 0xd432 +#define USB_TX_DMA 0xd434 +#define USB_UPS_CTRL 0xd800 +#define USB_BP_BA 0xfc26 +#define USB_BP_0 0xfc28 +#define USB_BP_1 0xfc2a +#define USB_BP_2 0xfc2c +#define USB_BP_3 0xfc2e +#define USB_BP_4 0xfc30 +#define USB_BP_5 0xfc32 +#define USB_BP_6 0xfc34 +#define USB_BP_7 0xfc36 + +/* OCP Registers */ +#define OCP_ALDPS_CONFIG 0x2010 +#define OCP_EEE_CONFIG1 0x2080 +#define OCP_EEE_CONFIG2 0x2092 +#define OCP_EEE_CONFIG3 0x2094 +#define OCP_EEE_AR 0xa41a +#define OCP_EEE_DATA 0xa41c + +/* PLA_RCR */ +#define RCR_AAP 0x00000001 +#define RCR_APM 0x00000002 +#define RCR_AM 0x00000004 +#define RCR_AB 0x00000008 +#define RCR_ACPT_ALL (RCR_AAP | RCR_APM | RCR_AM | RCR_AB) + +/* PLA_RXFIFO_CTRL0 */ +#define RXFIFO_THR1_NORMAL 0x00080002 +#define RXFIFO_THR1_OOB 0x01800003 + +/* PLA_RXFIFO_CTRL1 */ +#define RXFIFO_THR2_FULL 0x00000060 +#define RXFIFO_THR2_HIGH 0x00000038 +#define RXFIFO_THR2_OOB 0x0000004a + +/* PLA_RXFIFO_CTRL2 */ +#define RXFIFO_THR3_FULL 0x00000078 +#define RXFIFO_THR3_HIGH 0x00000048 +#define RXFIFO_THR3_OOB 0x0000005a + +/* PLA_TXFIFO_CTRL */ +#define TXFIFO_THR_NORMAL 0x00400008 + +/* PLA_FMC */ +#define FMC_FCR_MCU_EN 0x0001 + +/* PLA_EEEP_CR */ +#define EEEP_CR_EEEP_TX 0x0002 + +/* PLA_TCR0 */ +#define TCR0_TX_EMPTY 0x0800 +#define TCR0_AUTO_FIFO 0x0080 + +/* PLA_TCR1 */ +#define VERSION_MASK 0x7cf0 + +/* PLA_CR */ +#define CR_RST 0x10 +#define CR_RE 0x08 +#define CR_TE 0x04 + +/* PLA_CRWECR */ +#define CRWECR_NORAML 0x00 +#define CRWECR_CONFIG 0xc0 + +/* PLA_OOB_CTRL */ +#define NOW_IS_OOB 0x80 +#define TXFIFO_EMPTY 0x20 +#define RXFIFO_EMPTY 0x10 +#define LINK_LIST_READY 0x02 +#define DIS_MCU_CLROOB 0x01 +#define FIFO_EMPTY (TXFIFO_EMPTY | RXFIFO_EMPTY) + +/* PLA_MISC_1 */ +#define RXDY_GATED_EN 0x0008 + +/* PLA_SFF_STS_7 */ +#define RE_INIT_LL 0x8000 +#define MCU_BORW_EN 0x4000 + +/* PLA_CPCR */ +#define CPCR_RX_VLAN 0x0040 + +/* PLA_CFG_WOL */ +#define MAGIC_EN 0x0001 + +/* PAL_BDC_CR */ +#define ALDPS_PROXY_MODE 0x0001 + +/* PLA_CONFIG5 */ +#define LAN_WAKE_EN 0x0002 + +/* PLA_LED_FEATURE */ +#define LED_MODE_MASK 0x0700 + +/* PLA_PHY_PWR */ +#define TX_10M_IDLE_EN 0x0080 +#define PFM_PWM_SWITCH 0x0040 + +/* PLA_MAC_PWR_CTRL */ +#define D3_CLK_GATED_EN 0x00004000 +#define MCU_CLK_RATIO 0x07010f07 +#define MCU_CLK_RATIO_MASK 0x0f0f0f0f + +/* PLA_GPHY_INTR_IMR */ +#define GPHY_STS_MSK 0x0001 +#define SPEED_DOWN_MSK 0x0002 +#define SPDWN_RXDV_MSK 0x0004 +#define SPDWN_LINKCHG_MSK 0x0008 + +/* PLA_PHYAR */ +#define PHYAR_FLAG 0x80000000 + +/* PLA_EEE_CR */ +#define EEE_RX_EN 0x0001 +#define EEE_TX_EN 0x0002 + +/* USB_DEV_STAT */ +#define STAT_SPEED_MASK 0x0006 +#define STAT_SPEED_HIGH 0x0000 +#define STAT_SPEED_FULL 0x0001 + +/* USB_TX_AGG */ +#define TX_AGG_MAX_THRESHOLD 0x03 + +/* USB_RX_BUF_TH */ +#define RX_BUF_THR 0x7a120180 + +/* USB_TX_DMA */ +#define TEST_MODE_DISABLE 0x00000001 +#define TX_SIZE_ADJUST1 0x00000100 + +/* USB_UPS_CTRL */ +#define POWER_CUT 0x0100 + +/* USB_PM_CTRL_STATUS */ +#define RWSUME_INDICATE 0x0001 + +/* USB_USB_CTRL */ +#define RX_AGG_DISABLE 0x0010 + +/* OCP_ALDPS_CONFIG */ +#define ENPWRSAVE 0x8000 +#define ENPDNPS 0x0200 +#define LINKENA 0x0100 +#define DIS_SDSAVE 0x0010 + +/* OCP_EEE_CONFIG1 */ +#define RG_TXLPI_MSK_HFDUP 0x8000 +#define RG_MATCLR_EN 0x4000 +#define EEE_10_CAP 0x2000 +#define EEE_NWAY_EN 0x1000 +#define TX_QUIET_EN 0x0200 +#define RX_QUIET_EN 0x0100 +#define SDRISETIME 0x0010 /* bit 4 ~ 6 */ +#define RG_RXLPI_MSK_HFDUP 0x0008 +#define SDFALLTIME 0x0007 /* bit 0 ~ 2 */ + +/* OCP_EEE_CONFIG2 */ +#define RG_LPIHYS_NUM 0x7000 /* bit 12 ~ 15 */ +#define RG_DACQUIET_EN 0x0400 +#define RG_LDVQUIET_EN 0x0200 +#define RG_CKRSEL 0x0020 +#define RG_EEEPRG_EN 0x0010 + +/* OCP_EEE_CONFIG3 */ +#define FST_SNR_EYE_R 0x1500 /* bit 7 ~ 15 */ +#define RG_LFS_SEL 0x0060 /* bit 6 ~ 5 */ +#define MSK_PH 0x0006 /* bit 0 ~ 3 */ + +/* OCP_EEE_AR */ +/* bit[15:14] function */ +#define FUN_ADDR 0x0000 +#define FUN_DATA 0x4000 +/* bit[4:0] device addr */ +#define DEVICE_ADDR 0x0007 + +/* OCP_EEE_DATA */ +#define EEE_ADDR 0x003C +#define EEE_DATA 0x0002 + +enum rtl_register_content { + _100bps = 0x08, + _10bps = 0x04, + LINK_STATUS = 0x02, + FULL_DUP = 0x01, +}; + +#define RTL8152_REQT_READ 0xc0 +#define RTL8152_REQT_WRITE 0x40 +#define RTL8152_REQ_GET_REGS 0x05 +#define RTL8152_REQ_SET_REGS 0x05 + +#define BYTE_EN_DWORD 0xff +#define BYTE_EN_WORD 0x33 +#define BYTE_EN_BYTE 0x11 +#define BYTE_EN_SIX_BYTES 0x3f +#define BYTE_EN_START_MASK 0x0f +#define BYTE_EN_END_MASK 0xf0 + +#define RTL8152_RMS (VLAN_ETH_FRAME_LEN + VLAN_HLEN) +#define RTL8152_TX_TIMEOUT (HZ) + +/* rtl8152 flags */ +enum rtl8152_flags { + RTL8152_UNPLUG = 0, + RX_URB_FAIL, + RTL8152_SET_RX_MODE, + WORK_ENABLE +}; + +/* Define these values to match your device */ +#define VENDOR_ID_REALTEK 0x0bda +#define PRODUCT_ID_RTL8152 0x8152 + +#define MCU_TYPE_PLA 0x0100 +#define MCU_TYPE_USB 0x0000 + +struct rx_desc { + u32 opts1; +#define RX_LEN_MASK 0x7fff + u32 opts2; + u32 opts3; + u32 opts4; + u32 opts5; + u32 opts6; +}; + +struct tx_desc { + u32 opts1; +#define TX_FS (1 << 31) /* First segment of a packet */ +#define TX_LS (1 << 30) /* Final segment of a packet */ +#define TX_LEN_MASK 0xffff + u32 opts2; +}; + +struct r8152 { + unsigned long flags; + struct usb_device *udev; + struct tasklet_struct tl; + struct net_device *netdev; + struct urb *rx_urb, *tx_urb; + struct sk_buff *tx_skb, *rx_skb; + struct delayed_work schedule; + struct mii_if_info mii; + u32 msg_enable; + u16 ocp_base; + u8 version; + u8 speed; +}; + +enum rtl_version { + RTL_VER_UNKNOWN = 0, + RTL_VER_01, + RTL_VER_02 +}; + +/* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). + * The RTL chips use a 64 element hash table based on the Ethernet CRC. + */ +static const int multicast_filter_limit = 32; + +static +int get_registers(struct r8152 *tp, u16 value, u16 index, u16 size, void *data) +{ + return usb_control_msg(tp->udev, usb_rcvctrlpipe(tp->udev, 0), + RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, + value, index, data, size, 500); +} + +static +int set_registers(struct r8152 *tp, u16 value, u16 index, u16 size, void *data) +{ + return usb_control_msg(tp->udev, usb_sndctrlpipe(tp->udev, 0), + RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE, + value, index, data, size, 500); +} + +static int generic_ocp_read(struct r8152 *tp, u16 index, u16 size, + void *data, u16 type) +{ + u16 limit = 64; + int ret = 0; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + /* both size and indix must be 4 bytes align */ + if ((size & 3) || !size || (index & 3) || !data) + return -EPERM; + + if ((u32)index + (u32)size > 0xffff) + return -EPERM; + + while (size) { + if (size > limit) { + ret = get_registers(tp, index, type, limit, data); + if (ret < 0) + break; + + index += limit; + data += limit; + size -= limit; + } else { + ret = get_registers(tp, index, type, size, data); + if (ret < 0) + break; + + index += size; + data += size; + size = 0; + break; + } + } + + return ret; +} + +static int generic_ocp_write(struct r8152 *tp, u16 index, u16 byteen, + u16 size, void *data, u16 type) +{ + int ret; + u16 byteen_start, byteen_end, byen; + u16 limit = 512; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + /* both size and indix must be 4 bytes align */ + if ((size & 3) || !size || (index & 3) || !data) + return -EPERM; + + if ((u32)index + (u32)size > 0xffff) + return -EPERM; + + byteen_start = byteen & BYTE_EN_START_MASK; + byteen_end = byteen & BYTE_EN_END_MASK; + + byen = byteen_start | (byteen_start << 4); + ret = set_registers(tp, index, type | byen, 4, data); + if (ret < 0) + goto error1; + + index += 4; + data += 4; + size -= 4; + + if (size) { + size -= 4; + + while (size) { + if (size > limit) { + ret = set_registers(tp, index, + type | BYTE_EN_DWORD, + limit, data); + if (ret < 0) + goto error1; + + index += limit; + data += limit; + size -= limit; + } else { + ret = set_registers(tp, index, + type | BYTE_EN_DWORD, + size, data); + if (ret < 0) + goto error1; + + index += size; + data += size; + size = 0; + break; + } + } + + byen = byteen_end | (byteen_end >> 4); + ret = set_registers(tp, index, type | byen, 4, data); + if (ret < 0) + goto error1; + } + +error1: + return ret; +} + +static inline +int pla_ocp_read(struct r8152 *tp, u16 index, u16 size, void *data) +{ + return generic_ocp_read(tp, index, size, data, MCU_TYPE_PLA); +} + +static inline +int pla_ocp_write(struct r8152 *tp, u16 index, u16 byteen, u16 size, void *data) +{ + return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_PLA); +} + +static inline +int usb_ocp_read(struct r8152 *tp, u16 index, u16 size, void *data) +{ + return generic_ocp_read(tp, index, size, data, MCU_TYPE_USB); +} + +static inline +int usb_ocp_write(struct r8152 *tp, u16 index, u16 byteen, u16 size, void *data) +{ + return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_USB); +} + +static u32 ocp_read_dword(struct r8152 *tp, u16 type, u16 index) +{ + u32 data; + + if (type == MCU_TYPE_PLA) + pla_ocp_read(tp, index, sizeof(data), &data); + else + usb_ocp_read(tp, index, sizeof(data), &data); + + return __le32_to_cpu(data); +} + +static void ocp_write_dword(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + if (type == MCU_TYPE_PLA) + pla_ocp_write(tp, index, BYTE_EN_DWORD, sizeof(data), &data); + else + usb_ocp_write(tp, index, BYTE_EN_DWORD, sizeof(data), &data); +} + +static u16 ocp_read_word(struct r8152 *tp, u16 type, u16 index) +{ + u32 data; + u8 shift = index & 2; + + index &= ~3; + + if (type == MCU_TYPE_PLA) + pla_ocp_read(tp, index, sizeof(data), &data); + else + usb_ocp_read(tp, index, sizeof(data), &data); + + data = __le32_to_cpu(data); + data >>= (shift * 8); + data &= 0xffff; + + return (u16)data; +} + +static void ocp_write_word(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + u32 tmp, mask = 0xffff; + u16 byen = BYTE_EN_WORD; + u8 shift = index & 2; + + data &= mask; + + if (index & 2) { + byen <<= shift; + mask <<= (shift * 8); + data <<= (shift * 8); + index &= ~3; + } + + if (type == MCU_TYPE_PLA) + pla_ocp_read(tp, index, sizeof(tmp), &tmp); + else + usb_ocp_read(tp, index, sizeof(tmp), &tmp); + + tmp = __le32_to_cpu(tmp) & ~mask; + tmp |= data; + tmp = __cpu_to_le32(tmp); + + if (type == MCU_TYPE_PLA) + pla_ocp_write(tp, index, byen, sizeof(tmp), &tmp); + else + usb_ocp_write(tp, index, byen, sizeof(tmp), &tmp); +} + +static u8 ocp_read_byte(struct r8152 *tp, u16 type, u16 index) +{ + u32 data; + u8 shift = index & 3; + + index &= ~3; + + if (type == MCU_TYPE_PLA) + pla_ocp_read(tp, index, sizeof(data), &data); + else + usb_ocp_read(tp, index, sizeof(data), &data); + + data = __le32_to_cpu(data); + data >>= (shift * 8); + data &= 0xff; + + return (u8)data; +} + +static void ocp_write_byte(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + u32 tmp, mask = 0xff; + u16 byen = BYTE_EN_BYTE; + u8 shift = index & 3; + + data &= mask; + + if (index & 3) { + byen <<= shift; + mask <<= (shift * 8); + data <<= (shift * 8); + index &= ~3; + } + + if (type == MCU_TYPE_PLA) + pla_ocp_read(tp, index, sizeof(tmp), &tmp); + else + usb_ocp_read(tp, index, sizeof(tmp), &tmp); + + tmp = __le32_to_cpu(tmp) & ~mask; + tmp |= data; + tmp = __cpu_to_le32(tmp); + + if (type == MCU_TYPE_PLA) + pla_ocp_write(tp, index, byen, sizeof(tmp), &tmp); + else + usb_ocp_write(tp, index, byen, sizeof(tmp), &tmp); +} + +static void r8152_mdio_write(struct r8152 *tp, u32 reg_addr, u32 value) +{ + u32 ocp_data; + int i; + + ocp_data = PHYAR_FLAG | ((reg_addr & 0x1f) << 16) | + (value & 0xffff); + + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_PHYAR, ocp_data); + + for (i = 20; i > 0; i--) { + udelay(25); + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_PHYAR); + if (!(ocp_data & PHYAR_FLAG)) + break; + } + udelay(20); +} + +static int r8152_mdio_read(struct r8152 *tp, u32 reg_addr) +{ + u32 ocp_data; + int i; + + ocp_data = (reg_addr & 0x1f) << 16; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_PHYAR, ocp_data); + + for (i = 20; i > 0; i--) { + udelay(25); + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_PHYAR); + if (ocp_data & PHYAR_FLAG) + break; + } + udelay(20); + + if (!(ocp_data & PHYAR_FLAG)) + return -EAGAIN; + + return (u16)(ocp_data & 0xffff); +} + +static int read_mii_word(struct net_device *netdev, int phy_id, int reg) +{ + struct r8152 *tp = netdev_priv(netdev); + + if (phy_id != R8152_PHY_ID) + return -EINVAL; + + return r8152_mdio_read(tp, reg); +} + +static +void write_mii_word(struct net_device *netdev, int phy_id, int reg, int val) +{ + struct r8152 *tp = netdev_priv(netdev); + + if (phy_id != R8152_PHY_ID) + return; + + r8152_mdio_write(tp, reg, val); +} + +static void ocp_reg_write(struct r8152 *tp, u16 addr, u16 data) +{ + u16 ocp_base, ocp_index; + + ocp_base = addr & 0xf000; + if (ocp_base != tp->ocp_base) { + ocp_write_word(tp, MCU_TYPE_PLA, PLA_OCP_GPHY_BASE, ocp_base); + tp->ocp_base = ocp_base; + } + + ocp_index = (addr & 0x0fff) | 0xb000; + ocp_write_word(tp, MCU_TYPE_PLA, ocp_index, data); +} + +static inline void set_ethernet_addr(struct r8152 *tp) +{ + struct net_device *dev = tp->netdev; + u8 *node_id; + + node_id = kmalloc(sizeof(u8) * 8, GFP_KERNEL); + if (!node_id) { + netif_err(tp, probe, dev, "out of memory"); + return; + } + + if (pla_ocp_read(tp, PLA_IDR, sizeof(u8) * 8, node_id) < 0) + netif_notice(tp, probe, dev, "inet addr fail\n"); + else { + memcpy(dev->dev_addr, node_id, dev->addr_len); + memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len); + } + kfree(node_id); +} + +static int rtl8152_set_mac_address(struct net_device *netdev, void *p) +{ + struct r8152 *tp = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_CONFIG); + pla_ocp_write(tp, PLA_IDR, BYTE_EN_SIX_BYTES, 8, addr->sa_data); + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + + return 0; +} + +static int alloc_all_urbs(struct r8152 *tp) +{ + tp->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tp->rx_urb) + return 0; + tp->tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tp->tx_urb) { + usb_free_urb(tp->rx_urb); + return 0; + } + + return 1; +} + +static void free_all_urbs(struct r8152 *tp) +{ + usb_free_urb(tp->rx_urb); + usb_free_urb(tp->tx_urb); +} + +static struct net_device_stats *rtl8152_get_stats(struct net_device *dev) +{ + return &dev->stats; +} + +static void read_bulk_callback(struct urb *urb) +{ + struct r8152 *tp; + unsigned pkt_len; + struct sk_buff *skb; + struct net_device *netdev; + struct net_device_stats *stats; + int status = urb->status; + int result; + struct rx_desc *rx_desc; + + tp = urb->context; + if (!tp) + return; + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + netdev = tp->netdev; + if (!netif_device_present(netdev)) + return; + + stats = rtl8152_get_stats(netdev); + switch (status) { + case 0: + break; + case -ESHUTDOWN: + set_bit(RTL8152_UNPLUG, &tp->flags); + netif_device_detach(tp->netdev); + case -ENOENT: + return; /* the urb is in unlink state */ + case -ETIME: + pr_warn_ratelimited("may be reset is needed?..\n"); + goto goon; + default: + pr_warn_ratelimited("Rx status %d\n", status); + goto goon; + } + + /* protect against short packets (tell me why we got some?!?) */ + if (urb->actual_length < sizeof(*rx_desc)) + goto goon; + + + rx_desc = (struct rx_desc *)urb->transfer_buffer; + pkt_len = le32_to_cpu(rx_desc->opts1) & RX_LEN_MASK; + if (urb->actual_length < sizeof(struct rx_desc) + pkt_len) + goto goon; + + skb = netdev_alloc_skb_ip_align(netdev, pkt_len); + if (!skb) + goto goon; + + memcpy(skb->data, tp->rx_skb->data + sizeof(struct rx_desc), pkt_len); + skb_put(skb, pkt_len); + skb->protocol = eth_type_trans(skb, netdev); + netif_rx(skb); + stats->rx_packets++; + stats->rx_bytes += pkt_len; +goon: + usb_fill_bulk_urb(tp->rx_urb, tp->udev, usb_rcvbulkpipe(tp->udev, 1), + tp->rx_skb->data, RTL8152_RMS + sizeof(struct rx_desc), + (usb_complete_t)read_bulk_callback, tp); + result = usb_submit_urb(tp->rx_urb, GFP_ATOMIC); + if (result == -ENODEV) { + netif_device_detach(tp->netdev); + } else if (result) { + set_bit(RX_URB_FAIL, &tp->flags); + goto resched; + } else { + clear_bit(RX_URB_FAIL, &tp->flags); + } + + return; +resched: + tasklet_schedule(&tp->tl); +} + +static void rx_fixup(unsigned long data) +{ + struct r8152 *tp; + int status; + + tp = (struct r8152 *)data; + if (!test_bit(WORK_ENABLE, &tp->flags)) + return; + + status = usb_submit_urb(tp->rx_urb, GFP_ATOMIC); + if (status == -ENODEV) { + netif_device_detach(tp->netdev); + } else if (status) { + set_bit(RX_URB_FAIL, &tp->flags); + goto tlsched; + } else { + clear_bit(RX_URB_FAIL, &tp->flags); + } + + return; +tlsched: + tasklet_schedule(&tp->tl); +} + +static void write_bulk_callback(struct urb *urb) +{ + struct r8152 *tp; + int status = urb->status; + + tp = urb->context; + if (!tp) + return; + dev_kfree_skb_irq(tp->tx_skb); + if (!netif_device_present(tp->netdev)) + return; + if (status) + dev_info(&urb->dev->dev, "%s: Tx status %d\n", + tp->netdev->name, status); + tp->netdev->trans_start = jiffies; + netif_wake_queue(tp->netdev); +} + +static void rtl8152_tx_timeout(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + struct net_device_stats *stats = rtl8152_get_stats(netdev); + netif_warn(tp, tx_err, netdev, "Tx timeout.\n"); + usb_unlink_urb(tp->tx_urb); + stats->tx_errors++; +} + +static void rtl8152_set_rx_mode(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + + if (tp->speed & LINK_STATUS) + set_bit(RTL8152_SET_RX_MODE, &tp->flags); +} + +static void _rtl8152_set_rx_mode(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + u32 tmp, *mc_filter; /* Multicast hash filter */ + u32 ocp_data; + + mc_filter = kmalloc(sizeof(u32) * 2, GFP_KERNEL); + if (!mc_filter) { + netif_err(tp, link, netdev, "out of memory"); + return; + } + + clear_bit(RTL8152_SET_RX_MODE, &tp->flags); + netif_stop_queue(netdev); + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_data |= RCR_AB | RCR_APM; + + if (netdev->flags & IFF_PROMISC) { + /* Unconditionally log net taps. */ + netif_notice(tp, link, netdev, "Promiscuous mode enabled\n"); + ocp_data |= RCR_AM | RCR_AAP; + mc_filter[1] = mc_filter[0] = 0xffffffff; + } else if ((netdev_mc_count(netdev) > multicast_filter_limit) || + (netdev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + ocp_data |= RCR_AM; + mc_filter[1] = mc_filter[0] = 0xffffffff; + } else { + struct netdev_hw_addr *ha; + + mc_filter[1] = mc_filter[0] = 0; + netdev_for_each_mc_addr(ha, netdev) { + int bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26; + mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); + ocp_data |= RCR_AM; + } + } + + tmp = mc_filter[0]; + mc_filter[0] = __cpu_to_le32(swab32(mc_filter[1])); + mc_filter[1] = __cpu_to_le32(swab32(tmp)); + + pla_ocp_write(tp, PLA_MAR, BYTE_EN_DWORD, sizeof(u32) * 2, mc_filter); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + netif_wake_queue(netdev); + kfree(mc_filter); +} + +static netdev_tx_t rtl8152_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + struct net_device_stats *stats = rtl8152_get_stats(netdev); + struct tx_desc *tx_desc; + int len, res; + + netif_stop_queue(netdev); + len = skb->len; + if (skb_header_cloned(skb) || skb_headroom(skb) < sizeof(*tx_desc)) { + struct sk_buff *tx_skb; + + tx_skb = skb_copy_expand(skb, sizeof(*tx_desc), 0, GFP_ATOMIC); + dev_kfree_skb_any(skb); + if (!tx_skb) { + stats->tx_dropped++; + netif_wake_queue(netdev); + return NETDEV_TX_OK; + } + skb = tx_skb; + } + tx_desc = (struct tx_desc *)skb_push(skb, sizeof(*tx_desc)); + memset(tx_desc, 0, sizeof(*tx_desc)); + tx_desc->opts1 = cpu_to_le32((len & TX_LEN_MASK) | TX_FS | TX_LS); + tp->tx_skb = skb; + skb_tx_timestamp(skb); + usb_fill_bulk_urb(tp->tx_urb, tp->udev, usb_sndbulkpipe(tp->udev, 2), + skb->data, skb->len, + (usb_complete_t)write_bulk_callback, tp); + res = usb_submit_urb(tp->tx_urb, GFP_ATOMIC); + if (res) { + /* Can we get/handle EPIPE here? */ + if (res == -ENODEV) { + netif_device_detach(tp->netdev); + } else { + netif_warn(tp, tx_err, netdev, + "failed tx_urb %d\n", res); + stats->tx_errors++; + netif_start_queue(netdev); + } + } else { + stats->tx_packets++; + stats->tx_bytes += skb->len; + } + + return NETDEV_TX_OK; +} + +static void r8152b_reset_packet_filter(struct r8152 *tp) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_FMC); + ocp_data &= ~FMC_FCR_MCU_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data); + ocp_data |= FMC_FCR_MCU_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data); +} + +static void rtl8152_nic_reset(struct r8152 *tp) +{ + int i; + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, CR_RST); + + for (i = 0; i < 1000; i++) { + if (!(ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR) & CR_RST)) + break; + udelay(100); + } +} + +static inline u8 rtl8152_get_speed(struct r8152 *tp) +{ + return ocp_read_byte(tp, MCU_TYPE_PLA, PLA_PHYSTATUS); +} + +static int rtl8152_enable(struct r8152 *tp) +{ + u32 ocp_data; + u8 speed; + + speed = rtl8152_get_speed(tp); + if (speed & _100bps) { + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR); + ocp_data &= ~EEEP_CR_EEEP_TX; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR, ocp_data); + } else { + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR); + ocp_data |= EEEP_CR_EEEP_TX; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR, ocp_data); + } + + r8152b_reset_packet_filter(tp); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR); + ocp_data |= CR_RE | CR_TE; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_MISC_1); + ocp_data &= ~RXDY_GATED_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MISC_1, ocp_data); + + usb_fill_bulk_urb(tp->rx_urb, tp->udev, usb_rcvbulkpipe(tp->udev, 1), + tp->rx_skb->data, RTL8152_RMS + sizeof(struct rx_desc), + (usb_complete_t)read_bulk_callback, tp); + + return usb_submit_urb(tp->rx_urb, GFP_KERNEL); +} + +static void rtl8152_disable(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + + usb_kill_urb(tp->tx_urb); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_MISC_1); + ocp_data |= RXDY_GATED_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MISC_1, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if ((ocp_data & FIFO_EMPTY) == FIFO_EMPTY) + break; + mdelay(1); + } + + for (i = 0; i < 1000; i++) { + if (ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0) & TCR0_TX_EMPTY) + break; + mdelay(1); + } + + usb_kill_urb(tp->rx_urb); + + rtl8152_nic_reset(tp); +} + +static void r8152b_exit_oob(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_MISC_1); + ocp_data |= RXDY_GATED_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MISC_1, ocp_data); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, 0x00); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data &= ~NOW_IS_OOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data &= ~MCU_BORW_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + mdelay(1); + } + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data |= RE_INIT_LL; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + mdelay(1); + } + + rtl8152_nic_reset(tp); + + /* rx share fifo credit full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_NORMAL); + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_DEV_STAT); + ocp_data &= STAT_SPEED_MASK; + if (ocp_data == STAT_SPEED_FULL) { + /* rx share fifo credit near full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, + RXFIFO_THR2_FULL); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, + RXFIFO_THR3_FULL); + } else { + /* rx share fifo credit near full threshold */ + ocp_write_dword(t |