diff options
Diffstat (limited to 'drivers/net/ethernet/realtek/r8169.c')
-rw-r--r-- | drivers/net/ethernet/realtek/r8169.c | 158 |
1 files changed, 155 insertions, 3 deletions
diff --git a/drivers/net/ethernet/realtek/r8169.c b/drivers/net/ethernet/realtek/r8169.c index 0921302553c6..2b1ff6fd3f1f 100644 --- a/drivers/net/ethernet/realtek/r8169.c +++ b/drivers/net/ethernet/realtek/r8169.c @@ -29,6 +29,7 @@ #include <linux/prefetch.h> #include <linux/ipv6.h> #include <net/ip6_checksum.h> +#include <linux/eeprom_93cx6.h> #include <asm/io.h> #include <asm/irq.h> @@ -349,6 +350,7 @@ enum rtl_registers { #define RXCFG_DMA_SHIFT 8 /* Unlimited maximum PCI burst. */ #define RX_DMA_BURST (7 << RXCFG_DMA_SHIFT) +#define RX_9356SEL (1 << 6) /* EEPROM type */ RxMissed = 0x4c, Cfg9346 = 0x50, @@ -413,7 +415,8 @@ enum rtl8168_8101_registers { DBG_REG = 0xd1, #define FIX_NAK_1 (1 << 4) #define FIX_NAK_2 (1 << 3) - TWSI = 0xd2, + TWSI = 0xd2, /* Two Wire Serial Interface */ +#define TWSI_TYPE_EEPROM (1 << 2) MCU = 0xd3, #define NOW_IS_OOB (1 << 7) #define TX_EMPTY (1 << 5) @@ -505,8 +508,14 @@ enum rtl_register_content { FSWInt = 0x01, /* Forced software interrupt */ /* Cfg9346Bits */ - Cfg9346_Lock = 0x00, - Cfg9346_Unlock = 0xc0, + Cfg9346_Lock = (0 << 6), /* Normal communication mode */ + Cfg9346_Program = (2 << 6), /* Programming mode */ + Cfg9346_Unlock = (3 << 6), /* config register write enable */ + + Cfg9346_EECS = (1 << 3), /* Chip select */ + Cfg9346_EESK = (1 << 2), /* Serial data clock */ + Cfg9346_EEDI = (1 << 1), /* Data input */ + Cfg9346_EEDO = (1 << 0), /* Data output */ /* rx_mode_bits */ AcceptErr = 0x20, @@ -1629,6 +1638,146 @@ static int rtl8169_get_regs_len(struct net_device *dev) return R8169_REGS_SIZE; } +static int rtl8169_get_eeprom_len(struct net_device *dev) +{ + struct rtl8169_private *tp = netdev_priv(dev); + void __iomem *ioaddr = tp->mmio_addr; + + if (RTL_R8(TWSI) & TWSI_TYPE_EEPROM) + return 0; /* 2-Wire Interface is unsupported for now */ + + /* 3-Wire Interface */ + if (RTL_R8(RxConfig) & RX_9356SEL) + return 256; /* 93C56/93C66 */ + else + return 128; /* 93C46 */ +} + +static void rtl_eeprom_read(struct eeprom_93cx6 *eeprom) +{ + void __iomem *ioaddr = eeprom->data; + u8 reg = RTL_R8(Cfg9346); + + eeprom->reg_data_in = reg & Cfg9346_EEDI; + eeprom->reg_data_out = reg & Cfg9346_EEDO; + eeprom->reg_data_clock = reg & Cfg9346_EESK; + eeprom->reg_chip_select = reg & Cfg9346_EECS; +} + +static void rtl_eeprom_write(struct eeprom_93cx6 *eeprom) +{ + void __iomem *ioaddr = eeprom->data; + u8 reg = Cfg9346_Program; + + if (eeprom->reg_data_in) + reg |= Cfg9346_EEDI; + if (eeprom->reg_data_clock) + reg |= Cfg9346_EESK; + if (eeprom->reg_chip_select) + reg |= Cfg9346_EECS; + + RTL_W8(Cfg9346, reg); + udelay(3); /* matches RTL_CLOCK_RATE in r8168 */ +} + +static void rtl_init_93cx6(void __iomem *ioaddr, struct eeprom_93cx6 *eeprom) +{ + eeprom->data = ioaddr; + eeprom->register_read = rtl_eeprom_read; + eeprom->register_write = rtl_eeprom_write; + + /* assume 3-Wire Interface, not TWI */ + if (RTL_R8(RxConfig) & RX_9356SEL) + eeprom->width = PCI_EEPROM_WIDTH_93C56; + else + eeprom->width = PCI_EEPROM_WIDTH_93C46; +} + +/* semi-randomly chosen magic for ethtool --change-eeprom option */ +#define R8169_EEPROM_MAGIC (0x00008169) + +static int rtl8169_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee_eeprom, u8 *data) +{ + struct rtl8169_private *tp = netdev_priv(dev); + void __iomem *ioaddr = tp->mmio_addr; + struct eeprom_93cx6 eeprom; + int i = 0; + u8 offset = ee_eeprom->offset >> 1; + u16 val; + + ee_eeprom->magic = R8169_EEPROM_MAGIC; + + rtl_lock_work(tp); + rtl_init_93cx6(ioaddr, &eeprom); + + /* Do not use eeprom_93cx6_multiread, that returns data in an array of + * little endian words which is not compatible with BE arches. */ + + if (ee_eeprom->offset & 1) { + eeprom_93cx6_read(&eeprom, offset++, &val); + data[i++] = val >> 8; + } + + while (i < ee_eeprom->len - 1) { + eeprom_93cx6_read(&eeprom, offset++, &val); + data[i++] = val & 0xFF; + data[i++] = val >> 8; + } + + if (i < ee_eeprom->len) { + eeprom_93cx6_read(&eeprom, offset, &val); + data[i] = val & 0xFF; + } + + RTL_W8(Cfg9346, Cfg9346_Lock); + rtl_unlock_work(tp); + return 0; +} + +static int rtl8169_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee_eeprom, u8 *data) +{ + struct rtl8169_private *tp = netdev_priv(dev); + void __iomem *ioaddr = tp->mmio_addr; + struct eeprom_93cx6 eeprom; + int i = 0; + u8 offset = ee_eeprom->offset >> 1; + u16 val; + + if (ee_eeprom->magic != R8169_EEPROM_MAGIC) + return -EINVAL; + + rtl_lock_work(tp); + rtl_init_93cx6(ioaddr, &eeprom); + eeprom_93cx6_wren(&eeprom, true); + + if (ee_eeprom->offset & 1) { + eeprom_93cx6_read(&eeprom, offset, &val); + val &= 0xFF; + val |= ((u16)data[i++]) << 8; + eeprom_93cx6_write(&eeprom, offset++, val); + } + + while (i < ee_eeprom->len - 1) { + val = data[i++]; + val |= ((u16)data[i++]) << 8; + eeprom_93cx6_write(&eeprom, offset++, val); + } + + if (i < ee_eeprom->len) { + eeprom_93cx6_read(&eeprom, offset, &val); + val &= 0xFF00; + val |= data[i++]; + eeprom_93cx6_write(&eeprom, offset, val); + } + + eeprom_93cx6_wren(&eeprom, false); + RTL_W8(Cfg9346, Cfg9346_Lock); + rtl_unlock_work(tp); + return 0; +} + static int rtl8169_set_speed_tbi(struct net_device *dev, u8 autoneg, u16 speed, u8 duplex, u32 ignored) { @@ -2012,6 +2161,9 @@ static const struct ethtool_ops rtl8169_ethtool_ops = { .get_drvinfo = rtl8169_get_drvinfo, .get_regs_len = rtl8169_get_regs_len, .get_link = ethtool_op_get_link, + .get_eeprom_len = rtl8169_get_eeprom_len, + .get_eeprom = rtl8169_get_eeprom, + .set_eeprom = rtl8169_set_eeprom, .get_settings = rtl8169_get_settings, .set_settings = rtl8169_set_settings, .get_msglevel = rtl8169_get_msglevel, |