From 9ad661a072d094e9b3624caf74d14beec9537df5 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 16 Aug 2010 19:21:57 +0000 Subject: [PATCH] ar71xx: add a new driver for the ar7240 switch using swconfig. hooks directly into the ethernet driver, as MAC resets also require switch reinitializations and the switch is part of the cpu core anyway switch only tl-wr741nd (and other devices using this board file, such as tl-wr841nd) over to using this by default, as other devices are still untested fixes #7563 SVN-Revision: 22675 --- .../etc/defconfig/tl-wr741nd/network | 14 +- .../files/arch/mips/ar71xx/mach-tl-wr741nd.c | 18 +- .../mips/include/asm/mach-ar71xx/platform.h | 1 + .../ar71xx/files/drivers/net/ag71xx/Makefile | 1 + .../ar71xx/files/drivers/net/ag71xx/ag71xx.h | 8 +- .../files/drivers/net/ag71xx/ag71xx_ar7240.c | 851 ++++++++++++++++++ .../files/drivers/net/ag71xx/ag71xx_phy.c | 17 +- 7 files changed, 902 insertions(+), 8 deletions(-) create mode 100644 target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_ar7240.c diff --git a/target/linux/ar71xx/base-files/etc/defconfig/tl-wr741nd/network b/target/linux/ar71xx/base-files/etc/defconfig/tl-wr741nd/network index 2108d3ae8e..de201bf5ec 100644 --- a/target/linux/ar71xx/base-files/etc/defconfig/tl-wr741nd/network +++ b/target/linux/ar71xx/base-files/etc/defconfig/tl-wr741nd/network @@ -4,12 +4,8 @@ config interface loopback option ipaddr 127.0.0.1 option netmask 255.0.0.0 -config interface eth - option ifname eth0 - option proto none - config interface lan - option ifname 'lan1 lan2 lan3 lan4' + option ifname eth0 option type bridge option proto static option ipaddr 192.168.1.1 @@ -18,3 +14,11 @@ config interface lan config interface wan option ifname eth1 option proto dhcp + +config switch eth0 + option enable_vlan 1 + +config switch_vlan + option device eth0 + option vlan 1 + option ports "0 1 2 3 4" diff --git a/target/linux/ar71xx/files/arch/mips/ar71xx/mach-tl-wr741nd.c b/target/linux/ar71xx/files/arch/mips/ar71xx/mach-tl-wr741nd.c index 93332228df..ed217f9e23 100644 --- a/target/linux/ar71xx/files/arch/mips/ar71xx/mach-tl-wr741nd.c +++ b/target/linux/ar71xx/files/arch/mips/ar71xx/mach-tl-wr741nd.c @@ -108,7 +108,23 @@ static void __init tl_wr741nd_setup(void) ARRAY_SIZE(tl_wr741nd_gpio_buttons), tl_wr741nd_gpio_buttons); - ap91_eth_init(mac, NULL); + ar71xx_eth1_data.has_ar7240_switch = 1; + ar71xx_set_mac_base(mac); + + /* WAN port */ + ar71xx_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; + ar71xx_eth0_data.speed = SPEED_100; + ar71xx_eth0_data.duplex = DUPLEX_FULL; + + /* LAN ports */ + ar71xx_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; + ar71xx_eth1_data.speed = SPEED_1000; + ar71xx_eth1_data.duplex = DUPLEX_FULL; + + ar71xx_add_device_mdio(0x0); + ar71xx_add_device_eth(1); + ar71xx_add_device_eth(0); + ap91_pci_init(ee, mac); } MIPS_MACHINE(AR71XX_MACH_TL_WR741ND, "TL-WR741ND", "TP-LINK TL-WR741ND", diff --git a/target/linux/ar71xx/files/arch/mips/include/asm/mach-ar71xx/platform.h b/target/linux/ar71xx/files/arch/mips/include/asm/mach-ar71xx/platform.h index 145e79fcea..cf198d2bfa 100644 --- a/target/linux/ar71xx/files/arch/mips/include/asm/mach-ar71xx/platform.h +++ b/target/linux/ar71xx/files/arch/mips/include/asm/mach-ar71xx/platform.h @@ -31,6 +31,7 @@ struct ag71xx_platform_data { u8 is_ar91xx:1; u8 is_ar724x:1; u8 has_ar8216:1; + u8 has_ar7240_switch:1; void (* ddr_flush)(void); void (* set_pll)(int speed); diff --git a/target/linux/ar71xx/files/drivers/net/ag71xx/Makefile b/target/linux/ar71xx/files/drivers/net/ag71xx/Makefile index 3485ab385d..b3ec4084c8 100644 --- a/target/linux/ar71xx/files/drivers/net/ag71xx/Makefile +++ b/target/linux/ar71xx/files/drivers/net/ag71xx/Makefile @@ -6,6 +6,7 @@ ag71xx-y += ag71xx_main.o ag71xx-y += ag71xx_ethtool.o ag71xx-y += ag71xx_phy.o ag71xx-y += ag71xx_mdio.o +ag71xx-y += ag71xx_ar7240.o ag71xx-$(CONFIG_AG71XX_DEBUG_FS) += ag71xx_debugfs.o ag71xx-$(CONFIG_AG71XX_AR8216_SUPPORT) += ag71xx_ar8216.o diff --git a/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx.h b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx.h index af41972481..be14e7e08b 100644 --- a/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx.h +++ b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx.h @@ -51,7 +51,7 @@ #define AG71XX_INT_INIT (AG71XX_INT_ERR | AG71XX_INT_POLL) #define AG71XX_TX_FIFO_LEN 2048 -#define AG71XX_TX_MTU_LEN 1536 +#define AG71XX_TX_MTU_LEN 1540 #define AG71XX_RX_PKT_RESERVE 64 #define AG71XX_RX_PKT_SIZE \ (AG71XX_RX_PKT_RESERVE + ETH_HLEN + ETH_FRAME_LEN + ETH_FCS_LEN) @@ -158,6 +158,7 @@ struct ag71xx { struct mii_bus *mii_bus; struct phy_device *phy_dev; + void *phy_priv; unsigned int link; unsigned int speed; @@ -497,4 +498,9 @@ static inline void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx) {} #endif /* CONFIG_AG71XX_DEBUG_FS */ +void ag71xx_ar7240_start(struct ag71xx *ag); +void ag71xx_ar7240_stop(struct ag71xx *ag); +int ag71xx_ar7240_init(struct ag71xx *ag); +void ag71xx_ar7240_cleanup(struct ag71xx *ag); + #endif /* _AG71XX_H */ diff --git a/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_ar7240.c b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_ar7240.c new file mode 100644 index 0000000000..e299e68af2 --- /dev/null +++ b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_ar7240.c @@ -0,0 +1,851 @@ +/* + * Driver for the built-in ethernet switch of the Atheros AR7240 SoC + * Copyright (c) 2010 Gabor Juhos + * Copyright (c) 2010 Felix Fietkau + * + * 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 +#include "ag71xx.h" + +#define BITM(_count) (BIT(_count) - 1) +#define BITS(_shift, _count) (BITM(_count) << _shift) + +#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_VTU 0x0040 +#define AR7240_VTU_OP BITM(3) +#define AR7240_VTU_OP_NOOP 0x0 +#define AR7240_VTU_OP_FLUSH 0x1 +#define AR7240_VTU_OP_LOAD 0x2 +#define AR7240_VTU_OP_PURGE 0x3 +#define AR7240_VTU_OP_REMOVE_PORT 0x4 +#define AR7240_VTU_ACTIVE BIT(3) +#define AR7240_VTU_FULL BIT(4) +#define AR7240_VTU_PORT BITS(8, 4) +#define AR7240_VTU_PORT_S 8 +#define AR7240_VTU_VID BITS(16, 12) +#define AR7240_VTU_VID_S 16 +#define AR7240_VTU_PRIO BITS(28, 3) +#define AR7240_VTU_PRIO_S 28 +#define AR7240_VTU_PRIO_EN BIT(31) + +#define AR7240_REG_VTU_DATA 0x0044 +#define AR7240_VTUDATA_MEMBER BITS(0, 10) +#define AR7240_VTUDATA_VALID BIT(11) + +#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_PORT_VLAN_MODE_S 30 +#define AR7240_PORT_VLAN_MODE_PORT_ONLY 0 +#define AR7240_PORT_VLAN_MODE_PORT_FALLBACK 1 +#define AR7240_PORT_VLAN_MODE_VLAN_ONLY 2 +#define AR7240_PORT_VLAN_MODE_SECURE 3 + + +#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))) + +#define AR7240_MAX_VLANS 16 + +#define sw_to_ar7240(_dev) container_of(_dev, struct ar7240sw, swdev) + +struct ar7240sw { + struct mii_bus *mii_bus; + struct mutex reg_mutex; + struct switch_dev swdev; + bool vlan; + u16 vlan_id[AR7240_MAX_VLANS]; + u8 vlan_table[AR7240_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR7240_NUM_PORTS]; +}; + +struct ar7240sw_hw_stat { + char string[ETH_GSTRING_LEN]; + int sizeof_stat; + int reg; +}; + + +static inline void ar7240sw_init(struct ar7240sw *as, struct mii_bus *mii) +{ + as->mii_bus = mii; + mutex_init(&as->reg_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, 0); +} + +static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port, u8 portmask) +{ + u32 ctrl; + u32 dest_ports; + u32 vlan; + + ctrl = AR7240_PORT_CTRL_STATE_FORWARD | AR7240_PORT_CTRL_LEARN | + AR7240_PORT_CTRL_SINGLE_VLAN; + + 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); + } else { + ar7240sw_reg_write(as, AR7240_REG_PORT_STATUS(port), + AR7240_PORT_STATUS_LINK_AUTO); + } + + /* Set the default VID for this port */ + if (as->vlan) { + vlan = as->vlan_id[as->pvid[port]]; + vlan |= AR7240_PORT_VLAN_MODE_SECURE << + AR7240_PORT_VLAN_MODE_S; + } else { + vlan = port; + vlan |= AR7240_PORT_VLAN_MODE_PORT_ONLY << + AR7240_PORT_VLAN_MODE_S; + } + + if (as->vlan && (as->vlan_tagged & BIT(port))) { + ctrl |= AR7240_PORT_CTRL_VLAN_MODE_ADD << + AR7240_PORT_CTRL_VLAN_MODE_S; + } else { + ctrl |= AR7240_PORT_CTRL_VLAN_MODE_STRIP << + AR7240_PORT_CTRL_VLAN_MODE_S; + } + + if (!portmask) { + if (port == AR7240_PORT_CPU) + portmask = AR7240_PORT_MASK_BUT(AR7240_PORT_CPU); + else + portmask = AR7240_PORT_MASK(AR7240_PORT_CPU); + } + + /* allow the port to talk to all other ports, but exclude its + * own ID to prevent frames from being reflected back to the + * port that they came from */ + dest_ports = AR7240_PORT_MASK_BUT(port); + + /* set default VID and and destination ports for this VLAN */ + vlan |= (portmask << 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 int ar7240_set_addr(struct ar7240sw *as, u8 *addr) +{ + 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_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + as->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar7240_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + val->value.i = as->vlan_id[val->port_vlan]; + return 0; +} + +static int +ar7240_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan >= dev->vlans) + return -EINVAL; + + as->pvid[port] = vlan; + return 0; +} + +static int +ar7240_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + *vlan = as->pvid[port]; + return 0; +} + +static int +ar7240_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 ports = as->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < AR7240_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (as->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar7240_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 *vt = &as->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + as->vlan_tagged |= (1 << p->id); + else { + as->vlan_tagged &= ~(1 << p->id); + as->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR7240_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + as->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int +ar7240_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + as->vlan = !!val->value.i; + return 0; +} + +static int +ar7240_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + val->value.i = as->vlan; + return 0; +} + + +static void +ar7240_vtu_op(struct ar7240sw *as, u32 op, u32 val) +{ + if (ar7240sw_reg_wait(as, AR7240_REG_VTU, AR7240_VTU_ACTIVE, 0, 5)) + return; + + if ((op & AR7240_VTU_OP) == AR7240_VTU_OP_LOAD) { + val &= AR7240_VTUDATA_MEMBER; + val |= AR7240_VTUDATA_VALID; + ar7240sw_reg_write(as, AR7240_REG_VTU_DATA, val); + } + op |= AR7240_VTU_ACTIVE; + ar7240sw_reg_write(as, AR7240_REG_VTU, op); +} + +static int +ar7240_hw_apply(struct switch_dev *dev) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 portmask[AR7240_NUM_PORTS]; + int i, j; + + /* flush all vlan translation unit entries */ + ar7240_vtu_op(as, AR7240_VTU_OP_FLUSH, 0); + + memset(portmask, 0, sizeof(portmask)); + if (as->vlan) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR7240_MAX_VLANS; j++) { + u8 vp = as->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < AR7240_NUM_PORTS; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + ar7240_vtu_op(as, + AR7240_VTU_OP_LOAD | + (as->vlan_id[j] << AR7240_VTU_VID_S), + as->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < AR7240_NUM_PORTS; i++) { + if (i == AR7240_PORT_CPU) + continue; + + portmask[i] = 1 << AR7240_PORT_CPU; + portmask[AR7240_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < AR7240_NUM_PORTS; i++) + ar7240sw_setup_port(as, i, portmask[i]); + + return 0; +} + +static int +ar7240_reset_switch(struct switch_dev *dev) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + ar7240sw_reset(as); + return 0; +} + +static struct switch_attr ar7240_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar7240_set_vlan, + .get = ar7240_get_vlan, + .max = 1 + }, +}; + +static struct switch_attr ar7240_port[] = { +}; + +static struct switch_attr ar7240_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = ar7240_set_vid, + .get = ar7240_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar7240_ops = { + .attr_global = { + .attr = ar7240_globals, + .n_attr = ARRAY_SIZE(ar7240_globals), + }, + .attr_port = { + .attr = ar7240_port, + .n_attr = ARRAY_SIZE(ar7240_port), + }, + .attr_vlan = { + .attr = ar7240_vlan, + .n_attr = ARRAY_SIZE(ar7240_vlan), + }, + .get_port_pvid = ar7240_get_pvid, + .set_port_pvid = ar7240_set_pvid, + .get_vlan_ports = ar7240_get_ports, + .set_vlan_ports = ar7240_set_ports, + .apply_config = ar7240_hw_apply, + .reset_switch = ar7240_reset_switch, +}; + +static struct ar7240sw *ar7240_probe(struct ag71xx *ag) +{ + struct mii_bus *mii = ag->mii_bus; + struct ar7240sw *as; + struct switch_dev *swdev; + u32 ctrl; + u16 phy_id1; + u16 phy_id2; + u8 ver; + int i; + + as = kzalloc(sizeof(*as), GFP_KERNEL); + if (!as) + return NULL; + + 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("%s: unsupported chip, ctrl=%08x\n", ag->dev->name, 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("%s: unknown phy id '%04x:%04x'\n", + ag->dev->name, phy_id1, phy_id2); + return NULL; + } + + swdev = &as->swdev; + swdev->name = "AR7240 built-in switch"; + swdev->ports = AR7240_NUM_PORTS; + swdev->cpu_port = AR7240_PORT_CPU; + swdev->vlans = AR7240_MAX_VLANS; + swdev->ops = &ar7240_ops; + + if (register_switch(&as->swdev, ag->dev) < 0) { + kfree(as); + return NULL; + } + + printk("%s: Found an AR7240 built-in switch\n", ag->dev->name); + + /* initialize defaults */ + for (i = 0; i < AR7240_MAX_VLANS; i++) + as->vlan_id[i] = i; + + as->vlan_table[0] = AR7240_PORT_MASK_ALL; + + return as; +} + +void ag71xx_ar7240_start(struct ag71xx *ag) +{ + struct ar7240sw *as = ag->phy_priv; + + ar7240sw_reset(as); + ar7240sw_setup(as); + + ag->speed = SPEED_1000; + ag->link = 1; + ag->duplex = 1; + + ar7240_set_addr(as, ag->dev->dev_addr); + ar7240_hw_apply(&as->swdev); +} + +void ag71xx_ar7240_stop(struct ag71xx *ag) +{ +} + +int __init ag71xx_ar7240_init(struct ag71xx *ag) +{ + struct ar7240sw *as; + + as = ar7240_probe(ag); + if (!as) + return -ENODEV; + + ag->phy_priv = as; + ar7240sw_reset(as); + + return 0; +} + +void __exit ag71xx_ar7240_cleanup(struct ag71xx *ag) +{ + struct ar7240sw *as = ag->phy_priv; + + if (!as) + return; + + unregister_switch(&as->swdev); + kfree(as); + ag->phy_priv = NULL; +} diff --git a/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_phy.c b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_phy.c index 9c76544aff..eada693e77 100644 --- a/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_phy.c +++ b/target/linux/ar71xx/files/drivers/net/ag71xx/ag71xx_phy.c @@ -44,9 +44,13 @@ static void ag71xx_phy_link_adjust(struct net_device *dev) void ag71xx_phy_start(struct ag71xx *ag) { + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + if (ag->phy_dev) { phy_start(ag->phy_dev); } else { + if (pdata->has_ar7240_switch) + ag71xx_ar7240_start(ag); ag->link = 1; ag71xx_link_adjust(ag); } @@ -54,9 +58,13 @@ void ag71xx_phy_start(struct ag71xx *ag) void ag71xx_phy_stop(struct ag71xx *ag) { + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + if (ag->phy_dev) { phy_stop(ag->phy_dev); } else { + if (pdata->has_ar7240_switch) + ag71xx_ar7240_stop(ag); ag->link = 0; ag71xx_link_adjust(ag); } @@ -200,6 +208,9 @@ int ag71xx_phy_connect(struct ag71xx *ag) mutex_unlock(&ag->mii_bus->mdio_lock); } + if (pdata->has_ar7240_switch) + return ag71xx_ar7240_init(ag); + if (pdata->phy_mask) return ag71xx_phy_connect_multi(ag); @@ -208,6 +219,10 @@ int ag71xx_phy_connect(struct ag71xx *ag) void ag71xx_phy_disconnect(struct ag71xx *ag) { - if (ag->phy_dev) + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (pdata->has_ar7240_switch) + ag71xx_ar7240_cleanup(ag); + else if (ag->phy_dev) phy_disconnect(ag->phy_dev); } -- 2.30.2