diff options
Diffstat (limited to 'target/linux/lantiq/patches-3.2/0042-NET-adds-driver-for-lantiq-vr9-ethernet.patch')
-rw-r--r-- | target/linux/lantiq/patches-3.2/0042-NET-adds-driver-for-lantiq-vr9-ethernet.patch | 1470 |
1 files changed, 1470 insertions, 0 deletions
diff --git a/target/linux/lantiq/patches-3.2/0042-NET-adds-driver-for-lantiq-vr9-ethernet.patch b/target/linux/lantiq/patches-3.2/0042-NET-adds-driver-for-lantiq-vr9-ethernet.patch new file mode 100644 index 0000000000..3a0cd0f67d --- /dev/null +++ b/target/linux/lantiq/patches-3.2/0042-NET-adds-driver-for-lantiq-vr9-ethernet.patch @@ -0,0 +1,1470 @@ +From 29be0a0cb78942568ea4dc164a86da1bcf7b3ad6 Mon Sep 17 00:00:00 2001 +From: John Crispin <blogic@openwrt.org> +Date: Fri, 9 Mar 2012 19:03:40 +0100 +Subject: [PATCH 42/73] NET: adds driver for lantiq vr9 ethernet + +--- + .../mips/include/asm/mach-lantiq/xway/lantiq_soc.h | 2 +- + arch/mips/lantiq/xway/devices.c | 20 + + arch/mips/lantiq/xway/devices.h | 1 + + drivers/net/ethernet/Kconfig | 6 + + drivers/net/ethernet/Makefile | 1 + + drivers/net/ethernet/lantiq_vrx200.c | 1358 ++++++++++++++++++++ + 6 files changed, 1387 insertions(+), 1 deletions(-) + create mode 100644 drivers/net/ethernet/lantiq_vrx200.c + +diff --git a/arch/mips/include/asm/mach-lantiq/xway/lantiq_soc.h b/arch/mips/include/asm/mach-lantiq/xway/lantiq_soc.h +index ab2d236..d1b8cc8 100644 +--- a/arch/mips/include/asm/mach-lantiq/xway/lantiq_soc.h ++++ b/arch/mips/include/asm/mach-lantiq/xway/lantiq_soc.h +@@ -102,7 +102,7 @@ + + /* GBIT - gigabit switch */ + #define LTQ_GBIT_BASE_ADDR 0x1E108000 +-#define LTQ_GBIT_SIZE 0x200 ++#define LTQ_GBIT_SIZE 0x4000 + + /* DMA */ + #define LTQ_DMA_BASE_ADDR 0x1E104100 +diff --git a/arch/mips/lantiq/xway/devices.c b/arch/mips/lantiq/xway/devices.c +index eab4644d..5efa4f3 100644 +--- a/arch/mips/lantiq/xway/devices.c ++++ b/arch/mips/lantiq/xway/devices.c +@@ -83,6 +83,7 @@ static struct platform_device ltq_etop = { + .name = "ltq_etop", + .resource = ltq_etop_resources, + .num_resources = 1, ++ .id = -1, + }; + + void __init +@@ -96,3 +97,22 @@ ltq_register_etop(struct ltq_eth_data *eth) + platform_device_register(<q_etop); + } + } ++ ++/* ethernet */ ++static struct resource ltq_vrx200_resources[] = { ++ MEM_RES("gbit", LTQ_GBIT_BASE_ADDR, LTQ_GBIT_SIZE), ++}; ++ ++static struct platform_device ltq_vrx200 = { ++ .name = "ltq_vrx200", ++ .resource = ltq_vrx200_resources, ++ .num_resources = 1, ++ .id = -1, ++}; ++ ++void __init ++ltq_register_vrx200(struct ltq_eth_data *eth) ++{ ++ ltq_vrx200.dev.platform_data = eth; ++ platform_device_register(<q_vrx200); ++} +diff --git a/arch/mips/lantiq/xway/devices.h b/arch/mips/lantiq/xway/devices.h +index d825cbd..08befd9 100644 +--- a/arch/mips/lantiq/xway/devices.h ++++ b/arch/mips/lantiq/xway/devices.h +@@ -17,5 +17,6 @@ extern void ltq_register_gpio_stp(void); + extern void ltq_register_ase_asc(void); + extern void ltq_register_etop(struct ltq_eth_data *eth); + extern void xway_register_nand(struct mtd_partition *parts, int count); ++extern void ltq_register_vrx200(struct ltq_eth_data *eth); + + #endif +diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig +index 597f4d4..2035cab 100644 +--- a/drivers/net/ethernet/Kconfig ++++ b/drivers/net/ethernet/Kconfig +@@ -84,6 +84,12 @@ config LANTIQ_ETOP + ---help--- + Support for the MII0 inside the Lantiq SoC + ++config LANTIQ_VRX200 ++ tristate "Lantiq SoC vrx200 driver" ++ depends on SOC_TYPE_XWAY ++ ---help--- ++ Support for the MII0 inside the Lantiq SoC ++ + source "drivers/net/ethernet/marvell/Kconfig" + source "drivers/net/ethernet/mellanox/Kconfig" + source "drivers/net/ethernet/micrel/Kconfig" +diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile +index be5dde0..4fde2be 100644 +--- a/drivers/net/ethernet/Makefile ++++ b/drivers/net/ethernet/Makefile +@@ -35,6 +35,7 @@ obj-$(CONFIG_IP1000) += icplus/ + obj-$(CONFIG_JME) += jme.o + obj-$(CONFIG_KORINA) += korina.o + obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o ++obj-$(CONFIG_LANTIQ_VRX200) += lantiq_vrx200.o + obj-$(CONFIG_NET_VENDOR_MARVELL) += marvell/ + obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ + obj-$(CONFIG_NET_VENDOR_MICREL) += micrel/ +diff --git a/drivers/net/ethernet/lantiq_vrx200.c b/drivers/net/ethernet/lantiq_vrx200.c +new file mode 100644 +index 0000000..d79d380 +--- /dev/null ++++ b/drivers/net/ethernet/lantiq_vrx200.c +@@ -0,0 +1,1358 @@ ++/* ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License version 2 as published ++ * by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that 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, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ++ * ++ * Copyright (C) 2011 John Crispin <blogic@openwrt.org> ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/slab.h> ++#include <linux/errno.h> ++#include <linux/types.h> ++#include <linux/interrupt.h> ++#include <linux/uaccess.h> ++#include <linux/in.h> ++#include <linux/netdevice.h> ++#include <linux/etherdevice.h> ++#include <linux/phy.h> ++#include <linux/ip.h> ++#include <linux/tcp.h> ++#include <linux/skbuff.h> ++#include <linux/mm.h> ++#include <linux/platform_device.h> ++#include <linux/ethtool.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++#include <linux/io.h> ++#include <linux/dma-mapping.h> ++#include <linux/module.h> ++#include <linux/clk.h> ++ ++#include <asm/checksum.h> ++ ++#include <lantiq_soc.h> ++#include <xway_dma.h> ++#include <lantiq_platform.h> ++ ++#define LTQ_SWITCH_BASE 0x1E108000 ++#define LTQ_SWITCH_CORE_BASE LTQ_SWITCH_BASE ++#define LTQ_SWITCH_TOP_PDI_BASE LTQ_SWITCH_CORE_BASE ++#define LTQ_SWITCH_BM_PDI_BASE (LTQ_SWITCH_CORE_BASE + 4 * 0x40) ++#define LTQ_SWITCH_MAC_PDI_0_BASE (LTQ_SWITCH_CORE_BASE + 4 * 0x900) ++#define LTQ_SWITCH_MAC_PDI_X_BASE(x) (LTQ_SWITCH_MAC_PDI_0_BASE + x * 0x30) ++#define LTQ_SWITCH_TOPLEVEL_BASE (LTQ_SWITCH_BASE + 4 * 0xC40) ++#define LTQ_SWITCH_MDIO_PDI_BASE (LTQ_SWITCH_TOPLEVEL_BASE) ++#define LTQ_SWITCH_MII_PDI_BASE (LTQ_SWITCH_TOPLEVEL_BASE + 4 * 0x36) ++#define LTQ_SWITCH_PMAC_PDI_BASE (LTQ_SWITCH_TOPLEVEL_BASE + 4 * 0x82) ++ ++#define LTQ_ETHSW_MAC_CTRL0_PADEN (1 << 8) ++#define LTQ_ETHSW_MAC_CTRL0_FCS (1 << 7) ++#define LTQ_ETHSW_MAC_CTRL1_SHORTPRE (1 << 8) ++#define LTQ_ETHSW_MAC_CTRL2_MLEN (1 << 3) ++#define LTQ_ETHSW_MAC_CTRL2_LCHKL (1 << 2) ++#define LTQ_ETHSW_MAC_CTRL2_LCHKS_DIS 0 ++#define LTQ_ETHSW_MAC_CTRL2_LCHKS_UNTAG 1 ++#define LTQ_ETHSW_MAC_CTRL2_LCHKS_TAG 2 ++#define LTQ_ETHSW_MAC_CTRL6_RBUF_DLY_WP_SHIFT 9 ++#define LTQ_ETHSW_MAC_CTRL6_RXBUF_BYPASS (1 << 6) ++#define LTQ_ETHSW_GLOB_CTRL_SE (1 << 15) ++#define LTQ_ETHSW_MDC_CFG1_MCEN (1 << 8) ++#define LTQ_ETHSW_PMAC_HD_CTL_FC (1 << 10) ++#define LTQ_ETHSW_PMAC_HD_CTL_RC (1 << 4) ++#define LTQ_ETHSW_PMAC_HD_CTL_AC (1 << 2) ++#define ADVERTIZE_MPD (1 << 10) ++ ++#define MDIO_DEVAD_NONE (-1) ++ ++#define LTQ_ETH_RX_BUFFER_CNT PKTBUFSRX ++ ++#define LTQ_MDIO_DRV_NAME "ltq-mdio" ++#define LTQ_ETH_DRV_NAME "ltq-eth" ++ ++#define LTQ_ETHSW_MAX_GMAC 1 ++#define LTQ_ETHSW_PMAC 1 ++ ++#define ltq_setbits(a, set) \ ++ ltq_w32(ltq_r32(a) | (set), a) ++ ++enum ltq_reset_modules { ++ LTQ_RESET_CORE, ++ LTQ_RESET_DMA, ++ LTQ_RESET_ETH, ++ LTQ_RESET_PHY, ++ LTQ_RESET_HARD, ++ LTQ_RESET_SOFT, ++}; ++ ++static inline void ++dbg_ltq_writel(void *a, unsigned int b) ++{ ++ ltq_w32(b, a); ++} ++ ++int ltq_reset_once(enum ltq_reset_modules module, ulong usec); ++ ++struct ltq_ethsw_mac_pdi_x_regs { ++ u32 pstat; /* Port status */ ++ u32 pisr; /* Interrupt status */ ++ u32 pier; /* Interrupt enable */ ++ u32 ctrl_0; /* Control 0 */ ++ u32 ctrl_1; /* Control 1 */ ++ u32 ctrl_2; /* Control 2 */ ++ u32 ctrl_3; /* Control 3 */ ++ u32 ctrl_4; /* Control 4 */ ++ u32 ctrl_5; /* Control 5 */ ++ u32 ctrl_6; /* Control 6 */ ++ u32 bufst; /* TX/RX buffer control */ ++ u32 testen; /* Test enable */ ++}; ++ ++struct ltq_ethsw_mac_pdi_regs { ++ struct ltq_ethsw_mac_pdi_x_regs mac[12]; ++}; ++ ++struct ltq_ethsw_mdio_pdi_regs { ++ u32 glob_ctrl; /* Global control 0 */ ++ u32 rsvd0[7]; ++ u32 mdio_ctrl; /* MDIO control */ ++ u32 mdio_read; /* MDIO read data */ ++ u32 mdio_write; /* MDIO write data */ ++ u32 mdc_cfg_0; /* MDC clock configuration 0 */ ++ u32 mdc_cfg_1; /* MDC clock configuration 1 */ ++ u32 rsvd[3]; ++ u32 phy_addr_5; /* PHY address port 5 */ ++ u32 phy_addr_4; /* PHY address port 4 */ ++ u32 phy_addr_3; /* PHY address port 3 */ ++ u32 phy_addr_2; /* PHY address port 2 */ ++ u32 phy_addr_1; /* PHY address port 1 */ ++ u32 phy_addr_0; /* PHY address port 0 */ ++ u32 mdio_stat_0; /* MDIO PHY polling status port 0 */ ++ u32 mdio_stat_1; /* MDIO PHY polling status port 1 */ ++ u32 mdio_stat_2; /* MDIO PHY polling status port 2 */ ++ u32 mdio_stat_3; /* MDIO PHY polling status port 3 */ ++ u32 mdio_stat_4; /* MDIO PHY polling status port 4 */ ++ u32 mdio_stat_5; /* MDIO PHY polling status port 5 */ ++}; ++ ++struct ltq_ethsw_mii_pdi_regs { ++ u32 mii_cfg0; /* xMII port 0 configuration */ ++ u32 pcdu0; /* Port 0 clock delay configuration */ ++ u32 mii_cfg1; /* xMII port 1 configuration */ ++ u32 pcdu1; /* Port 1 clock delay configuration */ ++ u32 mii_cfg2; /* xMII port 2 configuration */ ++ u32 rsvd0; ++ u32 mii_cfg3; /* xMII port 3 configuration */ ++ u32 rsvd1; ++ u32 mii_cfg4; /* xMII port 4 configuration */ ++ u32 rsvd2; ++ u32 mii_cfg5; /* xMII port 5 configuration */ ++ u32 pcdu5; /* Port 5 clock delay configuration */ ++}; ++ ++struct ltq_ethsw_pmac_pdi_regs { ++ u32 hd_ctl; /* PMAC header control */ ++ u32 tl; /* PMAC type/length */ ++ u32 sa1; /* PMAC source address 1 */ ++ u32 sa2; /* PMAC source address 2 */ ++ u32 sa3; /* PMAC source address 3 */ ++ u32 da1; /* PMAC destination address 1 */ ++ u32 da2; /* PMAC destination address 2 */ ++ u32 da3; /* PMAC destination address 3 */ ++ u32 vlan; /* PMAC VLAN */ ++ u32 rx_ipg; /* PMAC interpacket gap in RX direction */ ++ u32 st_etype; /* PMAC special tag ethertype */ ++ u32 ewan; /* PMAC ethernet WAN group */ ++}; ++ ++struct ltq_mdio_phy_addr_reg { ++ union { ++ struct { ++ unsigned rsvd:1; ++ unsigned lnkst:2; /* Link status control */ ++ unsigned speed:2; /* Speed control */ ++ unsigned fdup:2; /* Full duplex control */ ++ unsigned fcontx:2; /* Flow control mode TX */ ++ unsigned fconrx:2; /* Flow control mode RX */ ++ unsigned addr:5; /* PHY address */ ++ } bits; ++ u16 val; ++ }; ++}; ++ ++enum ltq_mdio_phy_addr_lnkst { ++ LTQ_MDIO_PHY_ADDR_LNKST_AUTO = 0, ++ LTQ_MDIO_PHY_ADDR_LNKST_UP = 1, ++ LTQ_MDIO_PHY_ADDR_LNKST_DOWN = 2, ++}; ++ ++enum ltq_mdio_phy_addr_speed { ++ LTQ_MDIO_PHY_ADDR_SPEED_M10 = 0, ++ LTQ_MDIO_PHY_ADDR_SPEED_M100 = 1, ++ LTQ_MDIO_PHY_ADDR_SPEED_G1 = 2, ++ LTQ_MDIO_PHY_ADDR_SPEED_AUTO = 3, ++}; ++ ++enum ltq_mdio_phy_addr_fdup { ++ LTQ_MDIO_PHY_ADDR_FDUP_AUTO = 0, ++ LTQ_MDIO_PHY_ADDR_FDUP_ENABLE = 1, ++ LTQ_MDIO_PHY_ADDR_FDUP_DISABLE = 3, ++}; ++ ++enum ltq_mdio_phy_addr_fcon { ++ LTQ_MDIO_PHY_ADDR_FCON_AUTO = 0, ++ LTQ_MDIO_PHY_ADDR_FCON_ENABLE = 1, ++ LTQ_MDIO_PHY_ADDR_FCON_DISABLE = 3, ++}; ++ ++struct ltq_mii_mii_cfg_reg { ++ union { ++ struct { ++ unsigned res:1; /* Hardware reset */ ++ unsigned en:1; /* xMII interface enable */ ++ unsigned isol:1; /* xMII interface isolate */ ++ unsigned ldclkdis:1; /* Link down clock disable */ ++ unsigned rsvd:1; ++ unsigned crs:2; /* CRS sensitivity config */ ++ unsigned rgmii_ibs:1; /* RGMII In Band status */ ++ unsigned rmii:1; /* RMII ref clock direction */ ++ unsigned miirate:3; /* xMII interface clock rate */ ++ unsigned miimode:4; /* xMII interface mode */ ++ } bits; ++ u16 val; ++ }; ++}; ++ ++enum ltq_mii_mii_cfg_miirate { ++ LTQ_MII_MII_CFG_MIIRATE_M2P5 = 0, ++ LTQ_MII_MII_CFG_MIIRATE_M25 = 1, ++ LTQ_MII_MII_CFG_MIIRATE_M125 = 2, ++ LTQ_MII_MII_CFG_MIIRATE_M50 = 3, ++ LTQ_MII_MII_CFG_MIIRATE_AUTO = 4, ++}; ++ ++enum ltq_mii_mii_cfg_miimode { ++ LTQ_MII_MII_CFG_MIIMODE_MIIP = 0, ++ LTQ_MII_MII_CFG_MIIMODE_MIIM = 1, ++ LTQ_MII_MII_CFG_MIIMODE_RMIIP = 2, ++ LTQ_MII_MII_CFG_MIIMODE_RMIIM = 3, ++ LTQ_MII_MII_CFG_MIIMODE_RGMII = 4, ++}; ++ ++struct ltq_eth_priv { ++ struct ltq_dma_device *dma_dev; ++ struct mii_dev *bus; ++ struct eth_device *dev; ++ struct phy_device *phymap[LTQ_ETHSW_MAX_GMAC]; ++ int rx_num; ++}; ++ ++enum ltq_mdio_mbusy { ++ LTQ_MDIO_MBUSY_IDLE = 0, ++ LTQ_MDIO_MBUSY_BUSY = 1, ++}; ++ ++enum ltq_mdio_op { ++ LTQ_MDIO_OP_WRITE = 1, ++ LTQ_MDIO_OP_READ = 2, ++}; ++ ++struct ltq_mdio_access { ++ union { ++ struct { ++ unsigned rsvd:3; ++ unsigned mbusy:1; ++ unsigned op:2; ++ unsigned phyad:5; ++ unsigned regad:5; ++ } bits; ++ u16 val; ++ }; ++}; ++ ++enum LTQ_ETH_PORT_FLAGS { ++ LTQ_ETH_PORT_NONE = 0, ++ LTQ_ETH_PORT_PHY = 1, ++ LTQ_ETH_PORT_SWITCH = (1 << 1), ++ LTQ_ETH_PORT_MAC = (1 << 2), ++}; ++ ++struct ltq_eth_port_config { ++ u8 num; ++ u8 phy_addr; ++ u16 flags; ++ phy_interface_t phy_if; ++}; ++ ++struct ltq_eth_board_config { ++ const struct ltq_eth_port_config *ports; ++ int num_ports; ++}; ++ ++static const struct ltq_eth_port_config eth_port_config[] = { ++ /* GMAC0: external Lantiq PEF7071 10/100/1000 PHY for LAN port 0 */ ++ { 0, 0x0, LTQ_ETH_PORT_PHY, PHY_INTERFACE_MODE_RGMII }, ++ /* GMAC1: external Lantiq PEF7071 10/100/1000 PHY for LAN port 1 */ ++ { 1, 0x1, LTQ_ETH_PORT_PHY, PHY_INTERFACE_MODE_RGMII }, ++}; ++ ++static const struct ltq_eth_board_config board_config = { ++ .ports = eth_port_config, ++ .num_ports = ARRAY_SIZE(eth_port_config), ++}; ++ ++static struct ltq_ethsw_mac_pdi_regs *ltq_ethsw_mac_pdi_regs = ++ (struct ltq_ethsw_mac_pdi_regs *) CKSEG1ADDR(LTQ_SWITCH_MAC_PDI_0_BASE); ++ ++static struct ltq_ethsw_mdio_pdi_regs *ltq_ethsw_mdio_pdi_regs = ++ (struct ltq_ethsw_mdio_pdi_regs *) CKSEG1ADDR(LTQ_SWITCH_MDIO_PDI_BASE); ++ ++static struct ltq_ethsw_mii_pdi_regs *ltq_ethsw_mii_pdi_regs = ++ (struct ltq_ethsw_mii_pdi_regs *) CKSEG1ADDR(LTQ_SWITCH_MII_PDI_BASE); ++ ++static struct ltq_ethsw_pmac_pdi_regs *ltq_ethsw_pmac_pdi_regs = ++ (struct ltq_ethsw_pmac_pdi_regs *) CKSEG1ADDR(LTQ_SWITCH_PMAC_PDI_BASE); ++ ++ ++#define MAX_DMA_CHAN 0x8 ++#define MAX_DMA_CRC_LEN 0x4 ++#define MAX_DMA_DATA_LEN 0x600 ++ ++/* use 2 static channels for TX/RX ++ depending on the SoC we need to use different DMA channels for ethernet */ ++#define LTQ_ETOP_TX_CHANNEL 1 ++#define LTQ_ETOP_RX_CHANNEL 0 ++ ++#define IS_TX(x) (x == LTQ_ETOP_TX_CHANNEL) ++#define IS_RX(x) (x == LTQ_ETOP_RX_CHANNEL) ++ ++#define DRV_VERSION "1.0" ++ ++static void __iomem *ltq_vrx200_membase; ++ ++struct ltq_vrx200_chan { ++ int idx; ++ int tx_free; ++ struct net_device *netdev; ++ struct napi_struct napi; ++ struct ltq_dma_channel dma; ++ struct sk_buff *skb[LTQ_DESC_NUM]; ++}; ++ ++struct ltq_vrx200_priv { ++ struct net_device *netdev; ++ struct ltq_eth_data *pldata; ++ struct resource *res; ++ ++ struct mii_bus *mii_bus; ++ struct phy_device *phydev; ++ ++ struct ltq_vrx200_chan ch[MAX_DMA_CHAN]; ++ int tx_free[MAX_DMA_CHAN >> 1]; ++ ++ spinlock_t lock; ++ ++ struct clk *clk_ppe; ++}; ++ ++static int ltq_vrx200_mdio_wr(struct mii_bus *bus, int phy_addr, ++ int phy_reg, u16 phy_data); ++ ++static int ++ltq_vrx200_alloc_skb(struct ltq_vrx200_chan *ch) ++{ ++ ch->skb[ch->dma.desc] = dev_alloc_skb(MAX_DMA_DATA_LEN); ++ if (!ch->skb[ch->dma.desc]) ++ return -ENOMEM; ++ ch->dma.desc_base[ch->dma.desc].addr = dma_map_single(NULL, ++ ch->skb[ch->dma.desc]->data, MAX_DMA_DATA_LEN, ++ DMA_FROM_DEVICE); ++ ch->dma.desc_base[ch->dma.desc].addr = ++ CPHYSADDR(ch->skb[ch->dma.desc]->data); ++ ch->dma.desc_base[ch->dma.desc].ctl = ++ LTQ_DMA_OWN | LTQ_DMA_RX_OFFSET(NET_IP_ALIGN) | ++ MAX_DMA_DATA_LEN; ++ skb_reserve(ch->skb[ch->dma.desc], NET_IP_ALIGN); ++ return 0; ++} ++ ++static void ++ltq_vrx200_hw_receive(struct ltq_vrx200_chan *ch) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(ch->netdev); ++ struct ltq_dma_desc *desc = &ch->dma.desc_base[ch->dma.desc]; ++ struct sk_buff *skb = ch->skb[ch->dma.desc]; ++ int len = (desc->ctl & LTQ_DMA_SIZE_MASK) - MAX_DMA_CRC_LEN; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ if (ltq_vrx200_alloc_skb(ch)) { ++ netdev_err(ch->netdev, ++ "failed to allocate new rx buffer, stopping DMA\n"); ++ ltq_dma_close(&ch->dma); ++ } ++ ch->dma.desc++; ++ ch->dma.desc %= LTQ_DESC_NUM; ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ skb_put(skb, len); ++ skb->dev = ch->netdev; ++ skb->protocol = eth_type_trans(skb, ch->netdev); ++ netif_receive_skb(skb); ++} ++ ++static int ++ltq_vrx200_poll_rx(struct napi_struct *napi, int budget) ++{ ++ struct ltq_vrx200_chan *ch = container_of(napi, ++ struct ltq_vrx200_chan, napi); ++ struct ltq_vrx200_priv *priv = netdev_priv(ch->netdev); ++ int rx = 0; ++ int complete = 0; ++ unsigned long flags; ++ ++ while ((rx < budget) && !complete) { ++ struct ltq_dma_desc *desc = &ch->dma.desc_base[ch->dma.desc]; ++ ++ if ((desc->ctl & (LTQ_DMA_OWN | LTQ_DMA_C)) == LTQ_DMA_C) { ++ ltq_vrx200_hw_receive(ch); ++ rx++; ++ } else { ++ complete = 1; ++ } ++ } ++ if (complete || !rx) { ++ napi_complete(&ch->napi); ++ spin_lock_irqsave(&priv->lock, flags); ++ ltq_dma_ack_irq(&ch->dma); ++ spin_unlock_irqrestore(&priv->lock, flags); ++ } ++ return rx; ++} ++ ++static int ++ltq_vrx200_poll_tx(struct napi_struct *napi, int budget) ++{ ++ struct ltq_vrx200_chan *ch = ++ container_of(napi, struct ltq_vrx200_chan, napi); ++ struct ltq_vrx200_priv *priv = netdev_priv(ch->netdev); ++ struct netdev_queue *txq = ++ netdev_get_tx_queue(ch->netdev, ch->idx >> 1); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ while ((ch->dma.desc_base[ch->tx_free].ctl & ++ (LTQ_DMA_OWN | LTQ_DMA_C)) == LTQ_DMA_C) { ++ dev_kfree_skb_any(ch->skb[ch->tx_free]); ++ ch->skb[ch->tx_free] = NULL; ++ memset(&ch->dma.desc_base[ch->tx_free], 0, ++ sizeof(struct ltq_dma_desc)); ++ ch->tx_free++; ++ ch->tx_free %= LTQ_DESC_NUM; ++ } ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ if (netif_tx_queue_stopped(txq)) ++ netif_tx_start_queue(txq); ++ napi_complete(&ch->napi); ++ spin_lock_irqsave(&priv->lock, flags); ++ ltq_dma_ack_irq(&ch->dma); ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return 1; ++} ++ ++static irqreturn_t ++ltq_vrx200_dma_irq(int irq, void *_priv) ++{ ++ struct ltq_vrx200_priv *priv = _priv; ++ int ch = irq - LTQ_DMA_ETOP; ++ ++ napi_schedule(&priv->ch[ch].napi); ++ return IRQ_HANDLED; ++} ++ ++static void ++ltq_vrx200_free_channel(struct net_device *dev, struct ltq_vrx200_chan *ch) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ ltq_dma_free(&ch->dma); ++ if (ch->dma.irq) ++ free_irq(ch->dma.irq, priv); ++ if (IS_RX(ch->idx)) { ++ int desc; ++ for (desc = 0; desc < LTQ_DESC_NUM; desc++) ++ dev_kfree_skb_any(ch->skb[ch->dma.desc]); ++ } ++} ++ ++static void ++ltq_vrx200_hw_exit(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int i; ++ ++ clk_disable(priv->clk_ppe); ++ ++ for (i = 0; i < MAX_DMA_CHAN; i++) ++ if (IS_TX(i) || IS_RX(i)) ++ ltq_vrx200_free_channel(dev, &priv->ch[i]); ++} ++ ++static void *ltq_eth_phy_addr_reg(int num) ++{ ++ switch (num) { ++ case 0: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_0; ++ case 1: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_1; ++ case 2: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_2; ++ case 3: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_3; ++ case 4: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_4; ++ case 5: ++ return <q_ethsw_mdio_pdi_regs->phy_addr_5; ++ } ++ ++ return NULL; ++} ++ ++static void *ltq_eth_mii_cfg_reg(int num) ++{ ++ switch (num) { ++ case 0: ++ return <q_ethsw_mii_pdi_regs->mii_cfg0; ++ case 1: ++ return <q_ethsw_mii_pdi_regs->mii_cfg1; ++ case 2: ++ return <q_ethsw_mii_pdi_regs->mii_cfg2; ++ case 3: ++ return <q_ethsw_mii_pdi_regs->mii_cfg3; ++ case 4: ++ return <q_ethsw_mii_pdi_regs->mii_cfg4; ++ case 5: ++ return <q_ethsw_mii_pdi_regs->mii_cfg5; ++ } ++ ++ return NULL; ++} ++ ++static void ltq_eth_gmac_update(struct phy_device *phydev, int num) ++{ ++ struct ltq_mdio_phy_addr_reg phy_addr_reg; ++ struct ltq_mii_mii_cfg_reg mii_cfg_reg; ++ void *phy_addr = ltq_eth_phy_addr_reg(num); ++ void *mii_cfg = ltq_eth_mii_cfg_reg(num); ++ ++ phy_addr_reg.val = ltq_r32(phy_addr); ++ mii_cfg_reg.val = ltq_r32(mii_cfg); ++ ++ phy_addr_reg.bits.addr = phydev->addr; ++ ++ if (phydev->link) ++ phy_addr_reg.bits.lnkst = LTQ_MDIO_PHY_ADDR_LNKST_UP; ++ else ++ phy_addr_reg.bits.lnkst = LTQ_MDIO_PHY_ADDR_LNKST_DOWN; ++ ++ switch (phydev->speed) { ++ case SPEED_1000: ++ phy_addr_reg.bits.speed = LTQ_MDIO_PHY_ADDR_SPEED_G1; ++ mii_cfg_reg.bits.miirate = LTQ_MII_MII_CFG_MIIRATE_M125; ++ break; ++ case SPEED_100: ++ phy_addr_reg.bits.speed = LTQ_MDIO_PHY_ADDR_SPEED_M100; ++ switch (mii_cfg_reg.bits.miimode) { ++ case LTQ_MII_MII_CFG_MIIMODE_RMIIM: ++ case LTQ_MII_MII_CFG_MIIMODE_RMIIP: ++ mii_cfg_reg.bits.miirate = LTQ_MII_MII_CFG_MIIRATE_M50; ++ break; ++ default: ++ mii_cfg_reg.bits.miirate = LTQ_MII_MII_CFG_MIIRATE_M25; ++ break; ++ } ++ break; ++ default: ++ phy_addr_reg.bits.speed = LTQ_MDIO_PHY_ADDR_SPEED_M10; ++ mii_cfg_reg.bits.miirate = LTQ_MII_MII_CFG_MIIRATE_M2P5; ++ break; ++ } ++ ++ if (phydev->duplex == DUPLEX_FULL) ++ phy_addr_reg.bits.fdup = LTQ_MDIO_PHY_ADDR_FDUP_ENABLE; ++ else ++ phy_addr_reg.bits.fdup = LTQ_MDIO_PHY_ADDR_FDUP_DISABLE; ++ ++ dbg_ltq_writel(phy_addr, phy_addr_reg.val); ++ dbg_ltq_writel(mii_cfg, mii_cfg_reg.val); ++ udelay(1); ++} ++ ++ ++static void ltq_eth_port_config(struct ltq_vrx200_priv *priv, ++ const struct ltq_eth_port_config *port) ++{ ++ struct ltq_mii_mii_cfg_reg mii_cfg_reg; ++ void *mii_cfg = ltq_eth_mii_cfg_reg(port->num); ++ int setup_gpio = 0; ++ ++ mii_cfg_reg.val = ltq_r32(mii_cfg); ++ ++ ++ switch (port->num) { ++ case 0: /* xMII0 */ ++ case 1: /* xMII1 */ ++ switch (port->phy_if) { ++ case PHY_INTERFACE_MODE_MII: ++ if (port->flags & LTQ_ETH_PORT_PHY) ++ /* MII MAC mode, connected to external PHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_MIIM; ++ else ++ /* MII PHY mode, connected to external MAC */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_MIIP; ++ setup_gpio = 1; ++ break; ++ case PHY_INTERFACE_MODE_RMII: ++ if (port->flags & LTQ_ETH_PORT_PHY) ++ /* RMII MAC mode, connected to external PHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_RMIIM; ++ else ++ /* RMII PHY mode, connected to external MAC */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_RMIIP; ++ setup_gpio = 1; ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ /* RGMII MAC mode, connected to external PHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_RGMII; ++ setup_gpio = 1; ++ break; ++ default: ++ break; ++ } ++ break; ++ case 2: /* internal GPHY0 */ ++ case 3: /* internal GPHY0 */ ++ case 4: /* internal GPHY1 */ ++ switch (port->phy_if) { ++ case PHY_INTERFACE_MODE_MII: ++ case PHY_INTERFACE_MODE_GMII: ++ /* MII MAC mode, connected to internal GPHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_MIIM; ++ setup_gpio = 1; ++ break; ++ default: ++ break; ++ } ++ break; ++ case 5: /* internal GPHY1 or xMII2 */ ++ switch (port->phy_if) { ++ case PHY_INTERFACE_MODE_MII: ++ /* MII MAC mode, connected to internal GPHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_MIIM; ++ setup_gpio = 1; ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ /* RGMII MAC mode, connected to external PHY */ ++ mii_cfg_reg.bits.miimode = ++ LTQ_MII_MII_CFG_MIIMODE_RGMII; ++ setup_gpio = 1; ++ break; ++ default: ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ /* Enable MII interface */ ++ mii_cfg_reg.bits.en = port->flags ? 1 : 0; ++ dbg_ltq_writel(mii_cfg, mii_cfg_reg.val); ++ ++} ++ ++static void ltq_eth_gmac_init(int num) ++{ ++ struct ltq_mdio_phy_addr_reg phy_addr_reg; ++ struct ltq_mii_mii_cfg_reg mii_cfg_reg; ++ void *phy_addr = ltq_eth_phy_addr_reg(num); ++ void *mii_cfg = ltq_eth_mii_cfg_reg(num); ++ struct ltq_ethsw_mac_pdi_x_regs *mac_pdi_regs; ++ ++ mac_pdi_regs = <q_ethsw_mac_pdi_regs->mac[num]; ++ ++ /* Reset PHY status to link down */ ++ phy_addr_reg.val = ltq_r32(phy_addr); ++ phy_addr_reg.bits.addr = num; ++ phy_addr_reg.bits.lnkst = LTQ_MDIO_PHY_ADDR_LNKST_DOWN; ++ phy_addr_reg.bits.speed = LTQ_MDIO_PHY_ADDR_SPEED_M10; ++ phy_addr_reg.bits.fdup = LTQ_MDIO_PHY_ADDR_FDUP_DISABLE; ++ dbg_ltq_writel(phy_addr, phy_addr_reg.val); ++ ++ /* Reset and disable MII interface */ ++ mii_cfg_reg.val = ltq_r32(mii_cfg); ++ mii_cfg_reg.bits.en = 0; ++ mii_cfg_reg.bits.res = 1; ++ mii_cfg_reg.bits.miirate = LTQ_MII_MII_CFG_MIIRATE_M2P5; ++ dbg_ltq_writel(mii_cfg, mii_cfg_reg.val); ++ ++ /* ++ * Enable padding of short frames, enable frame checksum generation ++ * in transmit direction ++ */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_0, LTQ_ETHSW_MAC_CTRL0_PADEN | ++ LTQ_ETHSW_MAC_CTRL0_FCS); ++ ++ /* Set inter packet gap size to 12 bytes */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_1, 12); ++ ++ /* ++ * Configure frame length checks: ++ * - allow jumbo frames ++ * - enable long length check ++ * - enable short length without VLAN tags ++ */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_2, LTQ_ETHSW_MAC_CTRL2_MLEN | ++ LTQ_ETHSW_MAC_CTRL2_LCHKL | ++ LTQ_ETHSW_MAC_CTRL2_LCHKS_UNTAG); ++} ++ ++ ++static void ltq_eth_pmac_init(void) ++{ ++ struct ltq_ethsw_mac_pdi_x_regs *mac_pdi_regs; ++ ++ mac_pdi_regs = <q_ethsw_mac_pdi_regs->mac[LTQ_ETHSW_PMAC]; ++ ++ /* ++ * Enable padding of short frames, enable frame checksum generation ++ * in transmit direction ++ */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_0, LTQ_ETHSW_MAC_CTRL0_PADEN | ++ LTQ_ETHSW_MAC_CTRL0_FCS); ++ ++ /* ++ * Configure frame length checks: ++ * - allow jumbo frames ++ * - enable long length check ++ * - enable short length without VLAN tags ++ */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_2, LTQ_ETHSW_MAC_CTRL2_MLEN | ++ LTQ_ETHSW_MAC_CTRL2_LCHKL | ++ LTQ_ETHSW_MAC_CTRL2_LCHKS_UNTAG); ++ ++ /* ++ * Apply workaround for buffer congestion: ++ * - shorten preambel to 1 byte ++ * - set minimum inter packet gap size to 7 bytes ++ * - enable receive buffer bypass mode ++ */ ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_1, LTQ_ETHSW_MAC_CTRL1_SHORTPRE | 7); ++ dbg_ltq_writel(&mac_pdi_regs->ctrl_6, ++ (6 << LTQ_ETHSW_MAC_CTRL6_RBUF_DLY_WP_SHIFT) | ++ LTQ_ETHSW_MAC_CTRL6_RXBUF_BYPASS); ++ ++ /* Set request assertion threshold to 8, IPG counter to 11 */ ++ dbg_ltq_writel(<q_ethsw_pmac_pdi_regs->rx_ipg, 0x8B); ++ ++ /* ++ * Configure frame header control: ++ * - enable reaction on pause frames (flow control) ++ * - remove CRC for packets from PMAC to DMA ++ * - add CRC for packets from DMA to PMAC ++ */ ++ dbg_ltq_writel(<q_ethsw_pmac_pdi_regs->hd_ctl, LTQ_ETHSW_PMAC_HD_CTL_FC | ++ /*LTQ_ETHSW_PMAC_HD_CTL_RC | */LTQ_ETHSW_PMAC_HD_CTL_AC); ++} ++ ++static int ++ltq_vrx200_hw_init(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int err = 0; ++ int i; ++ ++ netdev_info(dev, "setting up dma\n"); ++ ltq_dma_init_port(DMA_PORT_ETOP); ++ ++ netdev_info(dev, "setting up pmu\n"); ++ clk_enable(priv->clk_ppe); ++ ++ /* Reset ethernet and switch subsystems */ ++ netdev_info(dev, "reset core\n"); ++ ltq_reset_once(BIT(8), 10); ++ ++ /* Enable switch macro */ ++ ltq_setbits(<q_ethsw_mdio_pdi_regs->glob_ctrl, ++ LTQ_ETHSW_GLOB_CTRL_SE); ++ ++ /* Disable MDIO auto-polling for all ports */ ++ dbg_ltq_writel(<q_ethsw_mdio_pdi_regs->mdc_cfg_0, 0); ++ ++ /* ++ * Enable and set MDIO management clock to 2.5 MHz. This is the ++ * maximum clock for FE PHYs. ++ * Formula for clock is: ++ * ++ * 50 MHz ++ * x = ----------- - 1 ++ * 2 * f_MDC ++ */ ++ dbg_ltq_writel(<q_ethsw_mdio_pdi_regs->mdc_cfg_1, ++ LTQ_ETHSW_MDC_CFG1_MCEN | 9); ++ ++ /* Init MAC connected to CPU */ ++ ltq_eth_pmac_init(); ++ ++ /* Init MACs connected to external MII interfaces */ ++ for (i = 0; i < LTQ_ETHSW_MAX_GMAC; i++) ++ ltq_eth_gmac_init(i); ++ ++ for (i = 0; i < MAX_DMA_CHAN && !err; i++) { ++ int irq = LTQ_DMA_ETOP + i; ++ struct ltq_vrx200_chan *ch = &priv->ch[i]; ++ ++ ch->idx = ch->dma.nr = i; ++ ++ if (IS_TX(i)) { ++ ltq_dma_alloc_tx(&ch->dma); ++ err = request_irq(irq, ltq_vrx200_dma_irq, IRQF_DISABLED, ++ "vrx200_tx", priv); ++ } else if (IS_RX(i)) { ++ ltq_dma_alloc_rx(&ch->dma); ++ for (ch->dma.desc = 0; ch->dma.desc < LTQ_DESC_NUM; ++ ch->dma.desc++) ++ if (ltq_vrx200_alloc_skb(ch)) ++ err = -ENOMEM; ++ ch->dma.desc = 0; ++ err = request_irq(irq, ltq_vrx200_dma_irq, IRQF_DISABLED, ++ "vrx200_rx", priv); ++ } ++ if (!err) ++ ch->dma.irq = irq; ++ } ++ for (i = 0; i < board_config.num_ports; i++) ++ ltq_eth_port_config(priv, &board_config.ports[i]); ++ return err; ++} ++ ++static void ++ltq_vrx200_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) ++{ ++ strcpy(info->driver, "Lantiq ETOP"); ++ strcpy(info->bus_info, "internal"); ++ strcpy(info->version, DRV_VERSION); ++} ++ ++static int ++ltq_vrx200_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ return phy_ethtool_gset(priv->phydev, cmd); ++} ++ ++static int ++ltq_vrx200_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ return phy_ethtool_sset(priv->phydev, cmd); ++} ++ ++static int ++ltq_vrx200_nway_reset(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ return phy_start_aneg(priv->phydev); ++} ++ ++static const struct ethtool_ops ltq_vrx200_ethtool_ops = { ++ .get_drvinfo = ltq_vrx200_get_drvinfo, ++ .get_settings = ltq_vrx200_get_settings, ++ .set_settings = ltq_vrx200_set_settings, ++ .nway_reset = ltq_vrx200_nway_reset, ++}; ++ ++static inline int ltq_mdio_poll(struct mii_bus *bus) ++{ ++ struct ltq_mdio_access acc; ++ unsigned cnt = 10000; ++ ++ while (likely(cnt--)) { ++ acc.val = ltq_r32(<q_ethsw_mdio_pdi_regs->mdio_ctrl); ++ if (!acc.bits.mbusy) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int ++ltq_vrx200_mdio_wr(struct mii_bus *bus, int addr, int regnum, u16 val) ++{ ++ struct ltq_mdio_access acc; ++ int ret; ++ ++ acc.val = 0; ++ acc.bits.mbusy = LTQ_MDIO_MBUSY_BUSY; ++ acc.bits.op = LTQ_MDIO_OP_WRITE; ++ acc.bits.phyad = addr; ++ acc.bits.regad = regnum; ++ ++ ret = ltq_mdio_poll(bus); ++ if (ret) ++ return ret; ++ ++ dbg_ltq_writel(<q_ethsw_mdio_pdi_regs->mdio_write, val); ++ dbg_ltq_writel(<q_ethsw_mdio_pdi_regs->mdio_ctrl, acc.val); ++ ++ return 0; ++} ++ ++static int ++ltq_vrx200_mdio_rd(struct mii_bus *bus, int addr, int regnum) ++{ ++ struct ltq_mdio_access acc; ++ int ret; ++ ++ acc.val = 0; ++ acc.bits.mbusy = LTQ_MDIO_MBUSY_BUSY; ++ acc.bits.op = LTQ_MDIO_OP_READ; ++ acc.bits.phyad = addr; ++ acc.bits.regad = regnum; ++ ++ ret = ltq_mdio_poll(bus); ++ if (ret) ++ goto timeout; ++ ++ dbg_ltq_writel(<q_ethsw_mdio_pdi_regs->mdio_ctrl, acc.val); ++ ++ ret = ltq_mdio_poll(bus); ++ if (ret) ++ goto timeout; ++ ++ ret = ltq_r32(<q_ethsw_mdio_pdi_regs->mdio_read); ++ ++ return ret; ++timeout: ++ return -1; ++} ++ ++static void ++ltq_vrx200_mdio_link(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ltq_eth_gmac_update(priv->phydev, 0); ++} ++ ++static int ++ltq_vrx200_mdio_probe(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ struct phy_device *phydev = NULL; ++ int val; ++ ++ phydev = priv->mii_bus->phy_map[0]; ++ ++ if (!phydev) { ++ netdev_err(dev, "no PHY found\n"); ++ return -ENODEV; ++ } ++ ++ phydev = phy_connect(dev, dev_name(&phydev->dev), <q_vrx200_mdio_link, ++ 0, 0); ++ ++ if (IS_ERR(phydev)) { ++ netdev_err(dev, "Could not attach to PHY\n"); ++ return PTR_ERR(phydev); ++ } ++ ++ phydev->supported &= (SUPPORTED_10baseT_Half ++ | SUPPORTED_10baseT_Full ++ | SUPPORTED_100baseT_Half ++ | SUPPORTED_100baseT_Full ++ | SUPPORTED_1000baseT_Half ++ | SUPPORTED_1000baseT_Full ++ | SUPPORTED_Autoneg ++ | SUPPORTED_MII ++ | SUPPORTED_TP); ++ phydev->advertising = phydev->supported; ++ priv->phydev = phydev; ++ ++ pr_info("%s: attached PHY [%s] (phy_addr=%s, irq=%d)\n", ++ dev->name, phydev->drv->name, ++ dev_name(&phydev->dev), phydev->irq); ++ ++ val = ltq_vrx200_mdio_rd(priv->mii_bus, MDIO_DEVAD_NONE, MII_CTRL1000); ++ val |= ADVERTIZE_MPD; ++ ltq_vrx200_mdio_wr(priv->mii_bus, MDIO_DEVAD_NONE, MII_CTRL1000, val); ++ ltq_vrx200_mdio_wr(priv->mii_bus, 0, 0, 0x1040); ++ ++ phy_start_aneg(phydev); ++ ++ return 0; ++} ++ ++static int ++ltq_vrx200_mdio_init(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int i; ++ int err; ++ ++ priv->mii_bus = mdiobus_alloc(); ++ if (!priv->mii_bus) { ++ netdev_err(dev, "failed to allocate mii bus\n"); ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ priv->mii_bus->priv = dev; ++ priv->mii_bus->read = ltq_vrx200_mdio_rd; ++ priv->mii_bus->write = ltq_vrx200_mdio_wr; ++ priv->mii_bus->name = "ltq_mii"; ++ snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%x", 0); ++ priv->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); ++ if (!priv->mii_bus->irq) { ++ err = -ENOMEM; ++ goto err_out_free_mdiobus; ++ } ++ ++ for (i = 0; i < PHY_MAX_ADDR; ++i) ++ priv->mii_bus->irq[i] = PHY_POLL; ++ ++ if (mdiobus_register(priv->mii_bus)) { ++ err = -ENXIO; ++ goto err_out_free_mdio_irq; ++ } ++ ++ if (ltq_vrx200_mdio_probe(dev)) { ++ err = -ENXIO; ++ goto err_out_unregister_bus; ++ } ++ return 0; ++ ++err_out_unregister_bus: ++ mdiobus_unregister(priv->mii_bus); ++err_out_free_mdio_irq: ++ kfree(priv->mii_bus->irq); ++err_out_free_mdiobus: ++ mdiobus_free(priv->mii_bus); ++err_out: ++ return err; ++} ++ ++static void ++ltq_vrx200_mdio_cleanup(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ phy_disconnect(priv->phydev); ++ mdiobus_unregister(priv->mii_bus); ++ kfree(priv->mii_bus->irq); ++ mdiobus_free(priv->mii_bus); ++} ++ ++void phy_dump(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int i; ++ for (i = 0; i < 0x1F; i++) { ++ unsigned int val = ltq_vrx200_mdio_rd(priv->mii_bus, 0, i); ++ printk("%d %4X\n", i, val); ++ } ++} ++ ++static int ++ltq_vrx200_open(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int i; ++ unsigned long flags; ++ ++ for (i = 0; i < MAX_DMA_CHAN; i++) { ++ struct ltq_vrx200_chan *ch = &priv->ch[i]; ++ ++ if (!IS_TX(i) && (!IS_RX(i))) ++ continue; ++ napi_enable(&ch->napi); ++ spin_lock_irqsave(&priv->lock, flags); ++ ltq_dma_open(&ch->dma); ++ spin_unlock_irqrestore(&priv->lock, flags); ++ } ++ if (priv->phydev) { ++ phy_start(priv->phydev); ++ phy_dump(dev); ++ } ++ netif_tx_start_all_queues(dev); ++ return 0; ++} ++ ++static int ++ltq_vrx200_stop(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ int i; ++ unsigned long flags; ++ ++ netif_tx_stop_all_queues(dev); ++ if (priv->phydev) ++ phy_stop(priv->phydev); ++ for (i = 0; i < MAX_DMA_CHAN; i++) { ++ struct ltq_vrx200_chan *ch = &priv->ch[i]; ++ ++ if (!IS_RX(i) && !IS_TX(i)) ++ continue; ++ napi_disable(&ch->napi); ++ spin_lock_irqsave(&priv->lock, flags); ++ ltq_dma_close(&ch->dma); ++ spin_unlock_irqrestore(&priv->lock, flags); ++ } ++ return 0; ++} ++ ++static int ++ltq_vrx200_tx(struct sk_buff *skb, struct net_device *dev) ++{ ++ int queue = skb_get_queue_mapping(skb); ++ struct netdev_queue *txq = netdev_get_tx_queue(dev, queue); ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ struct ltq_vrx200_chan *ch = &priv->ch[(queue << 1) | 1]; ++ struct ltq_dma_desc *desc = &ch->dma.desc_base[ch->dma.desc]; ++ unsigned long flags; ++ u32 byte_offset; ++ int len; ++ ++ len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; ++ ++ if ((desc->ctl & (LTQ_DMA_OWN | LTQ_DMA_C)) || ch->skb[ch->dma.desc]) { ++ netdev_err(dev, "tx ring full\n"); ++ netif_tx_stop_queue(txq); ++ return NETDEV_TX_BUSY; ++ } ++ ++ /* dma needs to start on a 16 byte aligned address */ ++ byte_offset = CPHYSADDR(skb->data) % 16; ++ ch->skb[ch->dma.desc] = skb; ++ ++ dev->trans_start = jiffies; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ desc->addr = ((unsigned int) dma_map_single(NULL, skb->data, len, ++ DMA_TO_DEVICE)) - byte_offset; ++ wmb(); ++ desc->ctl = LTQ_DMA_OWN | LTQ_DMA_SOP | LTQ_DMA_EOP | ++ LTQ_DMA_TX_OFFSET(byte_offset) | (len & LTQ_DMA_SIZE_MASK); ++ ch->dma.desc++; ++ ch->dma.desc %= LTQ_DESC_NUM; ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ if (ch->dma.desc_base[ch->dma.desc].ctl & LTQ_DMA_OWN) ++ netif_tx_stop_queue(txq); ++ ++ return NETDEV_TX_OK; ++} ++ ++static int ++ltq_vrx200_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ ++ /* TODO: mii-toll reports "No MII transceiver present!." ?!*/ ++ return phy_mii_ioctl(priv->phydev, rq, cmd); ++} ++ ++static u16 ++ltq_vrx200_select_queue(struct net_device *dev, struct sk_buff *skb) ++{ ++ /* we are currently only using the first queue */ ++ return 0; ++} ++ ++static int ++ltq_vrx200_init(struct net_device *dev) ++{ ++ struct ltq_vrx200_priv *priv = netdev_priv(dev); ++ struct sockaddr mac; ++ int err; ++ ++ ether_setup(dev); ++ dev->watchdog_timeo = 10 * HZ; ++ ++ err = ltq_vrx200_hw_init(dev); ++ if (err) ++ goto err_hw; ++ ++ memcpy(&mac, &priv->pldata->mac, sizeof(struct sockaddr)); ++ if (!is_valid_ether_addr(mac.sa_data)) { ++ pr_warn("vrx200: invalid MAC, using random\n"); ++ random_ether_addr(mac.sa_data); ++ } ++ eth_mac_addr(dev, &mac); ++ ++ if (!ltq_vrx200_mdio_init(dev)) ++ dev->ethtool_ops = <q_vrx200_ethtool_ops; ++ else ++ pr_warn("vrx200: mdio probe failed\n");; ++ return 0; ++ ++err_hw: ++ ltq_vrx200_hw_exit(dev); ++ return err; ++} ++ ++static void ++ltq_vrx200_tx_timeout(struct net_device *dev) ++{ ++ int err; ++ ++ ltq_vrx200_hw_exit(dev); ++ err = ltq_vrx200_hw_init(dev); ++ if (err) ++ goto err_hw; ++ dev->trans_start = jiffies; ++ netif_wake_queue(dev); ++ return; ++ ++err_hw: ++ ltq_vrx200_hw_exit(dev); ++ netdev_err(dev, "failed to restart vrx200 after TX timeout\n"); ++} ++ ++static const struct net_device_ops ltq_eth_netdev_ops = { ++ .ndo_open = ltq_vrx200_open, ++ .ndo_stop = ltq_vrx200_stop, ++ .ndo_start_xmit = ltq_vrx200_tx, ++ .ndo_change_mtu = eth_change_mtu, ++ .ndo_do_ioctl = ltq_vrx200_ioctl, ++ .ndo_set_mac_address = eth_mac_addr, ++ .ndo_validate_addr = eth_validate_addr, ++ .ndo_select_queue = ltq_vrx200_select_queue, ++ .ndo_init = ltq_vrx200_init, ++ .ndo_tx_timeout = ltq_vrx200_tx_timeout, ++}; ++ ++static int __devinit ++ltq_vrx200_probe(struct platform_device *pdev) ++{ ++ struct net_device *dev; ++ struct ltq_vrx200_priv *priv; ++ struct resource *res; ++ int err; ++ int i; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "failed to get vrx200 resource\n"); ++ err = -ENOENT; ++ goto err_out; ++ } ++ ++ res = devm_request_mem_region(&pdev->dev, res->start, ++ resource_size(res), dev_name(&pdev->dev)); ++ if (!res) { ++ dev_err(&pdev->dev, "failed to request vrx200 resource\n"); ++ err = -EBUSY; ++ goto err_out; ++ } ++ ++ ltq_vrx200_membase = devm_ioremap_nocache(&pdev->dev, ++ res->start, resource_size(res)); ++ if (!ltq_vrx200_membase) { ++ dev_err(&pdev->dev, "failed to remap vrx200 engine %d\n", ++ pdev->id); ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ if (ltq_gpio_request(&pdev->dev, 42, 2, 1, "MDIO") || ++ ltq_gpio_request(&pdev->dev, 43, 2, 1, "MDC")) { ++ dev_err(&pdev->dev, "failed to request MDIO gpios\n"); ++ err = -EBUSY; ++ goto err_out; ++ } ++ ++ dev = alloc_etherdev_mq(sizeof(struct ltq_vrx200_priv), 4); ++ strcpy(dev->name, "eth%d"); ++ dev->netdev_ops = <q_eth_netdev_ops; ++ priv = netdev_priv(dev); ++ priv->res = res; ++ priv->pldata = dev_get_platdata(&pdev->dev); ++ priv->netdev = dev; ++ ++ priv->clk_ppe = clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->clk_ppe)) ++ return PTR_ERR(priv->clk_ppe); ++ ++ spin_lock_init(&priv->lock); ++ ++ for (i = 0; i < MAX_DMA_CHAN; i++) { ++ if (IS_TX(i)) ++ netif_napi_add(dev, &priv->ch[i].napi, ++ ltq_vrx200_poll_tx, 8); ++ else if (IS_RX(i)) ++ netif_napi_add(dev, &priv->ch[i].napi, ++ ltq_vrx200_poll_rx, 32); ++ priv->ch[i].netdev = dev; ++ } ++ ++ err = register_netdev(dev); ++ if (err) ++ goto err_free; ++ ++ platform_set_drvdata(pdev, dev); ++ return 0; ++ ++err_free: ++ kfree(dev); ++err_out: ++ return err; ++} ++ ++static int __devexit ++ltq_vrx200_remove(struct platform_device *pdev) ++{ ++ struct net_device *dev = platform_get_drvdata(pdev); ++ ++ if (dev) { ++ netif_tx_stop_all_queues(dev); ++ ltq_vrx200_hw_exit(dev); ++ ltq_vrx200_mdio_cleanup(dev); ++ unregister_netdev(dev); ++ } ++ return 0; ++} ++ ++static struct platform_driver ltq_mii_driver = { ++ .probe = ltq_vrx200_probe, ++ .remove = __devexit_p(ltq_vrx200_remove), ++ .driver = { ++ .name = "ltq_vrx200", ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++module_platform_driver(ltq_mii_driver); ++ ++MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); ++MODULE_DESCRIPTION("Lantiq SoC ETOP"); ++MODULE_LICENSE("GPL"); +-- +1.7.9.1 + |