OpenWrt – Rev 1

Subversion Repositories:
Rev:
From 7288414298b34dcda1216fee1fe38d05ea0027a2 Mon Sep 17 00:00:00 2001
From: Daniel Schwierzeck <daniel.schwierzeck@gmail.com>
Date: Mon, 17 Dec 2012 23:32:39 +0100
Subject: net: add driver for Lantiq XWAY ARX100 switch

Signed-off-by: Daniel Schwierzeck <daniel.schwierzeck@gmail.com>

--- a/arch/mips/include/asm/arch-arx100/config.h
+++ b/arch/mips/include/asm/arch-arx100/config.h
@@ -10,17 +10,21 @@
  * and drivers for this SoC:
  *
  * CONFIG_LTQ_SUPPORT_UART
- * - support the Danube ASC/UART interface and console
+ * - support the ARX100 ASC/UART interface and console
  *
  * CONFIG_LTQ_SUPPORT_NOR_FLASH
  * - support a parallel NOR flash via the CFI interface in flash bank 0
  *
  * CONFIG_LTQ_SUPPORT_ETHERNET
- * - support the Danube ETOP and MAC interface
+ * - support the ARX100 ETOP and MAC interface
  *
  * CONFIG_LTQ_SUPPORT_SPI_FLASH
- * - support the Danube SPI interface and serial flash drivers
+ * - support the ARX100 SPI interface and serial flash drivers
  * - specific SPI flash drivers must be configured separately
+ *
+ * CONFIG_LTQ_SUPPORT_SPL_SPI_FLASH
+ * - build a preloader that runs in the internal SRAM and loads
+ *   the U-Boot from SPI flash into RAM
  */
 
 #ifndef __ARX100_CONFIG_H__
