[PATCH v2 18/19] net: add rtl8169 driver

Lucas Stach dev at lynxeye.de
Sat Oct 4 10:40:24 PDT 2014


This adds the driver for RealTek 8169 and compatible
pci attached network chips.

Signed-off-by: Lucas Stach <dev at lynxeye.de>
---
 drivers/net/Kconfig   |   8 +
 drivers/net/Makefile  |   1 +
 drivers/net/rtl8169.c | 566 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 575 insertions(+)
 create mode 100644 drivers/net/rtl8169.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index c99fcc8..24b9844 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -156,6 +156,14 @@ config DRIVER_NET_RTL8139
 	  This is a driver for the Fast Ethernet PCI network cards based on
 	  the RTL 8139 chips.
 
+config DRIVER_NET_RTL8169
+	bool "RealTek RTL-8169 PCI Ethernet driver"
+	depends on PCI
+	select PHYLIB
+	help
+	  This is a driver for the Fast Ethernet PCI network cards based on
+	  the RTL 8169 chips.
+
 config DRIVER_NET_SMC911X
 	bool "smc911x ethernet driver"
 	select PHYLIB
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 1b85778..3e66b31 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_DRIVER_NET_MPC5200)	+= fec_mpc5200.o
 obj-$(CONFIG_DRIVER_NET_NETX)		+= netx_eth.o
 obj-$(CONFIG_DRIVER_NET_ORION)		+= orion-gbe.o
 obj-$(CONFIG_DRIVER_NET_RTL8139)	+= rtl8139.o
+obj-$(CONFIG_DRIVER_NET_RTL8169)	+= rtl8169.o
 obj-$(CONFIG_DRIVER_NET_SMC911X)	+= smc911x.o
 obj-$(CONFIG_DRIVER_NET_SMC91111)	+= smc91111.o
 obj-$(CONFIG_DRIVER_NET_TAP)		+= tap.o
diff --git a/drivers/net/rtl8169.c b/drivers/net/rtl8169.c
new file mode 100644
index 0000000..3ed2e56
--- /dev/null
+++ b/drivers/net/rtl8169.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2014 Lucas Stach <l.stach at pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <asm/mmu.h>
+#include <common.h>
+#include <init.h>
+#include <net.h>
+#include <malloc.h>
+#include <linux/pci.h>
+
+#define NUM_TX_DESC	1
+#define NUM_RX_DESC	4
+#define PKT_BUF_SIZE	1536
+#define ETH_ZLEN	60
+
+struct rtl8169_chip_info {
+	const char *name;
+	u8 version;
+	u32 RxConfigMask;
+};
+
+#define BD_STAT_OWN	0x80000000
+#define BD_STAT_EOR	0x40000000
+#define BD_STAT_FS	0x20000000
+#define BD_STAT_LS	0x10000000
+#define BD_STAT_RX_RES	0x00200000
+struct bufdesc {
+	u32 status;
+	u32 vlan_tag;
+	u32 buf_addr;
+	u32 buf_Haddr;
+};
+
+struct rtl8169_priv {
+	struct eth_device	edev;
+	void __iomem		*base;
+	struct pci_dev		*pci_dev;
+	int			chipset;
+
+	struct bufdesc		*tx_desc;
+	void			*tx_buf;
+	unsigned int		cur_tx;
+
+	struct bufdesc		*rx_desc;
+	void			*rx_buf;
+	unsigned int		cur_rx;
+
+	struct mii_bus miibus;
+};
+
+#define MAC0 			0x00
+#define MAR0			0x08
+#define TxDescStartAddrLow	0x20
+#define TxDescStartAddrHigh	0x24
+#define TxHDescStartAddrLow	0x28
+#define TxHDescStartAddrHigh	0x2c
+#define FLASH			0x30
+#define ERSR			0x36
+#define ChipCmd			0x37
+#define  CmdReset		0x10
+#define  CmdRxEnb		0x08
+#define  CmdTxEnb		0x04
+#define  RxBufEmpty		0x01
+#define TxPoll			0x38
+#define IntrMask		0x3c
+#define IntrStatus		0x3e
+#define  SYSErr			0x8000
+#define  PCSTimeout		0x4000
+#define  SWInt			0x0100
+#define  TxDescUnavail		0x80
+#define  RxFIFOOver		0x40
+#define  RxUnderrun		0x20
+#define  RxOverflow		0x10
+#define  TxErr			0x08
+#define  TxOK			0x04
+#define  RxErr			0x02
+#define  RxOK			0x01
+#define TxConfig		0x40
+#define  TxInterFrameGapShift	24
+#define  TxDMAShift		8
+#define RxConfig		0x44
+#define  AcceptErr		0x20
+#define  AcceptRunt		0x10
+#define  AcceptBroadcast	0x08
+#define  AcceptMulticast	0x04
+#define  AcceptMyPhys		0x02
+#define  AcceptAllPhys		0x01
+#define  RxCfgFIFOShift		13
+#define  RxCfgDMAShift		8
+#define RxMissed		0x4c
+#define Cfg9346			0x50
+#define  Cfg9346_Lock		0x00
+#define  Cfg9346_Unlock		0xc0
+#define Config0			0x51
+#define Config1			0x52
+#define Config2			0x53
+#define Config3			0x54
+#define Config4			0x55
+#define Config5			0x56
+#define MultiIntr		0x5c
+#define PHYAR			0x60
+#define TBICSR			0x64
+#define TBI_ANAR		0x68
+#define TBI_LPAR		0x6a
+#define PHYstatus		0x6c
+#define RxMaxSize		0xda
+#define CPlusCmd		0xe0
+#define RxDescStartAddrLow	0xe4
+#define RxDescStartAddrHigh	0xe8
+#define EarlyTxThres		0xec
+#define FuncEvent		0xf0
+#define FuncEventMask		0xf4
+#define FuncPresetState		0xf8
+#define FuncForceEvent		0xfc
+
+/* write MMIO register */
+#define RTL_W8(priv, reg, val)	writeb(val, ((char *)(priv->base) + reg))
+#define RTL_W16(priv, reg, val)	writew(val, ((char *)(priv->base) + reg))
+#define RTL_W32(priv, reg, val)	writel(val, ((char *)(priv->base) + reg))
+
+/* read MMIO register */
+#define RTL_R8(priv, reg)	readb(((char *)(priv->base) + reg))
+#define RTL_R16(priv, reg)	readw(((char *)(priv->base) + reg))
+#define RTL_R32(priv, reg)	readl(((char *)(priv->base) + reg))
+
+static const u32 rtl8169_rx_config =
+		 (7 << RxCfgFIFOShift) | (6 << RxCfgDMAShift);
+
+static void rtl8169_chip_reset(struct rtl8169_priv *priv)
+{
+	int i;
+
+	/* Soft reset the chip. */
+	RTL_W8(priv, ChipCmd, CmdReset);
+
+	/* Check that the chip has finished the reset. */
+	for (i = 1000; i > 0; i--) {
+		if ((RTL_R8(priv, ChipCmd) & CmdReset) == 0)
+			break;
+		udelay(10);
+	}
+}
+
+static struct rtl8169_chip_info chip_info[] = {
+	{"RTL-8169",		0x00,	0xff7e1880},
+	{"RTL-8169",		0x04,	0xff7e1880},
+	{"RTL-8169",		0x00,	0xff7e1880},
+	{"RTL-8169s/8110s",	0x02,	0xff7e1880},
+	{"RTL-8169s/8110s",	0x04,	0xff7e1880},
+	{"RTL-8169sb/8110sb",	0x10,	0xff7e1880},
+	{"RTL-8169sc/8110sc",	0x18,	0xff7e1880},
+	{"RTL-8168b/8111sb",	0x30,	0xff7e1880},
+	{"RTL-8168b/8111sb",	0x38,	0xff7e1880},
+	{"RTL-8168d/8111d",	0x28,	0xff7e1880},
+	{"RTL-8168evl/8111evl",	0x2e,	0xff7e1880},
+	{"RTL-8101e",		0x34,	0xff7e1880},
+	{"RTL-8100e",		0x32,	0xff7e1880},
+};
+
+static void rtl8169_chip_identify(struct rtl8169_priv *priv)
+{
+	u32 val;
+	int i;
+
+	val = RTL_R32(priv, TxConfig);
+	val = ((val & 0x7c000000) + ((val & 0x00800000) << 2)) >> 24;
+
+	for (i = ARRAY_SIZE(chip_info) - 1; i >= 0; i--){
+		if (val == chip_info[i].version) {
+			priv->chipset = i;
+			dev_dbg(&priv->pci_dev->dev, "found %s chipset\n",
+				chip_info[i].name);
+			return;
+		}
+	}
+
+	dev_dbg(&priv->pci_dev->dev,
+		"no matching chip version found, assuming RTL-8169\n");
+	priv->chipset = 0;
+}
+
+static int rtl8169_init_dev(struct eth_device *edev)
+{
+	struct rtl8169_priv *priv = edev->priv;
+
+	rtl8169_chip_reset(priv);
+	rtl8169_chip_identify(priv);
+	pci_set_master(priv->pci_dev);
+
+	return 0;
+}
+
+static void __set_rx_mode(struct rtl8169_priv *priv)
+{
+	u32 mc_filter[2], val;
+
+	/* IFF_ALLMULTI */
+	/* Too many to filter perfectly -- accept all multicasts. */
+	mc_filter[1] = mc_filter[0] = 0xffffffff;
+
+	val = AcceptBroadcast | AcceptMulticast | AcceptMyPhys |
+	      rtl8169_rx_config | (RTL_R32(priv, RxConfig) &
+				  chip_info[priv->chipset].RxConfigMask);
+
+	RTL_W32(priv, RxConfig, val);
+	RTL_W32(priv, MAR0 + 0, mc_filter[0]);
+	RTL_W32(priv, MAR0 + 4, mc_filter[1]);
+}
+
+static void rtl8169_init_ring(struct rtl8169_priv *priv)
+{
+	int i;
+
+	priv->cur_rx = priv->cur_tx = 0;
+
+	priv->tx_desc = dma_alloc_coherent(NUM_TX_DESC *
+				sizeof(struct bufdesc));
+	priv->tx_buf = malloc(NUM_TX_DESC * PKT_BUF_SIZE);
+	priv->rx_desc = dma_alloc_coherent(NUM_RX_DESC *
+				sizeof(struct bufdesc));
+	priv->rx_buf = malloc(NUM_RX_DESC * PKT_BUF_SIZE);
+
+	memset(priv->tx_desc, 0, NUM_TX_DESC * sizeof(struct bufdesc));
+	memset(priv->rx_desc, 0, NUM_RX_DESC * sizeof(struct bufdesc));
+
+	for (i = 0; i < NUM_RX_DESC; i++) {
+		if (i == (NUM_RX_DESC - 1))
+			priv->rx_desc[i].status =
+				BD_STAT_OWN | BD_STAT_EOR | PKT_BUF_SIZE;
+		else
+			priv->rx_desc[i].status =
+				BD_STAT_OWN | PKT_BUF_SIZE;
+
+		priv->rx_desc[i].buf_addr =
+				virt_to_phys(priv->rx_buf + i * PKT_BUF_SIZE);
+	}
+
+	dma_flush_range((unsigned long)priv->rx_desc,
+			(unsigned long)priv->rx_desc +
+			NUM_RX_DESC * sizeof(struct bufdesc));
+}
+
+static void rtl8169_hw_start(struct rtl8169_priv *priv)
+{
+	u32 val;
+
+	RTL_W8(priv, Cfg9346, Cfg9346_Unlock);
+
+	/* RTL-8169sb/8110sb or previous version */
+	if (priv->chipset <= 5)
+		RTL_W8(priv, ChipCmd, CmdTxEnb | CmdRxEnb);
+
+	RTL_W8(priv, EarlyTxThres, 0x3f);
+
+	/* For gigabit rtl8169 */
+	RTL_W16(priv, RxMaxSize, 0x800);
+
+	/* Set Rx Config register */
+	val = rtl8169_rx_config | (RTL_R32(priv, RxConfig) &
+	      chip_info[priv->chipset].RxConfigMask);
+	RTL_W32(priv, RxConfig, val);
+
+	/* Set DMA burst size and Interframe Gap Time */
+	RTL_W32(priv, TxConfig, (6 << TxDMAShift) | (3 << TxInterFrameGapShift));
+
+	RTL_W32(priv, TxDescStartAddrLow, virt_to_phys(priv->tx_desc));
+	RTL_W32(priv, TxDescStartAddrHigh, 0);
+	RTL_W32(priv, RxDescStartAddrLow, virt_to_phys(priv->rx_desc));
+	RTL_W32(priv, RxDescStartAddrHigh, 0);
+
+	/* RTL-8169sc/8110sc or later version */
+	if (priv->chipset > 5)
+		RTL_W8(priv, ChipCmd, CmdTxEnb | CmdRxEnb);
+
+	RTL_W8(priv, Cfg9346, Cfg9346_Lock);
+	udelay(10);
+
+	RTL_W32(priv, RxMissed, 0);
+
+	__set_rx_mode(priv);
+
+	/* no early-rx interrupts */
+	RTL_W16(priv, MultiIntr, RTL_R16(priv, MultiIntr) & 0xf000);
+}
+
+static int rtl8169_eth_open(struct eth_device *edev)
+{
+	struct rtl8169_priv *priv = edev->priv;
+	int ret;
+
+	rtl8169_init_ring(priv);
+	rtl8169_hw_start(priv);
+
+	ret = phy_device_connect(edev, &priv->miibus, 0, NULL, 0,
+				 PHY_INTERFACE_MODE_NA);
+
+	return ret;
+}
+
+static int rtl8169_phy_write(struct mii_bus *bus, int phy_addr,
+	int reg, u16 val)
+{
+	struct rtl8169_priv *priv = bus->priv;
+	int i;
+
+	if (phy_addr != 0)
+		return -1;
+
+	RTL_W32(priv, PHYAR, 0x80000000 | (reg & 0xff) << 16 | val);
+	mdelay(1);
+
+	for (i = 2000; i > 0; i--) {
+		if (!(RTL_R32(priv, PHYAR) & 0x80000000)) {
+			return 0;
+		} else {
+			udelay(100);
+		}
+	}
+
+	return -1;
+}
+
+static int rtl8169_phy_read(struct mii_bus *bus, int phy_addr, int reg)
+{
+	struct rtl8169_priv *priv = bus->priv;
+	int i, val = 0xffff;
+
+	RTL_W32(priv, PHYAR, 0x0 | (reg & 0xff) << 16);
+	mdelay(10);
+
+	if (phy_addr != 0)
+		return val;
+
+	for (i = 2000; i > 0; i--) {
+		if (RTL_R32(priv, PHYAR) & 0x80000000) {
+			val = (int) (RTL_R32(priv, PHYAR) & 0xffff);
+			break;
+		} else {
+			udelay(100);
+		}
+	}
+	return val;
+}
+
+static int rtl8169_eth_send(struct eth_device *edev, void *packet,
+				int packet_length)
+{
+	struct rtl8169_priv *priv = edev->priv;
+	unsigned int entry;
+
+	entry = priv->cur_tx % NUM_TX_DESC;
+
+	if (packet_length < ETH_ZLEN)
+		memset(priv->tx_buf + entry * PKT_BUF_SIZE, 0, ETH_ZLEN);
+	memcpy(priv->tx_buf + entry * PKT_BUF_SIZE, packet, packet_length);
+	dma_flush_range((unsigned long)priv->tx_buf + entry * PKT_BUF_SIZE,
+			(unsigned long)priv->tx_buf + (entry + 1) * PKT_BUF_SIZE);
+
+	priv->tx_desc[entry].buf_Haddr = 0;
+	priv->tx_desc[entry].buf_addr =
+		virt_to_phys(priv->tx_buf + entry * PKT_BUF_SIZE);
+
+	if (entry != (NUM_TX_DESC - 1)) {
+		priv->tx_desc[entry].status =
+			BD_STAT_OWN | BD_STAT_FS | BD_STAT_LS |
+			((packet_length > ETH_ZLEN) ? packet_length : ETH_ZLEN);
+	} else {
+		priv->tx_desc[entry].status =
+			BD_STAT_OWN | BD_STAT_EOR | BD_STAT_FS | BD_STAT_LS |
+			((packet_length > ETH_ZLEN) ? packet_length : ETH_ZLEN);
+	}
+
+	dma_flush_range((unsigned long)&priv->tx_desc[entry],
+			(unsigned long)&priv->tx_desc[entry + 1]);
+
+	RTL_W8(priv, TxPoll, 0x40);
+	do {
+		dma_inv_range((unsigned long)&priv->tx_desc[entry],
+		              (unsigned long)&priv->tx_desc[entry + 1]);
+	} while (priv->tx_desc[entry].status & BD_STAT_OWN);
+
+	priv->cur_tx++;
+
+	return 0;
+}
+
+static int rtl8169_eth_rx(struct eth_device *edev)
+{
+	struct rtl8169_priv *priv = edev->priv;
+	unsigned int entry, pkt_size = 0;
+	u8 status;
+
+	entry = priv->cur_rx % NUM_RX_DESC;
+
+	dma_inv_range((unsigned long)&priv->rx_desc[entry],
+	              (unsigned long)&priv->rx_desc[entry + 1]);
+
+	if ((priv->rx_desc[entry].status & BD_STAT_OWN) == 0) {
+		if (!(priv->rx_desc[entry].status & BD_STAT_RX_RES)) {
+			pkt_size = (priv->rx_desc[entry].status & 0x1fff) - 4;
+
+			dma_inv_range((unsigned long)priv->rx_buf
+			               + entry * PKT_BUF_SIZE,
+			              (unsigned long)priv->rx_buf
+			               + entry * PKT_BUF_SIZE + pkt_size);
+
+			net_receive(edev, priv->rx_buf + entry * PKT_BUF_SIZE,
+			            pkt_size);
+
+			if (entry == NUM_RX_DESC - 1)
+				priv->rx_desc[entry].status = BD_STAT_OWN |
+					BD_STAT_EOR | PKT_BUF_SIZE;
+			else
+				priv->rx_desc[entry].status =
+					BD_STAT_OWN | PKT_BUF_SIZE;
+			priv->rx_desc[entry].buf_addr =
+				virt_to_phys(priv->rx_buf +
+				             entry * PKT_BUF_SIZE);
+
+			dma_flush_range((unsigned long)&priv->rx_desc[entry],
+			                (unsigned long)&priv->rx_desc[entry + 1]);
+		} else {
+			dev_err(&edev->dev, "rx error\n");
+		}
+
+		priv->cur_rx++;
+
+		return pkt_size;
+
+	} else {
+		status = RTL_R8(priv, IntrStatus);
+		RTL_W8(priv, IntrStatus, status & ~(TxErr | RxErr | SYSErr));
+		udelay(100);	/* wait */
+	}
+
+	return 0;
+}
+
+static int rtl8169_get_ethaddr(struct eth_device *edev, unsigned char *m)
+{
+	struct rtl8169_priv *priv = edev->priv;
+	int i;
+
+	for (i = 0; i < 6; i++) {
+		m[i] = RTL_R8(priv, MAC0 + i);
+	}
+
+	return 0;
+}
+
+static int rtl8169_set_ethaddr(struct eth_device *edev, unsigned char *mac_addr)
+{
+	struct rtl8169_priv *priv = edev->priv;
+	int i;
+
+	RTL_W8(priv, Cfg9346, Cfg9346_Unlock);
+
+	for (i = 0; i < 6; i++) {
+		RTL_W8(priv, (MAC0 + i), mac_addr[i]);
+		RTL_R8(priv, mac_addr[i]);
+	}
+
+	RTL_W8(priv, Cfg9346, Cfg9346_Lock);
+
+	return 0;
+}
+
+static void rtl8169_eth_halt(struct eth_device *edev)
+{
+	struct rtl8169_priv *priv = edev->priv;
+
+	/* Stop the chip's Tx and Rx DMA processes. */
+	RTL_W8(priv, ChipCmd, 0x00);
+
+	/* Disable interrupts by clearing the interrupt mask. */
+	RTL_W16(priv, IntrMask, 0x0000);
+	RTL_W32(priv, RxMissed, 0);
+
+	pci_clear_master(priv->pci_dev);
+}
+
+static int rtl8169_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct device_d *dev = &pdev->dev;
+	struct eth_device *edev;
+	struct rtl8169_priv *priv;
+	int ret;
+
+	/* enable pci device */
+	pci_enable_device(pdev);
+
+	priv = xzalloc(sizeof(struct rtl8169_priv));
+
+	edev = &priv->edev;
+	dev->type_data = edev;
+	edev->priv = priv;
+
+	priv->pci_dev = pdev;
+
+	priv->miibus.read = rtl8169_phy_read;
+	priv->miibus.write = rtl8169_phy_write;
+	priv->miibus.priv = priv;
+	priv->miibus.parent = &edev->dev;
+
+	priv->base = pci_iomap(pdev, pdev->device == 0x8168 ? 2 : 1);
+
+	dev_dbg(dev, "rtl%04x (rev %02x) (base=%p)\n",
+		 pdev->device, pdev->revision, priv->base);
+
+	edev->init = rtl8169_init_dev;
+	edev->open = rtl8169_eth_open;
+	edev->send = rtl8169_eth_send;
+	edev->recv = rtl8169_eth_rx;
+	edev->get_ethaddr = rtl8169_get_ethaddr;
+	edev->set_ethaddr = rtl8169_set_ethaddr;
+	edev->halt = rtl8169_eth_halt;
+	edev->parent = dev;
+	ret = eth_register(edev);
+	if (ret)
+		goto eth_err;
+
+	ret = mdiobus_register(&priv->miibus);
+	if (ret)
+		goto mdio_err;
+
+	return 0;
+
+mdio_err:
+	eth_unregister(edev);
+
+eth_err:
+	free(priv);
+
+	return ret;
+}
+static DEFINE_PCI_DEVICE_TABLE(rtl8169_pci_tbl) = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8167), },
+	{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8168), },
+	{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8169), },
+	{ /* sentinel */ }
+};
+
+static struct pci_driver rtl8169_eth_driver = {
+	.name = "rtl8169_eth",
+	.id_table = rtl8169_pci_tbl,
+	.probe = rtl8169_probe,
+};
+
+static int rtl8169_init(void)
+{
+	return pci_register_driver(&rtl8169_eth_driver);
+}
+device_initcall(rtl8169_init);
-- 
1.9.3




More information about the barebox mailing list