From 85c7de030947d8d6b656979b89f6602e12bc2296 Mon Sep 17 00:00:00 2001 From: Zumeng Chen Date: Wed, 29 Sep 2010 10:38:11 +0800 Subject: [PATCH 14/41] Add NET_DSA_MV88E6063 support for ar71xx Which is downloaded from the following: https://dev.openwrt.org/browser/branches branches_backfire_target_linux_ar71xx_files-r23140.zip patches come from the following: 120-dsa-trailer-tag-validation-fix.patch 121-dsa-add-88e6063-driver.patch 122-dsa-add-qinq-tagging-format.patch 123-dsa-add-ar7240-built-in-switch-support.patch Integrated-by: Zumeng Chen --- include/linux/if_ether.h | 1 + net/dsa/Kconfig | 20 ++ net/dsa/Makefile | 3 + net/dsa/ar7240.c | 736 ++++++++++++++++++++++++++++++++++++++++++++++ net/dsa/dsa_priv.h | 3 + net/dsa/mv88e6063.c | 294 ++++++++++++++++++ net/dsa/slave.c | 18 ++ net/dsa/tag_qinq.c | 127 ++++++++ net/dsa/tag_trailer.c | 2 +- 9 files changed, 1203 insertions(+), 1 deletions(-) create mode 100644 net/dsa/ar7240.c create mode 100644 net/dsa/mv88e6063.c create mode 100644 net/dsa/tag_qinq.c diff --git a/include/linux/if_ether.h b/include/linux/if_ether.h index 0065ffd..20c7cb3 100644 --- a/include/linux/if_ether.h +++ b/include/linux/if_ether.h @@ -82,6 +82,7 @@ #define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */ #define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */ #define ETH_P_FIP 0x8914 /* FCoE Initialization Protocol */ +#define ETH_P_QINQ 0x9100 /* QinQ VLAN Stacking Protocol */ #define ETH_P_EDSA 0xDADA /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ /* diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index c53ded2..9ffcc99 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -23,8 +23,21 @@ config NET_DSA_TAG_TRAILER bool default n +config NET_DSA_TAG_QINQ + bool + default y + # switch drivers +config NET_DSA_AR7240 + bool "Atheros AR7240 built-in ethernet switch support" + depends on ATHEROS_AR71XX + default n + select NET_DSA_TAG_QINQ + ---help--- + This enables support for the built-in ethernet switch of the + Atheros AR7240 SoC. + config NET_DSA_MV88E6XXX bool default n @@ -36,6 +49,13 @@ config NET_DSA_MV88E6060 This enables support for the Marvell 88E6060 ethernet switch chip. +config NET_DSA_MV88E6063 + bool "Marvell 88E6063 ethernet switch chip support" + select NET_DSA_TAG_TRAILER + ---help--- + This enables support for the Marvell 88E6063 ethernet switch + chip + config NET_DSA_MV88E6XXX_NEED_PPU bool default n diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 2374faf..0dcd638 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -1,11 +1,14 @@ # tagging formats obj-$(CONFIG_NET_DSA_TAG_DSA) += tag_dsa.o obj-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o +obj-$(CONFIG_NET_DSA_TAG_QINQ) += tag_qinq.o obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o # switch drivers +obj-$(CONFIG_NET_DSA_AR7240) += ar7240.o obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o +obj-$(CONFIG_NET_DSA_MV88E6063) += mv88e6063.o obj-$(CONFIG_NET_DSA_MV88E6123_61_65) += mv88e6123_61_65.o obj-$(CONFIG_NET_DSA_MV88E6131) += mv88e6131.o diff --git a/net/dsa/ar7240.c b/net/dsa/ar7240.c new file mode 100644 index 0000000..ba24815 --- /dev/null +++ b/net/dsa/ar7240.c @@ -0,0 +1,736 @@ +/* + * DSA driver for the built-in ethernet switch of the Atheros AR7240 SoC + * Copyright (c) 2010 Gabor Juhos + * + * This file was based on: + * net/dsa/mv88e6060.c - Driver for Marvell 88e6060 switch chips + * Copyright (c) 2008 Marvell Semiconductor + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "dsa_priv.h" + +#define BITM(_count) (BIT(_count) - 1) + +#define AR7240_REG_MASK_CTRL 0x00 +#define AR7240_MASK_CTRL_REVISION_M BITM(8) +#define AR7240_MASK_CTRL_VERSION_M BITM(8) +#define AR7240_MASK_CTRL_VERSION_S 8 +#define AR7240_MASK_CTRL_SOFT_RESET BIT(31) + +#define AR7240_REG_MAC_ADDR0 0x20 +#define AR7240_REG_MAC_ADDR1 0x24 + +#define AR7240_REG_FLOOD_MASK 0x2c +#define AR7240_FLOOD_MASK_BROAD_TO_CPU BIT(26) + +#define AR7240_REG_GLOBAL_CTRL 0x30 +#define AR7240_GLOBAL_CTRL_MTU_M BITM(12) + +#define AR7240_REG_AT_CTRL 0x5c +#define AR7240_AT_CTRL_ARP_EN BIT(20) + +#define AR7240_REG_TAG_PRIORITY 0x70 + +#define AR7240_REG_SERVICE_TAG 0x74 +#define AR7240_SERVICE_TAG_M BITM(16) + +#define AR7240_REG_CPU_PORT 0x78 +#define AR7240_MIRROR_PORT_S 4 +#define AR7240_CPU_PORT_EN BIT(8) + +#define AR7240_REG_MIB_FUNCTION0 0x80 +#define AR7240_MIB_TIMER_M BITM(16) +#define AR7240_MIB_AT_HALF_EN BIT(16) +#define AR7240_MIB_BUSY BIT(17) +#define AR7240_MIB_FUNC_S 24 +#define AR7240_MIB_FUNC_NO_OP 0x0 +#define AR7240_MIB_FUNC_FLUSH 0x1 +#define AR7240_MIB_FUNC_CAPTURE 0x3 + +#define AR7240_REG_MDIO_CTRL 0x98 +#define AR7240_MDIO_CTRL_DATA_M BITM(16) +#define AR7240_MDIO_CTRL_REG_ADDR_S 16 +#define AR7240_MDIO_CTRL_PHY_ADDR_S 21 +#define AR7240_MDIO_CTRL_CMD_WRITE 0 +#define AR7240_MDIO_CTRL_CMD_READ BIT(27) +#define AR7240_MDIO_CTRL_MASTER_EN BIT(30) +#define AR7240_MDIO_CTRL_BUSY BIT(31) + +#define AR7240_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100) + +#define AR7240_REG_PORT_STATUS(_port) (AR7240_REG_PORT_BASE((_port)) + 0x00) +#define AR7240_PORT_STATUS_SPEED_M BITM(2) +#define AR7240_PORT_STATUS_SPEED_10 0 +#define AR7240_PORT_STATUS_SPEED_100 1 +#define AR7240_PORT_STATUS_SPEED_1000 2 +#define AR7240_PORT_STATUS_TXMAC BIT(2) +#define AR7240_PORT_STATUS_RXMAC BIT(3) +#define AR7240_PORT_STATUS_TXFLOW BIT(4) +#define AR7240_PORT_STATUS_RXFLOW BIT(5) +#define AR7240_PORT_STATUS_DUPLEX BIT(6) +#define AR7240_PORT_STATUS_LINK_UP BIT(8) +#define AR7240_PORT_STATUS_LINK_AUTO BIT(9) +#define AR7240_PORT_STATUS_LINK_PAUSE BIT(10) + +#define AR7240_REG_PORT_CTRL(_port) (AR7240_REG_PORT_BASE((_port)) + 0x04) +#define AR7240_PORT_CTRL_STATE_M BITM(3) +#define AR7240_PORT_CTRL_STATE_DISABLED 0 +#define AR7240_PORT_CTRL_STATE_BLOCK 1 +#define AR7240_PORT_CTRL_STATE_LISTEN 2 +#define AR7240_PORT_CTRL_STATE_LEARN 3 +#define AR7240_PORT_CTRL_STATE_FORWARD 4 +#define AR7240_PORT_CTRL_LEARN_LOCK BIT(7) +#define AR7240_PORT_CTRL_VLAN_MODE_S 8 +#define AR7240_PORT_CTRL_VLAN_MODE_KEEP 0 +#define AR7240_PORT_CTRL_VLAN_MODE_STRIP 1 +#define AR7240_PORT_CTRL_VLAN_MODE_ADD 2 +#define AR7240_PORT_CTRL_VLAN_MODE_DOUBLE_TAG 3 +#define AR7240_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR7240_PORT_CTRL_HEADER BIT(11) +#define AR7240_PORT_CTRL_MAC_LOOP BIT(12) +#define AR7240_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR7240_PORT_CTRL_LEARN BIT(14) +#define AR7240_PORT_CTRL_DOUBLE_TAG BIT(15) +#define AR7240_PORT_CTRL_MIRROR_TX BIT(16) +#define AR7240_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR7240_REG_PORT_VLAN(_port) (AR7240_REG_PORT_BASE((_port)) + 0x08) + +#define AR7240_PORT_VLAN_DEFAULT_ID_S 0 +#define AR7240_PORT_VLAN_DEST_PORTS_S 16 + +#define AR7240_REG_STATS_BASE(_port) (0x20000 + (_port) * 0x100) + +#define AR7240_STATS_RXBROAD 0x00 +#define AR7240_STATS_RXPAUSE 0x04 +#define AR7240_STATS_RXMULTI 0x08 +#define AR7240_STATS_RXFCSERR 0x0c +#define AR7240_STATS_RXALIGNERR 0x10 +#define AR7240_STATS_RXRUNT 0x14 +#define AR7240_STATS_RXFRAGMENT 0x18 +#define AR7240_STATS_RX64BYTE 0x1c +#define AR7240_STATS_RX128BYTE 0x20 +#define AR7240_STATS_RX256BYTE 0x24 +#define AR7240_STATS_RX512BYTE 0x28 +#define AR7240_STATS_RX1024BYTE 0x2c +#define AR7240_STATS_RX1518BYTE 0x30 +#define AR7240_STATS_RXMAXBYTE 0x34 +#define AR7240_STATS_RXTOOLONG 0x38 +#define AR7240_STATS_RXGOODBYTE 0x3c +#define AR7240_STATS_RXBADBYTE 0x44 +#define AR7240_STATS_RXOVERFLOW 0x4c +#define AR7240_STATS_FILTERED 0x50 +#define AR7240_STATS_TXBROAD 0x54 +#define AR7240_STATS_TXPAUSE 0x58 +#define AR7240_STATS_TXMULTI 0x5c +#define AR7240_STATS_TXUNDERRUN 0x60 +#define AR7240_STATS_TX64BYTE 0x64 +#define AR7240_STATS_TX128BYTE 0x68 +#define AR7240_STATS_TX256BYTE 0x6c +#define AR7240_STATS_TX512BYTE 0x70 +#define AR7240_STATS_TX1024BYTE 0x74 +#define AR7240_STATS_TX1518BYTE 0x78 +#define AR7240_STATS_TXMAXBYTE 0x7c +#define AR7240_STATS_TXOVERSIZE 0x80 +#define AR7240_STATS_TXBYTE 0x84 +#define AR7240_STATS_TXCOLLISION 0x8c +#define AR7240_STATS_TXABORTCOL 0x90 +#define AR7240_STATS_TXMULTICOL 0x94 +#define AR7240_STATS_TXSINGLECOL 0x98 +#define AR7240_STATS_TXEXCDEFER 0x9c +#define AR7240_STATS_TXDEFER 0xa0 +#define AR7240_STATS_TXLATECOL 0xa4 + +#define AR7240_PORT_CPU 0 +#define AR7240_NUM_PORTS 6 +#define AR7240_NUM_PHYS 5 + +#define AR7240_PHY_ID1 0x004d +#define AR7240_PHY_ID2 0xd041 + +#define AR7240_PORT_MASK(_port) BIT((_port)) +#define AR7240_PORT_MASK_ALL BITM(AR7240_NUM_PORTS) +#define AR7240_PORT_MASK_BUT(_port) (AR7240_PORT_MASK_ALL & ~BIT((_port))) + +struct ar7240sw { + struct mii_bus *mii_bus; + struct mutex reg_mutex; + struct mutex stats_mutex; +}; + +struct ar7240sw_hw_stat { + char string[ETH_GSTRING_LEN]; + int sizeof_stat; + int reg; +}; + +static inline struct ar7240sw *dsa_to_ar7240sw(struct dsa_switch *ds) +{ + return (struct ar7240sw *)(ds + 1); +} + +static inline void ar7240sw_init(struct ar7240sw *as, struct mii_bus *mii) +{ + as->mii_bus = mii; + mutex_init(&as->reg_mutex); + mutex_init(&as->stats_mutex); +} + +static inline u16 mk_phy_addr(u32 reg) +{ + return (0x17 & ((reg >> 4) | 0x10)); +} + +static inline u16 mk_phy_reg(u32 reg) +{ + return ((reg << 1) & 0x1e); +} + +static inline u16 mk_high_addr(u32 reg) +{ + return ((reg >> 7) & 0x1ff); +} + +static u32 __ar7240sw_reg_read(struct ar7240sw *as, u32 reg) +{ + struct mii_bus *mii = as->mii_bus; + u16 phy_addr; + u16 phy_reg; + u32 hi, lo; + + reg = (reg & 0xfffffffc) >> 2; + + mdiobus_write(mii, 0x1f, 0x10, mk_high_addr(reg)); + + phy_addr = mk_phy_addr(reg); + phy_reg = mk_phy_reg(reg); + + lo = (u32) mdiobus_read(mii, phy_addr, phy_reg); + hi = (u32) mdiobus_read(mii, phy_addr, phy_reg + 1); + + return ((hi << 16) | lo); +} + +static void __ar7240sw_reg_write(struct ar7240sw *as, u32 reg, u32 val) +{ + struct mii_bus *mii = as->mii_bus; + u16 phy_addr; + u16 phy_reg; + + reg = (reg & 0xfffffffc) >> 2; + + mdiobus_write(mii, 0x1f, 0x10, mk_high_addr(reg)); + + phy_addr = mk_phy_addr(reg); + phy_reg = mk_phy_reg(reg); + + mdiobus_write(mii, phy_addr, phy_reg + 1, (val >> 16)); + mdiobus_write(mii, phy_addr, phy_reg, (val & 0xffff)); +} + +static u32 ar7240sw_reg_read(struct ar7240sw *as, u32 reg_addr) +{ + u32 ret; + + mutex_lock(&as->reg_mutex); + ret = __ar7240sw_reg_read(as, reg_addr); + mutex_unlock(&as->reg_mutex); + + return ret; +} + +static void ar7240sw_reg_write(struct ar7240sw *as, u32 reg_addr, u32 reg_val) +{ + mutex_lock(&as->reg_mutex); + __ar7240sw_reg_write(as, reg_addr, reg_val); + mutex_unlock(&as->reg_mutex); +} + +static u32 ar7240sw_reg_rmw(struct ar7240sw *as, u32 reg, u32 mask, u32 val) +{ + u32 t; + + mutex_lock(&as->reg_mutex); + t = __ar7240sw_reg_read(as, reg); + t &= ~mask; + t |= val; + __ar7240sw_reg_write(as, reg, t); + mutex_unlock(&as->reg_mutex); + + return t; +} + +static void ar7240sw_reg_set(struct ar7240sw *as, u32 reg, u32 val) +{ + u32 t; + + mutex_lock(&as->reg_mutex); + t = __ar7240sw_reg_read(as, reg); + t |= val; + __ar7240sw_reg_write(as, reg, t); + mutex_unlock(&as->reg_mutex); +} + +static int ar7240sw_reg_wait(struct ar7240sw *as, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + u32 t; + + t = ar7240sw_reg_read(as, reg); + if ((t & mask) == val) + return 0; + + msleep(1); + } + + return -ETIMEDOUT; +} + +static u16 ar7240sw_phy_read(struct ar7240sw *as, unsigned phy_addr, + unsigned reg_addr) +{ + u32 t; + int err; + + if (phy_addr >= AR7240_NUM_PHYS) + return 0xffff; + + t = (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) | + (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) | + AR7240_MDIO_CTRL_MASTER_EN | + AR7240_MDIO_CTRL_BUSY | + AR7240_MDIO_CTRL_CMD_READ; + + ar7240sw_reg_write(as, AR7240_REG_MDIO_CTRL, t); + err = ar7240sw_reg_wait(as, AR7240_REG_MDIO_CTRL, + AR7240_MDIO_CTRL_BUSY, 0, 5); + if (err) + return 0xffff; + + t = ar7240sw_reg_read(as, AR7240_REG_MDIO_CTRL); + return (t & AR7240_MDIO_CTRL_DATA_M); +} + +static int ar7240sw_phy_write(struct ar7240sw *as, unsigned phy_addr, + unsigned reg_addr, u16 reg_val) +{ + u32 t; + int ret; + + if (phy_addr >= AR7240_NUM_PHYS) + return -EINVAL; + + t = (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) | + (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) | + AR7240_MDIO_CTRL_MASTER_EN | + AR7240_MDIO_CTRL_BUSY | + AR7240_MDIO_CTRL_CMD_WRITE | + reg_val; + + ar7240sw_reg_write(as, AR7240_REG_MDIO_CTRL, t); + ret = ar7240sw_reg_wait(as, AR7240_REG_MDIO_CTRL, + AR7240_MDIO_CTRL_BUSY, 0, 5); + return ret; +} + +static int ar7240sw_capture_stats(struct ar7240sw *as) +{ + int ret; + + /* Capture the hardware statistics for all ports */ + ar7240sw_reg_write(as, AR7240_REG_MIB_FUNCTION0, + (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S)); + + /* Wait for the capturing to complete. */ + ret = ar7240sw_reg_wait(as, AR7240_REG_MIB_FUNCTION0, + AR7240_MIB_BUSY, 0, 10); + return ret; +} + +static void ar7240sw_disable_port(struct ar7240sw *as, unsigned port) +{ + ar7240sw_reg_write(as, AR7240_REG_PORT_CTRL(port), + AR7240_PORT_CTRL_STATE_DISABLED); +} + +static int ar7240sw_reset(struct ar7240sw *as) +{ + int ret; + int i; + + /* Set all ports to disabled state. */ + for (i = 0; i < AR7240_NUM_PORTS; i++) + ar7240sw_disable_port(as, i); + + /* Wait for transmit queues to drain. */ + msleep(2); + + /* Reset the switch. */ + ar7240sw_reg_write(as, AR7240_REG_MASK_CTRL, + AR7240_MASK_CTRL_SOFT_RESET); + + ret = ar7240sw_reg_wait(as, AR7240_REG_MASK_CTRL, + AR7240_MASK_CTRL_SOFT_RESET, 0, 1000); + return ret; +} + +static void ar7240sw_setup(struct ar7240sw *as) +{ + /* Enable CPU port, and disable mirror port */ + ar7240sw_reg_write(as, AR7240_REG_CPU_PORT, + AR7240_CPU_PORT_EN | + (15 << AR7240_MIRROR_PORT_S)); + + /* Setup TAG priority mapping */ + ar7240sw_reg_write(as, AR7240_REG_TAG_PRIORITY, 0xfa50); + + /* Enable ARP frame acknowledge */ + ar7240sw_reg_set(as, AR7240_REG_AT_CTRL, AR7240_AT_CTRL_ARP_EN); + + /* Enable Broadcast frames transmitted to the CPU */ + ar7240sw_reg_set(as, AR7240_REG_FLOOD_MASK, + AR7240_FLOOD_MASK_BROAD_TO_CPU); + + /* setup MTU */ + ar7240sw_reg_rmw(as, AR7240_REG_GLOBAL_CTRL, AR7240_GLOBAL_CTRL_MTU_M, + 1536); + + /* setup Service TAG */ + ar7240sw_reg_rmw(as, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, + ETH_P_QINQ); +} + +static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port) +{ + u32 ctrl; + u32 dest_ports; + u32 vlan; + + ctrl = AR7240_PORT_CTRL_STATE_FORWARD; + + if (port == AR7240_PORT_CPU) { + ar7240sw_reg_write(as, AR7240_REG_PORT_STATUS(port), + AR7240_PORT_STATUS_SPEED_1000 | + AR7240_PORT_STATUS_TXFLOW | + AR7240_PORT_STATUS_RXFLOW | + AR7240_PORT_STATUS_TXMAC | + AR7240_PORT_STATUS_RXMAC | + AR7240_PORT_STATUS_DUPLEX); + + /* allow the CPU port to talk to each of the 'real' ports */ + dest_ports = AR7240_PORT_MASK_BUT(port); + + /* remove service tag from ingress frames */ + ctrl |= AR7240_PORT_CTRL_DOUBLE_TAG; + } else { + ar7240sw_reg_write(as, AR7240_REG_PORT_STATUS(port), + AR7240_PORT_STATUS_LINK_AUTO); + + /* + * allow each of the 'real' ports to only talk to the CPU + * port. + */ + dest_ports = AR7240_PORT_MASK(port) | + AR7240_PORT_MASK(AR7240_PORT_CPU); + + /* add service tag to egress frames */ + ctrl |= (AR7240_PORT_CTRL_VLAN_MODE_DOUBLE_TAG << + AR7240_PORT_CTRL_VLAN_MODE_S); + } + + /* set default VID and and destination ports for this VLAN */ + vlan = port; + vlan |= (dest_ports << AR7240_PORT_VLAN_DEST_PORTS_S); + + ar7240sw_reg_write(as, AR7240_REG_PORT_CTRL(port), ctrl); + ar7240sw_reg_write(as, AR7240_REG_PORT_VLAN(port), vlan); +} + +static char *ar7240_dsa_probe(struct mii_bus *mii, int sw_addr) +{ + struct ar7240sw as; + u32 ctrl; + u16 phy_id1; + u16 phy_id2; + u8 ver; + + ar7240sw_init(&as, mii); + + ctrl = ar7240sw_reg_read(&as, AR7240_REG_MASK_CTRL); + + ver = (ctrl >> AR7240_MASK_CTRL_VERSION_S) & AR7240_MASK_CTRL_VERSION_M; + if (ver != 1) { + pr_err("ar7240_dsa: unsupported chip, ctrl=%08x\n", ctrl); + return NULL; + } + + phy_id1 = ar7240sw_phy_read(&as, 0, MII_PHYSID1); + phy_id2 = ar7240sw_phy_read(&as, 0, MII_PHYSID2); + if (phy_id1 != AR7240_PHY_ID1 || phy_id2 != AR7240_PHY_ID2) { + pr_err("ar7240_dsa: unknown phy id '%04x:%04x'\n", + phy_id1, phy_id2); + return NULL; + } + + return "Atheros AR7240 built-in"; +} + +static int ar7240_dsa_setup(struct dsa_switch *ds) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + int i; + int ret; + + ar7240sw_init(as, ds->master_mii_bus); + + ret = ar7240sw_reset(as); + if (ret) + return ret; + + ar7240sw_setup(as); + + for (i = 0; i < AR7240_NUM_PORTS; i++) { + if (dsa_is_cpu_port(ds, i) || (ds->phys_port_mask & (1 << i))) + ar7240sw_setup_port(as, i); + else + ar7240sw_disable_port(as, i); + } + + return 0; +} + +static int ar7240_dsa_set_addr(struct dsa_switch *ds, u8 *addr) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + u32 t; + + t = (addr[4] << 8) | addr[5]; + ar7240sw_reg_write(as, AR7240_REG_MAC_ADDR0, t); + + t = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; + ar7240sw_reg_write(as, AR7240_REG_MAC_ADDR1, t); + + return 0; +} + +static int ar7240_iort_to_phy_addr(int port) +{ + if (port > 0 && port < AR7240_NUM_PORTS) + return port - 1; + + return -EINVAL; +} + +static int ar7240_dsa_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + int phy_addr; + + phy_addr = ar7240_iort_to_phy_addr(port); + if (phy_addr < 0) + return 0xffff; + + return ar7240sw_phy_read(as, phy_addr, regnum); +} + +static int ar7240_dsa_phy_write(struct dsa_switch *ds, int port, int regnum, + u16 val) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + int phy_addr; + + phy_addr = ar7240_iort_to_phy_addr(port); + if (phy_addr < 0) + return 0xffff; + + return ar7240sw_phy_write(as, phy_addr, regnum, val); +} + +static const char *ar7240sw_speed_str(unsigned speed) +{ + switch (speed) { + case AR7240_PORT_STATUS_SPEED_10: + return "10"; + case AR7240_PORT_STATUS_SPEED_100: + return "100"; + case AR7240_PORT_STATUS_SPEED_1000: + return "1000"; + } + + return "????"; +} + +static void ar7240_dsa_poll_link(struct dsa_switch *ds) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + int i; + + for (i = 0; i < DSA_MAX_PORTS; i++) { + struct net_device *dev; + u32 status; + int link; + unsigned speed; + int duplex; + + dev = ds->ports[i]; + if (dev == NULL) + continue; + + link = 0; + if (dev->flags & IFF_UP) { + status = ar7240sw_reg_read(as, + AR7240_REG_PORT_STATUS(i)); + link = !!(status & AR7240_PORT_STATUS_LINK_UP); + } + + if (!link) { + if (netif_carrier_ok(dev)) { + pr_info("%s: link down\n", dev->name); + netif_carrier_off(dev); + } + continue; + } + + speed = (status & AR7240_PORT_STATUS_SPEED_M); + duplex = (status & AR7240_PORT_STATUS_DUPLEX) ? 1 : 0; + if (!netif_carrier_ok(dev)) { + pr_info("%s: link up, %sMb/s, %s duplex", + dev->name, + ar7240sw_speed_str(speed), + duplex ? "full" : "half"); + netif_carrier_on(dev); + } + } +} + +static const struct ar7240sw_hw_stat ar7240_hw_stats[] = { + { "rx_broadcast" , 4, AR7240_STATS_RXBROAD, }, + { "rx_pause" , 4, AR7240_STATS_RXPAUSE, }, + { "rx_multicast" , 4, AR7240_STATS_RXMULTI, }, + { "rx_fcs_error" , 4, AR7240_STATS_RXFCSERR, }, + { "rx_align_error" , 4, AR7240_STATS_RXALIGNERR, }, + { "rx_undersize" , 4, AR7240_STATS_RXRUNT, }, + { "rx_fragments" , 4, AR7240_STATS_RXFRAGMENT, }, + { "rx_64bytes" , 4, AR7240_STATS_RX64BYTE, }, + { "rx_65_127bytes" , 4, AR7240_STATS_RX128BYTE, }, + { "rx_128_255bytes" , 4, AR7240_STATS_RX256BYTE, }, + { "rx_256_511bytes" , 4, AR7240_STATS_RX512BYTE, }, + { "rx_512_1023bytes" , 4, AR7240_STATS_RX1024BYTE, }, + { "rx_1024_1518bytes" , 4, AR7240_STATS_RX1518BYTE, }, + { "rx_1519_max_bytes" , 4, AR7240_STATS_RXMAXBYTE, }, + { "rx_oversize" , 4, AR7240_STATS_RXTOOLONG, }, + { "rx_good_bytes" , 8, AR7240_STATS_RXGOODBYTE, }, + { "rx_bad_bytes" , 8, AR7240_STATS_RXBADBYTE, }, + { "rx_overflow" , 4, AR7240_STATS_RXOVERFLOW, }, + { "filtered" , 4, AR7240_STATS_FILTERED, }, + { "tx_broadcast" , 4, AR7240_STATS_TXBROAD, }, + { "tx_pause" , 4, AR7240_STATS_TXPAUSE, }, + { "tx_multicast" , 4, AR7240_STATS_TXMULTI, }, + { "tx_underrun" , 4, AR7240_STATS_TXUNDERRUN, }, + { "tx_64bytes" , 4, AR7240_STATS_TX64BYTE, }, + { "tx_65_127bytes" , 4, AR7240_STATS_TX128BYTE, }, + { "tx_128_255bytes" , 4, AR7240_STATS_TX256BYTE, }, + { "tx_256_511bytes" , 4, AR7240_STATS_TX512BYTE, }, + { "tx_512_1023bytes" , 4, AR7240_STATS_TX1024BYTE, }, + { "tx_1024_1518bytes" , 4, AR7240_STATS_TX1518BYTE, }, + { "tx_1519_max_bytes" , 4, AR7240_STATS_TXMAXBYTE, }, + { "tx_oversize" , 4, AR7240_STATS_TXOVERSIZE, }, + { "tx_bytes" , 8, AR7240_STATS_TXBYTE, }, + { "tx_collisions" , 4, AR7240_STATS_TXCOLLISION, }, + { "tx_abort_collisions" , 4, AR7240_STATS_TXABORTCOL, }, + { "tx_multi_collisions" , 4, AR7240_STATS_TXMULTICOL, }, + { "tx_single_collisions", 4, AR7240_STATS_TXSINGLECOL, }, + { "tx_excessive_deferred", 4, AR7240_STATS_TXEXCDEFER, }, + { "tx_deferred" , 4, AR7240_STATS_TXDEFER, }, + { "tx_late_collisions" , 4, AR7240_STATS_TXLATECOL, }, +}; + +static void ar7240_dsa_get_strings(struct dsa_switch *ds, int port, + uint8_t *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ar7240_hw_stats); i++) { + memcpy(data + i * ETH_GSTRING_LEN, + ar7240_hw_stats[i].string, ETH_GSTRING_LEN); + } +} + +static void ar7240_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct ar7240sw *as = dsa_to_ar7240sw(ds); + int err; + int i; + + mutex_lock(&as->stats_mutex); + + err = ar7240sw_capture_stats(as); + if (err) + goto unlock; + + for (i = 0; i < ARRAY_SIZE(ar7240_hw_stats); i++) { + const struct ar7240sw_hw_stat *s = &ar7240_hw_stats[i]; + u32 reg = AR7240_REG_STATS_BASE(port); + u32 low; + u32 high; + + low = ar7240sw_reg_read(as, reg + s->reg); + if (s->sizeof_stat == 8) + high = ar7240sw_reg_read(as, reg + s->reg); + else + high = 0; + + data[i] = (((u64) high) << 32) | low; + } + + unlock: + mutex_unlock(&as->stats_mutex); +} + +static int ar7240_dsa_get_sset_count(struct dsa_switch *ds) +{ + return ARRAY_SIZE(ar7240_hw_stats); +} + +static struct dsa_switch_driver ar7240_dsa_driver = { + .tag_protocol = htons(ETH_P_QINQ), + .priv_size = sizeof(struct ar7240sw), + .probe = ar7240_dsa_probe, + .setup = ar7240_dsa_setup, + .set_addr = ar7240_dsa_set_addr, + .phy_read = ar7240_dsa_phy_read, + .phy_write = ar7240_dsa_phy_write, + .poll_link = ar7240_dsa_poll_link, + .get_strings = ar7240_dsa_get_strings, + .get_ethtool_stats = ar7240_dsa_get_ethtool_stats, + .get_sset_count = ar7240_dsa_get_sset_count, +}; + +int __init dsa_ar7240_init(void) +{ + register_switch_driver(&ar7240_dsa_driver); + return 0; +} +module_init(dsa_ar7240_init); + +void __exit dsa_ar7240_cleanup(void) +{ + unregister_switch_driver(&ar7240_dsa_driver); +} +module_exit(dsa_ar7240_cleanup); diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h index 4b0ea05..f2813fc 100644 --- a/net/dsa/dsa_priv.h +++ b/net/dsa/dsa_priv.h @@ -174,6 +174,9 @@ netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev); /* tag_edsa.c */ netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev); +/* tag_qinq.c */ +netdev_tx_t qinq_xmit(struct sk_buff *skb, struct net_device *dev); + /* tag_trailer.c */ netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev); diff --git a/net/dsa/mv88e6063.c b/net/dsa/mv88e6063.c new file mode 100644 index 0000000..5638a9f --- /dev/null +++ b/net/dsa/mv88e6063.c @@ -0,0 +1,294 @@ +/* + * net/dsa/mv88e6063.c - Driver for Marvell 88e6063 switch chips + * Copyright (c) 2009 Gabor Juhos + * + * This driver was base on: net/dsa/mv88e6060.c + * net/dsa/mv88e6063.c - Driver for Marvell 88e6060 switch chips + * Copyright (c) 2008-2009 Marvell Semiconductor + * + * 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 +#include +#include +#include "dsa_priv.h" + +#define REG_BASE 0x10 +#define REG_PHY(p) (REG_BASE + (p)) +#define REG_PORT(p) (REG_BASE + 8 + (p)) +#define REG_GLOBAL (REG_BASE + 0x0f) +#define NUM_PORTS 7 + +static int reg_read(struct dsa_switch *ds, int addr, int reg) +{ + return mdiobus_read(ds->master_mii_bus, addr, reg); +} + +#define REG_READ(addr, reg) \ + ({ \ + int __ret; \ + \ + __ret = reg_read(ds, addr, reg); \ + if (__ret < 0) \ + return __ret; \ + __ret; \ + }) + + +static int reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +{ + return mdiobus_write(ds->master_mii_bus, addr, reg, val); +} + +#define REG_WRITE(addr, reg, val) \ + ({ \ + int __ret; \ + \ + __ret = reg_write(ds, addr, reg, val); \ + if (__ret < 0) \ + return __ret; \ + }) + +static char *mv88e6063_probe(struct mii_bus *bus, int sw_addr) +{ + int ret; + + ret = mdiobus_read(bus, REG_PORT(0), 0x03); + if (ret >= 0) { + ret &= 0xfff0; + if (ret == 0x1530) + return "Marvell 88E6063"; + } + + return NULL; +} + +static int mv88e6063_switch_reset(struct dsa_switch *ds) +{ + int i; + int ret; + + /* + * Set all ports to the disabled state. + */ + for (i = 0; i < NUM_PORTS; i++) { + ret = REG_READ(REG_PORT(i), 0x04); + REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc); + } + + /* + * Wait for transmit queues to drain. + */ + msleep(2); + + /* + * Reset the switch. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0xa130); + + /* + * Wait up to one second for reset to complete. + */ + for (i = 0; i < 1000; i++) { + ret = REG_READ(REG_GLOBAL, 0x00); + if ((ret & 0x8000) == 0x0000) + break; + + msleep(1); + } + if (i == 1000) + return -ETIMEDOUT; + + return 0; +} + +static int mv88e6063_setup_global(struct dsa_switch *ds) +{ + /* + * Disable discarding of frames with excessive collisions, + * set the maximum frame size to 1536 bytes, and mask all + * interrupt sources. + */ + REG_WRITE(REG_GLOBAL, 0x04, 0x0800); + + /* + * Enable automatic address learning, set the address + * database size to 1024 entries, and set the default aging + * time to 5 minutes. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0x2130); + + return 0; +} + +static int mv88e6063_setup_port(struct dsa_switch *ds, int p) +{ + int addr = REG_PORT(p); + + /* + * Do not force flow control, disable Ingress and Egress + * Header tagging, disable VLAN tunneling, and set the port + * state to Forwarding. Additionally, if this is the CPU + * port, enable Ingress and Egress Trailer tagging mode. + */ + REG_WRITE(addr, 0x04, dsa_is_cpu_port(ds, p) ? 0x4103 : 0x0003); + + /* + * Port based VLAN map: give each port its own address + * database, allow the CPU port to talk to each of the 'real' + * ports, and allow each of the 'real' ports to only talk to + * the CPU port. + */ + REG_WRITE(addr, 0x06, + ((p & 0xf) << 12) | + (dsa_is_cpu_port(ds, p) ? + ds->phys_port_mask : + (1 << ds->dst->cpu_port))); + + /* + * Port Association Vector: when learning source addresses + * of packets, add the address to the address database using + * a port bitmap that has only the bit for this port set and + * the other bits clear. + */ + REG_WRITE(addr, 0x0b, 1 << p); + + return 0; +} + +static int mv88e6063_setup(struct dsa_switch *ds) +{ + int i; + int ret; + + ret = mv88e6063_switch_reset(ds); + if (ret < 0) + return ret; + + /* @@@ initialise atu */ + + ret = mv88e6063_setup_global(ds); + if (ret < 0) + return ret; + + for (i = 0; i < NUM_PORTS; i++) { + ret = mv88e6063_setup_port(ds, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static int mv88e6063_set_addr(struct dsa_switch *ds, u8 *addr) +{ + REG_WRITE(REG_GLOBAL, 0x01, (addr[0] << 8) | addr[1]); + REG_WRITE(REG_GLOBAL, 0x02, (addr[2] << 8) | addr[3]); + REG_WRITE(REG_GLOBAL, 0x03, (addr[4] << 8) | addr[5]); + + return 0; +} + +static int mv88e6063_port_to_phy_addr(int port) +{ + if (port >= 0 && port <= NUM_PORTS) + return REG_PHY(port); + return -1; +} + +static int mv88e6063_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_read(ds, addr, regnum); +} + +static int +mv88e6063_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_write(ds, addr, regnum, val); +} + +static void mv88e6063_poll_link(struct dsa_switch *ds) +{ + int i; + + for (i = 0; i < DSA_MAX_PORTS; i++) { + struct net_device *dev; + int uninitialized_var(port_status); + int link; + int speed; + int duplex; + int fc; + + dev = ds->ports[i]; + if (dev == NULL) + continue; + + link = 0; + if (dev->flags & IFF_UP) { + port_status = reg_read(ds, REG_PORT(i), 0x00); + if (port_status < 0) + continue; + + link = !!(port_status & 0x1000); + } + + if (!link) { + if (netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link down\n", dev->name); + netif_carrier_off(dev); + } + continue; + } + + speed = (port_status & 0x0100) ? 100 : 10; + duplex = (port_status & 0x0200) ? 1 : 0; + fc = ((port_status & 0xc000) == 0xc000) ? 1 : 0; + + if (!netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link up, %d Mb/s, %s duplex, " + "flow control %sabled\n", dev->name, + speed, duplex ? "full" : "half", + fc ? "en" : "dis"); + netif_carrier_on(dev); + } + } +} + +static struct dsa_switch_driver mv88e6063_switch_driver = { + .tag_protocol = htons(ETH_P_TRAILER), + .probe = mv88e6063_probe, + .setup = mv88e6063_setup, + .set_addr = mv88e6063_set_addr, + .phy_read = mv88e6063_phy_read, + .phy_write = mv88e6063_phy_write, + .poll_link = mv88e6063_poll_link, +}; + +static int __init mv88e6063_init(void) +{ + register_switch_driver(&mv88e6063_switch_driver); + return 0; +} +module_init(mv88e6063_init); + +static void __exit mv88e6063_cleanup(void) +{ + unregister_switch_driver(&mv88e6063_switch_driver); +} +module_exit(mv88e6063_cleanup); diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 0a47b6c..919d63f 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -319,6 +319,19 @@ static const struct net_device_ops edsa_netdev_ops = { .ndo_do_ioctl = dsa_slave_ioctl, }; #endif +#ifdef CONFIG_NET_DSA_TAG_QINQ +static const struct net_device_ops qinq_netdev_ops = { + .ndo_init = dsa_slave_init, + .ndo_open = dsa_slave_open, + .ndo_stop = dsa_slave_close, + .ndo_start_xmit = qinq_xmit, + .ndo_change_rx_flags = dsa_slave_change_rx_flags, + .ndo_set_rx_mode = dsa_slave_set_rx_mode, + .ndo_set_multicast_list = dsa_slave_set_rx_mode, + .ndo_set_mac_address = dsa_slave_set_mac_address, + .ndo_do_ioctl = dsa_slave_ioctl, +}; +#endif #ifdef CONFIG_NET_DSA_TAG_TRAILER static const struct net_device_ops trailer_netdev_ops = { .ndo_init = dsa_slave_init, @@ -364,6 +377,11 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent, slave_dev->netdev_ops = &edsa_netdev_ops; break; #endif +#ifdef CONFIG_NET_DSA_TAG_QINQ + case htons(ETH_P_QINQ): + slave_dev->netdev_ops = &qinq_netdev_ops; + break; +#endif #ifdef CONFIG_NET_DSA_TAG_TRAILER case htons(ETH_P_TRAILER): slave_dev->netdev_ops = &trailer_netdev_ops; diff --git a/net/dsa/tag_qinq.c b/net/dsa/tag_qinq.c new file mode 100644 index 0000000..68bcc38 --- /dev/null +++ b/net/dsa/tag_qinq.c @@ -0,0 +1,127 @@ +/* + * net/dsa/tag_qinq.c - QinQ tag format handling + * Copyright (c) 2010 Gabor Juhos + * + * This file was based on: + * net/dsa/tag_edsa.c - Ethertype DSA tagging + * Copyright (c) 2008-2009 Marvell Semiconductor + * + * 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 +#include +#include +#include + +#include "dsa_priv.h" + +netdev_tx_t qinq_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct dsa_slave_priv *p = netdev_priv(dev); + struct vlan_ethhdr *veth; + unsigned int len; + int ret; + + if (skb_cow_head(skb, VLAN_HLEN) < 0) + goto out_free_skb; + + veth = (struct vlan_ethhdr *)skb_push(skb, VLAN_HLEN); + + /* Move the mac addresses to the beginning of the new header. */ + memmove(skb->data, skb->data + VLAN_HLEN, 2 * VLAN_ETH_ALEN); + skb->mac_header -= VLAN_HLEN; + + /* setup VLAN header fields */ + veth->h_vlan_proto = htons(ETH_P_QINQ); + veth->h_vlan_TCI = htons(p->port); + + len = skb->len; + skb->protocol = htons(ETH_P_QINQ); + skb->dev = p->parent->dst->master_netdev; + + ret = dev_queue_xmit(skb); + if (unlikely(ret != NET_XMIT_SUCCESS)) + goto out_dropped; + + dev->stats.tx_packets++; + dev->stats.tx_bytes += len; + + return NETDEV_TX_OK; + + out_free_skb: + kfree_skb(skb); + out_dropped: + dev->stats.tx_dropped++; + return NETDEV_TX_OK; +} + +static int qinq_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *pt, struct net_device *orig_dev) +{ + struct dsa_switch_tree *dst; + struct dsa_switch *ds; + struct vlan_hdr *vhdr; + int source_port; + + dst = dev->dsa_ptr; + if (unlikely(dst == NULL)) + goto out_drop; + ds = dst->ds[0]; + + skb = skb_unshare(skb, GFP_ATOMIC); + if (skb == NULL) + goto out; + + if (unlikely(!pskb_may_pull(skb, VLAN_HLEN))) + goto out_drop; + + vhdr = (struct vlan_hdr *)skb->data; + source_port = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL) + goto out_drop; + + /* Remove the outermost VLAN tag and update checksum. */ + skb_pull_rcsum(skb, VLAN_HLEN); + memmove(skb->data - ETH_HLEN, + skb->data - ETH_HLEN - VLAN_HLEN, + 2 * ETH_ALEN); + + skb->dev = ds->ports[source_port]; + skb_push(skb, ETH_HLEN); + skb->pkt_type = PACKET_HOST; + skb->protocol = eth_type_trans(skb, skb->dev); + + skb->dev->stats.rx_packets++; + skb->dev->stats.rx_bytes += skb->len; + + netif_receive_skb(skb); + + return 0; + + out_drop: + kfree_skb(skb); + out: + return 0; +} + +static struct packet_type qinq_packet_type __read_mostly = { + .type = cpu_to_be16(ETH_P_QINQ), + .func = qinq_rcv, +}; + +static int __init qinq_init_module(void) +{ + dev_add_pack(&qinq_packet_type); + return 0; +} +module_init(qinq_init_module); + +static void __exit qinq_cleanup_module(void) +{ + dev_remove_pack(&qinq_packet_type); +} +module_exit(qinq_cleanup_module); diff --git a/net/dsa/tag_trailer.c b/net/dsa/tag_trailer.c index d6d7d0a..9aa5ede 100644 --- a/net/dsa/tag_trailer.c +++ b/net/dsa/tag_trailer.c @@ -87,7 +87,7 @@ static int trailer_rcv(struct sk_buff *skb, struct net_device *dev, trailer = skb_tail_pointer(skb) - 4; if (trailer[0] != 0x80 || (trailer[1] & 0xf8) != 0x00 || - (trailer[3] & 0xef) != 0x00 || trailer[3] != 0x00) + (trailer[2] & 0xef) != 0x00 || (trailer[3] & 0xfe) != 0x00) goto out_drop; source_port = trailer[1] & 7; -- 1.7.4.1