--- /dev/null
+++ b/arch/mips/include/asm/arch-arx100/switch.h
@@ -0,0 +1,86 @@
+/*
+ *   Copyright (C) 2012-2013 Daniel Schwierzeck <daniel.schwierzeck@gmail.com>
+ *
+ *   SPDX-License-Identifier:  GPL-2.0+
+ */
+
+#ifndef __ARX100_SWITCH_H__
+#define __ARX100_SWITCH_H__
+
+struct ar9_switch_regs {
+       __be32  ps;             /* Port status*/
+       __be32  p0_ctl;         /* Port 0 control */
+       __be32  p1_ctl;         /* Port 1 control */
+       __be32  p2_ctl;         /* Port 2 control */
+       __be32  p0_vlan;        /* Port 0 VLAN control */
+       __be32  p1_vlan;        /* Port 1 VLAN control */
+       __be32  p2_vlan;        /* Port 2 VLAN control */
+       __be32  p0_inctl;       /* Port 0 ingress control */
+       __be32  p1_inctl;       /* Port 1 ingress control */
+       __be32  p2_inctl;       /* Port 2 ingress control */
+       u32     rsvd0[16];
+       __be32  sw_gctl0;       /* Switch global control 0 */
+       __be32  sw_gctl1;       /* Switch global control 1 */
+       __be32  arp;            /* ARP/RARP */
+       __be32  strm_ctl;       /* Storm control */
+       __be32  rgmii_ctl;      /* RGMII/GMII port control */
+       u32     rsvd1[4];
+       __be32  pmac_hd_ctl;    /* PMAC header control */
+       u32     rsvd2[15];
+       __be32  mdio_ctrl;      /* MDIO indirect access control */
+       __be32  mdio_data;      /* MDIO indirect read data */
+};
+
+#define BUILD_CHECK_AR9_REG(name, offset)      \
+       BUILD_BUG_ON(offsetof(struct ar9_switch_regs, name) != (offset))
+
+static inline void build_check_ar9_registers(void)
+{
+       BUILD_CHECK_AR9_REG(sw_gctl0, 0x68);
+       BUILD_CHECK_AR9_REG(rgmii_ctl, 0x78);
+       BUILD_CHECK_AR9_REG(pmac_hd_ctl, 0x8c);
+       BUILD_CHECK_AR9_REG(mdio_ctrl, 0xcc);
+       BUILD_CHECK_AR9_REG(mdio_data, 0xd0);
+}
+
+#define P0_CTL_FLP             (1 << 18)
+#define P0_CTL_FLD             (1 << 17)
+
+#define SW_GCTL0_SE            (1 << 31)
+
+#define RGMII_CTL_P1_SHIFT     10
+#define RGMII_CTL_P1_MASK      (0x3FF << RGMII_CTL_P1_SHIFT)
+#define RGMII_CTL_P0_MASK      0x3FF
+#define RGMII_CTL_P0IS_SHIFT   8
+#define RGMII_CTL_P0IS_RGMII   (0x0 << RGMII_CTL_P0IS_SHIFT)
+#define RGMII_CTL_P0IS_MII     (0x1 << RGMII_CTL_P0IS_SHIFT)
+#define RGMII_CTL_P0IS_REVMII  (0x2 << RGMII_CTL_P0IS_SHIFT)
+#define RGMII_CTL_P0IS_RMII    (0x3 << RGMII_CTL_P0IS_SHIFT)
+#define RGMII_CTL_P0RDLY_SHIFT 6
+#define RGMII_CTL_P0RDLY_0_0   (0x0 << RGMII_CTL_P0RDLY_SHIFT)
+#define RGMII_CTL_P0RDLY_1_5   (0x1 << RGMII_CTL_P0RDLY_SHIFT)
+#define RGMII_CTL_P0RDLY_1_75  (0x2 << RGMII_CTL_P0RDLY_SHIFT)
+#define RGMII_CTL_P0RDLY_2_0   (0x3 << RGMII_CTL_P0RDLY_SHIFT)
+#define RGMII_CTL_P0TDLY_SHIFT 4
+#define RGMII_CTL_P0TDLY_0_0   (0x0 << RGMII_CTL_P0TDLY_SHIFT)
+#define RGMII_CTL_P0TDLY_1_5   (0x1 << RGMII_CTL_P0TDLY_SHIFT)
+#define RGMII_CTL_P0TDLY_1_75  (0x2 << RGMII_CTL_P0TDLY_SHIFT)
+#define RGMII_CTL_P0TDLY_2_0   (0x3 << RGMII_CTL_P0TDLY_SHIFT)
+#define RGMII_CTL_P0SPD_SHIFT  2
+#define RGMII_CTL_P0SPD_10     (0x0 << RGMII_CTL_P0SPD_SHIFT)
+#define RGMII_CTL_P0SPD_100    (0x1 << RGMII_CTL_P0SPD_SHIFT)
+#define RGMII_CTL_P0SPD_1000   (0x2 << RGMII_CTL_P0SPD_SHIFT)
+#define RGMII_CTL_P0DUP_FULL   (1 << 1)
+#define RGMII_CTL_P0FCE_EN     (1 << 0)
+
+#define PMAC_HD_CTL_AC         (1 << 18)
+
+#define MDIO_CTRL_WD_SHIFT     16
+#define MDIO_CTRL_MBUSY                (1 << 15)
+#define MDIO_CTRL_OP_READ      (1 << 11)
+#define MDIO_CTRL_OP_WRITE     (1 << 10)
+#define MDIO_CTRL_PHYAD_SHIFT  5
+#define MDIO_CTRL_PHYAD_MASK   (0x1f << MDIO_CTRL_PHYAD_SHIFT)
+#define MDIO_CTRL_REGAD_MASK   0x1f
+
+#endif /* __ARX100_SWITCH_H__ */
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -38,6 +38,7 @@ COBJS-$(CONFIG_DRIVER_KS8695ETH) += ks86
 COBJS-$(CONFIG_KS8851_MLL) += ks8851_mll.o
 COBJS-$(CONFIG_LAN91C96) += lan91c96.o
 COBJS-$(CONFIG_LANTIQ_DANUBE_ETOP) += lantiq_danube_etop.o
+COBJS-$(CONFIG_LANTIQ_ARX100_SWITCH) += lantiq_arx100_switch.o
 COBJS-$(CONFIG_LANTIQ_VRX200_SWITCH) += lantiq_vrx200_switch.o
 COBJS-$(CONFIG_MACB) += macb.o
 COBJS-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o
--- /dev/null
+++ b/drivers/net/lantiq_arx100_switch.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2011-2013 Daniel Schwierzeck, daniel.schwierzeck@gmail.com
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+#define DEBUG
+#include <common.h>
+#include <malloc.h>
+#include <netdev.h>
+#include <miiphy.h>
+#include <switch.h>
+#include <linux/compiler.h>
+#include <asm/gpio.h>
+#include <asm/processor.h>
+#include <asm/lantiq/io.h>
+#include <asm/lantiq/eth.h>
+#include <asm/lantiq/pm.h>
+#include <asm/lantiq/reset.h>
+#include <asm/lantiq/dma.h>
+#include <asm/arch/soc.h>
+#include <asm/arch/switch.h>
+
+#define LTQ_ETH_RX_BUFFER_CNT          PKTBUFSRX
+#define LTQ_ETH_TX_BUFFER_CNT          8
+#define LTQ_ETH_RX_DATA_SIZE           PKTSIZE_ALIGN
+#define LTQ_ETH_IP_ALIGN               2
+
+#define LTQ_MDIO_DRV_NAME              "ltq-mdio"
+#define LTQ_ETH_DRV_NAME               "ltq-eth"
+
+#define LTQ_ETHSW_MAX_GMAC             2
+#define LTQ_ETHSW_PMAC                 2
+
+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;
+       int tx_num;
+};
+
+static struct ar9_switch_regs *switch_regs =
+       (struct ar9_switch_regs *) CKSEG1ADDR(LTQ_SWITCH_BASE);
+
+static int ltq_mdio_is_busy(void)
+{
+       u32 mdio_ctrl = ltq_readl(&switch_regs->mdio_ctrl);
+
+       return mdio_ctrl & MDIO_CTRL_MBUSY;
+}
+
+static void ltq_mdio_poll(void)
+{
+       while (ltq_mdio_is_busy())
+               cpu_relax();
+
+       __udelay(1000);
+}
+
+static int ltq_mdio_read(struct mii_dev *bus, int phyad, int devad,
+                                       int regad)
+{
+       u32 mdio_ctrl;
+       int retval;
+
+       mdio_ctrl = MDIO_CTRL_MBUSY | MDIO_CTRL_OP_READ |
+               ((phyad << MDIO_CTRL_PHYAD_SHIFT) & MDIO_CTRL_PHYAD_MASK) |
+               (regad & MDIO_CTRL_REGAD_MASK);
+
+       ltq_mdio_poll();
+       ltq_writel(&switch_regs->mdio_ctrl, mdio_ctrl);
+       ltq_mdio_poll();
+       retval = ltq_readl(&switch_regs->mdio_data);
+       ltq_writel(&switch_regs->mdio_data, 0xFFFF);
+
+       debug("%s: phyad %02x, regad %02x, val %02x\n", __func__, phyad, regad, retval);
+
+       return retval;
+}
+
+static int ltq_mdio_write(struct mii_dev *bus, int phyad, int devad,
+                                       int regad, u16 val)
+{
+       u32 mdio_ctrl;
+
+       debug("%s: phyad %02x, regad %02x, val %02x\n", __func__, phyad, regad, val);
+
+       mdio_ctrl = (val << MDIO_CTRL_WD_SHIFT) | MDIO_CTRL_MBUSY |
+               MDIO_CTRL_OP_WRITE |
+               ((phyad << MDIO_CTRL_PHYAD_SHIFT) & MDIO_CTRL_PHYAD_MASK) |
+               (regad & MDIO_CTRL_REGAD_MASK);
+
+       ltq_mdio_poll();
+       ltq_writel(&switch_regs->mdio_ctrl, mdio_ctrl);
+
+       return 0;
+}
+
+static void ltq_eth_gmac_update(struct phy_device *phydev, int num)
+{
+}
+
+static inline u8 *ltq_eth_rx_packet_align(int rx_num)
+{
+       u8 *packet = (u8 *) NetRxPackets[rx_num];
+
+       /*
+        * IP header needs
+        */
+       return packet + LTQ_ETH_IP_ALIGN;
+}
+
+static int ltq_eth_init(struct eth_device *dev, bd_t *bis)
+{
+       struct ltq_eth_priv *priv = dev->priv;
+       struct ltq_dma_device *dma_dev = &priv->dma_dev;
+       struct phy_device *phydev;
+       int i;
+
+       for (i = 0; i < LTQ_ETHSW_MAX_GMAC; i++) {
+               phydev = priv->phymap[i];
+               if (!phydev)
+                       continue;
+
+               phy_startup(phydev);
+               ltq_eth_gmac_update(phydev, i);
+       }
+
+       for (i = 0; i < LTQ_ETH_RX_BUFFER_CNT; i++)
+               ltq_dma_rx_map(dma_dev, i, ltq_eth_rx_packet_align(i),
+                       LTQ_ETH_RX_DATA_SIZE);
+
+       ltq_dma_enable(dma_dev);
+
+       priv->rx_num = 0;
+       priv->tx_num = 0;
+
+       return 0;
+}
+
+static void ltq_eth_halt(struct eth_device *dev)
+{
+       struct ltq_eth_priv *priv = dev->priv;
+       struct ltq_dma_device *dma_dev = &priv->dma_dev;
+       struct phy_device *phydev;
+       int i;
+
+       ltq_dma_reset(dma_dev);
+
+       for (i = 0; i < LTQ_ETHSW_MAX_GMAC; i++) {
+               phydev = priv->phymap[i];
+               if (!phydev)
+                       continue;
+
+               phy_shutdown(phydev);
+               phydev->link = 0;
+               ltq_eth_gmac_update(phydev, i);
+       }
+}
+
+static int ltq_eth_send(struct eth_device *dev, void *packet, int length)
+{
+       struct ltq_eth_priv *priv = dev->priv;
+       struct ltq_dma_device *dma_dev = &priv->dma_dev;
+       int err;
+
+       err = ltq_dma_tx_map(dma_dev, priv->tx_num, packet, length, 10);
+       if (err) {
+               puts("NET: timeout on waiting for TX descriptor\n");
+               return -1;
+       }
+
+       priv->tx_num = (priv->tx_num + 1) % LTQ_ETH_TX_BUFFER_CNT;
+
+       return err;
+}
+
+static int ltq_eth_recv(struct eth_device *dev)
+{
+       struct ltq_eth_priv *priv = dev->priv;
+       struct ltq_dma_device *dma_dev = &priv->dma_dev;
+       u8 *packet;
+       int len;
+
+       if (!ltq_dma_rx_poll(dma_dev, priv->rx_num))
+               return 0;
+
+#if 0
+       printf("%s: rx_num %d\n", __func__, priv->rx_num);
+#endif
+
+       len = ltq_dma_rx_length(dma_dev, priv->rx_num);
+       packet = ltq_eth_rx_packet_align(priv->rx_num);
+
+#if 0
+       printf("%s: received: packet %p, len %u, rx_num %d\n",
+               __func__, packet, len, priv->rx_num);
+#endif
+
+       if (len)
+               NetReceive(packet, len);
+
+       ltq_dma_rx_map(dma_dev, priv->rx_num, packet,
+               LTQ_ETH_RX_DATA_SIZE);
+
+       priv->rx_num = (priv->rx_num + 1) % LTQ_ETH_RX_BUFFER_CNT;
+
+       return 0;
+}
+
+static void ltq_eth_pmac_init(void)
+{
+       /* Add CRC to packets from DMA to PMAC */
+       ltq_setbits(&switch_regs->pmac_hd_ctl, PMAC_HD_CTL_AC);
+
+       /* Force link up */
+       ltq_setbits(&switch_regs->p2_ctl, P0_CTL_FLP);
+}
+
+static void ltq_eth_hw_init(const struct ltq_eth_port_config *port)
+{
+       /* Power up ethernet subsystems */
+       ltq_pm_enable(LTQ_PM_ETH);
+
+       /* Enable switch core */
+       ltq_setbits(&switch_regs->sw_gctl0, SW_GCTL0_SE);
+
+       /* MII/MDIO */
+       gpio_set_altfunc(42, GPIO_ALTSEL_SET, GPIO_ALTSEL_CLR, GPIO_DIR_OUT);
+       /* MII/MDC */
+       gpio_set_altfunc(43, GPIO_ALTSEL_SET, GPIO_ALTSEL_CLR, GPIO_DIR_OUT);
+
+       ltq_eth_pmac_init();
+}
+
+static void ltq_eth_port_config(struct ltq_eth_priv *priv,
+                               const struct ltq_eth_port_config *port)
+{
+       struct phy_device *phydev;
+       struct switch_device *sw;
+       u32 rgmii_ctl;
+       unsigned int port_ctl, port_xmii = 0;
+
+       if (port->num > 1)
+               return;
+
+       rgmii_ctl = ltq_readl(&switch_regs->rgmii_ctl);
+
+       if (port->num == 1)
+               port_ctl = ltq_readl(&switch_regs->p1_ctl);
+       else
+               port_ctl = ltq_readl(&switch_regs->p0_ctl);
+
+       switch (port->phy_if) {
+       case PHY_INTERFACE_MODE_RGMII:
+               port_xmii = RGMII_CTL_P0IS_RGMII;
+
+               switch (port->rgmii_tx_delay) {
+               case 1:
+                       port_xmii |= RGMII_CTL_P0TDLY_1_5;
+                       break;
+               case 2:
+                       port_xmii |= RGMII_CTL_P0TDLY_1_75;
+                       break;
+               case 3:
+                       port_xmii |= RGMII_CTL_P0TDLY_2_0;
+                       break;
+               default:
+                       break;
+               }
+
+               switch (port->rgmii_rx_delay) {
+               case 1:
+                       port_xmii |= RGMII_CTL_P0RDLY_1_5;
+                       break;
+               case 2:
+                       port_xmii |= RGMII_CTL_P0RDLY_1_75;
+                       break;
+               case 3:
+                       port_xmii |= RGMII_CTL_P0RDLY_2_0;
+                       break;
+               default:
+                       break;
+               }
+
+               if (!(port->flags & LTQ_ETH_PORT_PHY)) {
+                       port_xmii |= (RGMII_CTL_P0SPD_1000 |
+                                       RGMII_CTL_P0DUP_FULL);
+                       port_ctl |= P0_CTL_FLP;
+               }
+
+               break;
+       case PHY_INTERFACE_MODE_MII:
+               port_xmii = RGMII_CTL_P0IS_MII;
+
+               if (!(port->flags & LTQ_ETH_PORT_PHY)) {
+                       port_xmii |= (RGMII_CTL_P0SPD_100 |
+                                       RGMII_CTL_P0DUP_FULL);
+                       port_ctl |= P0_CTL_FLP;
+               }
+
+               break;
+       default:
+               break;
+       }
+
+       if (port->num == 1) {
+               ltq_writel(&switch_regs->p1_ctl, port_ctl);
+
+               rgmii_ctl &= ~RGMII_CTL_P1_MASK;
+               rgmii_ctl |= (port_xmii << RGMII_CTL_P1_SHIFT);
+       } else {
+               ltq_writel(&switch_regs->p0_ctl, port_ctl);
+
+               rgmii_ctl &= ~RGMII_CTL_P0_MASK;
+               rgmii_ctl |= port_xmii;
+       }
+
+       ltq_writel(&switch_regs->rgmii_ctl, rgmii_ctl);
+
+       /* Connect to external switch */
+       if (port->flags & LTQ_ETH_PORT_SWITCH) {
+               sw = switch_connect(priv->bus);
+               if (sw)
+                       switch_setup(sw);
+       }
+
+       /* Connect to internal/external PHYs */
+       if (port->flags & LTQ_ETH_PORT_PHY) {
+               phydev = phy_connect(priv->bus, port->phy_addr, priv->dev,
+                                       port->phy_if);
+               if (phydev)
+                       phy_config(phydev);
+
+               priv->phymap[port->num] = phydev;
+       }
+}
+
+int ltq_eth_initialize(const struct ltq_eth_board_config *board_config)
+{
+       struct eth_device *dev;
+       struct mii_dev *bus;
+       struct ltq_eth_priv *priv;
+       struct ltq_dma_device *dma_dev;
+       const struct ltq_eth_port_config *port = &board_config->ports[0];
+       int i, ret;
+
+       build_check_ar9_registers();
+
+       ltq_dma_init();
+       ltq_eth_hw_init(port);
+
+       dev = calloc(1, sizeof(*dev));
+       if (!dev)
+               return -1;
+
+       priv = calloc(1, sizeof(*priv));
+       if (!priv)
+               return -1;
+
+       bus = mdio_alloc();
+       if (!bus)
+               return -1;
+
+       sprintf(dev->name, LTQ_ETH_DRV_NAME);
+       dev->priv = priv;
+       dev->init = ltq_eth_init;
+       dev->halt = ltq_eth_halt;
+       dev->recv = ltq_eth_recv;
+       dev->send = ltq_eth_send;
+
+       sprintf(bus->name, LTQ_MDIO_DRV_NAME);
+       bus->read = ltq_mdio_read;
+       bus->write = ltq_mdio_write;
+       bus->priv = priv;
+
+       dma_dev = &priv->dma_dev;
+       dma_dev->port = 0;
+       dma_dev->rx_chan.chan_no = 0;
+       dma_dev->rx_chan.class = 0;
+       dma_dev->rx_chan.num_desc = LTQ_ETH_RX_BUFFER_CNT;
+       dma_dev->rx_endian_swap = LTQ_DMA_ENDIANESS_B3_B2_B1_B0;
+       dma_dev->rx_burst_len = LTQ_DMA_BURST_2WORDS;
+       dma_dev->tx_chan.chan_no = 1;
+       dma_dev->tx_chan.class = 0;
+       dma_dev->tx_chan.num_desc = LTQ_ETH_TX_BUFFER_CNT;
+       dma_dev->tx_endian_swap = LTQ_DMA_ENDIANESS_B3_B2_B1_B0;
+       dma_dev->tx_burst_len = LTQ_DMA_BURST_2WORDS;
+
+       priv->bus = bus;
+       priv->dev = dev;
+
+       ret = ltq_dma_register(dma_dev);
+       if (ret)
+               return ret;
+
+       ret = mdio_register(bus);
+       if (ret)
+               return ret;
+
+       ret = eth_register(dev);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < board_config->num_ports; i++)
+               ltq_eth_port_config(priv, &board_config->ports[i]);
+
+       return 0;
+}

Generated by GNU Enscript 1.6.5.90.