summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLucas Stach <dev@lynxeye.de>2014-10-04 19:40:24 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2014-10-08 08:39:02 +0200
commitb64b24db752d450173bd0b0bc4a45d6fb7222847 (patch)
tree57ab1ca53646c15dc8018b2c31717b3bd41e0c23
parent7f5a86db039a27b09d4bef779134972efde0cb08 (diff)
downloadbarebox-b64b24db752d450173bd0b0bc4a45d6fb7222847.tar.gz
barebox-b64b24db752d450173bd0b0bc4a45d6fb7222847.tar.xz
net: add rtl8169 driver
This adds the driver for RealTek 8169 and compatible pci attached network chips. Signed-off-by: Lucas Stach <dev@lynxeye.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--drivers/net/Kconfig8
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/rtl8169.c566
3 files changed, 575 insertions, 0 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index c99fcc8316..24b984462d 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 1b85778f9f..3e66b3135c 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 0000000000..3ed2e562a4
--- /dev/null
+++ b/drivers/net/rtl8169.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2014 Lucas Stach <l.stach@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);