@@ -112,4 +112,13 @@ config R8169
To compile this driver as a module, choose M here: the module
will be called r8169. This is recommended.
+config R8169_93CX6
+ bool "Realtek 8169 external 93CX6 EEPROM support"
+ depends on R8169
+ select EEPROM_93CX6
+ ---help---
+ Select this if your want to access EEPROM of the Realtek 8169 PCI
+ Gigabit Ethernet adapter. This is necessary for dumping or changing
+ EEPROM using ethtool. If unsure, say N.
+
endif # NET_VENDOR_REALTEK
@@ -28,6 +28,7 @@
#include <linux/firmware.h>
#include <linux/pci-aspm.h>
#include <linux/prefetch.h>
+#include <linux/eeprom_93cx6.h>
#include <asm/io.h>
#include <asm/irq.h>
@@ -412,7 +413,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 +506,14 @@ enum rtl_register_content {
FSWInt = 0x01, /* Forced software interrupt */
/* Cfg9346Bits */
+ Cfg9346_mask = 0xc0,
Cfg9346_Lock = 0x00,
Cfg9346_Unlock = 0xc0,
+ Cfg9346_Program = 0x80, /* Programming mode */
+ Cfg9346_EECS = 0x08, /* Chip select */
+ Cfg9346_EESK = 0x04, /* Serial data clock */
+ Cfg9346_EEDI = 0x02, /* Data input */
+ Cfg9346_EEDO = 0x01, /* Data output */
/* rx_mode_bits */
AcceptErr = 0x20,
@@ -1643,6 +1651,172 @@ static int rtl8169_get_regs_len(struct net_device *dev)
return R8169_REGS_SIZE;
}
+#ifdef CONFIG_R8169_93CX6
+static int rtl8169_get_eeprom_len(struct net_device *dev) {
+ /* TODO do not hard code EEPROM size, detect it! */
+ /* 93c46 -> 1K bit, 93c56 -> 2K bit */
+ return 1024 / 8; /* must be ONE byte! */
+}
+
+static void rtl_eeprom_read(struct eeprom_93cx6 *eeprom)
+{
+ struct rtl8169_private *tp = eeprom->data;
+ void __iomem *ioaddr = tp->mmio_addr;
+ u8 reg;
+
+ 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;
+ if (0 && !(reg & Cfg9346_EECS)) { /* DEBUG - remove me! */
+ struct net_device *dev;
+ dev = (struct net_device *)((char *) tp - ALIGN(sizeof(struct net_device), NETDEV_ALIGN));
+ netif_warn(tp, link, dev, "CS is disabled, reg=0x%02x\n", reg);
+ }
+}
+
+static void rtl_eeprom_write(struct eeprom_93cx6 *eeprom)
+{
+ struct rtl8169_private *tp = eeprom->data;
+ void __iomem *ioaddr = tp->mmio_addr;
+ u8 reg;
+
+ reg = RTL_R8(Cfg9346) & ~Cfg9346_mask;
+ reg |= Cfg9346_Program;
+ reg &= ~(Cfg9346_EEDO | Cfg9346_EEDI | Cfg9346_EESK | Cfg9346_EECS);
+
+ 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);
+ RTL_R8(Cfg9346); /* sync? */
+ udelay(4); /* SK Clock Cycle Time */
+ //udelay(3); /* DO Setup time is min 2 us for 9346, DO hold time max 2us */
+}
+
+static void rtl_init_93cx6(struct net_device *dev, struct eeprom_93cx6 *eeprom)
+{
+ struct rtl8169_private *tp = netdev_priv(dev);
+
+ eeprom->data = tp;
+ eeprom->register_read = rtl_eeprom_read;
+ eeprom->register_write = rtl_eeprom_write;
+ /* TODO: check EEPROM type; note TWSI_TYPE_EEPROM is not supported */
+ eeprom->width = PCI_EEPROM_WIDTH_93C46;
+}
+
+/* semi-randomly chosen key 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(dev, &eeprom);
+ /* something odd is happening, I must write *at least once* to the
+ * eeprom (say, 00 to offset 0) before read returns something else than
+ * 00. IOTW, if no write is performed, all values read as 00. Attempted
+ * solutions that failed:
+ * eeprom_93cx6_wren(&eeprom, false); // also tried wr(1) before wren(0)
+ * W(Cfg9346, Cfg9346_Program);R(Cfg9346);udelay(10); // tried |CS too
+ *
+ * The oddiest thing is that this seems to survive a shutdown (even 15
+ * minutes). Next attempt wa taking this PCI card out of the machine and
+ * leave it disconnected for 20 minutes. This did not work either. The
+ * capacitors are likely still charged. When the machine was left
+ * powered off (not disconnected) for 9.5 hours, the eeprom read empty
+ * though. auto-loading from eeprom (Cfg9346 = 0x40) also seems to work?
+ * (or was it caused by writing 2981 to EEPROM[0:1] before autoload?)
+ */
+
+ /* this does not work (from rtl8180): */
+ //RTL_W8(Cfg9346, Cfg9346_Program);
+ //RTL_R8(Cfg9346);
+ //udelay(10);
+
+ /* 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(dev, &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;
+}
+#endif
+
static int rtl8169_set_speed_tbi(struct net_device *dev,
u8 autoneg, u16 speed, u8 duplex, u32 ignored)
{
@@ -2024,6 +2198,11 @@ 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,
+#ifdef CONFIG_R8169_93CX6
+ .get_eeprom_len = rtl8169_get_eeprom_len,
+ .get_eeprom = rtl8169_get_eeprom,
+ .set_eeprom = rtl8169_set_eeprom,
+#endif
.get_settings = rtl8169_get_settings,
.set_settings = rtl8169_set_settings,
.get_msglevel = rtl8169_get_msglevel,
@@ -7114,6 +7293,19 @@ rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
mutex_init(&tp->wk.mutex);
+ if (0) { /* DEBUG - remove me! */
+ u16 val = 0xAB;
+ struct eeprom_93cx6 eeprom = {
+ .data = tp,
+ .register_read = rtl_eeprom_read,
+ .register_write = rtl_eeprom_write,
+ .width = PCI_EEPROM_WIDTH_93C46
+ };
+ eeprom_93cx6_read(&eeprom, 0, &val);
+ pr_info("Read something: 0x%04x\n", val);
+ RTL_W8(Cfg9346, Cfg9346_Lock);
+ }
+
/* Get MAC address */
for (i = 0; i < ETH_ALEN; i++)
dev->dev_addr[i] = RTL_R8(MAC0 + i);