diff options
author | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
---|---|---|
committer | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
commit | 5c105d9f3fd086aff195d3849dcf847d6b0bd927 (patch) | |
tree | 1229a11f725bfa58aa7c57a76898553bb5f6654a /target/linux/lantiq/files/drivers/net/ethernet | |
download | openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.tar.gz openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.zip |
branch Attitude Adjustment
git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/lantiq/files/drivers/net/ethernet')
3 files changed, 2340 insertions, 0 deletions
diff --git a/target/linux/lantiq/files/drivers/net/ethernet/lantiq_vrx200.c b/target/linux/lantiq/files/drivers/net/ethernet/lantiq_vrx200.c new file mode 100644 index 000000000..d79d3803c --- /dev/null +++ b/target/linux/lantiq/files/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"); diff --git a/target/linux/lantiq/files/drivers/net/ethernet/svip_eth.c b/target/linux/lantiq/files/drivers/net/ethernet/svip_eth.c new file mode 100644 index 000000000..1e25795a2 --- /dev/null +++ b/target/linux/lantiq/files/drivers/net/ethernet/svip_eth.c @@ -0,0 +1,636 @@ +/************************************************************************ + * + * Copyright (c) 2005 + * Infineon Technologies AG + * St. Martin Strasse 53; 81669 Muenchen; Germany + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + ************************************************************************/ + +#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/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/module.h> +#include <linux/delay.h> +#include <asm/checksum.h> + +#if 1 /** TODO: MOVE TO APPROPRIATE PLACE */ + +#define ETHERNET_PACKET_DMA_BUFFER_SIZE 0x600 +#define REV_MII_MODE 2 + +#endif + +#define DRV_NAME "ifxmips_mii0" + +#include <lantiq_soc.h> +#include <svip_dma.h> + +#ifdef CONFIG_DEBUG_MINI_BOOT +#define IKOS_MINI_BOOT +#endif + +/* debugging */ +#undef INCAIP2_SW_DUMP + +#define INCAIP2_SW_EMSG(fmt,args...) printk("%s: " fmt, __FUNCTION__ , ##args) + +#define INCAIP2_SW_CHIP_NO 1 +#define INCAIP2_SW_CHIP_ID 0 +#define INCAIP2_SW_DEVICE_NO 1 + +#ifdef INCAIP2_SW_DEBUG_MSG +#define INCAIP2_SW_DMSG(fmt,args...) printk("%s: " fmt, __FUNCTION__ , ##args) +#else +#define INCAIP2_SW_DMSG(fmt,args...) +#endif + +/************************** Module Parameters *****************************/ +static char *mode = "bridge"; +module_param(mode, charp, 0000); +MODULE_PARM_DESC(mode, "<description>"); + +#ifdef HAVE_TX_TIMEOUT +static int timeout = 10*HZ; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Transmission watchdog timeout in seconds>"); +#endif + +#ifdef IKOS_MINI_BOOT +#ifdef CONFIG_INCAIP2 +extern s32 incaip2_sw_to_mbx(struct sk_buff* skb); +#endif +extern s32 svip_sw_to_mbx(struct sk_buff* skb); +#endif + +struct svip_mii_priv { + struct net_device_stats stats; + struct dma_device_info *dma_device; + struct sk_buff *skb; +}; + +static struct net_device *svip_mii0_dev; +static unsigned char mac_addr[MAX_ADDR_LEN]; +static unsigned char my_ethaddr[MAX_ADDR_LEN]; + +/** + * Initialize MAC address. + * This function copies the ethernet address from kernel command line. + * + * \param line Pointer to parameter + * \return 0 OK + * \ingroup Internal + */ +static int __init svip_eth_ethaddr_setup(char *line) +{ + char *ep; + int i; + + memset(my_ethaddr, 0, MAX_ADDR_LEN); + /* there should really be routines to do this stuff */ + for (i = 0; i < 6; i++) + { + my_ethaddr[i] = line ? simple_strtoul(line, &ep, 16) : 0; + if (line) + line = (*ep) ? ep+1 : ep; + } + INCAIP2_SW_DMSG("mac address %2x-%2x-%2x-%2x-%2x-%2x \n" + ,my_ethaddr[0] + ,my_ethaddr[1] + ,my_ethaddr[2] + ,my_ethaddr[3] + ,my_ethaddr[4] + ,my_ethaddr[5]); + return 0; +} +__setup("ethaddr=", svip_eth_ethaddr_setup); + + +/** + * Open RX DMA channels. + * This function opens all DMA rx channels. + * + * \param dma_dev pointer to DMA device information + * \ingroup Internal + */ +static void svip_eth_open_rx_dma(struct dma_device_info *dma_dev) +{ + int i; + + for(i=0; i<dma_dev->num_rx_chan; i++) + { + dma_dev->rx_chan[i]->open(dma_dev->rx_chan[i]); + } +} + + +/** + * Open TX DMA channels. + * This function opens all DMA tx channels. + * + * \param dev pointer to net device structure that comprises + * DMA device information pointed to by it's priv field. + * \ingroup Internal + */ +static void svip_eth_open_tx_dma(struct dma_device_info *dma_dev) +{ + int i; + + for (i=0; i<dma_dev->num_tx_chan; i++) + { + dma_dev->tx_chan[i]->open(dma_dev->tx_chan[i]); + } +} + + +#ifdef CONFIG_NET_HW_FLOWCONTROL +/** + * Enable receiving DMA. + * This function enables the receiving DMA channel. + * + * \param dev pointer to net device structure that comprises + * DMA device information pointed to by it's priv field. + * \ingroup Internal + */ +void svip_eth_xon(struct net_device *dev) +{ + struct switch_priv *sw_dev = (struct switch_priv *)dev->priv; + struct dma_device_info* dma_dev = + (struct dma_device_info *)sw_dev->dma_device; + unsigned long flag; + + local_irq_save(flag); + + INCAIP2_SW_DMSG("wakeup\n"); + svip_eth_open_rx_dma(dma_dev); + + local_irq_restore(flag); +} +#endif /* CONFIG_NET_HW_FLOWCONTROL */ + + +/** + * Open network device. + * This functions opens the network device and starts the interface queue. + * + * \param dev Device structure for Ethernet device + * \return 0 OK, device opened + * \return -1 Error, registering DMA device + * \ingroup API + */ +int svip_mii_open(struct net_device *dev) +{ + struct svip_mii_priv *priv = netdev_priv(dev); + struct dma_device_info *dma_dev = priv->dma_device; + + svip_eth_open_rx_dma(dma_dev); + svip_eth_open_tx_dma(dma_dev); + + netif_start_queue(dev); + return 0; +} + + +/** + * Close network device. + * This functions closes the network device, which will also stop the interface + * queue. + * + * \param dev Device structure for Ethernet device + * \return 0 OK, device closed (cannot fail) + * \ingroup API + */ +int svip_mii_release(struct net_device *dev) +{ + struct svip_mii_priv *priv = netdev_priv(dev); + struct dma_device_info *dma_dev = priv->dma_device; + int i; + + for (i = 0; i < dma_dev->max_rx_chan_num; i++) + dma_dev->rx_chan[i]->close(dma_dev->rx_chan[i]); + netif_stop_queue(dev); + return 0; +} + + +/** + * Read data from DMA device. + * This function reads data from the DMA device. The function is called by + * the switch/DMA pseudo interrupt handler dma_intr_handler on occurence of + * a DMA receive interrupt. + * + * \param dev Pointer to network device structure + * \param dma_dev Pointer to dma device structure + * \return OK In case of successful data reception from dma + * -EIO Incorrect opt pointer provided by device + * \ingroup Internal + */ +int svip_mii_hw_receive(struct net_device *dev, struct dma_device_info *dma_dev) +{ + struct svip_mii_priv *priv = netdev_priv(dev); + unsigned char *buf = NULL; + struct sk_buff *skb = NULL; + int len = 0; + + len = dma_device_read(dma_dev, &buf, (void **)&skb); + + if (len >= ETHERNET_PACKET_DMA_BUFFER_SIZE) { + printk(KERN_INFO DRV_NAME ": packet too large %d\n", len); + goto mii_hw_receive_err_exit; + } + + if (skb == NULL) { + printk(KERN_INFO DRV_NAME ": cannot restore pointer\n"); + goto mii_hw_receive_err_exit; + } + + if (len > (skb->end - skb->tail)) { + printk(KERN_INFO DRV_NAME ": BUG, len:%d end:%p tail:%p\n", + len, skb->end, skb->tail); + goto mii_hw_receive_err_exit; + } + + skb_put(skb, len); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + + priv->stats.rx_packets++; + priv->stats.rx_bytes += len; + return 0; + +mii_hw_receive_err_exit: + if (len == 0) { + if (skb) + dev_kfree_skb_any(skb); + priv->stats.rx_errors++; + priv->stats.rx_dropped++; + return -EIO; + } else { + return len; + } +} + + +/** + * Write data to Ethernet switch. + * This function writes the data comprised in skb structure via DMA to the + * Ethernet Switch. It is installed as the switch driver's hard_start_xmit + * method. + * + * \param skb Pointer to socket buffer structure that contains the data + * to be sent + * \param dev Pointer to network device structure which is used for + * data transmission + * \return 1 Transmission error + * \return 0 OK, successful data transmission + * \ingroup API + */ +static int svip_mii_hw_tx(char *buf, int len, struct net_device *dev) +{ + int ret = 0; + struct svip_mii_priv *priv = netdev_priv(dev); + struct dma_device_info *dma_dev = priv->dma_device; + ret = dma_device_write(dma_dev, buf, len, priv->skb); + return ret; +} + +static int svip_mii_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len; + char *data; + struct svip_mii_priv *priv = netdev_priv(dev); + struct dma_device_info *dma_dev = priv->dma_device; + + len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; + data = skb->data; + priv->skb = skb; + dev->trans_start = jiffies; + /* TODO: we got more than 1 dma channel, + so we should do something intelligent here to select one */ + dma_dev->current_tx_chan = 0; + + wmb(); + + if (svip_mii_hw_tx(data, len, dev) != len) { + dev_kfree_skb_any(skb); + priv->stats.tx_errors++; + priv->stats.tx_dropped++; + } else { + priv->stats.tx_packets++; + priv->stats.tx_bytes += len; + } + + return 0; +} + + +/** + * Transmission timeout callback. + * This functions is called when a trasmission timeout occurs. It will wake up + * the interface queue again. + * + * \param dev Device structure for Ethernet device + * \ingroup API + */ +void svip_mii_tx_timeout(struct net_device *dev) +{ + int i; + struct svip_mii_priv *priv = netdev_priv(dev); + + priv->stats.tx_errors++; + for (i = 0; i < priv->dma_device->max_tx_chan_num; i++) + priv->dma_device->tx_chan[i]->disable_irq(priv->dma_device->tx_chan[i]); + netif_wake_queue(dev); + return; +} + + +/** + * Get device statistics. + * This functions returns the device statistics, stored in the device structure. + * + * \param dev Device structure for Ethernet device + * \return stats Pointer to statistics structure + * \ingroup API + */ +static struct net_device_stats *svip_get_stats(struct net_device *dev) +{ + struct svip_mii_priv *priv = netdev_priv(dev); + return &priv->stats; +} + + +/** + * Pseudo Interrupt handler for DMA. + * This function processes DMA interrupts notified to the switch device driver. + * The function is installed at the DMA core as interrupt handler for the + * switch dma device. + * It handles the following DMA interrupts: + * passes received data to the upper layer in case of rx interrupt, + * In case of a dma receive interrupt the received data is passed to the upper layer. + * In case of a transmit buffer full interrupt the transmit queue is stopped. + * In case of a transmission complete interrupt the transmit queue is restarted. + * + * \param dma_dev pointer to dma device structure + * \param status type of interrupt being notified (RCV_INT: dma receive + * interrupt, TX_BUF_FULL_INT: transmit buffer full interrupt, + * TRANSMIT_CPT_INT: transmission complete interrupt) + * \return OK In case of successful data reception from dma + * \ingroup Internal + */ +int dma_intr_handler(struct dma_device_info *dma_dev, int status) +{ + int i; + + switch (status) { + case RCV_INT: + svip_mii_hw_receive(svip_mii0_dev, dma_dev); + break; + + case TX_BUF_FULL_INT: + printk(KERN_INFO DRV_NAME ": tx buffer full\n"); + netif_stop_queue(svip_mii0_dev); + for (i = 0; i < dma_dev->max_tx_chan_num; i++) { + if ((dma_dev->tx_chan[i])->control == LTQ_DMA_CH_ON) + dma_dev->tx_chan[i]->enable_irq(dma_dev->tx_chan[i]); + } + break; + + case TRANSMIT_CPT_INT: + +#if 0 + for (i = 0; i < dma_dev->max_tx_chan_num; i++) +#if 0 + dma_dev->tx_chan[i]->disable_irq(dma_dev->tx_chan[i]); +#else + dma_dev->tx_chan[i]->disable_irq(dma_dev->tx_chan[i], (char *)__FUNCTION__); +#endif + netif_wake_queue(svip_mii0_dev); +#endif + break; + } + + return 0; +} + + +/** + * Allocates buffer sufficient for Ethernet Frame. + * This function is installed as DMA callback function to be called on DMA + * receive interrupt. + * + * \param len Unused + * \param *byte_offset Pointer to byte offset + * \param **opt pointer to skb structure + * \return NULL In case of buffer allocation fails + * buffer Pointer to allocated memory + * \ingroup Internal + */ +unsigned char *svip_etop_dma_buffer_alloc(int len, int *byte_offset, void **opt) +{ + unsigned char *buffer = NULL; + struct sk_buff *skb = NULL; + + skb = dev_alloc_skb(ETHERNET_PACKET_DMA_BUFFER_SIZE); + if (skb == NULL) + return NULL; + + buffer = (unsigned char *)(skb->data); + skb_reserve(skb, 2); + *(int *)opt = (int)skb; + *byte_offset = 2; + + return buffer; +} + + +/** + * Free DMA buffer. + * This function frees a buffer, which can be either a data buffer or an + * skb structure. + * + * \param *dataptr Pointer to data buffer + * \param *opt Pointer to skb structure + * \return 0 OK + * \ingroup Internal + */ +void svip_etop_dma_buffer_free(unsigned char *dataptr, void *opt) +{ + struct sk_buff *skb = NULL; + + if (opt == NULL) { + kfree(dataptr); + } else { + skb = (struct sk_buff *)opt; + dev_kfree_skb_any(skb); + } +} + +static int svip_mii_dev_init(struct net_device *dev); + +static const struct net_device_ops svip_eth_netdev_ops = { + .ndo_init = svip_mii_dev_init, + .ndo_open = svip_mii_open, + .ndo_stop = svip_mii_release, + .ndo_start_xmit = svip_mii_tx, + .ndo_get_stats = svip_get_stats, + .ndo_tx_timeout = svip_mii_tx_timeout, +}; + +//#include <linux/device.h> + +/** + * Initialize switch driver. + * This functions initializes the switch driver structures and registers the + * Ethernet device. + * + * \param dev Device structure for Ethernet device + * \return 0 OK + * \return ENOMEM No memory for structures available + * \return -1 Error during DMA init or Ethernet address configuration. + * \ingroup API + */ +static int svip_mii_dev_init(struct net_device *dev) +{ + int i; + struct svip_mii_priv *priv = netdev_priv(dev); + + + ether_setup(dev); + printk(KERN_INFO DRV_NAME ": %s is up\n", dev->name); + dev->watchdog_timeo = 10 * HZ; + memset(priv, 0, sizeof(*priv)); + priv->dma_device = dma_device_reserve("SW"); + if (!priv->dma_device) { + BUG(); + return -ENODEV; + } + priv->dma_device->buffer_alloc = svip_etop_dma_buffer_alloc; + priv->dma_device->buffer_free = svip_etop_dma_buffer_free; + priv->dma_device->intr_handler = dma_intr_handler; + + for (i = 0; i < priv->dma_device->max_rx_chan_num; i++) + priv->dma_device->rx_chan[i]->packet_size = + ETHERNET_PACKET_DMA_BUFFER_SIZE; + + for (i = 0; i < priv->dma_device->max_tx_chan_num; i++) { + priv->dma_device->tx_chan[i]->tx_weight=DEFAULT_SW_CHANNEL_WEIGHT; + priv->dma_device->tx_chan[i]->packet_size = + ETHERNET_PACKET_DMA_BUFFER_SIZE; + } + + dma_device_register(priv->dma_device); + + printk(KERN_INFO DRV_NAME ": using mac="); + + for (i = 0; i < 6; i++) { + dev->dev_addr[i] = mac_addr[i]; + printk("%02X%c", dev->dev_addr[i], (i == 5) ? ('\n') : (':')); + } + + return 0; +} + +static void svip_mii_chip_init(int mode) +{ +} + +static int svip_mii_probe(struct platform_device *dev) +{ + int result = 0; + unsigned char *mac = (unsigned char *)dev->dev.platform_data; + svip_mii0_dev = alloc_etherdev(sizeof(struct svip_mii_priv)); + svip_mii0_dev->netdev_ops = &svip_eth_netdev_ops; + memcpy(mac_addr, mac, 6); + strcpy(svip_mii0_dev->name, "eth%d"); + svip_mii_chip_init(REV_MII_MODE); + result = register_netdev(svip_mii0_dev); + if (result) { + printk(KERN_INFO DRV_NAME + ": error %i registering device \"%s\"\n", + result, svip_mii0_dev->name); + goto out; + } + printk(KERN_INFO DRV_NAME ": driver loaded!\n"); + +out: + return result; +} + +static int svip_mii_remove(struct platform_device *dev) +{ + struct svip_mii_priv *priv = netdev_priv(svip_mii0_dev); + + printk(KERN_INFO DRV_NAME ": cleanup\n"); + + dma_device_unregister(priv->dma_device); + dma_device_release(priv->dma_device); + kfree(priv->dma_device); + unregister_netdev(svip_mii0_dev); + free_netdev(svip_mii0_dev); + return 0; +} + + +static struct platform_driver svip_mii_driver = { + .probe = svip_mii_probe, + .remove = svip_mii_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + + +/** + * Initialize switch driver as module. + * This functions initializes the switch driver structures and registers the + * Ethernet device for module usage. + * + * \return 0 OK + * \return ENODEV An error occured during initialization + * \ingroup API + */ +int __init svip_mii_init(void) +{ + int ret = platform_driver_register(&svip_mii_driver); + if (ret) + printk(KERN_INFO DRV_NAME + ": Error registering platfom driver!\n"); + return ret; +} + + +/** + * Remove driver module. + * This functions removes the driver and unregisters all devices. + * + * \ingroup API + */ +static void __exit svip_mii_cleanup(void) +{ + platform_driver_unregister(&svip_mii_driver); +} + +module_init(svip_mii_init); +module_exit(svip_mii_cleanup); + +MODULE_LICENSE("GPL"); diff --git a/target/linux/lantiq/files/drivers/net/ethernet/svip_virtual_eth.c b/target/linux/lantiq/files/drivers/net/ethernet/svip_virtual_eth.c new file mode 100644 index 000000000..6de0cfab8 --- /dev/null +++ b/target/linux/lantiq/files/drivers/net/ethernet/svip_virtual_eth.c @@ -0,0 +1,346 @@ +/****************************************************************************** + + Copyright (c) 2007 + Infineon Technologies AG + Am Campeon 1-12; 81726 Munich, Germany + + THE DELIVERY OF THIS SOFTWARE AS WELL AS THE HEREBY GRANTED NON-EXCLUSIVE, + WORLDWIDE LICENSE TO USE, COPY, MODIFY, DISTRIBUTE AND SUBLICENSE THIS + SOFTWARE IS FREE OF CHARGE. + + THE LICENSED SOFTWARE IS PROVIDED "AS IS" AND INFINEON EXPRESSLY DISCLAIMS + ALL REPRESENTATIONS AND WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING + WITHOUT LIMITATION, WARRANTIES OR REPRESENTATIONS OF WORKMANSHIP, + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, THAT THE + OPERATING OF THE LICENSED SOFTWARE WILL BE ERROR FREE OR FREE OF ANY THIRD + PARTY CLAIMS, INCLUDING WITHOUT LIMITATION CLAIMS OF THIRD PARTY INTELLECTUAL + PROPERTY INFRINGEMENT. + + EXCEPT FOR ANY LIABILITY DUE TO WILFUL ACTS OR GROSS NEGLIGENCE AND EXCEPT + FOR ANY PERSONAL INJURY INFINEON SHALL IN NO EVENT BE LIABLE FOR ANY CLAIM + OR DAMAGES OF ANY KIND, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + **************************************************************************** +Module : svip_virtual_eth.c + +Description : This file contains network driver implementation for a +Virtual Ethernet interface. The Virtual Ethernet interface +is part of Infineon's VINETIC-SVIP Linux BSP. + *******************************************************************************/ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/etherdevice.h> +#include <linux/init.h> + +#define SVIP_VETH_VER_STR "3.0" +#define SVIP_VETH_INFO_STR \ + "@(#)SVIP virtual ethernet interface, version " SVIP_VETH_VER_STR + +/****************************************************************************** + * Local define/macro definitions + ******************************************************************************/ +struct svip_ve_priv +{ + struct net_device_stats stats; +}; + +/****************************************************************************** + * Global function declarations + ******************************************************************************/ +int svip_ve_rx(struct sk_buff *skb); + +/****************************************************************************** + * Local variable declarations + ******************************************************************************/ +static struct net_device *svip_ve_dev; +static int watchdog_timeout = 10*HZ; +static int (*svip_ve_mps_xmit)(struct sk_buff *skb) = NULL; + + +/****************************************************************************** + * Global function declarations + ******************************************************************************/ + +/** + * Called by MPS driver to register a transmit routine called for each outgoing + * VoFW0 message. + * + * \param mps_xmit pointer to transmit routine + * + * \return none + * + * \ingroup Internal + */ +void register_mps_xmit_routine(int (*mps_xmit)(struct sk_buff *skb)) +{ + svip_ve_mps_xmit = mps_xmit; +} +EXPORT_SYMBOL(register_mps_xmit_routine); + +/** + * Returns a pointer to the routine used to deliver an incoming packet/message + * from the MPS mailbox to the networking layer. This routine is called by MPS + * driver during initialisation time. + * + * \param skb pointer to incoming socket buffer + * + * \return svip_ve_rx pointer to incoming messages delivering routine + * + * \ingroup Internal + */ +int (*register_mps_recv_routine(void)) (struct sk_buff *skb) +{ + return svip_ve_rx; +} + +/** + * Used to deliver outgoing packets to VoFW0 module through the MPS driver. + * Upon loading/initialisation the MPS driver is registering a transmitting + * routine, which is called here to deliver the packet to the VoFW0 module. + * + * \param skb pointer to skb containing outgoing data + * \param dev pointer to this networking device's data + * + * \return 0 on success + * \return non-zero on error + * + * \ingroup Internal + */ +static int svip_ve_xmit(struct sk_buff *skb, struct net_device *dev) +{ + int err; + struct svip_ve_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &priv->stats; + + stats->tx_packets++; + stats->tx_bytes += skb->len; + + if (svip_ve_mps_xmit) + { + err = svip_ve_mps_xmit(skb); + if (err) + stats->tx_errors++; + dev->trans_start = jiffies; + return err; + } + else + printk(KERN_ERR "%s: MPS driver not registered, outgoing packet not delivered\n", dev->name); + + dev_kfree_skb(skb); + + return -1; +} + +/** + * Called by MPS driver upon receipt of a new message from VoFW0 module in + * the data inbox. The packet is pushed up the IP module for further processing. + * + * \param skb pointer to skb containing the incoming message + * + * \return 0 on success + * \return non-zero on error + * + * \ingroup Internal + */ +int svip_ve_rx(struct sk_buff *skb) +{ + int err; + struct svip_ve_priv *priv = netdev_priv(svip_ve_dev); + struct net_device_stats *stats = &priv->stats; + + skb->dev = svip_ve_dev; + skb->protocol = eth_type_trans(skb, svip_ve_dev); + + stats->rx_packets++; + stats->rx_bytes += skb->len; + + err = netif_rx(skb); + switch (err) + { + case NET_RX_SUCCESS: + return 0; + break; + case NET_RX_DROP: + default: + stats->rx_dropped++; + break; + } + + return 1; +} +EXPORT_SYMBOL(svip_ve_rx); + +/** + * Returns a pointer to the device's networking statistics data + * + * \param dev pointer to this networking device's data + * + * \return stats pointer to this network device's statistics data + * + * \ingroup Internal + */ +static struct net_device_stats *svip_ve_get_stats(struct net_device *dev) +{ + struct svip_ve_priv *priv = netdev_priv(dev); + + return &priv->stats; +} + +static void svip_ve_tx_timeout(struct net_device *dev) +{ + struct svip_ve_priv *priv = netdev_priv(dev); + + priv->stats.tx_errors++; + netif_wake_queue(dev); +} + +/** + * Device open routine. Called e.g. upon setting of an IP address using, + * 'ifconfig veth0 YYY.YYY.YYY.YYY netmask ZZZ.ZZZ.ZZZ.ZZZ' or + * 'ifconfig veth0 up' + * + * \param dev pointer to this network device's data + * + * \return 0 on success + * \return non-zero on error + * + * \ingroup Internal + */ +int svip_ve_open(struct net_device *dev) +{ + netif_start_queue(dev); + return 0; +} + +/** + * Device close routine. Called e.g. upon calling + * 'ifconfig veth0 down' + * + * \param dev pointer to this network device's data + * + * \return 0 on success + * \return non-zero on error + * + * \ingroup Internal + */ + +int svip_ve_release(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static int svip_ve_dev_init(struct net_device *dev); + +static const struct net_device_ops svip_virtual_eth_netdev_ops = { + .ndo_init = svip_ve_dev_init, + .ndo_open = svip_ve_open, + .ndo_stop = svip_ve_release, + .ndo_start_xmit = svip_ve_xmit, + .ndo_get_stats = svip_ve_get_stats, + .ndo_tx_timeout = svip_ve_tx_timeout, +}; + + +/** + * Device initialisation routine which registers device interface routines. + * It is called upon execution of 'register_netdev' routine. + * + * \param dev pointer to this network device's data + * + * \return 0 on success + * \return non-zero on error + * + * \ingroup Internal + */ +static int svip_ve_dev_init(struct net_device *dev) +{ + ether_setup(dev); /* assign some of the fields */ + + dev->watchdog_timeo = watchdog_timeout; + memset(netdev_priv(dev), 0, sizeof(struct svip_ve_priv)); + dev->flags |= IFF_NOARP|IFF_PROMISC; + dev->flags &= ~IFF_MULTICAST; + + /* dedicated MAC address to veth0, 00:03:19:00:15:80 */ + dev->dev_addr[0] = 0x00; + dev->dev_addr[1] = 0x03; + dev->dev_addr[2] = 0x19; + dev->dev_addr[3] = 0x00; + dev->dev_addr[4] = 0x15; + dev->dev_addr[5] = 0x80; + + return 0; +} + +static int svip_ve_probe(struct platform_device *dev) +{ + int result = 0; + + svip_ve_dev = alloc_etherdev(sizeof(struct svip_ve_priv)); + svip_ve_dev->netdev_ops = &svip_virtual_eth_netdev_ops; + + strcpy(svip_ve_dev->name, "veth%d"); + + result = register_netdev(svip_ve_dev); + if (result) + { + printk(KERN_INFO "error %i registering device \"%s\"\n", result, svip_ve_dev->name); + goto out; + } + + printk (KERN_INFO "%s, (c) 2009, Lantiq Deutschland GmbH\n", &SVIP_VETH_INFO_STR[4]); + +out: + return result; +} + +static int svip_ve_remove(struct platform_device *dev) +{ + unregister_netdev(svip_ve_dev); + free_netdev(svip_ve_dev); + + printk(KERN_INFO "%s removed\n", svip_ve_dev->name); + return 0; +} + +static struct platform_driver svip_ve_driver = { + .probe = svip_ve_probe, + .remove = svip_ve_remove, + .driver = { + .name = "ifxmips_svip_ve", + .owner = THIS_MODULE, + }, +}; + +/** + * Module/driver entry routine + */ +static int __init svip_ve_init_module(void) +{ + int ret; + + ret = platform_driver_register(&svip_ve_driver); + if (ret) + printk(KERN_INFO "SVIP: error(%d) registering virtual Ethernet driver!\n", ret); + return ret; +} + +/** + * Module exit routine (never called for statically linked driver) + */ +static void __exit svip_ve_cleanup_module(void) +{ + platform_driver_unregister(&svip_ve_driver); +} + +module_init(svip_ve_init_module); +module_exit(svip_ve_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("virtual ethernet driver for LANTIQ SVIP system"); + +EXPORT_SYMBOL(register_mps_recv_routine); |