From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from ns.lynxeye.de ([87.118.118.114] helo=lynxeye.de) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1XaTFt-0005fu-C8 for barebox@lists.infradead.org; Sat, 04 Oct 2014 17:36:31 +0000 Received: from tellur.intern.lynxeye.de (p57B5E0B8.dip0.t-ipconnect.de [87.181.224.184]) by lynxeye.de (Postfix) with ESMTPA id D79CC26C200E for ; Sat, 4 Oct 2014 19:34:06 +0200 (CEST) From: Lucas Stach Date: Sat, 4 Oct 2014 19:40:24 +0200 Message-Id: <1412444425-2569-19-git-send-email-dev@lynxeye.de> In-Reply-To: <1412444425-2569-1-git-send-email-dev@lynxeye.de> References: <1412444425-2569-1-git-send-email-dev@lynxeye.de> List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "barebox" Errors-To: barebox-bounces+u.kleine-koenig=pengutronix.de@lists.infradead.org Subject: [PATCH v2 18/19] net: add rtl8169 driver To: barebox@lists.infradead.org This adds the driver for RealTek 8169 and compatible pci attached network chips. Signed-off-by: Lucas Stach --- 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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#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 _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox