From patchwork Thu Aug 29 14:44:51 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Wu X-Patchwork-Id: 270868 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id E52572C00D3 for ; Fri, 30 Aug 2013 00:45:07 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755571Ab3H2Oo7 (ORCPT ); Thu, 29 Aug 2013 10:44:59 -0400 Received: from mail-we0-f182.google.com ([74.125.82.182]:58691 "EHLO mail-we0-f182.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755436Ab3H2Oo5 (ORCPT ); Thu, 29 Aug 2013 10:44:57 -0400 Received: by mail-we0-f182.google.com with SMTP id q59so510556wes.41 for ; Thu, 29 Aug 2013 07:44:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=nJmnZyR6L+4BFIo5I+WFTjpjWNZP2sF8cwGXt1udqZ4=; b=D0iS4jdAjdEYPs4eOMgHoqJGXW3kpX1L84AVXb3G8eD9NL0C+7LuCOiL8zpiTWaBZS b8AJa6gFY9sq+v9NVP7+iTCWi0wSPbq05X1nRpKzLb5Y61zflN7QBIdMfxgap0P05KkN phw//7lu6xBn/bncADZijBYTPniF12VHnHUzg71l0kUZLB9/2GWwMJV0BOqi3P5lyoSk Dr5o4s+BLmehYWmBzUGgWVvFqfuFU5brZEtm9k5n8I4d4/dXp164VdufM5UyTg+ZZEsl DLNCiavGNkX+2qy1oR7WtTg+GiPZRcYnUu5jPFu87ROiu5c0OV7BVjxXy2OfPN/G8H2I JwQA== X-Received: by 10.195.13.45 with SMTP id ev13mr6635327wjd.20.1377787496139; Thu, 29 Aug 2013 07:44:56 -0700 (PDT) Received: from localhost.localdomain (ip4da018ae.direct-adsl.nl. [77.160.24.174]) by mx.google.com with ESMTPSA id p3sm12986059wia.5.1969.12.31.16.00.00 (version=TLSv1.2 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Thu, 29 Aug 2013 07:44:55 -0700 (PDT) From: Peter Wu To: netdev@vger.kernel.org, Francois Romieu , hayeswang Cc: lekensteyn@gmail.com, Ben Hutchings Subject: [PATCH] r8169: add ethtool eeprom change/dump feature Date: Thu, 29 Aug 2013 16:44:51 +0200 Message-Id: <1377787491-2932-1-git-send-email-lekensteyn@gmail.com> X-Mailer: git-send-email 1.8.4 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This adds the ability to read and change EEPROM for 93C46/93C56 serial EEPROM. Two-Wire serial interface and SPI are not supported. (Do not even try this for SPI or other chips, it may break your hardware.) Works with RTL8169SCL (driver detects RTL8169sb), with some quirks. Not sure if it is a hardware bug, but to be able to read EEPROM, one has to write something, e.g.: printf '\0\0' | ethtool -E eth0 magic 0x8169 offset 0x40 length 2 Otherwise, only zeroes are read. Another note for this NIC, one has to "enable" the eeprom by writing to it (as shown above) and then make the NIC to reload the contents from firmware such that the PCI ID gets detected correctly (10ec:8169 instead of 10ec:8129). Reload by writing 0x40 (Auto-load) to register 0x50 (9346CR): printf '\x40' | dd seek=80 bs=1 \ of=/sys/bus/pci/devices/0000:03:00.0/resource0 Then detach and rescan the PCI device (use `lspci -tv` to find parent): echo 1 > /sys/bus/pci/devices/0000\:03\:00.0/remove echo 1 > /sys/bus/pci/devices/0000\:02\:00.0/rescan After this, this RTL8169sb PCI GbE NIC appears to work. On a second RTL8188E onboard GbE chip, EEPROM cannot be dumped, writing also does nothing. Here, reads all return FFs. Signed-off-by: Peter Wu --- Hi, An RFC of this patch was created a month ago[1]. Ben Hutchings suggested to drop the CONFIG knob and always include the ethtool operations for getting and setting the EEPROM, so I did that for later versions. This new patch also checks the EEPROM size (depends on whether 93C46 or 93C56 is used) and has a minor style fix (brace on new line). I do not know why exactly I have to do funky things with the EEPROM to fix the PCI IDs. Hayes, perhaps you have an idea? [ lspci of broken device ] 03:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL-8129 [10ec:8129] (rev 10) Subsystem: Coreco Inc RT8129 Fast Ethernet Adapter [11ec:8129] Control: I/O+ Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx- Status: Cap+ 66MHz+ UDF- FastB2B+ ParErr- DEVSEL=medium >TAbort- SERR- TAbort- SERR- #include #include +#include #include #include @@ -348,6 +349,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, @@ -412,7 +414,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) @@ -504,8 +507,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, @@ -1643,6 +1652,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) { @@ -2025,6 +2174,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,