diff options
Diffstat (limited to 'target/mips')
28 files changed, 14925 insertions, 0 deletions
diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0001-mtd-add-rb4xx-nand-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0001-mtd-add-rb4xx-nand-driver.patch new file mode 100644 index 000000000..8199de991 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0001-mtd-add-rb4xx-nand-driver.patch @@ -0,0 +1,351 @@ +From 1e692cc0c53202b932eedabd0315107910c5b093 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:08:54 +0200 +Subject: [PATCH] mtd: add rb4xx nand driver + +--- + drivers/mtd/nand/Kconfig | 4 + + drivers/mtd/nand/Makefile | 1 + + drivers/mtd/nand/rb4xx_nand.c | 305 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 310 insertions(+) + create mode 100644 drivers/mtd/nand/rb4xx_nand.c + +diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig +index 90ff447..bb01309 100644 +--- a/drivers/mtd/nand/Kconfig ++++ b/drivers/mtd/nand/Kconfig +@@ -510,4 +510,8 @@ config MTD_NAND_XWAY + Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached + to the External Bus Unit (EBU). + ++config MTD_NAND_RB4XX ++ tristate "NAND flash driver for RouterBoard 4xx series" ++ depends on MTD_NAND && ATH79_MACH_RB4XX ++ + endif # MTD_NAND +diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile +index 542b568..e2b5e1c 100644 +--- a/drivers/mtd/nand/Makefile ++++ b/drivers/mtd/nand/Makefile +@@ -31,6 +31,7 @@ obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o + obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o + obj-$(CONFIG_MTD_NAND_TMIO) += tmio_nand.o + obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o ++obj-$(CONFIG_MTD_NAND_RB4XX) += rb4xx_nand.o + obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o + obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o + obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o +diff --git a/drivers/mtd/nand/rb4xx_nand.c b/drivers/mtd/nand/rb4xx_nand.c +new file mode 100644 +index 0000000..5b9841b +--- /dev/null ++++ b/drivers/mtd/nand/rb4xx_nand.c +@@ -0,0 +1,305 @@ ++/* ++ * NAND flash driver for the MikroTik RouterBoard 4xx series ++ * ++ * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * This file was based on the driver for Linux 2.6.22 published by ++ * MikroTik for their RouterBoard 4xx series devices. ++ * ++ * 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 <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/mtd/nand.h> ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/partitions.h> ++#include <linux/platform_device.h> ++#include <linux/delay.h> ++#include <linux/io.h> ++#include <linux/gpio.h> ++#include <linux/slab.h> ++ ++#include <asm/mach-ath79/ath79.h> ++#include <asm/mach-ath79/rb4xx_cpld.h> ++ ++#define DRV_NAME "rb4xx-nand" ++#define DRV_VERSION "0.2.0" ++#define DRV_DESC "NAND flash driver for RouterBoard 4xx series" ++ ++#define RB4XX_NAND_GPIO_READY 5 ++#define RB4XX_NAND_GPIO_ALE 37 ++#define RB4XX_NAND_GPIO_CLE 38 ++#define RB4XX_NAND_GPIO_NCE 39 ++ ++struct rb4xx_nand_info { ++ struct nand_chip chip; ++ struct mtd_info mtd; ++}; ++ ++/* ++ * We need to use the OLD Yaffs-1 OOB layout, otherwise the RB bootloader ++ * will not be able to find the kernel that we load. ++ */ ++static struct nand_ecclayout rb4xx_nand_ecclayout = { ++ .eccbytes = 6, ++ .eccpos = { 8, 9, 10, 13, 14, 15 }, ++ .oobavail = 9, ++ .oobfree = { { 0, 4 }, { 6, 2 }, { 11, 2 }, { 4, 1 } } ++}; ++ ++static struct mtd_partition rb4xx_nand_partitions[] = { ++ { ++ .name = "booter", ++ .offset = 0, ++ .size = (256 * 1024), ++ .mask_flags = MTD_WRITEABLE, ++ }, ++ { ++ .name = "kernel", ++ .offset = (256 * 1024), ++ .size = (4 * 1024 * 1024) - (256 * 1024), ++ }, ++ { ++ .name = "rootfs", ++ .offset = MTDPART_OFS_NXTBLK, ++ .size = MTDPART_SIZ_FULL, ++ }, ++}; ++ ++static int rb4xx_nand_dev_ready(struct mtd_info *mtd) ++{ ++ return gpio_get_value_cansleep(RB4XX_NAND_GPIO_READY); ++} ++ ++static void rb4xx_nand_write_cmd(unsigned char cmd) ++{ ++ unsigned char data = cmd; ++ int err; ++ ++ err = rb4xx_cpld_write(&data, 1); ++ if (err) ++ pr_err("rb4xx_nand: write cmd failed, err=%d\n", err); ++} ++ ++static void rb4xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, ++ unsigned int ctrl) ++{ ++ if (ctrl & NAND_CTRL_CHANGE) { ++ gpio_set_value_cansleep(RB4XX_NAND_GPIO_CLE, ++ (ctrl & NAND_CLE) ? 1 : 0); ++ gpio_set_value_cansleep(RB4XX_NAND_GPIO_ALE, ++ (ctrl & NAND_ALE) ? 1 : 0); ++ gpio_set_value_cansleep(RB4XX_NAND_GPIO_NCE, ++ (ctrl & NAND_NCE) ? 0 : 1); ++ } ++ ++ if (cmd != NAND_CMD_NONE) ++ rb4xx_nand_write_cmd(cmd); ++} ++ ++static unsigned char rb4xx_nand_read_byte(struct mtd_info *mtd) ++{ ++ unsigned char data = 0; ++ int err; ++ ++ err = rb4xx_cpld_read(&data, NULL, 1); ++ if (err) { ++ pr_err("rb4xx_nand: read data failed, err=%d\n", err); ++ data = 0xff; ++ } ++ ++ return data; ++} ++ ++static void rb4xx_nand_write_buf(struct mtd_info *mtd, const unsigned char *buf, ++ int len) ++{ ++ int err; ++ ++ err = rb4xx_cpld_write(buf, len); ++ if (err) ++ pr_err("rb4xx_nand: write buf failed, err=%d\n", err); ++} ++ ++static void rb4xx_nand_read_buf(struct mtd_info *mtd, unsigned char *buf, ++ int len) ++{ ++ int err; ++ ++ err = rb4xx_cpld_read(buf, NULL, len); ++ if (err) ++ pr_err("rb4xx_nand: read buf failed, err=%d\n", err); ++} ++ ++static int rb4xx_nand_probe(struct platform_device *pdev) ++{ ++ struct rb4xx_nand_info *info; ++ int ret; ++ ++ printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); ++ ++ ret = gpio_request(RB4XX_NAND_GPIO_READY, "NAND RDY"); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to request gpio %d\n", ++ RB4XX_NAND_GPIO_READY); ++ goto err; ++ } ++ ++ ret = gpio_direction_input(RB4XX_NAND_GPIO_READY); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to set input mode on gpio %d\n", ++ RB4XX_NAND_GPIO_READY); ++ goto err_free_gpio_ready; ++ } ++ ++ ret = gpio_request(RB4XX_NAND_GPIO_ALE, "NAND ALE"); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to request gpio %d\n", ++ RB4XX_NAND_GPIO_ALE); ++ goto err_free_gpio_ready; ++ } ++ ++ ret = gpio_direction_output(RB4XX_NAND_GPIO_ALE, 0); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", ++ RB4XX_NAND_GPIO_ALE); ++ goto err_free_gpio_ale; ++ } ++ ++ ret = gpio_request(RB4XX_NAND_GPIO_CLE, "NAND CLE"); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to request gpio %d\n", ++ RB4XX_NAND_GPIO_CLE); ++ goto err_free_gpio_ale; ++ } ++ ++ ret = gpio_direction_output(RB4XX_NAND_GPIO_CLE, 0); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", ++ RB4XX_NAND_GPIO_CLE); ++ goto err_free_gpio_cle; ++ } ++ ++ ret = gpio_request(RB4XX_NAND_GPIO_NCE, "NAND NCE"); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to request gpio %d\n", ++ RB4XX_NAND_GPIO_NCE); ++ goto err_free_gpio_cle; ++ } ++ ++ ret = gpio_direction_output(RB4XX_NAND_GPIO_NCE, 1); ++ if (ret) { ++ dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", ++ RB4XX_NAND_GPIO_ALE); ++ goto err_free_gpio_nce; ++ } ++ ++ info = kzalloc(sizeof(*info), GFP_KERNEL); ++ if (!info) { ++ dev_err(&pdev->dev, "rb4xx-nand: no memory for private data\n"); ++ ret = -ENOMEM; ++ goto err_free_gpio_nce; ++ } ++ ++ info->chip.priv = &info; ++ info->mtd.priv = &info->chip; ++ info->mtd.owner = THIS_MODULE; ++ ++ info->chip.cmd_ctrl = rb4xx_nand_cmd_ctrl; ++ info->chip.dev_ready = rb4xx_nand_dev_ready; ++ info->chip.read_byte = rb4xx_nand_read_byte; ++ info->chip.write_buf = rb4xx_nand_write_buf; ++ info->chip.read_buf = rb4xx_nand_read_buf; ++ ++ info->chip.chip_delay = 25; ++ info->chip.ecc.mode = NAND_ECC_SOFT; ++ ++ platform_set_drvdata(pdev, info); ++ ++ ret = nand_scan_ident(&info->mtd, 1, NULL); ++ if (ret) { ++ ret = -ENXIO; ++ goto err_free_info; ++ } ++ ++ if (info->mtd.writesize == 512) ++ info->chip.ecc.layout = &rb4xx_nand_ecclayout; ++ ++ ret = nand_scan_tail(&info->mtd); ++ if (ret) { ++ return -ENXIO; ++ goto err_set_drvdata; ++ } ++ ++ mtd_device_register(&info->mtd, rb4xx_nand_partitions, ++ ARRAY_SIZE(rb4xx_nand_partitions)); ++ if (ret) ++ goto err_release_nand; ++ ++ return 0; ++ ++err_release_nand: ++ nand_release(&info->mtd); ++err_set_drvdata: ++ platform_set_drvdata(pdev, NULL); ++err_free_info: ++ kfree(info); ++err_free_gpio_nce: ++ gpio_free(RB4XX_NAND_GPIO_NCE); ++err_free_gpio_cle: ++ gpio_free(RB4XX_NAND_GPIO_CLE); ++err_free_gpio_ale: ++ gpio_free(RB4XX_NAND_GPIO_ALE); ++err_free_gpio_ready: ++ gpio_free(RB4XX_NAND_GPIO_READY); ++err: ++ return ret; ++} ++ ++static int rb4xx_nand_remove(struct platform_device *pdev) ++{ ++ struct rb4xx_nand_info *info = platform_get_drvdata(pdev); ++ ++ nand_release(&info->mtd); ++ platform_set_drvdata(pdev, NULL); ++ kfree(info); ++ gpio_free(RB4XX_NAND_GPIO_NCE); ++ gpio_free(RB4XX_NAND_GPIO_CLE); ++ gpio_free(RB4XX_NAND_GPIO_ALE); ++ gpio_free(RB4XX_NAND_GPIO_READY); ++ ++ return 0; ++} ++ ++static struct platform_driver rb4xx_nand_driver = { ++ .probe = rb4xx_nand_probe, ++ .remove = rb4xx_nand_remove, ++ .driver = { ++ .name = DRV_NAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init rb4xx_nand_init(void) ++{ ++ return platform_driver_register(&rb4xx_nand_driver); ++} ++ ++static void __exit rb4xx_nand_exit(void) ++{ ++ platform_driver_unregister(&rb4xx_nand_driver); ++} ++ ++module_init(rb4xx_nand_init); ++module_exit(rb4xx_nand_exit); ++ ++MODULE_DESCRIPTION(DRV_DESC); ++MODULE_VERSION(DRV_VERSION); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0002-phy-add-ethtool-ioctl-support-used-by-ag71xx-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0002-phy-add-ethtool-ioctl-support-used-by-ag71xx-driver.patch new file mode 100644 index 000000000..ba7fbfad8 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0002-phy-add-ethtool-ioctl-support-used-by-ag71xx-driver.patch @@ -0,0 +1,80 @@ +From 7b864612a6e3b139a5a607abd0048a19078fe42f Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Wed, 14 May 2014 02:55:06 +0200 +Subject: [PATCH] phy: add ethtool ioctl support, used by ag71xx driver + +--- + drivers/net/phy/phy.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ + include/linux/phy.h | 1 + + 2 files changed, 45 insertions(+) + +diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c +index 76d96b9..9439ef3 100644 +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -293,6 +293,50 @@ int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd) + } + EXPORT_SYMBOL(phy_ethtool_gset); + ++int phy_ethtool_ioctl(struct phy_device *phydev, void *useraddr) ++{ ++ u32 cmd; ++ int tmp; ++ struct ethtool_cmd ecmd = { ETHTOOL_GSET }; ++ struct ethtool_value edata = { ETHTOOL_GLINK }; ++ ++ if (get_user(cmd, (u32 *) useraddr)) ++ return -EFAULT; ++ ++ switch (cmd) { ++ case ETHTOOL_GSET: ++ phy_ethtool_gset(phydev, &ecmd); ++ if (copy_to_user(useraddr, &ecmd, sizeof(ecmd))) ++ return -EFAULT; ++ return 0; ++ ++ case ETHTOOL_SSET: ++ if (copy_from_user(&ecmd, useraddr, sizeof(ecmd))) ++ return -EFAULT; ++ return phy_ethtool_sset(phydev, &ecmd); ++ ++ case ETHTOOL_NWAY_RST: ++ /* if autoneg is off, it's an error */ ++ tmp = phy_read(phydev, MII_BMCR); ++ if (tmp & BMCR_ANENABLE) { ++ tmp |= (BMCR_ANRESTART); ++ phy_write(phydev, MII_BMCR, tmp); ++ return 0; ++ } ++ return -EINVAL; ++ ++ case ETHTOOL_GLINK: ++ edata.data = (phy_read(phydev, ++ MII_BMSR) & BMSR_LSTATUS) ? 1 : 0; ++ if (copy_to_user(useraddr, &edata, sizeof(edata))) ++ return -EFAULT; ++ return 0; ++ } ++ ++ return -EOPNOTSUPP; ++} ++EXPORT_SYMBOL(phy_ethtool_ioctl); ++ + /** + * phy_mii_ioctl - generic PHY MII ioctl interface + * @phydev: the phy_device struct +diff --git a/include/linux/phy.h b/include/linux/phy.h +index 565188c..9ab0d79 100644 +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -628,6 +628,7 @@ void phy_stop_machine(struct phy_device *phydev); + int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd); + int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd); + int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd); ++int phy_ethtool_ioctl(struct phy_device *phydev, void *useraddr); + int phy_start_interrupts(struct phy_device *phydev); + void phy_print_status(struct phy_device *phydev); + void phy_device_free(struct phy_device *phydev); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0003-net-add-ag71xx-mac-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0003-net-add-ag71xx-mac-driver.patch new file mode 100644 index 000000000..1915c184c --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0003-net-add-ag71xx-mac-driver.patch @@ -0,0 +1,4245 @@ +From c5eb03f91f9185f4813431692f36db3862716a35 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:12:37 +0200 +Subject: [PATCH] net: add ag71xx mac driver + +--- + arch/mips/include/asm/mach-ath79/ag71xx_platform.h | 65 + + drivers/net/ethernet/atheros/Kconfig | 2 + + drivers/net/ethernet/atheros/Makefile | 1 + + drivers/net/ethernet/atheros/ag71xx/Kconfig | 33 + + drivers/net/ethernet/atheros/ag71xx/Makefile | 15 + + drivers/net/ethernet/atheros/ag71xx/ag71xx.h | 476 +++++++ + .../net/ethernet/atheros/ag71xx/ag71xx_ar7240.c | 1202 ++++++++++++++++++ + .../net/ethernet/atheros/ag71xx/ag71xx_ar8216.c | 44 + + .../net/ethernet/atheros/ag71xx/ag71xx_debugfs.c | 284 +++++ + .../net/ethernet/atheros/ag71xx/ag71xx_ethtool.c | 124 ++ + drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c | 1325 ++++++++++++++++++++ + drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c | 318 +++++ + drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c | 235 ++++ + 13 files changed, 4124 insertions(+) + create mode 100644 arch/mips/include/asm/mach-ath79/ag71xx_platform.h + create mode 100644 drivers/net/ethernet/atheros/ag71xx/Kconfig + create mode 100644 drivers/net/ethernet/atheros/ag71xx/Makefile + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx.h + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c + create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c + +diff --git a/arch/mips/include/asm/mach-ath79/ag71xx_platform.h b/arch/mips/include/asm/mach-ath79/ag71xx_platform.h +new file mode 100644 +index 0000000..d46dc4e +--- /dev/null ++++ b/arch/mips/include/asm/mach-ath79/ag71xx_platform.h +@@ -0,0 +1,65 @@ ++/* ++ * Atheros AR71xx SoC specific platform data definitions ++ * ++ * Copyright (C) 2008-2012 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * 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. ++ */ ++ ++#ifndef __ASM_MACH_ATH79_PLATFORM_H ++#define __ASM_MACH_ATH79_PLATFORM_H ++ ++#include <linux/if_ether.h> ++#include <linux/skbuff.h> ++#include <linux/phy.h> ++#include <linux/spi/spi.h> ++ ++struct ag71xx_switch_platform_data { ++ u8 phy4_mii_en:1; ++ u8 phy_poll_mask; ++}; ++ ++struct ag71xx_platform_data { ++ phy_interface_t phy_if_mode; ++ u32 phy_mask; ++ int speed; ++ int duplex; ++ u32 reset_bit; ++ u8 mac_addr[ETH_ALEN]; ++ struct device *mii_bus_dev; ++ ++ u8 has_gbit:1; ++ u8 is_ar91xx:1; ++ u8 is_ar7240:1; ++ u8 is_ar724x:1; ++ u8 has_ar8216:1; ++ ++ struct ag71xx_switch_platform_data *switch_data; ++ ++ void (*ddr_flush)(void); ++ void (*set_speed)(int speed); ++ ++ u32 fifo_cfg1; ++ u32 fifo_cfg2; ++ u32 fifo_cfg3; ++ ++ unsigned int max_frame_len; ++ unsigned int desc_pktlen_mask; ++}; ++ ++struct ag71xx_mdio_platform_data { ++ u32 phy_mask; ++ u8 builtin_switch:1; ++ u8 is_ar7240:1; ++ u8 is_ar9330:1; ++ u8 is_ar934x:1; ++ unsigned long mdio_clock; ++ unsigned long ref_clock; ++ ++ void (*reset)(struct mii_bus *bus); ++}; ++ ++#endif /* __ASM_MACH_ATH79_PLATFORM_H */ +diff --git a/drivers/net/ethernet/atheros/Kconfig b/drivers/net/ethernet/atheros/Kconfig +index 58ad37c..1fae572 100644 +--- a/drivers/net/ethernet/atheros/Kconfig ++++ b/drivers/net/ethernet/atheros/Kconfig +@@ -80,4 +80,6 @@ config ALX + To compile this driver as a module, choose M here. The module + will be called alx. + ++source drivers/net/ethernet/atheros/ag71xx/Kconfig ++ + endif # NET_VENDOR_ATHEROS +diff --git a/drivers/net/ethernet/atheros/Makefile b/drivers/net/ethernet/atheros/Makefile +index 5cf1c65..d1c5a49 100644 +--- a/drivers/net/ethernet/atheros/Makefile ++++ b/drivers/net/ethernet/atheros/Makefile +@@ -2,6 +2,7 @@ + # Makefile for the Atheros network device drivers. + # + ++obj-$(CONFIG_AG71XX) += ag71xx/ + obj-$(CONFIG_ATL1) += atlx/ + obj-$(CONFIG_ATL2) += atlx/ + obj-$(CONFIG_ATL1E) += atl1e/ +diff --git a/drivers/net/ethernet/atheros/ag71xx/Kconfig b/drivers/net/ethernet/atheros/ag71xx/Kconfig +new file mode 100644 +index 0000000..42d544f +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/Kconfig +@@ -0,0 +1,33 @@ ++config AG71XX ++ tristate "Atheros AR7XXX/AR9XXX built-in ethernet mac support" ++ depends on ATH79 ++ select PHYLIB ++ help ++ If you wish to compile a kernel for AR7XXX/91XXX and enable ++ ethernet support, then you should always answer Y to this. ++ ++if AG71XX ++ ++config AG71XX_DEBUG ++ bool "Atheros AR71xx built-in ethernet driver debugging" ++ default n ++ help ++ Atheros AR71xx built-in ethernet driver debugging messages. ++ ++config AG71XX_DEBUG_FS ++ bool "Atheros AR71xx built-in ethernet driver debugfs support" ++ depends on DEBUG_FS ++ default n ++ help ++ Say Y, if you need access to various statistics provided by ++ the ag71xx driver. ++ ++config AG71XX_AR8216_SUPPORT ++ bool "special support for the Atheros AR8216 switch" ++ default n ++ default y if ATH79_MACH_WNR2000 || ATH79_MACH_MZK_W04NU ++ help ++ Say 'y' here if you want to enable special support for the ++ Atheros AR8216 switch found on some boards. ++ ++endif +diff --git a/drivers/net/ethernet/atheros/ag71xx/Makefile b/drivers/net/ethernet/atheros/ag71xx/Makefile +new file mode 100644 +index 0000000..b3ec408 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/Makefile +@@ -0,0 +1,15 @@ ++# ++# Makefile for the Atheros AR71xx built-in ethernet macs ++# ++ ++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 ++ ++obj-$(CONFIG_AG71XX) += ag71xx.o ++ +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx.h b/drivers/net/ethernet/atheros/ag71xx/ag71xx.h +new file mode 100644 +index 0000000..f6d85b9 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx.h +@@ -0,0 +1,476 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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. ++ */ ++ ++#ifndef __AG71XX_H ++#define __AG71XX_H ++ ++#include <linux/kernel.h> ++#include <linux/version.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/types.h> ++#include <linux/random.h> ++#include <linux/spinlock.h> ++#include <linux/interrupt.h> ++#include <linux/platform_device.h> ++#include <linux/ethtool.h> ++#include <linux/etherdevice.h> ++#include <linux/if_vlan.h> ++#include <linux/phy.h> ++#include <linux/skbuff.h> ++#include <linux/dma-mapping.h> ++#include <linux/workqueue.h> ++ ++#include <linux/bitops.h> ++ ++#include <asm/mach-ath79/ar71xx_regs.h> ++#include <asm/mach-ath79/ath79.h> ++#include <asm/mach-ath79/ag71xx_platform.h> ++ ++#define AG71XX_DRV_NAME "ag71xx" ++#define AG71XX_DRV_VERSION "0.5.35" ++ ++#define AG71XX_NAPI_WEIGHT 64 ++#define AG71XX_OOM_REFILL (1 + HZ/10) ++ ++#define AG71XX_INT_ERR (AG71XX_INT_RX_BE | AG71XX_INT_TX_BE) ++#define AG71XX_INT_TX (AG71XX_INT_TX_PS) ++#define AG71XX_INT_RX (AG71XX_INT_RX_PR | AG71XX_INT_RX_OF) ++ ++#define AG71XX_INT_POLL (AG71XX_INT_RX | AG71XX_INT_TX) ++#define AG71XX_INT_INIT (AG71XX_INT_ERR | AG71XX_INT_POLL) ++ ++#define AG71XX_TX_MTU_LEN 1540 ++ ++#define AG71XX_TX_RING_SIZE_DEFAULT 32 ++#define AG71XX_RX_RING_SIZE_DEFAULT 128 ++ ++#define AG71XX_TX_RING_SIZE_MAX 32 ++#define AG71XX_RX_RING_SIZE_MAX 128 ++ ++#ifdef CONFIG_AG71XX_DEBUG ++#define DBG(fmt, args...) pr_debug(fmt, ## args) ++#else ++#define DBG(fmt, args...) do {} while (0) ++#endif ++ ++#define ag71xx_assert(_cond) \ ++do { \ ++ if (_cond) \ ++ break; \ ++ printk("%s,%d: assertion failed\n", __FILE__, __LINE__); \ ++ BUG(); \ ++} while (0) ++ ++struct ag71xx_desc { ++ u32 data; ++ u32 ctrl; ++#define DESC_EMPTY BIT(31) ++#define DESC_MORE BIT(24) ++#define DESC_PKTLEN_M 0xfff ++ u32 next; ++ u32 pad; ++} __attribute__((aligned(4))); ++ ++struct ag71xx_buf { ++ union { ++ struct sk_buff *skb; ++ void *rx_buf; ++ }; ++ struct ag71xx_desc *desc; ++ union { ++ dma_addr_t dma_addr; ++ unsigned long timestamp; ++ }; ++ unsigned int len; ++}; ++ ++struct ag71xx_ring { ++ struct ag71xx_buf *buf; ++ u8 *descs_cpu; ++ dma_addr_t descs_dma; ++ unsigned int desc_size; ++ unsigned int curr; ++ unsigned int dirty; ++ unsigned int size; ++}; ++ ++struct ag71xx_mdio { ++ struct mii_bus *mii_bus; ++ int mii_irq[PHY_MAX_ADDR]; ++ void __iomem *mdio_base; ++ struct ag71xx_mdio_platform_data *pdata; ++}; ++ ++struct ag71xx_int_stats { ++ unsigned long rx_pr; ++ unsigned long rx_be; ++ unsigned long rx_of; ++ unsigned long tx_ps; ++ unsigned long tx_be; ++ unsigned long tx_ur; ++ unsigned long total; ++}; ++ ++struct ag71xx_napi_stats { ++ unsigned long napi_calls; ++ unsigned long rx_count; ++ unsigned long rx_packets; ++ unsigned long rx_packets_max; ++ unsigned long tx_count; ++ unsigned long tx_packets; ++ unsigned long tx_packets_max; ++ ++ unsigned long rx[AG71XX_NAPI_WEIGHT + 1]; ++ unsigned long tx[AG71XX_NAPI_WEIGHT + 1]; ++}; ++ ++struct ag71xx_debug { ++ struct dentry *debugfs_dir; ++ ++ struct ag71xx_int_stats int_stats; ++ struct ag71xx_napi_stats napi_stats; ++}; ++ ++struct ag71xx { ++ void __iomem *mac_base; ++ ++ spinlock_t lock; ++ struct platform_device *pdev; ++ struct net_device *dev; ++ struct napi_struct napi; ++ u32 msg_enable; ++ ++ struct ag71xx_desc *stop_desc; ++ dma_addr_t stop_desc_dma; ++ ++ struct ag71xx_ring rx_ring; ++ struct ag71xx_ring tx_ring; ++ ++ struct mii_bus *mii_bus; ++ struct phy_device *phy_dev; ++ void *phy_priv; ++ ++ unsigned int link; ++ unsigned int speed; ++ int duplex; ++ ++ unsigned int max_frame_len; ++ unsigned int desc_pktlen_mask; ++ unsigned int rx_buf_size; ++ ++ struct work_struct restart_work; ++ struct delayed_work link_work; ++ struct timer_list oom_timer; ++ ++#ifdef CONFIG_AG71XX_DEBUG_FS ++ struct ag71xx_debug debug; ++#endif ++}; ++ ++extern struct ethtool_ops ag71xx_ethtool_ops; ++void ag71xx_link_adjust(struct ag71xx *ag); ++ ++int ag71xx_mdio_driver_init(void) __init; ++void ag71xx_mdio_driver_exit(void); ++ ++int ag71xx_phy_connect(struct ag71xx *ag); ++void ag71xx_phy_disconnect(struct ag71xx *ag); ++void ag71xx_phy_start(struct ag71xx *ag); ++void ag71xx_phy_stop(struct ag71xx *ag); ++ ++static inline struct ag71xx_platform_data *ag71xx_get_pdata(struct ag71xx *ag) ++{ ++ return ag->pdev->dev.platform_data; ++} ++ ++static inline int ag71xx_desc_empty(struct ag71xx_desc *desc) ++{ ++ return (desc->ctrl & DESC_EMPTY) != 0; ++} ++ ++/* Register offsets */ ++#define AG71XX_REG_MAC_CFG1 0x0000 ++#define AG71XX_REG_MAC_CFG2 0x0004 ++#define AG71XX_REG_MAC_IPG 0x0008 ++#define AG71XX_REG_MAC_HDX 0x000c ++#define AG71XX_REG_MAC_MFL 0x0010 ++#define AG71XX_REG_MII_CFG 0x0020 ++#define AG71XX_REG_MII_CMD 0x0024 ++#define AG71XX_REG_MII_ADDR 0x0028 ++#define AG71XX_REG_MII_CTRL 0x002c ++#define AG71XX_REG_MII_STATUS 0x0030 ++#define AG71XX_REG_MII_IND 0x0034 ++#define AG71XX_REG_MAC_IFCTL 0x0038 ++#define AG71XX_REG_MAC_ADDR1 0x0040 ++#define AG71XX_REG_MAC_ADDR2 0x0044 ++#define AG71XX_REG_FIFO_CFG0 0x0048 ++#define AG71XX_REG_FIFO_CFG1 0x004c ++#define AG71XX_REG_FIFO_CFG2 0x0050 ++#define AG71XX_REG_FIFO_CFG3 0x0054 ++#define AG71XX_REG_FIFO_CFG4 0x0058 ++#define AG71XX_REG_FIFO_CFG5 0x005c ++#define AG71XX_REG_FIFO_RAM0 0x0060 ++#define AG71XX_REG_FIFO_RAM1 0x0064 ++#define AG71XX_REG_FIFO_RAM2 0x0068 ++#define AG71XX_REG_FIFO_RAM3 0x006c ++#define AG71XX_REG_FIFO_RAM4 0x0070 ++#define AG71XX_REG_FIFO_RAM5 0x0074 ++#define AG71XX_REG_FIFO_RAM6 0x0078 ++#define AG71XX_REG_FIFO_RAM7 0x007c ++ ++#define AG71XX_REG_TX_CTRL 0x0180 ++#define AG71XX_REG_TX_DESC 0x0184 ++#define AG71XX_REG_TX_STATUS 0x0188 ++#define AG71XX_REG_RX_CTRL 0x018c ++#define AG71XX_REG_RX_DESC 0x0190 ++#define AG71XX_REG_RX_STATUS 0x0194 ++#define AG71XX_REG_INT_ENABLE 0x0198 ++#define AG71XX_REG_INT_STATUS 0x019c ++ ++#define AG71XX_REG_FIFO_DEPTH 0x01a8 ++#define AG71XX_REG_RX_SM 0x01b0 ++#define AG71XX_REG_TX_SM 0x01b4 ++ ++#define MAC_CFG1_TXE BIT(0) /* Tx Enable */ ++#define MAC_CFG1_STX BIT(1) /* Synchronize Tx Enable */ ++#define MAC_CFG1_RXE BIT(2) /* Rx Enable */ ++#define MAC_CFG1_SRX BIT(3) /* Synchronize Rx Enable */ ++#define MAC_CFG1_TFC BIT(4) /* Tx Flow Control Enable */ ++#define MAC_CFG1_RFC BIT(5) /* Rx Flow Control Enable */ ++#define MAC_CFG1_LB BIT(8) /* Loopback mode */ ++#define MAC_CFG1_SR BIT(31) /* Soft Reset */ ++ ++#define MAC_CFG2_FDX BIT(0) ++#define MAC_CFG2_CRC_EN BIT(1) ++#define MAC_CFG2_PAD_CRC_EN BIT(2) ++#define MAC_CFG2_LEN_CHECK BIT(4) ++#define MAC_CFG2_HUGE_FRAME_EN BIT(5) ++#define MAC_CFG2_IF_1000 BIT(9) ++#define MAC_CFG2_IF_10_100 BIT(8) ++ ++#define FIFO_CFG0_WTM BIT(0) /* Watermark Module */ ++#define FIFO_CFG0_RXS BIT(1) /* Rx System Module */ ++#define FIFO_CFG0_RXF BIT(2) /* Rx Fabric Module */ ++#define FIFO_CFG0_TXS BIT(3) /* Tx System Module */ ++#define FIFO_CFG0_TXF BIT(4) /* Tx Fabric Module */ ++#define FIFO_CFG0_ALL (FIFO_CFG0_WTM | FIFO_CFG0_RXS | FIFO_CFG0_RXF \ ++ | FIFO_CFG0_TXS | FIFO_CFG0_TXF) ++ ++#define FIFO_CFG0_ENABLE_SHIFT 8 ++ ++#define FIFO_CFG4_DE BIT(0) /* Drop Event */ ++#define FIFO_CFG4_DV BIT(1) /* RX_DV Event */ ++#define FIFO_CFG4_FC BIT(2) /* False Carrier */ ++#define FIFO_CFG4_CE BIT(3) /* Code Error */ ++#define FIFO_CFG4_CR BIT(4) /* CRC error */ ++#define FIFO_CFG4_LM BIT(5) /* Length Mismatch */ ++#define FIFO_CFG4_LO BIT(6) /* Length out of range */ ++#define FIFO_CFG4_OK BIT(7) /* Packet is OK */ ++#define FIFO_CFG4_MC BIT(8) /* Multicast Packet */ ++#define FIFO_CFG4_BC BIT(9) /* Broadcast Packet */ ++#define FIFO_CFG4_DR BIT(10) /* Dribble */ ++#define FIFO_CFG4_LE BIT(11) /* Long Event */ ++#define FIFO_CFG4_CF BIT(12) /* Control Frame */ ++#define FIFO_CFG4_PF BIT(13) /* Pause Frame */ ++#define FIFO_CFG4_UO BIT(14) /* Unsupported Opcode */ ++#define FIFO_CFG4_VT BIT(15) /* VLAN tag detected */ ++#define FIFO_CFG4_FT BIT(16) /* Frame Truncated */ ++#define FIFO_CFG4_UC BIT(17) /* Unicast Packet */ ++ ++#define FIFO_CFG5_DE BIT(0) /* Drop Event */ ++#define FIFO_CFG5_DV BIT(1) /* RX_DV Event */ ++#define FIFO_CFG5_FC BIT(2) /* False Carrier */ ++#define FIFO_CFG5_CE BIT(3) /* Code Error */ ++#define FIFO_CFG5_LM BIT(4) /* Length Mismatch */ ++#define FIFO_CFG5_LO BIT(5) /* Length Out of Range */ ++#define FIFO_CFG5_OK BIT(6) /* Packet is OK */ ++#define FIFO_CFG5_MC BIT(7) /* Multicast Packet */ ++#define FIFO_CFG5_BC BIT(8) /* Broadcast Packet */ ++#define FIFO_CFG5_DR BIT(9) /* Dribble */ ++#define FIFO_CFG5_CF BIT(10) /* Control Frame */ ++#define FIFO_CFG5_PF BIT(11) /* Pause Frame */ ++#define FIFO_CFG5_UO BIT(12) /* Unsupported Opcode */ ++#define FIFO_CFG5_VT BIT(13) /* VLAN tag detected */ ++#define FIFO_CFG5_LE BIT(14) /* Long Event */ ++#define FIFO_CFG5_FT BIT(15) /* Frame Truncated */ ++#define FIFO_CFG5_16 BIT(16) /* unknown */ ++#define FIFO_CFG5_17 BIT(17) /* unknown */ ++#define FIFO_CFG5_SF BIT(18) /* Short Frame */ ++#define FIFO_CFG5_BM BIT(19) /* Byte Mode */ ++ ++#define AG71XX_INT_TX_PS BIT(0) ++#define AG71XX_INT_TX_UR BIT(1) ++#define AG71XX_INT_TX_BE BIT(3) ++#define AG71XX_INT_RX_PR BIT(4) ++#define AG71XX_INT_RX_OF BIT(6) ++#define AG71XX_INT_RX_BE BIT(7) ++ ++#define MAC_IFCTL_SPEED BIT(16) ++ ++#define MII_CFG_CLK_DIV_4 0 ++#define MII_CFG_CLK_DIV_6 2 ++#define MII_CFG_CLK_DIV_8 3 ++#define MII_CFG_CLK_DIV_10 4 ++#define MII_CFG_CLK_DIV_14 5 ++#define MII_CFG_CLK_DIV_20 6 ++#define MII_CFG_CLK_DIV_28 7 ++#define MII_CFG_CLK_DIV_34 8 ++#define MII_CFG_CLK_DIV_42 9 ++#define MII_CFG_CLK_DIV_50 10 ++#define MII_CFG_CLK_DIV_58 11 ++#define MII_CFG_CLK_DIV_66 12 ++#define MII_CFG_CLK_DIV_74 13 ++#define MII_CFG_CLK_DIV_82 14 ++#define MII_CFG_CLK_DIV_98 15 ++#define MII_CFG_RESET BIT(31) ++ ++#define MII_CMD_WRITE 0x0 ++#define MII_CMD_READ 0x1 ++#define MII_ADDR_SHIFT 8 ++#define MII_IND_BUSY BIT(0) ++#define MII_IND_INVALID BIT(2) ++ ++#define TX_CTRL_TXE BIT(0) /* Tx Enable */ ++ ++#define TX_STATUS_PS BIT(0) /* Packet Sent */ ++#define TX_STATUS_UR BIT(1) /* Tx Underrun */ ++#define TX_STATUS_BE BIT(3) /* Bus Error */ ++ ++#define RX_CTRL_RXE BIT(0) /* Rx Enable */ ++ ++#define RX_STATUS_PR BIT(0) /* Packet Received */ ++#define RX_STATUS_OF BIT(2) /* Rx Overflow */ ++#define RX_STATUS_BE BIT(3) /* Bus Error */ ++ ++static inline void ag71xx_check_reg_offset(struct ag71xx *ag, unsigned reg) ++{ ++ switch (reg) { ++ case AG71XX_REG_MAC_CFG1 ... AG71XX_REG_MAC_MFL: ++ case AG71XX_REG_MAC_IFCTL ... AG71XX_REG_TX_SM: ++ case AG71XX_REG_MII_CFG: ++ break; ++ ++ default: ++ BUG(); ++ } ++} ++ ++static inline void ag71xx_wr(struct ag71xx *ag, unsigned reg, u32 value) ++{ ++ ag71xx_check_reg_offset(ag, reg); ++ ++ __raw_writel(value, ag->mac_base + reg); ++ /* flush write */ ++ (void) __raw_readl(ag->mac_base + reg); ++} ++ ++static inline u32 ag71xx_rr(struct ag71xx *ag, unsigned reg) ++{ ++ ag71xx_check_reg_offset(ag, reg); ++ ++ return __raw_readl(ag->mac_base + reg); ++} ++ ++static inline void ag71xx_sb(struct ag71xx *ag, unsigned reg, u32 mask) ++{ ++ void __iomem *r; ++ ++ ag71xx_check_reg_offset(ag, reg); ++ ++ r = ag->mac_base + reg; ++ __raw_writel(__raw_readl(r) | mask, r); ++ /* flush write */ ++ (void)__raw_readl(r); ++} ++ ++static inline void ag71xx_cb(struct ag71xx *ag, unsigned reg, u32 mask) ++{ ++ void __iomem *r; ++ ++ ag71xx_check_reg_offset(ag, reg); ++ ++ r = ag->mac_base + reg; ++ __raw_writel(__raw_readl(r) & ~mask, r); ++ /* flush write */ ++ (void) __raw_readl(r); ++} ++ ++static inline void ag71xx_int_enable(struct ag71xx *ag, u32 ints) ++{ ++ ag71xx_sb(ag, AG71XX_REG_INT_ENABLE, ints); ++} ++ ++static inline void ag71xx_int_disable(struct ag71xx *ag, u32 ints) ++{ ++ ag71xx_cb(ag, AG71XX_REG_INT_ENABLE, ints); ++} ++ ++#ifdef CONFIG_AG71XX_AR8216_SUPPORT ++void ag71xx_add_ar8216_header(struct ag71xx *ag, struct sk_buff *skb); ++int ag71xx_remove_ar8216_header(struct ag71xx *ag, struct sk_buff *skb, ++ int pktlen); ++static inline int ag71xx_has_ar8216(struct ag71xx *ag) ++{ ++ return ag71xx_get_pdata(ag)->has_ar8216; ++} ++#else ++static inline void ag71xx_add_ar8216_header(struct ag71xx *ag, ++ struct sk_buff *skb) ++{ ++} ++ ++static inline int ag71xx_remove_ar8216_header(struct ag71xx *ag, ++ struct sk_buff *skb, ++ int pktlen) ++{ ++ return 0; ++} ++static inline int ag71xx_has_ar8216(struct ag71xx *ag) ++{ ++ return 0; ++} ++#endif ++ ++#ifdef CONFIG_AG71XX_DEBUG_FS ++int ag71xx_debugfs_root_init(void); ++void ag71xx_debugfs_root_exit(void); ++int ag71xx_debugfs_init(struct ag71xx *ag); ++void ag71xx_debugfs_exit(struct ag71xx *ag); ++void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, u32 status); ++void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx); ++#else ++static inline int ag71xx_debugfs_root_init(void) { return 0; } ++static inline void ag71xx_debugfs_root_exit(void) {} ++static inline int ag71xx_debugfs_init(struct ag71xx *ag) { return 0; } ++static inline void ag71xx_debugfs_exit(struct ag71xx *ag) {} ++static inline void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, ++ u32 status) {} ++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); ++ ++int ag71xx_mdio_mii_read(struct ag71xx_mdio *am, int addr, int reg); ++void ag71xx_mdio_mii_write(struct ag71xx_mdio *am, int addr, int reg, u16 val); ++ ++u16 ar7240sw_phy_read(struct mii_bus *mii, unsigned phy_addr, ++ unsigned reg_addr); ++int ar7240sw_phy_write(struct mii_bus *mii, unsigned phy_addr, ++ unsigned reg_addr, u16 reg_val); ++ ++#endif /* _AG71XX_H */ +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c +new file mode 100644 +index 0000000..d4ccc02 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c +@@ -0,0 +1,1202 @@ ++/* ++ * Driver for the built-in ethernet switch of the Atheros AR7240 SoC ++ * Copyright (c) 2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (c) 2010 Felix Fietkau <nbd@openwrt.org> ++ * ++ * 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 <linux/etherdevice.h> ++#include <linux/list.h> ++#include <linux/netdevice.h> ++#include <linux/phy.h> ++#include <linux/mii.h> ++#include <linux/bitops.h> ++#include <linux/switch.h> ++#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_VERSION_AR7240 0x01 ++#define AR7240_MASK_CTRL_VERSION_AR934X 0x02 ++#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(11) ++#define AR9340_GLOBAL_CTRL_MTU_M BITM(14) ++ ++#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_ATU 0x50 ++#define AR7240_ATU_FLUSH_ALL 0x1 ++ ++#define AR7240_REG_AT_CTRL 0x5c ++#define AR7240_AT_CTRL_AGE_TIME BITS(0, 15) ++#define AR7240_AT_CTRL_AGE_EN BIT(17) ++#define AR7240_AT_CTRL_LEARN_CHANGE BIT(18) ++#define AR7240_AT_CTRL_RESERVED BIT(19) ++#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_M BITM(3) ++#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_S 0 ++#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 AR934X_PHY_ID1 0x004d ++#define AR934X_PHY_ID2 0xd042 ++ ++#define AR7240_MAX_VLANS 16 ++ ++#define AR934X_REG_OPER_MODE0 0x04 ++#define AR934X_OPER_MODE0_MAC_GMII_EN BIT(6) ++#define AR934X_OPER_MODE0_PHY_MII_EN BIT(10) ++ ++#define AR934X_REG_OPER_MODE1 0x08 ++#define AR934X_REG_OPER_MODE1_PHY4_MII_EN BIT(28) ++ ++#define AR934X_REG_FLOOD_MASK 0x2c ++#define AR934X_FLOOD_MASK_MC_DP(_p) BIT(16 + (_p)) ++#define AR934X_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p)) ++ ++#define AR934X_REG_QM_CTRL 0x3c ++#define AR934X_QM_CTRL_ARP_EN BIT(15) ++ ++#define AR934X_REG_AT_CTRL 0x5c ++#define AR934X_AT_CTRL_AGE_TIME BITS(0, 15) ++#define AR934X_AT_CTRL_AGE_EN BIT(17) ++#define AR934X_AT_CTRL_LEARN_CHANGE BIT(18) ++ ++#define AR934X_MIB_ENABLE BIT(30) ++ ++#define AR934X_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100) ++ ++#define AR934X_REG_PORT_VLAN1(_port) (AR934X_REG_PORT_BASE((_port)) + 0x08) ++#define AR934X_PORT_VLAN1_DEFAULT_SVID_S 0 ++#define AR934X_PORT_VLAN1_FORCE_DEFAULT_VID_EN BIT(12) ++#define AR934X_PORT_VLAN1_PORT_TLS_MODE BIT(13) ++#define AR934X_PORT_VLAN1_PORT_VLAN_PROP_EN BIT(14) ++#define AR934X_PORT_VLAN1_PORT_CLONE_EN BIT(15) ++#define AR934X_PORT_VLAN1_DEFAULT_CVID_S 16 ++#define AR934X_PORT_VLAN1_FORCE_PORT_VLAN_EN BIT(28) ++#define AR934X_PORT_VLAN1_ING_PORT_PRI_S 29 ++ ++#define AR934X_REG_PORT_VLAN2(_port) (AR934X_REG_PORT_BASE((_port)) + 0x0c) ++#define AR934X_PORT_VLAN2_PORT_VID_MEM_S 16 ++#define AR934X_PORT_VLAN2_8021Q_MODE_S 30 ++#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_ONLY 0 ++#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_FALLBACK 1 ++#define AR934X_PORT_VLAN2_8021Q_MODE_VLAN_ONLY 2 ++#define AR934X_PORT_VLAN2_8021Q_MODE_SECURE 3 ++ ++#define sw_to_ar7240(_dev) container_of(_dev, struct ar7240sw, swdev) ++ ++struct ar7240sw_port_stat { ++ unsigned long rx_broadcast; ++ unsigned long rx_pause; ++ unsigned long rx_multicast; ++ unsigned long rx_fcs_error; ++ unsigned long rx_align_error; ++ unsigned long rx_runt; ++ unsigned long rx_fragments; ++ unsigned long rx_64byte; ++ unsigned long rx_128byte; ++ unsigned long rx_256byte; ++ unsigned long rx_512byte; ++ unsigned long rx_1024byte; ++ unsigned long rx_1518byte; ++ unsigned long rx_maxbyte; ++ unsigned long rx_toolong; ++ unsigned long rx_good_byte; ++ unsigned long rx_bad_byte; ++ unsigned long rx_overflow; ++ unsigned long filtered; ++ ++ unsigned long tx_broadcast; ++ unsigned long tx_pause; ++ unsigned long tx_multicast; ++ unsigned long tx_underrun; ++ unsigned long tx_64byte; ++ unsigned long tx_128byte; ++ unsigned long tx_256byte; ++ unsigned long tx_512byte; ++ unsigned long tx_1024byte; ++ unsigned long tx_1518byte; ++ unsigned long tx_maxbyte; ++ unsigned long tx_oversize; ++ unsigned long tx_byte; ++ unsigned long tx_collision; ++ unsigned long tx_abortcol; ++ unsigned long tx_multicol; ++ unsigned long tx_singlecol; ++ unsigned long tx_excdefer; ++ unsigned long tx_defer; ++ unsigned long tx_xlatecol; ++}; ++ ++struct ar7240sw { ++ struct mii_bus *mii_bus; ++ struct ag71xx_switch_platform_data *swdata; ++ struct switch_dev swdev; ++ int num_ports; ++ u8 ver; ++ bool vlan; ++ u16 vlan_id[AR7240_MAX_VLANS]; ++ u8 vlan_table[AR7240_MAX_VLANS]; ++ u8 vlan_tagged; ++ u16 pvid[AR7240_NUM_PORTS]; ++ char buf[80]; ++ ++ rwlock_t stats_lock; ++ struct ar7240sw_port_stat port_stats[AR7240_NUM_PORTS]; ++}; ++ ++struct ar7240sw_hw_stat { ++ char string[ETH_GSTRING_LEN]; ++ int sizeof_stat; ++ int reg; ++}; ++ ++static DEFINE_MUTEX(reg_mutex); ++ ++static inline int sw_is_ar7240(struct ar7240sw *as) ++{ ++ return as->ver == AR7240_MASK_CTRL_VERSION_AR7240; ++} ++ ++static inline int sw_is_ar934x(struct ar7240sw *as) ++{ ++ return as->ver == AR7240_MASK_CTRL_VERSION_AR934X; ++} ++ ++static inline u32 ar7240sw_port_mask(struct ar7240sw *as, int port) ++{ ++ return BIT(port); ++} ++ ++static inline u32 ar7240sw_port_mask_all(struct ar7240sw *as) ++{ ++ return BIT(as->swdev.ports) - 1; ++} ++ ++static inline u32 ar7240sw_port_mask_but(struct ar7240sw *as, int port) ++{ ++ return ar7240sw_port_mask_all(as) & ~BIT(port); ++} ++ ++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 mii_bus *mii, u32 reg) ++{ ++ unsigned long flags; ++ u16 phy_addr; ++ u16 phy_reg; ++ u32 hi, lo; ++ ++ reg = (reg & 0xfffffffc) >> 2; ++ phy_addr = mk_phy_addr(reg); ++ phy_reg = mk_phy_reg(reg); ++ ++ local_irq_save(flags); ++ ag71xx_mdio_mii_write(mii->priv, 0x1f, 0x10, mk_high_addr(reg)); ++ lo = (u32) ag71xx_mdio_mii_read(mii->priv, phy_addr, phy_reg); ++ hi = (u32) ag71xx_mdio_mii_read(mii->priv, phy_addr, phy_reg + 1); ++ local_irq_restore(flags); ++ ++ return (hi << 16) | lo; ++} ++ ++static void __ar7240sw_reg_write(struct mii_bus *mii, u32 reg, u32 val) ++{ ++ unsigned long flags; ++ u16 phy_addr; ++ u16 phy_reg; ++ ++ reg = (reg & 0xfffffffc) >> 2; ++ phy_addr = mk_phy_addr(reg); ++ phy_reg = mk_phy_reg(reg); ++ ++ local_irq_save(flags); ++ ag71xx_mdio_mii_write(mii->priv, 0x1f, 0x10, mk_high_addr(reg)); ++ ag71xx_mdio_mii_write(mii->priv, phy_addr, phy_reg + 1, (val >> 16)); ++ ag71xx_mdio_mii_write(mii->priv, phy_addr, phy_reg, (val & 0xffff)); ++ local_irq_restore(flags); ++} ++ ++static u32 ar7240sw_reg_read(struct mii_bus *mii, u32 reg_addr) ++{ ++ u32 ret; ++ ++ mutex_lock(®_mutex); ++ ret = __ar7240sw_reg_read(mii, reg_addr); ++ mutex_unlock(®_mutex); ++ ++ return ret; ++} ++ ++static void ar7240sw_reg_write(struct mii_bus *mii, u32 reg_addr, u32 reg_val) ++{ ++ mutex_lock(®_mutex); ++ __ar7240sw_reg_write(mii, reg_addr, reg_val); ++ mutex_unlock(®_mutex); ++} ++ ++static u32 ar7240sw_reg_rmw(struct mii_bus *mii, u32 reg, u32 mask, u32 val) ++{ ++ u32 t; ++ ++ mutex_lock(®_mutex); ++ t = __ar7240sw_reg_read(mii, reg); ++ t &= ~mask; ++ t |= val; ++ __ar7240sw_reg_write(mii, reg, t); ++ mutex_unlock(®_mutex); ++ ++ return t; ++} ++ ++static void ar7240sw_reg_set(struct mii_bus *mii, u32 reg, u32 val) ++{ ++ u32 t; ++ ++ mutex_lock(®_mutex); ++ t = __ar7240sw_reg_read(mii, reg); ++ t |= val; ++ __ar7240sw_reg_write(mii, reg, t); ++ mutex_unlock(®_mutex); ++} ++ ++static int __ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val, ++ unsigned timeout) ++{ ++ int i; ++ ++ for (i = 0; i < timeout; i++) { ++ u32 t; ++ ++ t = __ar7240sw_reg_read(mii, reg); ++ if ((t & mask) == val) ++ return 0; ++ ++ msleep(1); ++ } ++ ++ return -ETIMEDOUT; ++} ++ ++static int ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val, ++ unsigned timeout) ++{ ++ int ret; ++ ++ mutex_lock(®_mutex); ++ ret = __ar7240sw_reg_wait(mii, reg, mask, val, timeout); ++ mutex_unlock(®_mutex); ++ return ret; ++} ++ ++u16 ar7240sw_phy_read(struct mii_bus *mii, unsigned phy_addr, ++ unsigned reg_addr) ++{ ++ u32 t, val = 0xffff; ++ int err; ++ ++ if (phy_addr >= AR7240_NUM_PHYS) ++ return 0xffff; ++ ++ mutex_lock(®_mutex); ++ 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(mii, AR7240_REG_MDIO_CTRL, t); ++ err = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL, ++ AR7240_MDIO_CTRL_BUSY, 0, 5); ++ if (!err) ++ val = __ar7240sw_reg_read(mii, AR7240_REG_MDIO_CTRL); ++ mutex_unlock(®_mutex); ++ ++ return val & AR7240_MDIO_CTRL_DATA_M; ++} ++ ++int ar7240sw_phy_write(struct mii_bus *mii, unsigned phy_addr, ++ unsigned reg_addr, u16 reg_val) ++{ ++ u32 t; ++ int ret; ++ ++ if (phy_addr >= AR7240_NUM_PHYS) ++ return -EINVAL; ++ ++ mutex_lock(®_mutex); ++ 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(mii, AR7240_REG_MDIO_CTRL, t); ++ ret = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL, ++ AR7240_MDIO_CTRL_BUSY, 0, 5); ++ mutex_unlock(®_mutex); ++ ++ return ret; ++} ++ ++static int ar7240sw_capture_stats(struct ar7240sw *as) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ int port; ++ int ret; ++ ++ write_lock(&as->stats_lock); ++ ++ /* Capture the hardware statistics for all ports */ ++ ar7240sw_reg_rmw(mii, AR7240_REG_MIB_FUNCTION0, ++ (AR7240_MIB_FUNC_M << AR7240_MIB_FUNC_S), ++ (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S)); ++ ++ /* Wait for the capturing to complete. */ ++ ret = ar7240sw_reg_wait(mii, AR7240_REG_MIB_FUNCTION0, ++ AR7240_MIB_BUSY, 0, 10); ++ ++ if (ret) ++ goto unlock; ++ ++ for (port = 0; port < AR7240_NUM_PORTS; port++) { ++ unsigned int base; ++ struct ar7240sw_port_stat *stats; ++ ++ base = AR7240_REG_STATS_BASE(port); ++ stats = &as->port_stats[port]; ++ ++#define READ_STAT(_r) ar7240sw_reg_read(mii, base + AR7240_STATS_ ## _r) ++ ++ stats->rx_good_byte += READ_STAT(RXGOODBYTE); ++ stats->tx_byte += READ_STAT(TXBYTE); ++ ++#undef READ_STAT ++ } ++ ++ ret = 0; ++ ++unlock: ++ write_unlock(&as->stats_lock); ++ return ret; ++} ++ ++static void ar7240sw_disable_port(struct ar7240sw *as, unsigned port) ++{ ++ ar7240sw_reg_write(as->mii_bus, AR7240_REG_PORT_CTRL(port), ++ AR7240_PORT_CTRL_STATE_DISABLED); ++} ++ ++static void ar7240sw_setup(struct ar7240sw *as) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ ++ /* Enable CPU port, and disable mirror port */ ++ ar7240sw_reg_write(mii, AR7240_REG_CPU_PORT, ++ AR7240_CPU_PORT_EN | ++ (15 << AR7240_MIRROR_PORT_S)); ++ ++ /* Setup TAG priority mapping */ ++ ar7240sw_reg_write(mii, AR7240_REG_TAG_PRIORITY, 0xfa50); ++ ++ if (sw_is_ar934x(as)) { ++ /* Enable aging, MAC replacing */ ++ ar7240sw_reg_write(mii, AR934X_REG_AT_CTRL, ++ 0x2b /* 5 min age time */ | ++ AR934X_AT_CTRL_AGE_EN | ++ AR934X_AT_CTRL_LEARN_CHANGE); ++ /* Enable ARP frame acknowledge */ ++ ar7240sw_reg_set(mii, AR934X_REG_QM_CTRL, ++ AR934X_QM_CTRL_ARP_EN); ++ /* Enable Broadcast/Multicast frames transmitted to the CPU */ ++ ar7240sw_reg_set(mii, AR934X_REG_FLOOD_MASK, ++ AR934X_FLOOD_MASK_BC_DP(0) | ++ AR934X_FLOOD_MASK_MC_DP(0)); ++ ++ /* setup MTU */ ++ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL, ++ AR9340_GLOBAL_CTRL_MTU_M, ++ AR9340_GLOBAL_CTRL_MTU_M); ++ ++ /* Enable MIB counters */ ++ ar7240sw_reg_set(mii, AR7240_REG_MIB_FUNCTION0, ++ AR934X_MIB_ENABLE); ++ ++ } else { ++ /* Enable ARP frame acknowledge, aging, MAC replacing */ ++ ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL, ++ AR7240_AT_CTRL_RESERVED | ++ 0x2b /* 5 min age time */ | ++ AR7240_AT_CTRL_AGE_EN | ++ AR7240_AT_CTRL_ARP_EN | ++ AR7240_AT_CTRL_LEARN_CHANGE); ++ /* Enable Broadcast frames transmitted to the CPU */ ++ ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK, ++ AR7240_FLOOD_MASK_BROAD_TO_CPU); ++ ++ /* setup MTU */ ++ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL, ++ AR7240_GLOBAL_CTRL_MTU_M, ++ AR7240_GLOBAL_CTRL_MTU_M); ++ } ++ ++ /* setup Service TAG */ ++ ar7240sw_reg_rmw(mii, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, 0); ++} ++ ++static int ar7240sw_reset(struct ar7240sw *as) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ 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(mii, AR7240_REG_MASK_CTRL, ++ AR7240_MASK_CTRL_SOFT_RESET); ++ ++ ret = ar7240sw_reg_wait(mii, AR7240_REG_MASK_CTRL, ++ AR7240_MASK_CTRL_SOFT_RESET, 0, 1000); ++ ++ /* setup PHYs */ ++ for (i = 0; i < AR7240_NUM_PHYS; i++) { ++ ar7240sw_phy_write(mii, i, MII_ADVERTISE, ++ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ++ ADVERTISE_PAUSE_ASYM); ++ ar7240sw_phy_write(mii, i, MII_BMCR, ++ BMCR_RESET | BMCR_ANENABLE); ++ } ++ msleep(1000); ++ ++ ar7240sw_setup(as); ++ return ret; ++} ++ ++static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port, u8 portmask) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ u32 ctrl; ++ u32 vid, mode; ++ ++ ctrl = AR7240_PORT_CTRL_STATE_FORWARD | AR7240_PORT_CTRL_LEARN | ++ AR7240_PORT_CTRL_SINGLE_VLAN; ++ ++ if (port == AR7240_PORT_CPU) { ++ ar7240sw_reg_write(mii, 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(mii, AR7240_REG_PORT_STATUS(port), ++ AR7240_PORT_STATUS_LINK_AUTO); ++ } ++ ++ /* Set the default VID for this port */ ++ if (as->vlan) { ++ vid = as->vlan_id[as->pvid[port]]; ++ mode = AR7240_PORT_VLAN_MODE_SECURE; ++ } else { ++ vid = port; ++ mode = AR7240_PORT_VLAN_MODE_PORT_ONLY; ++ } ++ ++ if (as->vlan) { ++ if (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; ++ } else { ++ ctrl |= AR7240_PORT_CTRL_VLAN_MODE_KEEP << ++ AR7240_PORT_CTRL_VLAN_MODE_S; ++ } ++ ++ if (!portmask) { ++ if (port == AR7240_PORT_CPU) ++ portmask = ar7240sw_port_mask_but(as, AR7240_PORT_CPU); ++ else ++ portmask = ar7240sw_port_mask(as, 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 */ ++ portmask &= ar7240sw_port_mask_but(as, port); ++ ++ ar7240sw_reg_write(mii, AR7240_REG_PORT_CTRL(port), ctrl); ++ if (sw_is_ar934x(as)) { ++ u32 vlan1, vlan2; ++ ++ vlan1 = (vid << AR934X_PORT_VLAN1_DEFAULT_CVID_S); ++ vlan2 = (portmask << AR934X_PORT_VLAN2_PORT_VID_MEM_S) | ++ (mode << AR934X_PORT_VLAN2_8021Q_MODE_S); ++ ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN1(port), vlan1); ++ ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN2(port), vlan2); ++ } else { ++ u32 vlan; ++ ++ vlan = vid | (mode << AR7240_PORT_VLAN_MODE_S) | ++ (portmask << AR7240_PORT_VLAN_DEST_PORTS_S); ++ ++ ar7240sw_reg_write(mii, AR7240_REG_PORT_VLAN(port), vlan); ++ } ++} ++ ++static int ar7240_set_addr(struct ar7240sw *as, u8 *addr) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ u32 t; ++ ++ t = (addr[4] << 8) | addr[5]; ++ ar7240sw_reg_write(mii, AR7240_REG_MAC_ADDR0, t); ++ ++ t = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; ++ ar7240sw_reg_write(mii, 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 < as->swdev.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) ++{ ++ struct mii_bus *mii = as->mii_bus; ++ ++ if (ar7240sw_reg_wait(mii, 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(mii, AR7240_REG_VTU_DATA, val); ++ } ++ op |= AR7240_VTU_ACTIVE; ++ ar7240sw_reg_write(mii, 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 < as->swdev.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 < as->swdev.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 < as->swdev.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 int ++ar7240_get_port_link(struct switch_dev *dev, int port, ++ struct switch_port_link *link) ++{ ++ struct ar7240sw *as = sw_to_ar7240(dev); ++ struct mii_bus *mii = as->mii_bus; ++ u32 status; ++ ++ if (port > AR7240_NUM_PORTS) ++ return -EINVAL; ++ ++ status = ar7240sw_reg_read(mii, AR7240_REG_PORT_STATUS(port)); ++ link->aneg = !!(status & AR7240_PORT_STATUS_LINK_AUTO); ++ if (link->aneg) { ++ link->link = !!(status & AR7240_PORT_STATUS_LINK_UP); ++ if (!link->link) ++ return 0; ++ } else { ++ link->link = true; ++ } ++ ++ link->duplex = !!(status & AR7240_PORT_STATUS_DUPLEX); ++ link->tx_flow = !!(status & AR7240_PORT_STATUS_TXFLOW); ++ link->rx_flow = !!(status & AR7240_PORT_STATUS_RXFLOW); ++ switch (status & AR7240_PORT_STATUS_SPEED_M) { ++ case AR7240_PORT_STATUS_SPEED_10: ++ link->speed = SWITCH_PORT_SPEED_10; ++ break; ++ case AR7240_PORT_STATUS_SPEED_100: ++ link->speed = SWITCH_PORT_SPEED_100; ++ break; ++ case AR7240_PORT_STATUS_SPEED_1000: ++ link->speed = SWITCH_PORT_SPEED_1000; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ++ar7240_get_port_stats(struct switch_dev *dev, int port, ++ struct switch_port_stats *stats) ++{ ++ struct ar7240sw *as = sw_to_ar7240(dev); ++ ++ if (port > AR7240_NUM_PORTS) ++ return -EINVAL; ++ ++ ar7240sw_capture_stats(as); ++ ++ read_lock(&as->stats_lock); ++ stats->rx_bytes = as->port_stats[port].rx_good_byte; ++ stats->tx_bytes = as->port_stats[port].tx_byte; ++ read_unlock(&as->stats_lock); ++ ++ 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, ++ .get_port_link = ar7240_get_port_link, ++ .get_port_stats = ar7240_get_port_stats, ++}; ++ ++static struct ar7240sw *ar7240_probe(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ struct mii_bus *mii = ag->mii_bus; ++ struct ar7240sw *as; ++ struct switch_dev *swdev; ++ u32 ctrl; ++ u16 phy_id1; ++ u16 phy_id2; ++ int i; ++ ++ phy_id1 = ar7240sw_phy_read(mii, 0, MII_PHYSID1); ++ phy_id2 = ar7240sw_phy_read(mii, 0, MII_PHYSID2); ++ if ((phy_id1 != AR7240_PHY_ID1 || phy_id2 != AR7240_PHY_ID2) && ++ (phy_id1 != AR934X_PHY_ID1 || phy_id2 != AR934X_PHY_ID2)) { ++ pr_err("%s: unknown phy id '%04x:%04x'\n", ++ dev_name(&mii->dev), phy_id1, phy_id2); ++ return NULL; ++ } ++ ++ as = kzalloc(sizeof(*as), GFP_KERNEL); ++ if (!as) ++ return NULL; ++ ++ as->mii_bus = mii; ++ as->swdata = pdata->switch_data; ++ ++ swdev = &as->swdev; ++ ++ ctrl = ar7240sw_reg_read(mii, AR7240_REG_MASK_CTRL); ++ as->ver = (ctrl >> AR7240_MASK_CTRL_VERSION_S) & ++ AR7240_MASK_CTRL_VERSION_M; ++ ++ if (sw_is_ar7240(as)) { ++ swdev->name = "AR7240/AR9330 built-in switch"; ++ swdev->ports = AR7240_NUM_PORTS - 1; ++ } else if (sw_is_ar934x(as)) { ++ swdev->name = "AR934X built-in switch"; ++ ++ if (pdata->phy_if_mode == PHY_INTERFACE_MODE_GMII) { ++ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0, ++ AR934X_OPER_MODE0_MAC_GMII_EN); ++ } else if (pdata->phy_if_mode == PHY_INTERFACE_MODE_MII) { ++ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0, ++ AR934X_OPER_MODE0_PHY_MII_EN); ++ } else { ++ pr_err("%s: invalid PHY interface mode\n", ++ dev_name(&mii->dev)); ++ goto err_free; ++ } ++ ++ if (as->swdata->phy4_mii_en) { ++ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE1, ++ AR934X_REG_OPER_MODE1_PHY4_MII_EN); ++ swdev->ports = AR7240_NUM_PORTS - 1; ++ } else { ++ swdev->ports = AR7240_NUM_PORTS; ++ } ++ } else { ++ pr_err("%s: unsupported chip, ctrl=%08x\n", ++ dev_name(&mii->dev), ctrl); ++ goto err_free; ++ } ++ ++ swdev->cpu_port = AR7240_PORT_CPU; ++ swdev->vlans = AR7240_MAX_VLANS; ++ swdev->ops = &ar7240_ops; ++ ++ if (register_switch(&as->swdev, ag->dev) < 0) ++ goto err_free; ++ ++ pr_info("%s: Found an %s\n", dev_name(&mii->dev), swdev->name); ++ ++ /* initialize defaults */ ++ for (i = 0; i < AR7240_MAX_VLANS; i++) ++ as->vlan_id[i] = i; ++ ++ as->vlan_table[0] = ar7240sw_port_mask_all(as); ++ ++ return as; ++ ++err_free: ++ kfree(as); ++ return NULL; ++} ++ ++static void link_function(struct work_struct *work) { ++ struct ag71xx *ag = container_of(work, struct ag71xx, link_work.work); ++ struct ar7240sw *as = ag->phy_priv; ++ unsigned long flags; ++ u8 mask; ++ int i; ++ int status = 0; ++ ++ mask = ~as->swdata->phy_poll_mask; ++ for (i = 0; i < AR7240_NUM_PHYS; i++) { ++ int link; ++ ++ if (!(mask & BIT(i))) ++ continue; ++ ++ link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR); ++ if (link & BMSR_LSTATUS) { ++ status = 1; ++ break; ++ } ++ } ++ ++ spin_lock_irqsave(&ag->lock, flags); ++ if (status != ag->link) { ++ ag->link = status; ++ ag71xx_link_adjust(ag); ++ } ++ spin_unlock_irqrestore(&ag->lock, flags); ++ ++ schedule_delayed_work(&ag->link_work, HZ / 2); ++} ++ ++void ag71xx_ar7240_start(struct ag71xx *ag) ++{ ++ struct ar7240sw *as = ag->phy_priv; ++ ++ ar7240sw_reset(as); ++ ++ ag->speed = SPEED_1000; ++ ag->duplex = 1; ++ ++ ar7240_set_addr(as, ag->dev->dev_addr); ++ ar7240_hw_apply(&as->swdev); ++ ++ schedule_delayed_work(&ag->link_work, HZ / 10); ++} ++ ++void ag71xx_ar7240_stop(struct ag71xx *ag) ++{ ++ cancel_delayed_work_sync(&ag->link_work); ++} ++ ++int ag71xx_ar7240_init(struct ag71xx *ag) ++{ ++ struct ar7240sw *as; ++ ++ as = ar7240_probe(ag); ++ if (!as) ++ return -ENODEV; ++ ++ ag->phy_priv = as; ++ ar7240sw_reset(as); ++ ++ rwlock_init(&as->stats_lock); ++ INIT_DELAYED_WORK(&ag->link_work, link_function); ++ ++ return 0; ++} ++ ++void 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/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c +new file mode 100644 +index 0000000..7ec43b7 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c +@@ -0,0 +1,44 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * Special support for the Atheros ar8216 switch chip ++ * ++ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 "ag71xx.h" ++ ++#define AR8216_PACKET_TYPE_MASK 0xf ++#define AR8216_PACKET_TYPE_NORMAL 0 ++ ++#define AR8216_HEADER_LEN 2 ++ ++void ag71xx_add_ar8216_header(struct ag71xx *ag, struct sk_buff *skb) ++{ ++ skb_push(skb, AR8216_HEADER_LEN); ++ skb->data[0] = 0x10; ++ skb->data[1] = 0x80; ++} ++ ++int ag71xx_remove_ar8216_header(struct ag71xx *ag, struct sk_buff *skb, ++ int pktlen) ++{ ++ u8 type; ++ ++ type = skb->data[1] & AR8216_PACKET_TYPE_MASK; ++ switch (type) { ++ case AR8216_PACKET_TYPE_NORMAL: ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ skb_pull(skb, AR8216_HEADER_LEN); ++ return 0; ++} +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c +new file mode 100644 +index 0000000..757a572 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c +@@ -0,0 +1,284 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 <linux/debugfs.h> ++ ++#include "ag71xx.h" ++ ++static struct dentry *ag71xx_debugfs_root; ++ ++static int ag71xx_debugfs_generic_open(struct inode *inode, struct file *file) ++{ ++ file->private_data = inode->i_private; ++ return 0; ++} ++ ++void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, u32 status) ++{ ++ if (status) ++ ag->debug.int_stats.total++; ++ if (status & AG71XX_INT_TX_PS) ++ ag->debug.int_stats.tx_ps++; ++ if (status & AG71XX_INT_TX_UR) ++ ag->debug.int_stats.tx_ur++; ++ if (status & AG71XX_INT_TX_BE) ++ ag->debug.int_stats.tx_be++; ++ if (status & AG71XX_INT_RX_PR) ++ ag->debug.int_stats.rx_pr++; ++ if (status & AG71XX_INT_RX_OF) ++ ag->debug.int_stats.rx_of++; ++ if (status & AG71XX_INT_RX_BE) ++ ag->debug.int_stats.rx_be++; ++} ++ ++static ssize_t read_file_int_stats(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++#define PR_INT_STAT(_label, _field) \ ++ len += snprintf(buf + len, sizeof(buf) - len, \ ++ "%20s: %10lu\n", _label, ag->debug.int_stats._field); ++ ++ struct ag71xx *ag = file->private_data; ++ char buf[256]; ++ unsigned int len = 0; ++ ++ PR_INT_STAT("TX Packet Sent", tx_ps); ++ PR_INT_STAT("TX Underrun", tx_ur); ++ PR_INT_STAT("TX Bus Error", tx_be); ++ PR_INT_STAT("RX Packet Received", rx_pr); ++ PR_INT_STAT("RX Overflow", rx_of); ++ PR_INT_STAT("RX Bus Error", rx_be); ++ len += snprintf(buf + len, sizeof(buf) - len, "\n"); ++ PR_INT_STAT("Total", total); ++ ++ return simple_read_from_buffer(user_buf, count, ppos, buf, len); ++#undef PR_INT_STAT ++} ++ ++static const struct file_operations ag71xx_fops_int_stats = { ++ .open = ag71xx_debugfs_generic_open, ++ .read = read_file_int_stats, ++ .owner = THIS_MODULE ++}; ++ ++void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx) ++{ ++ struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; ++ ++ if (rx) { ++ stats->rx_count++; ++ stats->rx_packets += rx; ++ if (rx <= AG71XX_NAPI_WEIGHT) ++ stats->rx[rx]++; ++ if (rx > stats->rx_packets_max) ++ stats->rx_packets_max = rx; ++ } ++ ++ if (tx) { ++ stats->tx_count++; ++ stats->tx_packets += tx; ++ if (tx <= AG71XX_NAPI_WEIGHT) ++ stats->tx[tx]++; ++ if (tx > stats->tx_packets_max) ++ stats->tx_packets_max = tx; ++ } ++} ++ ++static ssize_t read_file_napi_stats(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct ag71xx *ag = file->private_data; ++ struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; ++ char *buf; ++ unsigned int buflen; ++ unsigned int len = 0; ++ unsigned long rx_avg = 0; ++ unsigned long tx_avg = 0; ++ int ret; ++ int i; ++ ++ buflen = 2048; ++ buf = kmalloc(buflen, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ if (stats->rx_count) ++ rx_avg = stats->rx_packets / stats->rx_count; ++ ++ if (stats->tx_count) ++ tx_avg = stats->tx_packets / stats->tx_count; ++ ++ len += snprintf(buf + len, buflen - len, "%3s %10s %10s\n", ++ "len", "rx", "tx"); ++ ++ for (i = 1; i <= AG71XX_NAPI_WEIGHT; i++) ++ len += snprintf(buf + len, buflen - len, ++ "%3d: %10lu %10lu\n", ++ i, stats->rx[i], stats->tx[i]); ++ ++ len += snprintf(buf + len, buflen - len, "\n"); ++ ++ len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", ++ "sum", stats->rx_count, stats->tx_count); ++ len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", ++ "avg", rx_avg, tx_avg); ++ len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", ++ "max", stats->rx_packets_max, stats->tx_packets_max); ++ len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", ++ "pkt", stats->rx_packets, stats->tx_packets); ++ ++ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static const struct file_operations ag71xx_fops_napi_stats = { ++ .open = ag71xx_debugfs_generic_open, ++ .read = read_file_napi_stats, ++ .owner = THIS_MODULE ++}; ++ ++#define DESC_PRINT_LEN 64 ++ ++static ssize_t read_file_ring(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos, ++ struct ag71xx *ag, ++ struct ag71xx_ring *ring, ++ unsigned desc_reg) ++{ ++ char *buf; ++ unsigned int buflen; ++ unsigned int len = 0; ++ unsigned long flags; ++ ssize_t ret; ++ int curr; ++ int dirty; ++ u32 desc_hw; ++ int i; ++ ++ buflen = (ring->size * DESC_PRINT_LEN); ++ buf = kmalloc(buflen, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ len += snprintf(buf + len, buflen - len, ++ "Idx ... %-8s %-8s %-8s %-8s . %-10s\n", ++ "desc", "next", "data", "ctrl", "timestamp"); ++ ++ spin_lock_irqsave(&ag->lock, flags); ++ ++ curr = (ring->curr % ring->size); ++ dirty = (ring->dirty % ring->size); ++ desc_hw = ag71xx_rr(ag, desc_reg); ++ for (i = 0; i < ring->size; i++) { ++ struct ag71xx_buf *ab = &ring->buf[i]; ++ u32 desc_dma = ((u32) ring->descs_dma) + i * ring->desc_size; ++ ++ len += snprintf(buf + len, buflen - len, ++ "%3d %c%c%c %08x %08x %08x %08x %c %10lu\n", ++ i, ++ (i == curr) ? 'C' : ' ', ++ (i == dirty) ? 'D' : ' ', ++ (desc_hw == desc_dma) ? 'H' : ' ', ++ desc_dma, ++ ab->desc->next, ++ ab->desc->data, ++ ab->desc->ctrl, ++ (ab->desc->ctrl & DESC_EMPTY) ? 'E' : '*', ++ ab->timestamp); ++ } ++ ++ spin_unlock_irqrestore(&ag->lock, flags); ++ ++ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); ++ kfree(buf); ++ ++ return ret; ++} ++ ++static ssize_t read_file_tx_ring(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct ag71xx *ag = file->private_data; ++ ++ return read_file_ring(file, user_buf, count, ppos, ag, &ag->tx_ring, ++ AG71XX_REG_TX_DESC); ++} ++ ++static const struct file_operations ag71xx_fops_tx_ring = { ++ .open = ag71xx_debugfs_generic_open, ++ .read = read_file_tx_ring, ++ .owner = THIS_MODULE ++}; ++ ++static ssize_t read_file_rx_ring(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct ag71xx *ag = file->private_data; ++ ++ return read_file_ring(file, user_buf, count, ppos, ag, &ag->rx_ring, ++ AG71XX_REG_RX_DESC); ++} ++ ++static const struct file_operations ag71xx_fops_rx_ring = { ++ .open = ag71xx_debugfs_generic_open, ++ .read = read_file_rx_ring, ++ .owner = THIS_MODULE ++}; ++ ++void ag71xx_debugfs_exit(struct ag71xx *ag) ++{ ++ debugfs_remove_recursive(ag->debug.debugfs_dir); ++} ++ ++int ag71xx_debugfs_init(struct ag71xx *ag) ++{ ++ struct device *dev = &ag->pdev->dev; ++ ++ ag->debug.debugfs_dir = debugfs_create_dir(dev_name(dev), ++ ag71xx_debugfs_root); ++ if (!ag->debug.debugfs_dir) { ++ dev_err(dev, "unable to create debugfs directory\n"); ++ return -ENOENT; ++ } ++ ++ debugfs_create_file("int_stats", S_IRUGO, ag->debug.debugfs_dir, ++ ag, &ag71xx_fops_int_stats); ++ debugfs_create_file("napi_stats", S_IRUGO, ag->debug.debugfs_dir, ++ ag, &ag71xx_fops_napi_stats); ++ debugfs_create_file("tx_ring", S_IRUGO, ag->debug.debugfs_dir, ++ ag, &ag71xx_fops_tx_ring); ++ debugfs_create_file("rx_ring", S_IRUGO, ag->debug.debugfs_dir, ++ ag, &ag71xx_fops_rx_ring); ++ ++ return 0; ++} ++ ++int ag71xx_debugfs_root_init(void) ++{ ++ if (ag71xx_debugfs_root) ++ return -EBUSY; ++ ++ ag71xx_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL); ++ if (!ag71xx_debugfs_root) ++ return -ENOENT; ++ ++ return 0; ++} ++ ++void ag71xx_debugfs_root_exit(void) ++{ ++ debugfs_remove(ag71xx_debugfs_root); ++ ag71xx_debugfs_root = NULL; ++} +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c +new file mode 100644 +index 0000000..498fbed +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c +@@ -0,0 +1,124 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 "ag71xx.h" ++ ++static int ag71xx_ethtool_get_settings(struct net_device *dev, ++ struct ethtool_cmd *cmd) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ struct phy_device *phydev = ag->phy_dev; ++ ++ if (!phydev) ++ return -ENODEV; ++ ++ return phy_ethtool_gset(phydev, cmd); ++} ++ ++static int ag71xx_ethtool_set_settings(struct net_device *dev, ++ struct ethtool_cmd *cmd) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ struct phy_device *phydev = ag->phy_dev; ++ ++ if (!phydev) ++ return -ENODEV; ++ ++ return phy_ethtool_sset(phydev, cmd); ++} ++ ++static void ag71xx_ethtool_get_drvinfo(struct net_device *dev, ++ struct ethtool_drvinfo *info) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ strcpy(info->driver, ag->pdev->dev.driver->name); ++ strcpy(info->version, AG71XX_DRV_VERSION); ++ strcpy(info->bus_info, dev_name(&ag->pdev->dev)); ++} ++ ++static u32 ag71xx_ethtool_get_msglevel(struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ return ag->msg_enable; ++} ++ ++static void ag71xx_ethtool_set_msglevel(struct net_device *dev, u32 msg_level) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ ag->msg_enable = msg_level; ++} ++ ++static void ag71xx_ethtool_get_ringparam(struct net_device *dev, ++ struct ethtool_ringparam *er) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ er->tx_max_pending = AG71XX_TX_RING_SIZE_MAX; ++ er->rx_max_pending = AG71XX_RX_RING_SIZE_MAX; ++ er->rx_mini_max_pending = 0; ++ er->rx_jumbo_max_pending = 0; ++ ++ er->tx_pending = ag->tx_ring.size; ++ er->rx_pending = ag->rx_ring.size; ++ er->rx_mini_pending = 0; ++ er->rx_jumbo_pending = 0; ++} ++ ++static int ag71xx_ethtool_set_ringparam(struct net_device *dev, ++ struct ethtool_ringparam *er) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ unsigned tx_size; ++ unsigned rx_size; ++ int err; ++ ++ if (er->rx_mini_pending != 0|| ++ er->rx_jumbo_pending != 0 || ++ er->rx_pending == 0 || ++ er->tx_pending == 0) ++ return -EINVAL; ++ ++ tx_size = er->tx_pending < AG71XX_TX_RING_SIZE_MAX ? ++ er->tx_pending : AG71XX_TX_RING_SIZE_MAX; ++ ++ rx_size = er->rx_pending < AG71XX_RX_RING_SIZE_MAX ? ++ er->rx_pending : AG71XX_RX_RING_SIZE_MAX; ++ ++ if (netif_running(dev)) { ++ err = dev->netdev_ops->ndo_stop(dev); ++ if (err) ++ return err; ++ } ++ ++ ag->tx_ring.size = tx_size; ++ ag->rx_ring.size = rx_size; ++ ++ if (netif_running(dev)) ++ err = dev->netdev_ops->ndo_open(dev); ++ ++ return err; ++} ++ ++struct ethtool_ops ag71xx_ethtool_ops = { ++ .set_settings = ag71xx_ethtool_set_settings, ++ .get_settings = ag71xx_ethtool_get_settings, ++ .get_drvinfo = ag71xx_ethtool_get_drvinfo, ++ .get_msglevel = ag71xx_ethtool_get_msglevel, ++ .set_msglevel = ag71xx_ethtool_set_msglevel, ++ .get_ringparam = ag71xx_ethtool_get_ringparam, ++ .set_ringparam = ag71xx_ethtool_set_ringparam, ++ .get_link = ethtool_op_get_link, ++}; +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c +new file mode 100644 +index 0000000..d010373 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c +@@ -0,0 +1,1325 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 "ag71xx.h" ++ ++#define AG71XX_DEFAULT_MSG_ENABLE \ ++ (NETIF_MSG_DRV \ ++ | NETIF_MSG_PROBE \ ++ | NETIF_MSG_LINK \ ++ | NETIF_MSG_TIMER \ ++ | NETIF_MSG_IFDOWN \ ++ | NETIF_MSG_IFUP \ ++ | NETIF_MSG_RX_ERR \ ++ | NETIF_MSG_TX_ERR) ++ ++static int ag71xx_msg_level = -1; ++ ++module_param_named(msg_level, ag71xx_msg_level, int, 0); ++MODULE_PARM_DESC(msg_level, "Message level (-1=defaults,0=none,...,16=all)"); ++ ++#define ETH_SWITCH_HEADER_LEN 2 ++ ++static inline unsigned int ag71xx_max_frame_len(unsigned int mtu) ++{ ++ return ETH_SWITCH_HEADER_LEN + ETH_HLEN + VLAN_HLEN + mtu + ETH_FCS_LEN; ++} ++ ++static void ag71xx_dump_dma_regs(struct ag71xx *ag) ++{ ++ DBG("%s: dma_tx_ctrl=%08x, dma_tx_desc=%08x, dma_tx_status=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_TX_CTRL), ++ ag71xx_rr(ag, AG71XX_REG_TX_DESC), ++ ag71xx_rr(ag, AG71XX_REG_TX_STATUS)); ++ ++ DBG("%s: dma_rx_ctrl=%08x, dma_rx_desc=%08x, dma_rx_status=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_RX_CTRL), ++ ag71xx_rr(ag, AG71XX_REG_RX_DESC), ++ ag71xx_rr(ag, AG71XX_REG_RX_STATUS)); ++} ++ ++static void ag71xx_dump_regs(struct ag71xx *ag) ++{ ++ DBG("%s: mac_cfg1=%08x, mac_cfg2=%08x, ipg=%08x, hdx=%08x, mfl=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_MAC_CFG1), ++ ag71xx_rr(ag, AG71XX_REG_MAC_CFG2), ++ ag71xx_rr(ag, AG71XX_REG_MAC_IPG), ++ ag71xx_rr(ag, AG71XX_REG_MAC_HDX), ++ ag71xx_rr(ag, AG71XX_REG_MAC_MFL)); ++ DBG("%s: mac_ifctl=%08x, mac_addr1=%08x, mac_addr2=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL), ++ ag71xx_rr(ag, AG71XX_REG_MAC_ADDR1), ++ ag71xx_rr(ag, AG71XX_REG_MAC_ADDR2)); ++ DBG("%s: fifo_cfg0=%08x, fifo_cfg1=%08x, fifo_cfg2=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG0), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG1), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG2)); ++ DBG("%s: fifo_cfg3=%08x, fifo_cfg4=%08x, fifo_cfg5=%08x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG3), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG4), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5)); ++} ++ ++static inline void ag71xx_dump_intr(struct ag71xx *ag, char *label, u32 intr) ++{ ++ DBG("%s: %s intr=%08x %s%s%s%s%s%s\n", ++ ag->dev->name, label, intr, ++ (intr & AG71XX_INT_TX_PS) ? "TXPS " : "", ++ (intr & AG71XX_INT_TX_UR) ? "TXUR " : "", ++ (intr & AG71XX_INT_TX_BE) ? "TXBE " : "", ++ (intr & AG71XX_INT_RX_PR) ? "RXPR " : "", ++ (intr & AG71XX_INT_RX_OF) ? "RXOF " : "", ++ (intr & AG71XX_INT_RX_BE) ? "RXBE " : ""); ++} ++ ++static void ag71xx_ring_free(struct ag71xx_ring *ring) ++{ ++ kfree(ring->buf); ++ ++ if (ring->descs_cpu) ++ dma_free_coherent(NULL, ring->size * ring->desc_size, ++ ring->descs_cpu, ring->descs_dma); ++} ++ ++static int ag71xx_ring_alloc(struct ag71xx_ring *ring) ++{ ++ int err; ++ int i; ++ ++ ring->desc_size = sizeof(struct ag71xx_desc); ++ if (ring->desc_size % cache_line_size()) { ++ DBG("ag71xx: ring %p, desc size %u rounded to %u\n", ++ ring, ring->desc_size, ++ roundup(ring->desc_size, cache_line_size())); ++ ring->desc_size = roundup(ring->desc_size, cache_line_size()); ++ } ++ ++ ring->descs_cpu = dma_alloc_coherent(NULL, ring->size * ring->desc_size, ++ &ring->descs_dma, GFP_ATOMIC); ++ if (!ring->descs_cpu) { ++ err = -ENOMEM; ++ goto err; ++ } ++ ++ ++ ring->buf = kzalloc(ring->size * sizeof(*ring->buf), GFP_KERNEL); ++ if (!ring->buf) { ++ err = -ENOMEM; ++ goto err; ++ } ++ ++ for (i = 0; i < ring->size; i++) { ++ int idx = i * ring->desc_size; ++ ring->buf[i].desc = (struct ag71xx_desc *)&ring->descs_cpu[idx]; ++ DBG("ag71xx: ring %p, desc %d at %p\n", ++ ring, i, ring->buf[i].desc); ++ } ++ ++ return 0; ++ ++err: ++ return err; ++} ++ ++static void ag71xx_ring_tx_clean(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->tx_ring; ++ struct net_device *dev = ag->dev; ++ u32 bytes_compl = 0, pkts_compl = 0; ++ ++ while (ring->curr != ring->dirty) { ++ u32 i = ring->dirty % ring->size; ++ ++ if (!ag71xx_desc_empty(ring->buf[i].desc)) { ++ ring->buf[i].desc->ctrl = 0; ++ dev->stats.tx_errors++; ++ } ++ ++ if (ring->buf[i].skb) { ++ bytes_compl += ring->buf[i].len; ++ pkts_compl++; ++ dev_kfree_skb_any(ring->buf[i].skb); ++ } ++ ring->buf[i].skb = NULL; ++ ring->dirty++; ++ } ++ ++ /* flush descriptors */ ++ wmb(); ++ ++ netdev_completed_queue(dev, pkts_compl, bytes_compl); ++} ++ ++static void ag71xx_ring_tx_init(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->tx_ring; ++ int i; ++ ++ for (i = 0; i < ring->size; i++) { ++ ring->buf[i].desc->next = (u32) (ring->descs_dma + ++ ring->desc_size * ((i + 1) % ring->size)); ++ ++ ring->buf[i].desc->ctrl = DESC_EMPTY; ++ ring->buf[i].skb = NULL; ++ } ++ ++ /* flush descriptors */ ++ wmb(); ++ ++ ring->curr = 0; ++ ring->dirty = 0; ++ netdev_reset_queue(ag->dev); ++} ++ ++static void ag71xx_ring_rx_clean(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->rx_ring; ++ int i; ++ ++ if (!ring->buf) ++ return; ++ ++ for (i = 0; i < ring->size; i++) ++ if (ring->buf[i].rx_buf) { ++ dma_unmap_single(&ag->dev->dev, ring->buf[i].dma_addr, ++ ag->rx_buf_size, DMA_FROM_DEVICE); ++ kfree(ring->buf[i].rx_buf); ++ } ++} ++ ++static int ag71xx_buffer_offset(struct ag71xx *ag) ++{ ++ int offset = NET_SKB_PAD; ++ ++ /* ++ * On AR71xx/AR91xx packets must be 4-byte aligned. ++ * ++ * When using builtin AR8216 support, hardware adds a 2-byte header, ++ * so we don't need any extra alignment in that case. ++ */ ++ if (!ag71xx_get_pdata(ag)->is_ar724x || ag71xx_has_ar8216(ag)) ++ return offset; ++ ++ return offset + NET_IP_ALIGN; ++} ++ ++static bool ag71xx_fill_rx_buf(struct ag71xx *ag, struct ag71xx_buf *buf, ++ int offset) ++{ ++ void *data; ++ ++ data = kmalloc(ag->rx_buf_size + ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)), ++ GFP_ATOMIC); ++ if (!data) ++ return false; ++ ++ buf->rx_buf = data; ++ buf->dma_addr = dma_map_single(&ag->dev->dev, data, ag->rx_buf_size, ++ DMA_FROM_DEVICE); ++ buf->desc->data = (u32) buf->dma_addr + offset; ++ return true; ++} ++ ++static int ag71xx_ring_rx_init(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->rx_ring; ++ unsigned int i; ++ int ret; ++ int offset = ag71xx_buffer_offset(ag); ++ ++ ret = 0; ++ for (i = 0; i < ring->size; i++) { ++ ring->buf[i].desc->next = (u32) (ring->descs_dma + ++ ring->desc_size * ((i + 1) % ring->size)); ++ ++ DBG("ag71xx: RX desc at %p, next is %08x\n", ++ ring->buf[i].desc, ++ ring->buf[i].desc->next); ++ } ++ ++ for (i = 0; i < ring->size; i++) { ++ if (!ag71xx_fill_rx_buf(ag, &ring->buf[i], offset)) { ++ ret = -ENOMEM; ++ break; ++ } ++ ++ ring->buf[i].desc->ctrl = DESC_EMPTY; ++ } ++ ++ /* flush descriptors */ ++ wmb(); ++ ++ ring->curr = 0; ++ ring->dirty = 0; ++ ++ return ret; ++} ++ ++static int ag71xx_ring_rx_refill(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->rx_ring; ++ unsigned int count; ++ int offset = ag71xx_buffer_offset(ag); ++ ++ count = 0; ++ for (; ring->curr - ring->dirty > 0; ring->dirty++) { ++ unsigned int i; ++ ++ i = ring->dirty % ring->size; ++ ++ if (!ring->buf[i].rx_buf && ++ !ag71xx_fill_rx_buf(ag, &ring->buf[i], offset)) ++ break; ++ ++ ring->buf[i].desc->ctrl = DESC_EMPTY; ++ count++; ++ } ++ ++ /* flush descriptors */ ++ wmb(); ++ ++ DBG("%s: %u rx descriptors refilled\n", ag->dev->name, count); ++ ++ return count; ++} ++ ++static int ag71xx_rings_init(struct ag71xx *ag) ++{ ++ int ret; ++ ++ ret = ag71xx_ring_alloc(&ag->tx_ring); ++ if (ret) ++ return ret; ++ ++ ag71xx_ring_tx_init(ag); ++ ++ ret = ag71xx_ring_alloc(&ag->rx_ring); ++ if (ret) ++ return ret; ++ ++ ret = ag71xx_ring_rx_init(ag); ++ return ret; ++} ++ ++static void ag71xx_rings_cleanup(struct ag71xx *ag) ++{ ++ ag71xx_ring_rx_clean(ag); ++ ag71xx_ring_free(&ag->rx_ring); ++ ++ ag71xx_ring_tx_clean(ag); ++ netdev_reset_queue(ag->dev); ++ ag71xx_ring_free(&ag->tx_ring); ++} ++ ++static unsigned char *ag71xx_speed_str(struct ag71xx *ag) ++{ ++ switch (ag->speed) { ++ case SPEED_1000: ++ return "1000"; ++ case SPEED_100: ++ return "100"; ++ case SPEED_10: ++ return "10"; ++ } ++ ++ return "?"; ++} ++ ++static void ag71xx_hw_set_macaddr(struct ag71xx *ag, unsigned char *mac) ++{ ++ u32 t; ++ ++ t = (((u32) mac[5]) << 24) | (((u32) mac[4]) << 16) ++ | (((u32) mac[3]) << 8) | ((u32) mac[2]); ++ ++ ag71xx_wr(ag, AG71XX_REG_MAC_ADDR1, t); ++ ++ t = (((u32) mac[1]) << 24) | (((u32) mac[0]) << 16); ++ ag71xx_wr(ag, AG71XX_REG_MAC_ADDR2, t); ++} ++ ++static void ag71xx_dma_reset(struct ag71xx *ag) ++{ ++ u32 val; ++ int i; ++ ++ ag71xx_dump_dma_regs(ag); ++ ++ /* stop RX and TX */ ++ ag71xx_wr(ag, AG71XX_REG_RX_CTRL, 0); ++ ag71xx_wr(ag, AG71XX_REG_TX_CTRL, 0); ++ ++ /* ++ * give the hardware some time to really stop all rx/tx activity ++ * clearing the descriptors too early causes random memory corruption ++ */ ++ mdelay(1); ++ ++ /* clear descriptor addresses */ ++ ag71xx_wr(ag, AG71XX_REG_TX_DESC, ag->stop_desc_dma); ++ ag71xx_wr(ag, AG71XX_REG_RX_DESC, ag->stop_desc_dma); ++ ++ /* clear pending RX/TX interrupts */ ++ for (i = 0; i < 256; i++) { ++ ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_PR); ++ ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_PS); ++ } ++ ++ /* clear pending errors */ ++ ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_BE | RX_STATUS_OF); ++ ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_BE | TX_STATUS_UR); ++ ++ val = ag71xx_rr(ag, AG71XX_REG_RX_STATUS); ++ if (val) ++ pr_alert("%s: unable to clear DMA Rx status: %08x\n", ++ ag->dev->name, val); ++ ++ val = ag71xx_rr(ag, AG71XX_REG_TX_STATUS); ++ ++ /* mask out reserved bits */ ++ val &= ~0xff000000; ++ ++ if (val) ++ pr_alert("%s: unable to clear DMA Tx status: %08x\n", ++ ag->dev->name, val); ++ ++ ag71xx_dump_dma_regs(ag); ++} ++ ++#define MAC_CFG1_INIT (MAC_CFG1_RXE | MAC_CFG1_TXE | \ ++ MAC_CFG1_SRX | MAC_CFG1_STX) ++ ++#define FIFO_CFG0_INIT (FIFO_CFG0_ALL << FIFO_CFG0_ENABLE_SHIFT) ++ ++#define FIFO_CFG4_INIT (FIFO_CFG4_DE | FIFO_CFG4_DV | FIFO_CFG4_FC | \ ++ FIFO_CFG4_CE | FIFO_CFG4_CR | FIFO_CFG4_LM | \ ++ FIFO_CFG4_LO | FIFO_CFG4_OK | FIFO_CFG4_MC | \ ++ FIFO_CFG4_BC | FIFO_CFG4_DR | FIFO_CFG4_LE | \ ++ FIFO_CFG4_CF | FIFO_CFG4_PF | FIFO_CFG4_UO | \ ++ FIFO_CFG4_VT) ++ ++#define FIFO_CFG5_INIT (FIFO_CFG5_DE | FIFO_CFG5_DV | FIFO_CFG5_FC | \ ++ FIFO_CFG5_CE | FIFO_CFG5_LO | FIFO_CFG5_OK | \ ++ FIFO_CFG5_MC | FIFO_CFG5_BC | FIFO_CFG5_DR | \ ++ FIFO_CFG5_CF | FIFO_CFG5_PF | FIFO_CFG5_VT | \ ++ FIFO_CFG5_LE | FIFO_CFG5_FT | FIFO_CFG5_16 | \ ++ FIFO_CFG5_17 | FIFO_CFG5_SF) ++ ++static void ag71xx_hw_stop(struct ag71xx *ag) ++{ ++ /* disable all interrupts and stop the rx/tx engine */ ++ ag71xx_wr(ag, AG71XX_REG_INT_ENABLE, 0); ++ ag71xx_wr(ag, AG71XX_REG_RX_CTRL, 0); ++ ag71xx_wr(ag, AG71XX_REG_TX_CTRL, 0); ++} ++ ++static void ag71xx_hw_setup(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ ++ /* setup MAC configuration registers */ ++ ag71xx_wr(ag, AG71XX_REG_MAC_CFG1, MAC_CFG1_INIT); ++ ++ ag71xx_sb(ag, AG71XX_REG_MAC_CFG2, ++ MAC_CFG2_PAD_CRC_EN | MAC_CFG2_LEN_CHECK); ++ ++ /* setup max frame length to zero */ ++ ag71xx_wr(ag, AG71XX_REG_MAC_MFL, 0); ++ ++ /* setup FIFO configuration registers */ ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG0, FIFO_CFG0_INIT); ++ if (pdata->is_ar724x) { ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG1, pdata->fifo_cfg1); ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG2, pdata->fifo_cfg2); ++ } else { ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG1, 0x0fff0000); ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG2, 0x00001fff); ++ } ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG4, FIFO_CFG4_INIT); ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG5, FIFO_CFG5_INIT); ++} ++ ++static void ag71xx_hw_init(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ u32 reset_mask = pdata->reset_bit; ++ ++ ag71xx_hw_stop(ag); ++ ++ if (pdata->is_ar724x) { ++ u32 reset_phy = reset_mask; ++ ++ reset_phy &= AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY; ++ reset_mask &= ~(AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY); ++ ++ ath79_device_reset_set(reset_phy); ++ mdelay(50); ++ ath79_device_reset_clear(reset_phy); ++ mdelay(200); ++ } ++ ++ ag71xx_sb(ag, AG71XX_REG_MAC_CFG1, MAC_CFG1_SR); ++ udelay(20); ++ ++ ath79_device_reset_set(reset_mask); ++ mdelay(100); ++ ath79_device_reset_clear(reset_mask); ++ mdelay(200); ++ ++ ag71xx_hw_setup(ag); ++ ++ ag71xx_dma_reset(ag); ++} ++ ++static void ag71xx_fast_reset(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ struct net_device *dev = ag->dev; ++ u32 reset_mask = pdata->reset_bit; ++ u32 rx_ds, tx_ds; ++ u32 mii_reg; ++ ++ reset_mask &= AR71XX_RESET_GE0_MAC | AR71XX_RESET_GE1_MAC; ++ ++ mii_reg = ag71xx_rr(ag, AG71XX_REG_MII_CFG); ++ rx_ds = ag71xx_rr(ag, AG71XX_REG_RX_DESC); ++ tx_ds = ag71xx_rr(ag, AG71XX_REG_TX_DESC); ++ ++ ath79_device_reset_set(reset_mask); ++ udelay(10); ++ ath79_device_reset_clear(reset_mask); ++ udelay(10); ++ ++ ag71xx_dma_reset(ag); ++ ag71xx_hw_setup(ag); ++ ++ /* setup max frame length */ ++ ag71xx_wr(ag, AG71XX_REG_MAC_MFL, ++ ag71xx_max_frame_len(ag->dev->mtu)); ++ ++ ag71xx_wr(ag, AG71XX_REG_RX_DESC, rx_ds); ++ ag71xx_wr(ag, AG71XX_REG_TX_DESC, tx_ds); ++ ag71xx_wr(ag, AG71XX_REG_MII_CFG, mii_reg); ++ ++ ag71xx_hw_set_macaddr(ag, dev->dev_addr); ++} ++ ++static void ag71xx_hw_start(struct ag71xx *ag) ++{ ++ /* start RX engine */ ++ ag71xx_wr(ag, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); ++ ++ /* enable interrupts */ ++ ag71xx_wr(ag, AG71XX_REG_INT_ENABLE, AG71XX_INT_INIT); ++} ++ ++void ag71xx_link_adjust(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ u32 cfg2; ++ u32 ifctl; ++ u32 fifo5; ++ ++ if (!ag->link) { ++ ag71xx_hw_stop(ag); ++ netif_carrier_off(ag->dev); ++ if (netif_msg_link(ag)) ++ pr_info("%s: link down\n", ag->dev->name); ++ return; ++ } ++ ++ if (pdata->is_ar724x) ++ ag71xx_fast_reset(ag); ++ ++ cfg2 = ag71xx_rr(ag, AG71XX_REG_MAC_CFG2); ++ cfg2 &= ~(MAC_CFG2_IF_1000 | MAC_CFG2_IF_10_100 | MAC_CFG2_FDX); ++ cfg2 |= (ag->duplex) ? MAC_CFG2_FDX : 0; ++ ++ ifctl = ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL); ++ ifctl &= ~(MAC_IFCTL_SPEED); ++ ++ fifo5 = ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5); ++ fifo5 &= ~FIFO_CFG5_BM; ++ ++ switch (ag->speed) { ++ case SPEED_1000: ++ cfg2 |= MAC_CFG2_IF_1000; ++ fifo5 |= FIFO_CFG5_BM; ++ break; ++ case SPEED_100: ++ cfg2 |= MAC_CFG2_IF_10_100; ++ ifctl |= MAC_IFCTL_SPEED; ++ break; ++ case SPEED_10: ++ cfg2 |= MAC_CFG2_IF_10_100; ++ break; ++ default: ++ BUG(); ++ return; ++ } ++ ++ if (pdata->is_ar91xx) ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, 0x00780fff); ++ else if (pdata->is_ar724x) ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, pdata->fifo_cfg3); ++ else ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, 0x008001ff); ++ ++ if (pdata->set_speed) ++ pdata->set_speed(ag->speed); ++ ++ ag71xx_wr(ag, AG71XX_REG_MAC_CFG2, cfg2); ++ ag71xx_wr(ag, AG71XX_REG_FIFO_CFG5, fifo5); ++ ag71xx_wr(ag, AG71XX_REG_MAC_IFCTL, ifctl); ++ ag71xx_hw_start(ag); ++ ++ netif_carrier_on(ag->dev); ++ if (netif_msg_link(ag)) ++ pr_info("%s: link up (%sMbps/%s duplex)\n", ++ ag->dev->name, ++ ag71xx_speed_str(ag), ++ (DUPLEX_FULL == ag->duplex) ? "Full" : "Half"); ++ ++ DBG("%s: fifo_cfg0=%#x, fifo_cfg1=%#x, fifo_cfg2=%#x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG0), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG1), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG2)); ++ ++ DBG("%s: fifo_cfg3=%#x, fifo_cfg4=%#x, fifo_cfg5=%#x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG3), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG4), ++ ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5)); ++ ++ DBG("%s: mac_cfg2=%#x, mac_ifctl=%#x\n", ++ ag->dev->name, ++ ag71xx_rr(ag, AG71XX_REG_MAC_CFG2), ++ ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL)); ++} ++ ++static int ag71xx_open(struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ unsigned int max_frame_len; ++ int ret; ++ ++ max_frame_len = ag71xx_max_frame_len(dev->mtu); ++ ag->rx_buf_size = max_frame_len + NET_SKB_PAD + NET_IP_ALIGN; ++ ++ /* setup max frame length */ ++ ag71xx_wr(ag, AG71XX_REG_MAC_MFL, max_frame_len); ++ ++ ret = ag71xx_rings_init(ag); ++ if (ret) ++ goto err; ++ ++ napi_enable(&ag->napi); ++ ++ netif_carrier_off(dev); ++ ag71xx_phy_start(ag); ++ ++ ag71xx_wr(ag, AG71XX_REG_TX_DESC, ag->tx_ring.descs_dma); ++ ag71xx_wr(ag, AG71XX_REG_RX_DESC, ag->rx_ring.descs_dma); ++ ++ ag71xx_hw_set_macaddr(ag, dev->dev_addr); ++ ++ netif_start_queue(dev); ++ ++ return 0; ++ ++err: ++ ag71xx_rings_cleanup(ag); ++ return ret; ++} ++ ++static int ag71xx_stop(struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ unsigned long flags; ++ ++ netif_carrier_off(dev); ++ ag71xx_phy_stop(ag); ++ ++ spin_lock_irqsave(&ag->lock, flags); ++ ++ netif_stop_queue(dev); ++ ++ ag71xx_hw_stop(ag); ++ ag71xx_dma_reset(ag); ++ ++ napi_disable(&ag->napi); ++ del_timer_sync(&ag->oom_timer); ++ ++ spin_unlock_irqrestore(&ag->lock, flags); ++ ++ ag71xx_rings_cleanup(ag); ++ ++ return 0; ++} ++ ++static netdev_tx_t ag71xx_hard_start_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ struct ag71xx_ring *ring = &ag->tx_ring; ++ struct ag71xx_desc *desc; ++ dma_addr_t dma_addr; ++ int i; ++ ++ i = ring->curr % ring->size; ++ desc = ring->buf[i].desc; ++ ++ if (!ag71xx_desc_empty(desc)) ++ goto err_drop; ++ ++ if (ag71xx_has_ar8216(ag)) ++ ag71xx_add_ar8216_header(ag, skb); ++ ++ if (skb->len <= 0) { ++ DBG("%s: packet len is too small\n", ag->dev->name); ++ goto err_drop; ++ } ++ ++ dma_addr = dma_map_single(&dev->dev, skb->data, skb->len, ++ DMA_TO_DEVICE); ++ ++ netdev_sent_queue(dev, skb->len); ++ ring->buf[i].len = skb->len; ++ ring->buf[i].skb = skb; ++ ring->buf[i].timestamp = jiffies; ++ ++ /* setup descriptor fields */ ++ desc->data = (u32) dma_addr; ++ desc->ctrl = skb->len & ag->desc_pktlen_mask; ++ ++ /* flush descriptor */ ++ wmb(); ++ ++ ring->curr++; ++ if (ring->curr == (ring->dirty + ring->size)) { ++ DBG("%s: tx queue full\n", ag->dev->name); ++ netif_stop_queue(dev); ++ } ++ ++ DBG("%s: packet injected into TX queue\n", ag->dev->name); ++ ++ /* enable TX engine */ ++ ag71xx_wr(ag, AG71XX_REG_TX_CTRL, TX_CTRL_TXE); ++ ++ return NETDEV_TX_OK; ++ ++err_drop: ++ dev->stats.tx_dropped++; ++ ++ dev_kfree_skb(skb); ++ return NETDEV_TX_OK; ++} ++ ++static int ag71xx_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ int ret; ++ ++ switch (cmd) { ++ case SIOCETHTOOL: ++ if (ag->phy_dev == NULL) ++ break; ++ ++ spin_lock_irq(&ag->lock); ++ ret = phy_ethtool_ioctl(ag->phy_dev, (void *) ifr->ifr_data); ++ spin_unlock_irq(&ag->lock); ++ return ret; ++ ++ case SIOCSIFHWADDR: ++ if (copy_from_user ++ (dev->dev_addr, ifr->ifr_data, sizeof(dev->dev_addr))) ++ return -EFAULT; ++ return 0; ++ ++ case SIOCGIFHWADDR: ++ if (copy_to_user ++ (ifr->ifr_data, dev->dev_addr, sizeof(dev->dev_addr))) ++ return -EFAULT; ++ return 0; ++ ++ case SIOCGMIIPHY: ++ case SIOCGMIIREG: ++ case SIOCSMIIREG: ++ if (ag->phy_dev == NULL) ++ break; ++ ++ return phy_mii_ioctl(ag->phy_dev, ifr, cmd); ++ ++ default: ++ break; ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++static void ag71xx_oom_timer_handler(unsigned long data) ++{ ++ struct net_device *dev = (struct net_device *) data; ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ napi_schedule(&ag->napi); ++} ++ ++static void ag71xx_tx_timeout(struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ if (netif_msg_tx_err(ag)) ++ pr_info("%s: tx timeout\n", ag->dev->name); ++ ++ schedule_work(&ag->restart_work); ++} ++ ++static void ag71xx_restart_work_func(struct work_struct *work) ++{ ++ struct ag71xx *ag = container_of(work, struct ag71xx, restart_work); ++ ++ if (ag71xx_get_pdata(ag)->is_ar724x) { ++ ag->link = 0; ++ ag71xx_link_adjust(ag); ++ return; ++ } ++ ++ ag71xx_stop(ag->dev); ++ ag71xx_open(ag->dev); ++} ++ ++static bool ag71xx_check_dma_stuck(struct ag71xx *ag, unsigned long timestamp) ++{ ++ u32 rx_sm, tx_sm, rx_fd; ++ ++ if (likely(time_before(jiffies, timestamp + HZ/10))) ++ return false; ++ ++ if (!netif_carrier_ok(ag->dev)) ++ return false; ++ ++ rx_sm = ag71xx_rr(ag, AG71XX_REG_RX_SM); ++ if ((rx_sm & 0x7) == 0x3 && ((rx_sm >> 4) & 0x7) == 0x6) ++ return true; ++ ++ tx_sm = ag71xx_rr(ag, AG71XX_REG_TX_SM); ++ rx_fd = ag71xx_rr(ag, AG71XX_REG_FIFO_DEPTH); ++ if (((tx_sm >> 4) & 0x7) == 0 && ((rx_sm & 0x7) == 0) && ++ ((rx_sm >> 4) & 0x7) == 0 && rx_fd == 0) ++ return true; ++ ++ return false; ++} ++ ++static int ag71xx_tx_packets(struct ag71xx *ag) ++{ ++ struct ag71xx_ring *ring = &ag->tx_ring; ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ int sent = 0; ++ int bytes_compl = 0; ++ ++ DBG("%s: processing TX ring\n", ag->dev->name); ++ ++ while (ring->dirty != ring->curr) { ++ unsigned int i = ring->dirty % ring->size; ++ struct ag71xx_desc *desc = ring->buf[i].desc; ++ struct sk_buff *skb = ring->buf[i].skb; ++ int len = ring->buf[i].len; ++ ++ if (!ag71xx_desc_empty(desc)) { ++ if (pdata->is_ar7240 && ++ ag71xx_check_dma_stuck(ag, ring->buf[i].timestamp)) ++ schedule_work(&ag->restart_work); ++ break; ++ } ++ ++ ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_PS); ++ ++ bytes_compl += len; ++ ag->dev->stats.tx_bytes += len; ++ ag->dev->stats.tx_packets++; ++ ++ dev_kfree_skb_any(skb); ++ ring->buf[i].skb = NULL; ++ ++ ring->dirty++; ++ sent++; ++ } ++ ++ DBG("%s: %d packets sent out\n", ag->dev->name, sent); ++ ++ if (!sent) ++ return 0; ++ ++ netdev_completed_queue(ag->dev, sent, bytes_compl); ++ if ((ring->curr - ring->dirty) < (ring->size * 3) / 4) ++ netif_wake_queue(ag->dev); ++ ++ return sent; ++} ++ ++static int ag71xx_rx_packets(struct ag71xx *ag, int limit) ++{ ++ struct net_device *dev = ag->dev; ++ struct ag71xx_ring *ring = &ag->rx_ring; ++ int offset = ag71xx_buffer_offset(ag); ++ unsigned int pktlen_mask = ag->desc_pktlen_mask; ++ int done = 0; ++ ++ DBG("%s: rx packets, limit=%d, curr=%u, dirty=%u\n", ++ dev->name, limit, ring->curr, ring->dirty); ++ ++ while (done < limit) { ++ unsigned int i = ring->curr % ring->size; ++ struct ag71xx_desc *desc = ring->buf[i].desc; ++ struct sk_buff *skb; ++ int pktlen; ++ int err = 0; ++ ++ if (ag71xx_desc_empty(desc)) ++ break; ++ ++ if ((ring->dirty + ring->size) == ring->curr) { ++ ag71xx_assert(0); ++ break; ++ } ++ ++ ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_PR); ++ ++ pktlen = desc->ctrl & pktlen_mask; ++ pktlen -= ETH_FCS_LEN; ++ ++ dma_unmap_single(&dev->dev, ring->buf[i].dma_addr, ++ ag->rx_buf_size, DMA_FROM_DEVICE); ++ ++ dev->stats.rx_packets++; ++ dev->stats.rx_bytes += pktlen; ++ ++ skb = build_skb(ring->buf[i].rx_buf, 0); ++ if (!skb) { ++ kfree(ring->buf[i].rx_buf); ++ goto next; ++ } ++ ++ skb_reserve(skb, offset); ++ skb_put(skb, pktlen); ++ ++ if (ag71xx_has_ar8216(ag)) ++ err = ag71xx_remove_ar8216_header(ag, skb, pktlen); ++ ++ if (err) { ++ dev->stats.rx_dropped++; ++ kfree_skb(skb); ++ } else { ++ skb->dev = dev; ++ skb->ip_summed = CHECKSUM_NONE; ++ skb->protocol = eth_type_trans(skb, dev); ++ netif_receive_skb(skb); ++ } ++ ++next: ++ ring->buf[i].rx_buf = NULL; ++ done++; ++ ++ ring->curr++; ++ } ++ ++ ag71xx_ring_rx_refill(ag); ++ ++ DBG("%s: rx finish, curr=%u, dirty=%u, done=%d\n", ++ dev->name, ring->curr, ring->dirty, done); ++ ++ return done; ++} ++ ++static int ag71xx_poll(struct napi_struct *napi, int limit) ++{ ++ struct ag71xx *ag = container_of(napi, struct ag71xx, napi); ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ struct net_device *dev = ag->dev; ++ struct ag71xx_ring *rx_ring; ++ unsigned long flags; ++ u32 status; ++ int tx_done; ++ int rx_done; ++ ++ pdata->ddr_flush(); ++ tx_done = ag71xx_tx_packets(ag); ++ ++ DBG("%s: processing RX ring\n", dev->name); ++ rx_done = ag71xx_rx_packets(ag, limit); ++ ++ ag71xx_debugfs_update_napi_stats(ag, rx_done, tx_done); ++ ++ rx_ring = &ag->rx_ring; ++ if (rx_ring->buf[rx_ring->dirty % rx_ring->size].rx_buf == NULL) ++ goto oom; ++ ++ status = ag71xx_rr(ag, AG71XX_REG_RX_STATUS); ++ if (unlikely(status & RX_STATUS_OF)) { ++ ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_OF); ++ dev->stats.rx_fifo_errors++; ++ ++ /* restart RX */ ++ ag71xx_wr(ag, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); ++ } ++ ++ if (rx_done < limit) { ++ if (status & RX_STATUS_PR) ++ goto more; ++ ++ status = ag71xx_rr(ag, AG71XX_REG_TX_STATUS); ++ if (status & TX_STATUS_PS) ++ goto more; ++ ++ DBG("%s: disable polling mode, rx=%d, tx=%d,limit=%d\n", ++ dev->name, rx_done, tx_done, limit); ++ ++ napi_complete(napi); ++ ++ /* enable interrupts */ ++ spin_lock_irqsave(&ag->lock, flags); ++ ag71xx_int_enable(ag, AG71XX_INT_POLL); ++ spin_unlock_irqrestore(&ag->lock, flags); ++ return rx_done; ++ } ++ ++more: ++ DBG("%s: stay in polling mode, rx=%d, tx=%d, limit=%d\n", ++ dev->name, rx_done, tx_done, limit); ++ return rx_done; ++ ++oom: ++ if (netif_msg_rx_err(ag)) ++ pr_info("%s: out of memory\n", dev->name); ++ ++ mod_timer(&ag->oom_timer, jiffies + AG71XX_OOM_REFILL); ++ napi_complete(napi); ++ return 0; ++} ++ ++static irqreturn_t ag71xx_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *dev = dev_id; ++ struct ag71xx *ag = netdev_priv(dev); ++ u32 status; ++ ++ status = ag71xx_rr(ag, AG71XX_REG_INT_STATUS); ++ ag71xx_dump_intr(ag, "raw", status); ++ ++ if (unlikely(!status)) ++ return IRQ_NONE; ++ ++ if (unlikely(status & AG71XX_INT_ERR)) { ++ if (status & AG71XX_INT_TX_BE) { ++ ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_BE); ++ dev_err(&dev->dev, "TX BUS error\n"); ++ } ++ if (status & AG71XX_INT_RX_BE) { ++ ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_BE); ++ dev_err(&dev->dev, "RX BUS error\n"); ++ } ++ } ++ ++ if (likely(status & AG71XX_INT_POLL)) { ++ ag71xx_int_disable(ag, AG71XX_INT_POLL); ++ DBG("%s: enable polling mode\n", dev->name); ++ napi_schedule(&ag->napi); ++ } ++ ++ ag71xx_debugfs_update_int_stats(ag, status); ++ ++ return IRQ_HANDLED; ++} ++ ++#ifdef CONFIG_NET_POLL_CONTROLLER ++/* ++ * Polling 'interrupt' - used by things like netconsole to send skbs ++ * without having to re-enable interrupts. It's not called while ++ * the interrupt routine is executing. ++ */ ++static void ag71xx_netpoll(struct net_device *dev) ++{ ++ disable_irq(dev->irq); ++ ag71xx_interrupt(dev->irq, dev); ++ enable_irq(dev->irq); ++} ++#endif ++ ++static int ag71xx_change_mtu(struct net_device *dev, int new_mtu) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ unsigned int max_frame_len; ++ ++ max_frame_len = ag71xx_max_frame_len(new_mtu); ++ if (new_mtu < 68 || max_frame_len > ag->max_frame_len) ++ return -EINVAL; ++ ++ if (netif_running(dev)) ++ return -EBUSY; ++ ++ dev->mtu = new_mtu; ++ return 0; ++} ++ ++static const struct net_device_ops ag71xx_netdev_ops = { ++ .ndo_open = ag71xx_open, ++ .ndo_stop = ag71xx_stop, ++ .ndo_start_xmit = ag71xx_hard_start_xmit, ++ .ndo_do_ioctl = ag71xx_do_ioctl, ++ .ndo_tx_timeout = ag71xx_tx_timeout, ++ .ndo_change_mtu = ag71xx_change_mtu, ++ .ndo_set_mac_address = eth_mac_addr, ++ .ndo_validate_addr = eth_validate_addr, ++#ifdef CONFIG_NET_POLL_CONTROLLER ++ .ndo_poll_controller = ag71xx_netpoll, ++#endif ++}; ++ ++static const char *ag71xx_get_phy_if_mode_name(phy_interface_t mode) ++{ ++ switch (mode) { ++ case PHY_INTERFACE_MODE_MII: ++ return "MII"; ++ case PHY_INTERFACE_MODE_GMII: ++ return "GMII"; ++ case PHY_INTERFACE_MODE_RMII: ++ return "RMII"; ++ case PHY_INTERFACE_MODE_RGMII: ++ return "RGMII"; ++ case PHY_INTERFACE_MODE_SGMII: ++ return "SGMII"; ++ default: ++ break; ++ } ++ ++ return "unknown"; ++} ++ ++ ++static int ag71xx_probe(struct platform_device *pdev) ++{ ++ struct net_device *dev; ++ struct resource *res; ++ struct ag71xx *ag; ++ struct ag71xx_platform_data *pdata; ++ int err; ++ ++ pdata = pdev->dev.platform_data; ++ if (!pdata) { ++ dev_err(&pdev->dev, "no platform data specified\n"); ++ err = -ENXIO; ++ goto err_out; ++ } ++ ++ if (pdata->mii_bus_dev == NULL && pdata->phy_mask) { ++ dev_err(&pdev->dev, "no MII bus device specified\n"); ++ err = -EINVAL; ++ goto err_out; ++ } ++ ++ dev = alloc_etherdev(sizeof(*ag)); ++ if (!dev) { ++ dev_err(&pdev->dev, "alloc_etherdev failed\n"); ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ if (!pdata->max_frame_len || !pdata->desc_pktlen_mask) ++ return -EINVAL; ++ ++ SET_NETDEV_DEV(dev, &pdev->dev); ++ ++ ag = netdev_priv(dev); ++ ag->pdev = pdev; ++ ag->dev = dev; ++ ag->msg_enable = netif_msg_init(ag71xx_msg_level, ++ AG71XX_DEFAULT_MSG_ENABLE); ++ spin_lock_init(&ag->lock); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac_base"); ++ if (!res) { ++ dev_err(&pdev->dev, "no mac_base resource found\n"); ++ err = -ENXIO; ++ goto err_out; ++ } ++ ++ ag->mac_base = ioremap_nocache(res->start, res->end - res->start + 1); ++ if (!ag->mac_base) { ++ dev_err(&pdev->dev, "unable to ioremap mac_base\n"); ++ err = -ENOMEM; ++ goto err_free_dev; ++ } ++ ++ dev->irq = platform_get_irq(pdev, 0); ++ err = request_irq(dev->irq, ag71xx_interrupt, ++ IRQF_DISABLED, ++ dev->name, dev); ++ if (err) { ++ dev_err(&pdev->dev, "unable to request IRQ %d\n", dev->irq); ++ goto err_unmap_base; ++ } ++ ++ dev->base_addr = (unsigned long)ag->mac_base; ++ dev->netdev_ops = &ag71xx_netdev_ops; ++ dev->ethtool_ops = &ag71xx_ethtool_ops; ++ ++ INIT_WORK(&ag->restart_work, ag71xx_restart_work_func); ++ ++ init_timer(&ag->oom_timer); ++ ag->oom_timer.data = (unsigned long) dev; ++ ag->oom_timer.function = ag71xx_oom_timer_handler; ++ ++ ag->tx_ring.size = AG71XX_TX_RING_SIZE_DEFAULT; ++ ag->rx_ring.size = AG71XX_RX_RING_SIZE_DEFAULT; ++ ++ ag->max_frame_len = pdata->max_frame_len; ++ ag->desc_pktlen_mask = pdata->desc_pktlen_mask; ++ ++ ag->stop_desc = dma_alloc_coherent(NULL, ++ sizeof(struct ag71xx_desc), &ag->stop_desc_dma, GFP_KERNEL); ++ ++ if (!ag->stop_desc) ++ goto err_free_irq; ++ ++ ag->stop_desc->data = 0; ++ ag->stop_desc->ctrl = 0; ++ ag->stop_desc->next = (u32) ag->stop_desc_dma; ++ ++ memcpy(dev->dev_addr, pdata->mac_addr, ETH_ALEN); ++ ++ netif_napi_add(dev, &ag->napi, ag71xx_poll, AG71XX_NAPI_WEIGHT); ++ ++ ag71xx_dump_regs(ag); ++ ++ ag71xx_hw_init(ag); ++ ++ ag71xx_dump_regs(ag); ++ ++ err = ag71xx_phy_connect(ag); ++ if (err) ++ goto err_free_desc; ++ ++ err = ag71xx_debugfs_init(ag); ++ if (err) ++ goto err_phy_disconnect; ++ ++ platform_set_drvdata(pdev, dev); ++ ++ err = register_netdev(dev); ++ if (err) { ++ dev_err(&pdev->dev, "unable to register net device\n"); ++ goto err_debugfs_exit; ++ } ++ ++ pr_info("%s: Atheros AG71xx at 0x%08lx, irq %d, mode:%s\n", ++ dev->name, dev->base_addr, dev->irq, ++ ag71xx_get_phy_if_mode_name(pdata->phy_if_mode)); ++ ++ return 0; ++ ++err_debugfs_exit: ++ ag71xx_debugfs_exit(ag); ++err_phy_disconnect: ++ ag71xx_phy_disconnect(ag); ++err_free_desc: ++ dma_free_coherent(NULL, sizeof(struct ag71xx_desc), ag->stop_desc, ++ ag->stop_desc_dma); ++err_free_irq: ++ free_irq(dev->irq, dev); ++err_unmap_base: ++ iounmap(ag->mac_base); ++err_free_dev: ++ kfree(dev); ++err_out: ++ platform_set_drvdata(pdev, NULL); ++ return err; ++} ++ ++static int ag71xx_remove(struct platform_device *pdev) ++{ ++ struct net_device *dev = platform_get_drvdata(pdev); ++ ++ if (dev) { ++ struct ag71xx *ag = netdev_priv(dev); ++ ++ ag71xx_debugfs_exit(ag); ++ ag71xx_phy_disconnect(ag); ++ unregister_netdev(dev); ++ free_irq(dev->irq, dev); ++ iounmap(ag->mac_base); ++ kfree(dev); ++ platform_set_drvdata(pdev, NULL); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver ag71xx_driver = { ++ .probe = ag71xx_probe, ++ .remove = ag71xx_remove, ++ .driver = { ++ .name = AG71XX_DRV_NAME, ++ } ++}; ++ ++static int __init ag71xx_module_init(void) ++{ ++ int ret; ++ ++ ret = ag71xx_debugfs_root_init(); ++ if (ret) ++ goto err_out; ++ ++ ret = ag71xx_mdio_driver_init(); ++ if (ret) ++ goto err_debugfs_exit; ++ ++ ret = platform_driver_register(&ag71xx_driver); ++ if (ret) ++ goto err_mdio_exit; ++ ++ return 0; ++ ++err_mdio_exit: ++ ag71xx_mdio_driver_exit(); ++err_debugfs_exit: ++ ag71xx_debugfs_root_exit(); ++err_out: ++ return ret; ++} ++ ++static void __exit ag71xx_module_exit(void) ++{ ++ platform_driver_unregister(&ag71xx_driver); ++ ag71xx_mdio_driver_exit(); ++ ag71xx_debugfs_root_exit(); ++} ++ ++module_init(ag71xx_module_init); ++module_exit(ag71xx_module_exit); ++ ++MODULE_VERSION(AG71XX_DRV_VERSION); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" AG71XX_DRV_NAME); +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c +new file mode 100644 +index 0000000..71ae825 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c +@@ -0,0 +1,318 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 "ag71xx.h" ++ ++#define AG71XX_MDIO_RETRY 1000 ++#define AG71XX_MDIO_DELAY 5 ++ ++static inline void ag71xx_mdio_wr(struct ag71xx_mdio *am, unsigned reg, ++ u32 value) ++{ ++ void __iomem *r; ++ ++ r = am->mdio_base + reg; ++ __raw_writel(value, r); ++ ++ /* flush write */ ++ (void) __raw_readl(r); ++} ++ ++static inline u32 ag71xx_mdio_rr(struct ag71xx_mdio *am, unsigned reg) ++{ ++ return __raw_readl(am->mdio_base + reg); ++} ++ ++static void ag71xx_mdio_dump_regs(struct ag71xx_mdio *am) ++{ ++ DBG("%s: mii_cfg=%08x, mii_cmd=%08x, mii_addr=%08x\n", ++ am->mii_bus->name, ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_CFG), ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_CMD), ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_ADDR)); ++ DBG("%s: mii_ctrl=%08x, mii_status=%08x, mii_ind=%08x\n", ++ am->mii_bus->name, ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_CTRL), ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS), ++ ag71xx_mdio_rr(am, AG71XX_REG_MII_IND)); ++} ++ ++static int ag71xx_mdio_wait_busy(struct ag71xx_mdio *am) ++{ ++ int i; ++ ++ for (i = 0; i < AG71XX_MDIO_RETRY; i++) { ++ u32 busy; ++ ++ udelay(AG71XX_MDIO_DELAY); ++ ++ busy = ag71xx_mdio_rr(am, AG71XX_REG_MII_IND); ++ if (!busy) ++ return 0; ++ ++ udelay(AG71XX_MDIO_DELAY); ++ } ++ ++ pr_err("%s: MDIO operation timed out\n", am->mii_bus->name); ++ ++ return -ETIMEDOUT; ++} ++ ++int ag71xx_mdio_mii_read(struct ag71xx_mdio *am, int addr, int reg) ++{ ++ int err; ++ int ret; ++ ++ err = ag71xx_mdio_wait_busy(am); ++ if (err) ++ return 0xffff; ++ ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, ++ ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_READ); ++ ++ err = ag71xx_mdio_wait_busy(am); ++ if (err) ++ return 0xffff; ++ ++ ret = ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS) & 0xffff; ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); ++ ++ DBG("mii_read: addr=%04x, reg=%04x, value=%04x\n", addr, reg, ret); ++ ++ return ret; ++} ++ ++void ag71xx_mdio_mii_write(struct ag71xx_mdio *am, int addr, int reg, u16 val) ++{ ++ DBG("mii_write: addr=%04x, reg=%04x, value=%04x\n", addr, reg, val); ++ ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, ++ ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CTRL, val); ++ ++ ag71xx_mdio_wait_busy(am); ++} ++ ++static const u32 ar71xx_mdio_div_table[] = { ++ 4, 4, 6, 8, 10, 14, 20, 28, ++}; ++ ++static const u32 ar7240_mdio_div_table[] = { ++ 2, 2, 4, 6, 8, 12, 18, 26, 32, 40, 48, 56, 62, 70, 78, 96, ++}; ++ ++static const u32 ar933x_mdio_div_table[] = { ++ 4, 4, 6, 8, 10, 14, 20, 28, 34, 42, 50, 58, 66, 74, 82, 98, ++}; ++ ++static int ag71xx_mdio_get_divider(struct ag71xx_mdio *am, u32 *div) ++{ ++ unsigned long ref_clock, mdio_clock; ++ const u32 *table; ++ int ndivs; ++ int i; ++ ++ ref_clock = am->pdata->ref_clock; ++ mdio_clock = am->pdata->mdio_clock; ++ ++ if (!ref_clock || !mdio_clock) ++ return -EINVAL; ++ ++ if (am->pdata->is_ar9330 || am->pdata->is_ar934x) { ++ table = ar933x_mdio_div_table; ++ ndivs = ARRAY_SIZE(ar933x_mdio_div_table); ++ } else if (am->pdata->is_ar7240) { ++ table = ar7240_mdio_div_table; ++ ndivs = ARRAY_SIZE(ar7240_mdio_div_table); ++ } else { ++ table = ar71xx_mdio_div_table; ++ ndivs = ARRAY_SIZE(ar71xx_mdio_div_table); ++ } ++ ++ for (i = 0; i < ndivs; i++) { ++ unsigned long t; ++ ++ t = ref_clock / table[i]; ++ if (t <= mdio_clock) { ++ *div = i; ++ return 0; ++ } ++ } ++ ++ dev_err(&am->mii_bus->dev, "no divider found for %lu/%lu\n", ++ ref_clock, mdio_clock); ++ return -ENOENT; ++} ++ ++static int ag71xx_mdio_reset(struct mii_bus *bus) ++{ ++ struct ag71xx_mdio *am = bus->priv; ++ u32 t; ++ int err; ++ ++ err = ag71xx_mdio_get_divider(am, &t); ++ if (err) { ++ /* fallback */ ++ if (am->pdata->is_ar7240) ++ t = MII_CFG_CLK_DIV_6; ++ else if (am->pdata->builtin_switch && !am->pdata->is_ar934x) ++ t = MII_CFG_CLK_DIV_10; ++ else if (!am->pdata->builtin_switch && am->pdata->is_ar934x) ++ t = MII_CFG_CLK_DIV_58; ++ else ++ t = MII_CFG_CLK_DIV_28; ++ } ++ ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t | MII_CFG_RESET); ++ udelay(100); ++ ++ ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t); ++ udelay(100); ++ ++ if (am->pdata->reset) ++ am->pdata->reset(bus); ++ ++ return 0; ++} ++ ++static int ag71xx_mdio_read(struct mii_bus *bus, int addr, int reg) ++{ ++ struct ag71xx_mdio *am = bus->priv; ++ ++ if (am->pdata->builtin_switch) ++ return ar7240sw_phy_read(bus, addr, reg); ++ else ++ return ag71xx_mdio_mii_read(am, addr, reg); ++} ++ ++static int ag71xx_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) ++{ ++ struct ag71xx_mdio *am = bus->priv; ++ ++ if (am->pdata->builtin_switch) ++ ar7240sw_phy_write(bus, addr, reg, val); ++ else ++ ag71xx_mdio_mii_write(am, addr, reg, val); ++ return 0; ++} ++ ++static int ag71xx_mdio_probe(struct platform_device *pdev) ++{ ++ struct ag71xx_mdio_platform_data *pdata; ++ struct ag71xx_mdio *am; ++ struct resource *res; ++ int i; ++ int err; ++ ++ pdata = pdev->dev.platform_data; ++ if (!pdata) { ++ dev_err(&pdev->dev, "no platform data specified\n"); ++ return -EINVAL; ++ } ++ ++ am = kzalloc(sizeof(*am), GFP_KERNEL); ++ if (!am) { ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ am->pdata = pdata; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "no iomem resource found\n"); ++ err = -ENXIO; ++ goto err_out; ++ } ++ ++ am->mdio_base = ioremap_nocache(res->start, res->end - res->start + 1); ++ if (!am->mdio_base) { ++ dev_err(&pdev->dev, "unable to ioremap registers\n"); ++ err = -ENOMEM; ++ goto err_free_mdio; ++ } ++ ++ am->mii_bus = mdiobus_alloc(); ++ if (am->mii_bus == NULL) { ++ err = -ENOMEM; ++ goto err_iounmap; ++ } ++ ++ am->mii_bus->name = "ag71xx_mdio"; ++ am->mii_bus->read = ag71xx_mdio_read; ++ am->mii_bus->write = ag71xx_mdio_write; ++ am->mii_bus->reset = ag71xx_mdio_reset; ++ am->mii_bus->irq = am->mii_irq; ++ am->mii_bus->priv = am; ++ am->mii_bus->parent = &pdev->dev; ++ snprintf(am->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&pdev->dev)); ++ am->mii_bus->phy_mask = pdata->phy_mask; ++ ++ for (i = 0; i < PHY_MAX_ADDR; i++) ++ am->mii_irq[i] = PHY_POLL; ++ ++ ag71xx_mdio_wr(am, AG71XX_REG_MAC_CFG1, 0); ++ ++ err = mdiobus_register(am->mii_bus); ++ if (err) ++ goto err_free_bus; ++ ++ ag71xx_mdio_dump_regs(am); ++ ++ platform_set_drvdata(pdev, am); ++ return 0; ++ ++err_free_bus: ++ mdiobus_free(am->mii_bus); ++err_iounmap: ++ iounmap(am->mdio_base); ++err_free_mdio: ++ kfree(am); ++err_out: ++ return err; ++} ++ ++static int ag71xx_mdio_remove(struct platform_device *pdev) ++{ ++ struct ag71xx_mdio *am = platform_get_drvdata(pdev); ++ ++ if (am) { ++ mdiobus_unregister(am->mii_bus); ++ mdiobus_free(am->mii_bus); ++ iounmap(am->mdio_base); ++ kfree(am); ++ platform_set_drvdata(pdev, NULL); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver ag71xx_mdio_driver = { ++ .probe = ag71xx_mdio_probe, ++ .remove = ag71xx_mdio_remove, ++ .driver = { ++ .name = "ag71xx-mdio", ++ } ++}; ++ ++int __init ag71xx_mdio_driver_init(void) ++{ ++ return platform_driver_register(&ag71xx_mdio_driver); ++} ++ ++void ag71xx_mdio_driver_exit(void) ++{ ++ platform_driver_unregister(&ag71xx_mdio_driver); ++} +diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c +new file mode 100644 +index 0000000..9de77e9 +--- /dev/null ++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c +@@ -0,0 +1,235 @@ ++/* ++ * Atheros AR71xx built-in ethernet mac driver ++ * ++ * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Based on Atheros' AG7100 driver ++ * ++ * 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 "ag71xx.h" ++ ++static void ag71xx_phy_link_adjust(struct net_device *dev) ++{ ++ struct ag71xx *ag = netdev_priv(dev); ++ struct phy_device *phydev = ag->phy_dev; ++ unsigned long flags; ++ int status_change = 0; ++ ++ spin_lock_irqsave(&ag->lock, flags); ++ ++ if (phydev->link) { ++ if (ag->duplex != phydev->duplex ++ || ag->speed != phydev->speed) { ++ status_change = 1; ++ } ++ } ++ ++ if (phydev->link != ag->link) ++ status_change = 1; ++ ++ ag->link = phydev->link; ++ ag->duplex = phydev->duplex; ++ ag->speed = phydev->speed; ++ ++ if (status_change) ++ ag71xx_link_adjust(ag); ++ ++ spin_unlock_irqrestore(&ag->lock, flags); ++} ++ ++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->mii_bus_dev && pdata->switch_data) { ++ ag71xx_ar7240_start(ag); ++ } else { ++ ag->link = 1; ++ ag71xx_link_adjust(ag); ++ } ++} ++ ++void ag71xx_phy_stop(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ unsigned long flags; ++ ++ if (ag->phy_dev) ++ phy_stop(ag->phy_dev); ++ else if (pdata->mii_bus_dev && pdata->switch_data) ++ ag71xx_ar7240_stop(ag); ++ ++ spin_lock_irqsave(&ag->lock, flags); ++ if (ag->link) { ++ ag->link = 0; ++ ag71xx_link_adjust(ag); ++ } ++ spin_unlock_irqrestore(&ag->lock, flags); ++} ++ ++static int ag71xx_phy_connect_fixed(struct ag71xx *ag) ++{ ++ struct device *dev = &ag->pdev->dev; ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ int ret = 0; ++ ++ /* use fixed settings */ ++ switch (pdata->speed) { ++ case SPEED_10: ++ case SPEED_100: ++ case SPEED_1000: ++ break; ++ default: ++ dev_err(dev, "invalid speed specified\n"); ++ ret = -EINVAL; ++ break; ++ } ++ ++ dev_dbg(dev, "using fixed link parameters\n"); ++ ++ ag->duplex = pdata->duplex; ++ ag->speed = pdata->speed; ++ ++ return ret; ++} ++ ++static int ag71xx_phy_connect_multi(struct ag71xx *ag) ++{ ++ struct device *dev = &ag->pdev->dev; ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ struct phy_device *phydev = NULL; ++ int phy_addr; ++ int ret = 0; ++ ++ for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { ++ if (!(pdata->phy_mask & (1 << phy_addr))) ++ continue; ++ ++ if (ag->mii_bus->phy_map[phy_addr] == NULL) ++ continue; ++ ++ DBG("%s: PHY found at %s, uid=%08x\n", ++ dev_name(dev), ++ dev_name(&ag->mii_bus->phy_map[phy_addr]->dev), ++ ag->mii_bus->phy_map[phy_addr]->phy_id); ++ ++ if (phydev == NULL) ++ phydev = ag->mii_bus->phy_map[phy_addr]; ++ } ++ ++ if (!phydev) { ++ dev_err(dev, "no PHY found with phy_mask=%08x\n", ++ pdata->phy_mask); ++ return -ENODEV; ++ } ++ ++ ag->phy_dev = phy_connect(ag->dev, dev_name(&phydev->dev), ++ &ag71xx_phy_link_adjust, ++ pdata->phy_if_mode); ++ ++ if (IS_ERR(ag->phy_dev)) { ++ dev_err(dev, "could not connect to PHY at %s\n", ++ dev_name(&phydev->dev)); ++ return PTR_ERR(ag->phy_dev); ++ } ++ ++ /* mask with MAC supported features */ ++ if (pdata->has_gbit) ++ phydev->supported &= PHY_GBIT_FEATURES; ++ else ++ phydev->supported &= PHY_BASIC_FEATURES; ++ ++ phydev->advertising = phydev->supported; ++ ++ dev_info(dev, "connected to PHY at %s [uid=%08x, driver=%s]\n", ++ dev_name(&phydev->dev), phydev->phy_id, phydev->drv->name); ++ ++ ag->link = 0; ++ ag->speed = 0; ++ ag->duplex = -1; ++ ++ return ret; ++} ++ ++static int dev_is_class(struct device *dev, void *class) ++{ ++ if (dev->class != NULL && !strcmp(dev->class->name, class)) ++ return 1; ++ ++ return 0; ++} ++ ++static struct device *dev_find_class(struct device *parent, char *class) ++{ ++ if (dev_is_class(parent, class)) { ++ get_device(parent); ++ return parent; ++ } ++ ++ return device_find_child(parent, class, dev_is_class); ++} ++ ++static struct mii_bus *dev_to_mii_bus(struct device *dev) ++{ ++ struct device *d; ++ ++ d = dev_find_class(dev, "mdio_bus"); ++ if (d != NULL) { ++ struct mii_bus *bus; ++ ++ bus = to_mii_bus(d); ++ put_device(d); ++ ++ return bus; ++ } ++ ++ return NULL; ++} ++ ++int ag71xx_phy_connect(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ ++ if (pdata->mii_bus_dev == NULL || ++ pdata->mii_bus_dev->bus == NULL ) ++ return ag71xx_phy_connect_fixed(ag); ++ ++ ag->mii_bus = dev_to_mii_bus(pdata->mii_bus_dev); ++ if (ag->mii_bus == NULL) { ++ dev_err(&ag->pdev->dev, "unable to find MII bus on device '%s'\n", ++ dev_name(pdata->mii_bus_dev)); ++ return -ENODEV; ++ } ++ ++ /* Reset the mdio bus explicitly */ ++ if (ag->mii_bus->reset) { ++ mutex_lock(&ag->mii_bus->mdio_lock); ++ ag->mii_bus->reset(ag->mii_bus); ++ mutex_unlock(&ag->mii_bus->mdio_lock); ++ } ++ ++ if (pdata->switch_data) ++ return ag71xx_ar7240_init(ag); ++ ++ if (pdata->phy_mask) ++ return ag71xx_phy_connect_multi(ag); ++ ++ return ag71xx_phy_connect_fixed(ag); ++} ++ ++void ag71xx_phy_disconnect(struct ag71xx *ag) ++{ ++ struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); ++ ++ if (pdata->switch_data) ++ ag71xx_ar7240_cleanup(ag); ++ else if (ag->phy_dev) ++ phy_disconnect(ag->phy_dev); ++} +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0004-drivers-link-SPI-drivers-before-MTD-drivers.patch b/target/mips/dragino-ms14s/patches/3.14.17/0004-drivers-link-SPI-drivers-before-MTD-drivers.patch new file mode 100644 index 000000000..824d6ebec --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0004-drivers-link-SPI-drivers-before-MTD-drivers.patch @@ -0,0 +1,27 @@ +From 3c367cc533d07353f60110340c110f6d622094b8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:14:15 +0200 +Subject: [PATCH] drivers: link SPI drivers before MTD drivers + +This prevents probe deferral in SPI-driven MTD drivers. +--- + drivers/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/Makefile b/drivers/Makefile +index 8e3b8b0..61bbeb2 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -64,8 +64,8 @@ obj-$(CONFIG_IDE) += ide/ + obj-$(CONFIG_SCSI) += scsi/ + obj-$(CONFIG_ATA) += ata/ + obj-$(CONFIG_TARGET_CORE) += target/ +-obj-$(CONFIG_MTD) += mtd/ + obj-$(CONFIG_SPI) += spi/ ++obj-$(CONFIG_MTD) += mtd/ + obj-y += hsi/ + obj-y += net/ + obj-$(CONFIG_ATM) += atm/ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0005-spi-add-various-flags-to-spi_transfer-and-spi_messag.patch b/target/mips/dragino-ms14s/patches/3.14.17/0005-spi-add-various-flags-to-spi_transfer-and-spi_messag.patch new file mode 100644 index 000000000..505562fe0 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0005-spi-add-various-flags-to-spi_transfer-and-spi_messag.patch @@ -0,0 +1,34 @@ +From 34dcc540e28cc2253fd3bdaacdd77faf1d42d759 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:17:06 +0200 +Subject: [PATCH] spi: add various flags to spi_transfer and spi_message + structs + +--- + include/linux/spi/spi.h | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h +index 4203c66..4ee1a02 100644 +--- a/include/linux/spi/spi.h ++++ b/include/linux/spi/spi.h +@@ -581,6 +581,8 @@ struct spi_transfer { + dma_addr_t rx_dma; + + unsigned cs_change:1; ++ unsigned verify:1; ++ unsigned fast_write:1; + unsigned tx_nbits:3; + unsigned rx_nbits:3; + #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */ +@@ -627,6 +629,7 @@ struct spi_message { + struct spi_device *spi; + + unsigned is_dma_mapped:1; ++ unsigned fast_read:1; + + /* REVISIT: we might want a flag affecting the behavior of the + * last transfer ... allowing things like "read 16 bit length L" +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0006-spi-add-rb4xx-SPI-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0006-spi-add-rb4xx-SPI-driver.patch new file mode 100644 index 000000000..757dc775b --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0006-spi-add-rb4xx-SPI-driver.patch @@ -0,0 +1,557 @@ +From 97ffc04a7528abe1d84c069d99afb19704d50224 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:18:58 +0200 +Subject: [PATCH] spi: add rb4xx SPI driver + +--- + drivers/spi/Kconfig | 6 + + drivers/spi/Makefile | 1 + + drivers/spi/spi-rb4xx.c | 507 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 514 insertions(+) + create mode 100644 drivers/spi/spi-rb4xx.c + +diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig +index 581ee2a..721f3a7 100644 +--- a/drivers/spi/Kconfig ++++ b/drivers/spi/Kconfig +@@ -381,6 +381,12 @@ config SPI_RSPI + help + SPI driver for Renesas RSPI and QSPI blocks. + ++config SPI_RB4XX ++ tristate "Mikrotik RB4XX SPI master" ++ depends on SPI_MASTER && ATH79_MACH_RB4XX ++ help ++ SPI controller driver for the Mikrotik RB4xx series boards. ++ + config SPI_S3C24XX + tristate "Samsung S3C24XX series SPI" + depends on ARCH_S3C24XX +diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile +index 95af48d..e738c7a 100644 +--- a/drivers/spi/Makefile ++++ b/drivers/spi/Makefile +@@ -59,6 +59,7 @@ spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_PXADMA) += spi-pxa2xx-pxadma.o + spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o + obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o + obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o ++obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o + obj-$(CONFIG_SPI_RSPI) += spi-rspi.o + obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o + spi-s3c24xx-hw-y := spi-s3c24xx.o +diff --git a/drivers/spi/spi-rb4xx.c b/drivers/spi/spi-rb4xx.c +new file mode 100644 +index 0000000..56260ff +--- /dev/null ++++ b/drivers/spi/spi-rb4xx.c +@@ -0,0 +1,507 @@ ++/* ++ * SPI controller driver for the Mikrotik RB4xx boards ++ * ++ * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * This file was based on the patches for Linux 2.6.27.39 published by ++ * MikroTik for their RouterBoard 4xx series devices. ++ * ++ * 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 <linux/clk.h> ++#include <linux/err.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++#include <linux/spinlock.h> ++#include <linux/workqueue.h> ++#include <linux/platform_device.h> ++#include <linux/spi/spi.h> ++ ++#include <asm/mach-ath79/ar71xx_regs.h> ++#include <asm/mach-ath79/ath79.h> ++ ++#define DRV_NAME "rb4xx-spi" ++#define DRV_DESC "Mikrotik RB4xx SPI controller driver" ++#define DRV_VERSION "0.1.0" ++ ++#define SPI_CTRL_FASTEST 0x40 ++#define SPI_FLASH_HZ 33333334 ++#define SPI_CPLD_HZ 33333334 ++ ++#define CPLD_CMD_READ_FAST 0x0b ++ ++#undef RB4XX_SPI_DEBUG ++ ++struct rb4xx_spi { ++ void __iomem *base; ++ struct spi_master *master; ++ ++ unsigned spi_ctrl_flash; ++ unsigned spi_ctrl_fread; ++ ++ struct clk *ahb_clk; ++ unsigned long ahb_freq; ++ ++ spinlock_t lock; ++ struct list_head queue; ++ int busy:1; ++ int cs_wait; ++}; ++ ++static unsigned spi_clk_low = AR71XX_SPI_IOC_CS1; ++ ++#ifdef RB4XX_SPI_DEBUG ++static inline void do_spi_delay(void) ++{ ++ ndelay(20000); ++} ++#else ++static inline void do_spi_delay(void) { } ++#endif ++ ++static inline void do_spi_init(struct spi_device *spi) ++{ ++ unsigned cs = AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1; ++ ++ if (!(spi->mode & SPI_CS_HIGH)) ++ cs ^= (spi->chip_select == 2) ? AR71XX_SPI_IOC_CS1 : ++ AR71XX_SPI_IOC_CS0; ++ ++ spi_clk_low = cs; ++} ++ ++static inline void do_spi_finish(void __iomem *base) ++{ ++ do_spi_delay(); ++ __raw_writel(AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1, ++ base + AR71XX_SPI_REG_IOC); ++} ++ ++static inline void do_spi_clk(void __iomem *base, int bit) ++{ ++ unsigned bval = spi_clk_low | ((bit & 1) ? AR71XX_SPI_IOC_DO : 0); ++ ++ do_spi_delay(); ++ __raw_writel(bval, base + AR71XX_SPI_REG_IOC); ++ do_spi_delay(); ++ __raw_writel(bval | AR71XX_SPI_IOC_CLK, base + AR71XX_SPI_REG_IOC); ++} ++ ++static void do_spi_byte(void __iomem *base, unsigned char byte) ++{ ++ do_spi_clk(base, byte >> 7); ++ do_spi_clk(base, byte >> 6); ++ do_spi_clk(base, byte >> 5); ++ do_spi_clk(base, byte >> 4); ++ do_spi_clk(base, byte >> 3); ++ do_spi_clk(base, byte >> 2); ++ do_spi_clk(base, byte >> 1); ++ do_spi_clk(base, byte); ++ ++ pr_debug("spi_byte sent 0x%02x got 0x%02x\n", ++ (unsigned)byte, ++ (unsigned char)__raw_readl(base + AR71XX_SPI_REG_RDS)); ++} ++ ++static inline void do_spi_clk_fast(void __iomem *base, unsigned bit1, ++ unsigned bit2) ++{ ++ unsigned bval = (spi_clk_low | ++ ((bit1 & 1) ? AR71XX_SPI_IOC_DO : 0) | ++ ((bit2 & 1) ? AR71XX_SPI_IOC_CS2 : 0)); ++ do_spi_delay(); ++ __raw_writel(bval, base + AR71XX_SPI_REG_IOC); ++ do_spi_delay(); ++ __raw_writel(bval | AR71XX_SPI_IOC_CLK, base + AR71XX_SPI_REG_IOC); ++} ++ ++static void do_spi_byte_fast(void __iomem *base, unsigned char byte) ++{ ++ do_spi_clk_fast(base, byte >> 7, byte >> 6); ++ do_spi_clk_fast(base, byte >> 5, byte >> 4); ++ do_spi_clk_fast(base, byte >> 3, byte >> 2); ++ do_spi_clk_fast(base, byte >> 1, byte >> 0); ++ ++ pr_debug("spi_byte_fast sent 0x%02x got 0x%02x\n", ++ (unsigned)byte, ++ (unsigned char) __raw_readl(base + AR71XX_SPI_REG_RDS)); ++} ++ ++static int rb4xx_spi_txrx(void __iomem *base, struct spi_transfer *t) ++{ ++ const unsigned char *rxv_ptr = NULL; ++ const unsigned char *tx_ptr = t->tx_buf; ++ unsigned char *rx_ptr = t->rx_buf; ++ unsigned i; ++ ++ pr_debug("spi_txrx len %u tx %u rx %u\n", ++ t->len, ++ (t->tx_buf ? 1 : 0), ++ (t->rx_buf ? 1 : 0)); ++ ++ if (t->verify) { ++ rxv_ptr = tx_ptr; ++ tx_ptr = NULL; ++ } ++ ++ for (i = 0; i < t->len; ++i) { ++ unsigned char sdata = tx_ptr ? tx_ptr[i] : 0; ++ ++ if (t->fast_write) ++ do_spi_byte_fast(base, sdata); ++ else ++ do_spi_byte(base, sdata); ++ ++ if (rx_ptr) { ++ rx_ptr[i] = __raw_readl(base + AR71XX_SPI_REG_RDS) & 0xff; ++ } else if (rxv_ptr) { ++ unsigned char c = __raw_readl(base + AR71XX_SPI_REG_RDS); ++ if (rxv_ptr[i] != c) ++ return i; ++ } ++ } ++ ++ return i; ++} ++ ++static int rb4xx_spi_read_fast(struct rb4xx_spi *rbspi, ++ struct spi_message *m) ++{ ++ struct spi_transfer *t; ++ const unsigned char *tx_ptr; ++ unsigned addr; ++ void __iomem *base = rbspi->base; ++ ++ /* check for exactly two transfers */ ++ if (list_empty(&m->transfers) || ++ list_is_last(m->transfers.next, &m->transfers) || ++ !list_is_last(m->transfers.next->next, &m->transfers)) { ++ return -1; ++ } ++ ++ /* first transfer contains command and address */ ++ t = list_entry(m->transfers.next, ++ struct spi_transfer, transfer_list); ++ ++ if (t->len != 5 || t->tx_buf == NULL) ++ return -1; ++ ++ tx_ptr = t->tx_buf; ++ if (tx_ptr[0] != CPLD_CMD_READ_FAST) ++ return -1; ++ ++ addr = tx_ptr[1]; ++ addr = tx_ptr[2] | (addr << 8); ++ addr = tx_ptr[3] | (addr << 8); ++ addr += (unsigned) base; ++ ++ m->actual_length += t->len; ++ ++ /* second transfer contains data itself */ ++ t = list_entry(m->transfers.next->next, ++ struct spi_transfer, transfer_list); ++ ++ if (t->tx_buf && !t->verify) ++ return -1; ++ ++ __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); ++ __raw_writel(rbspi->spi_ctrl_fread, base + AR71XX_SPI_REG_CTRL); ++ __raw_writel(0, base + AR71XX_SPI_REG_FS); ++ ++ if (t->rx_buf) { ++ memcpy(t->rx_buf, (const void *)addr, t->len); ++ } else if (t->tx_buf) { ++ unsigned char buf[t->len]; ++ memcpy(buf, (const void *)addr, t->len); ++ if (memcmp(t->tx_buf, buf, t->len) != 0) ++ m->status = -EMSGSIZE; ++ } ++ m->actual_length += t->len; ++ ++ if (rbspi->spi_ctrl_flash != rbspi->spi_ctrl_fread) { ++ __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); ++ __raw_writel(rbspi->spi_ctrl_flash, base + AR71XX_SPI_REG_CTRL); ++ __raw_writel(0, base + AR71XX_SPI_REG_FS); ++ } ++ ++ return 0; ++} ++ ++static int rb4xx_spi_msg(struct rb4xx_spi *rbspi, struct spi_message *m) ++{ ++ struct spi_transfer *t = NULL; ++ void __iomem *base = rbspi->base; ++ ++ m->status = 0; ++ if (list_empty(&m->transfers)) ++ return -1; ++ ++ if (m->fast_read) ++ if (rb4xx_spi_read_fast(rbspi, m) == 0) ++ return -1; ++ ++ __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); ++ __raw_writel(SPI_CTRL_FASTEST, base + AR71XX_SPI_REG_CTRL); ++ do_spi_init(m->spi); ++ ++ list_for_each_entry(t, &m->transfers, transfer_list) { ++ int len; ++ ++ len = rb4xx_spi_txrx(base, t); ++ if (len != t->len) { ++ m->status = -EMSGSIZE; ++ break; ++ } ++ m->actual_length += len; ++ ++ if (t->cs_change) { ++ if (list_is_last(&t->transfer_list, &m->transfers)) { ++ /* wait for continuation */ ++ return m->spi->chip_select; ++ } ++ do_spi_finish(base); ++ ndelay(100); ++ } ++ } ++ ++ do_spi_finish(base); ++ __raw_writel(rbspi->spi_ctrl_flash, base + AR71XX_SPI_REG_CTRL); ++ __raw_writel(0, base + AR71XX_SPI_REG_FS); ++ return -1; ++} ++ ++static void rb4xx_spi_process_queue_locked(struct rb4xx_spi *rbspi, ++ unsigned long *flags) ++{ ++ int cs = rbspi->cs_wait; ++ ++ rbspi->busy = 1; ++ while (!list_empty(&rbspi->queue)) { ++ struct spi_message *m; ++ ++ list_for_each_entry(m, &rbspi->queue, queue) ++ if (cs < 0 || cs == m->spi->chip_select) ++ break; ++ ++ if (&m->queue == &rbspi->queue) ++ break; ++ ++ list_del_init(&m->queue); ++ spin_unlock_irqrestore(&rbspi->lock, *flags); ++ ++ cs = rb4xx_spi_msg(rbspi, m); ++ m->complete(m->context); ++ ++ spin_lock_irqsave(&rbspi->lock, *flags); ++ } ++ ++ rbspi->cs_wait = cs; ++ rbspi->busy = 0; ++ ++ if (cs >= 0) { ++ /* TODO: add timer to unlock cs after 1s inactivity */ ++ } ++} ++ ++static int rb4xx_spi_transfer(struct spi_device *spi, ++ struct spi_message *m) ++{ ++ struct rb4xx_spi *rbspi = spi_master_get_devdata(spi->master); ++ unsigned long flags; ++ ++ m->actual_length = 0; ++ m->status = -EINPROGRESS; ++ ++ spin_lock_irqsave(&rbspi->lock, flags); ++ list_add_tail(&m->queue, &rbspi->queue); ++ if (rbspi->busy || ++ (rbspi->cs_wait >= 0 && rbspi->cs_wait != m->spi->chip_select)) { ++ /* job will be done later */ ++ spin_unlock_irqrestore(&rbspi->lock, flags); ++ return 0; ++ } ++ ++ /* process job in current context */ ++ rb4xx_spi_process_queue_locked(rbspi, &flags); ++ spin_unlock_irqrestore(&rbspi->lock, flags); ++ ++ return 0; ++} ++ ++static int rb4xx_spi_setup(struct spi_device *spi) ++{ ++ struct rb4xx_spi *rbspi = spi_master_get_devdata(spi->master); ++ unsigned long flags; ++ ++ if (spi->mode & ~(SPI_CS_HIGH)) { ++ dev_err(&spi->dev, "mode %x not supported\n", ++ (unsigned) spi->mode); ++ return -EINVAL; ++ } ++ ++ if (spi->bits_per_word != 8 && spi->bits_per_word != 0) { ++ dev_err(&spi->dev, "bits_per_word %u not supported\n", ++ (unsigned) spi->bits_per_word); ++ return -EINVAL; ++ } ++ ++ spin_lock_irqsave(&rbspi->lock, flags); ++ if (rbspi->cs_wait == spi->chip_select && !rbspi->busy) { ++ rbspi->cs_wait = -1; ++ rb4xx_spi_process_queue_locked(rbspi, &flags); ++ } ++ spin_unlock_irqrestore(&rbspi->lock, flags); ++ ++ return 0; ++} ++ ++static unsigned get_spi_ctrl(struct rb4xx_spi *rbspi, unsigned hz_max, ++ const char *name) ++{ ++ unsigned div; ++ ++ div = (rbspi->ahb_freq - 1) / (2 * hz_max); ++ ++ /* ++ * CPU has a bug at (div == 0) - first bit read is random ++ */ ++ if (div == 0) ++ ++div; ++ ++ if (name) { ++ unsigned ahb_khz = (rbspi->ahb_freq + 500) / 1000; ++ unsigned div_real = 2 * (div + 1); ++ pr_debug("rb4xx: %s SPI clock %u kHz (AHB %u kHz / %u)\n", ++ name, ++ ahb_khz / div_real, ++ ahb_khz, div_real); ++ } ++ ++ return SPI_CTRL_FASTEST + div; ++} ++ ++static int rb4xx_spi_probe(struct platform_device *pdev) ++{ ++ struct spi_master *master; ++ struct rb4xx_spi *rbspi; ++ struct resource *r; ++ int err = 0; ++ ++ master = spi_alloc_master(&pdev->dev, sizeof(*rbspi)); ++ if (master == NULL) { ++ dev_err(&pdev->dev, "no memory for spi_master\n"); ++ err = -ENOMEM; ++ goto err_out; ++ } ++ ++ master->bus_num = 0; ++ master->num_chipselect = 3; ++ master->setup = rb4xx_spi_setup; ++ master->transfer = rb4xx_spi_transfer; ++ ++ rbspi = spi_master_get_devdata(master); ++ ++ rbspi->ahb_clk = clk_get(&pdev->dev, "ahb"); ++ if (IS_ERR(rbspi->ahb_clk)) { ++ err = PTR_ERR(rbspi->ahb_clk); ++ goto err_put_master; ++ } ++ ++ err = clk_enable(rbspi->ahb_clk); ++ if (err) ++ goto err_clk_put; ++ ++ rbspi->ahb_freq = clk_get_rate(rbspi->ahb_clk); ++ if (!rbspi->ahb_freq) { ++ err = -EINVAL; ++ goto err_clk_disable; ++ } ++ ++ platform_set_drvdata(pdev, rbspi); ++ ++ r = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (r == NULL) { ++ err = -ENOENT; ++ goto err_clk_disable; ++ } ++ ++ rbspi->base = ioremap(r->start, r->end - r->start + 1); ++ if (!rbspi->base) { ++ err = -ENXIO; ++ goto err_clk_disable; ++ } ++ ++ rbspi->master = master; ++ rbspi->spi_ctrl_flash = get_spi_ctrl(rbspi, SPI_FLASH_HZ, "FLASH"); ++ rbspi->spi_ctrl_fread = get_spi_ctrl(rbspi, SPI_CPLD_HZ, "CPLD"); ++ rbspi->cs_wait = -1; ++ ++ spin_lock_init(&rbspi->lock); ++ INIT_LIST_HEAD(&rbspi->queue); ++ ++ err = spi_register_master(master); ++ if (err) { ++ dev_err(&pdev->dev, "failed to register SPI master\n"); ++ goto err_iounmap; ++ } ++ ++ return 0; ++ ++err_iounmap: ++ iounmap(rbspi->base); ++err_clk_disable: ++ clk_disable(rbspi->ahb_clk); ++err_clk_put: ++ clk_put(rbspi->ahb_clk); ++err_put_master: ++ platform_set_drvdata(pdev, NULL); ++ spi_master_put(master); ++err_out: ++ return err; ++} ++ ++static int rb4xx_spi_remove(struct platform_device *pdev) ++{ ++ struct rb4xx_spi *rbspi = platform_get_drvdata(pdev); ++ ++ iounmap(rbspi->base); ++ clk_disable(rbspi->ahb_clk); ++ clk_put(rbspi->ahb_clk); ++ platform_set_drvdata(pdev, NULL); ++ spi_master_put(rbspi->master); ++ ++ return 0; ++} ++ ++static struct platform_driver rb4xx_spi_drv = { ++ .probe = rb4xx_spi_probe, ++ .remove = rb4xx_spi_remove, ++ .driver = { ++ .name = DRV_NAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init rb4xx_spi_init(void) ++{ ++ return platform_driver_register(&rb4xx_spi_drv); ++} ++subsys_initcall(rb4xx_spi_init); ++ ++static void __exit rb4xx_spi_exit(void) ++{ ++ platform_driver_unregister(&rb4xx_spi_drv); ++} ++ ++module_exit(rb4xx_spi_exit); ++ ++MODULE_DESCRIPTION(DRV_DESC); ++MODULE_VERSION(DRV_VERSION); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0007-spi-add-rb4xx-cpld-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0007-spi-add-rb4xx-cpld-driver.patch new file mode 100644 index 000000000..452f2e761 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0007-spi-add-rb4xx-cpld-driver.patch @@ -0,0 +1,548 @@ +From bb8d2a4ebf63bc2f04f15a28c92652700416ff83 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:20:04 +0200 +Subject: [PATCH] spi: add rb4xx cpld driver + +--- + arch/mips/include/asm/mach-ath79/rb4xx_cpld.h | 48 +++ + drivers/spi/Kconfig | 7 + + drivers/spi/Makefile | 1 + + drivers/spi/spi-rb4xx-cpld.c | 441 ++++++++++++++++++++++++++ + 4 files changed, 497 insertions(+) + create mode 100644 arch/mips/include/asm/mach-ath79/rb4xx_cpld.h + create mode 100644 drivers/spi/spi-rb4xx-cpld.c + +diff --git a/arch/mips/include/asm/mach-ath79/rb4xx_cpld.h b/arch/mips/include/asm/mach-ath79/rb4xx_cpld.h +new file mode 100644 +index 0000000..5b17e94 +--- /dev/null ++++ b/arch/mips/include/asm/mach-ath79/rb4xx_cpld.h +@@ -0,0 +1,48 @@ ++/* ++ * SPI driver definitions for the CPLD chip on the Mikrotik RB4xx boards ++ * ++ * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * This file was based on the patches for Linux 2.6.27.39 published by ++ * MikroTik for their RouterBoard 4xx series devices. ++ * ++ * 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. ++ */ ++ ++#define CPLD_GPIO_nLED1 0 ++#define CPLD_GPIO_nLED2 1 ++#define CPLD_GPIO_nLED3 2 ++#define CPLD_GPIO_nLED4 3 ++#define CPLD_GPIO_FAN 4 ++#define CPLD_GPIO_ALE 5 ++#define CPLD_GPIO_CLE 6 ++#define CPLD_GPIO_nCE 7 ++#define CPLD_GPIO_nLED5 8 ++ ++#define CPLD_NUM_GPIOS 9 ++ ++#define CPLD_CFG_nLED1 BIT(CPLD_GPIO_nLED1) ++#define CPLD_CFG_nLED2 BIT(CPLD_GPIO_nLED2) ++#define CPLD_CFG_nLED3 BIT(CPLD_GPIO_nLED3) ++#define CPLD_CFG_nLED4 BIT(CPLD_GPIO_nLED4) ++#define CPLD_CFG_FAN BIT(CPLD_GPIO_FAN) ++#define CPLD_CFG_ALE BIT(CPLD_GPIO_ALE) ++#define CPLD_CFG_CLE BIT(CPLD_GPIO_CLE) ++#define CPLD_CFG_nCE BIT(CPLD_GPIO_nCE) ++#define CPLD_CFG_nLED5 BIT(CPLD_GPIO_nLED5) ++ ++struct rb4xx_cpld_platform_data { ++ unsigned gpio_base; ++}; ++ ++extern int rb4xx_cpld_change_cfg(unsigned mask, unsigned value); ++extern int rb4xx_cpld_read(unsigned char *rx_buf, ++ const unsigned char *verify_buf, ++ unsigned cnt); ++extern int rb4xx_cpld_read_from(unsigned addr, ++ unsigned char *rx_buf, ++ const unsigned char *verify_buf, ++ unsigned cnt); ++extern int rb4xx_cpld_write(const unsigned char *buf, unsigned count); +diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig +index 721f3a7..dbd7e98 100644 +--- a/drivers/spi/Kconfig ++++ b/drivers/spi/Kconfig +@@ -577,6 +577,13 @@ config SPI_TLE62X0 + sysfs interface, with each line presented as a kind of GPIO + exposing both switch control and diagnostic feedback. + ++config SPI_RB4XX_CPLD ++ tristate "MikroTik RB4XX CPLD driver" ++ depends on ATH79_MACH_RB4XX ++ help ++ SPI driver for the Xilinx CPLD chip present on the ++ MikroTik RB4xx boards. ++ + # + # Add new SPI protocol masters in alphabetical order above this line + # +diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile +index e738c7a..50913ae 100644 +--- a/drivers/spi/Makefile ++++ b/drivers/spi/Makefile +@@ -60,6 +60,7 @@ spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o + obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o + obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o + obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o ++obj-$(CONFIG_SPI_RB4XX_CPLD) += spi-rb4xx-cpld.o + obj-$(CONFIG_SPI_RSPI) += spi-rspi.o + obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o + spi-s3c24xx-hw-y := spi-s3c24xx.o +diff --git a/drivers/spi/spi-rb4xx-cpld.c b/drivers/spi/spi-rb4xx-cpld.c +new file mode 100644 +index 0000000..a8d5282 +--- /dev/null ++++ b/drivers/spi/spi-rb4xx-cpld.c +@@ -0,0 +1,441 @@ ++/* ++ * SPI driver for the CPLD chip on the Mikrotik RB4xx boards ++ * ++ * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * This file was based on the patches for Linux 2.6.27.39 published by ++ * MikroTik for their RouterBoard 4xx series devices. ++ * ++ * 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 <linux/types.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/device.h> ++#include <linux/bitops.h> ++#include <linux/spi/spi.h> ++#include <linux/gpio.h> ++#include <linux/slab.h> ++ ++#include <asm/mach-ath79/rb4xx_cpld.h> ++ ++#define DRV_NAME "spi-rb4xx-cpld" ++#define DRV_DESC "RB4xx CPLD driver" ++#define DRV_VERSION "0.1.0" ++ ++#define CPLD_CMD_WRITE_NAND 0x08 /* send cmd, n x send data, send indle */ ++#define CPLD_CMD_WRITE_CFG 0x09 /* send cmd, n x send cfg */ ++#define CPLD_CMD_READ_NAND 0x0a /* send cmd, send idle, n x read data */ ++#define CPLD_CMD_READ_FAST 0x0b /* send cmd, 4 x idle, n x read data */ ++#define CPLD_CMD_LED5_ON 0x0c /* send cmd */ ++#define CPLD_CMD_LED5_OFF 0x0d /* send cmd */ ++ ++struct rb4xx_cpld { ++ struct spi_device *spi; ++ struct mutex lock; ++ struct gpio_chip chip; ++ unsigned int config; ++}; ++ ++static struct rb4xx_cpld *rb4xx_cpld; ++ ++static inline struct rb4xx_cpld *gpio_to_cpld(struct gpio_chip *chip) ++{ ++ return container_of(chip, struct rb4xx_cpld, chip); ++} ++ ++static int rb4xx_cpld_write_cmd(struct rb4xx_cpld *cpld, unsigned char cmd) ++{ ++ struct spi_transfer t[1]; ++ struct spi_message m; ++ unsigned char tx_buf[1]; ++ int err; ++ ++ spi_message_init(&m); ++ memset(&t, 0, sizeof(t)); ++ ++ t[0].tx_buf = tx_buf; ++ t[0].len = sizeof(tx_buf); ++ spi_message_add_tail(&t[0], &m); ++ ++ tx_buf[0] = cmd; ++ ++ err = spi_sync(cpld->spi, &m); ++ return err; ++} ++ ++static int rb4xx_cpld_write_cfg(struct rb4xx_cpld *cpld, unsigned char config) ++{ ++ struct spi_transfer t[1]; ++ struct spi_message m; ++ unsigned char cmd[2]; ++ int err; ++ ++ spi_message_init(&m); ++ memset(&t, 0, sizeof(t)); ++ ++ t[0].tx_buf = cmd; ++ t[0].len = sizeof(cmd); ++ spi_message_add_tail(&t[0], &m); ++ ++ cmd[0] = CPLD_CMD_WRITE_CFG; ++ cmd[1] = config; ++ ++ err = spi_sync(cpld->spi, &m); ++ return err; ++} ++ ++static int __rb4xx_cpld_change_cfg(struct rb4xx_cpld *cpld, unsigned mask, ++ unsigned value) ++{ ++ unsigned int config; ++ int err; ++ ++ config = cpld->config & ~mask; ++ config |= value; ++ ++ if ((cpld->config ^ config) & 0xff) { ++ err = rb4xx_cpld_write_cfg(cpld, config); ++ if (err) ++ return err; ++ } ++ ++ if ((cpld->config ^ config) & CPLD_CFG_nLED5) { ++ err = rb4xx_cpld_write_cmd(cpld, (value) ? CPLD_CMD_LED5_ON : ++ CPLD_CMD_LED5_OFF); ++ if (err) ++ return err; ++ } ++ ++ cpld->config = config; ++ return 0; ++} ++ ++int rb4xx_cpld_change_cfg(unsigned mask, unsigned value) ++{ ++ int ret; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ mutex_lock(&rb4xx_cpld->lock); ++ ret = __rb4xx_cpld_change_cfg(rb4xx_cpld, mask, value); ++ mutex_unlock(&rb4xx_cpld->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(rb4xx_cpld_change_cfg); ++ ++int rb4xx_cpld_read_from(unsigned addr, unsigned char *rx_buf, ++ const unsigned char *verify_buf, unsigned count) ++{ ++ const unsigned char cmd[5] = { ++ CPLD_CMD_READ_FAST, ++ (addr >> 16) & 0xff, ++ (addr >> 8) & 0xff, ++ addr & 0xff, ++ 0 ++ }; ++ struct spi_transfer t[2] = { ++ { ++ .tx_buf = &cmd, ++ .len = 5, ++ }, ++ { ++ .tx_buf = verify_buf, ++ .rx_buf = rx_buf, ++ .len = count, ++ .verify = (verify_buf != NULL), ++ }, ++ }; ++ struct spi_message m; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ spi_message_init(&m); ++ m.fast_read = 1; ++ spi_message_add_tail(&t[0], &m); ++ spi_message_add_tail(&t[1], &m); ++ return spi_sync(rb4xx_cpld->spi, &m); ++} ++EXPORT_SYMBOL_GPL(rb4xx_cpld_read_from); ++ ++#if 0 ++int rb4xx_cpld_read(unsigned char *buf, unsigned char *verify_buf, ++ unsigned count) ++{ ++ struct spi_transfer t[2]; ++ struct spi_message m; ++ unsigned char cmd[2]; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ spi_message_init(&m); ++ memset(&t, 0, sizeof(t)); ++ ++ /* send command */ ++ t[0].tx_buf = cmd; ++ t[0].len = sizeof(cmd); ++ spi_message_add_tail(&t[0], &m); ++ ++ cmd[0] = CPLD_CMD_READ_NAND; ++ cmd[1] = 0; ++ ++ /* read data */ ++ t[1].rx_buf = buf; ++ t[1].len = count; ++ spi_message_add_tail(&t[1], &m); ++ ++ return spi_sync(rb4xx_cpld->spi, &m); ++} ++#else ++int rb4xx_cpld_read(unsigned char *rx_buf, const unsigned char *verify_buf, ++ unsigned count) ++{ ++ static const unsigned char cmd[2] = { CPLD_CMD_READ_NAND, 0 }; ++ struct spi_transfer t[2] = { ++ { ++ .tx_buf = &cmd, ++ .len = 2, ++ }, { ++ .tx_buf = verify_buf, ++ .rx_buf = rx_buf, ++ .len = count, ++ .verify = (verify_buf != NULL), ++ }, ++ }; ++ struct spi_message m; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t[0], &m); ++ spi_message_add_tail(&t[1], &m); ++ return spi_sync(rb4xx_cpld->spi, &m); ++} ++#endif ++EXPORT_SYMBOL_GPL(rb4xx_cpld_read); ++ ++int rb4xx_cpld_write(const unsigned char *buf, unsigned count) ++{ ++#if 0 ++ struct spi_transfer t[3]; ++ struct spi_message m; ++ unsigned char cmd[1]; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ memset(&t, 0, sizeof(t)); ++ spi_message_init(&m); ++ ++ /* send command */ ++ t[0].tx_buf = cmd; ++ t[0].len = sizeof(cmd); ++ spi_message_add_tail(&t[0], &m); ++ ++ cmd[0] = CPLD_CMD_WRITE_NAND; ++ ++ /* write data */ ++ t[1].tx_buf = buf; ++ t[1].len = count; ++ spi_message_add_tail(&t[1], &m); ++ ++ /* send idle */ ++ t[2].len = 1; ++ spi_message_add_tail(&t[2], &m); ++ ++ return spi_sync(rb4xx_cpld->spi, &m); ++#else ++ static const unsigned char cmd = CPLD_CMD_WRITE_NAND; ++ struct spi_transfer t[3] = { ++ { ++ .tx_buf = &cmd, ++ .len = 1, ++ }, { ++ .tx_buf = buf, ++ .len = count, ++ .fast_write = 1, ++ }, { ++ .len = 1, ++ .fast_write = 1, ++ }, ++ }; ++ struct spi_message m; ++ ++ if (rb4xx_cpld == NULL) ++ return -ENODEV; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t[0], &m); ++ spi_message_add_tail(&t[1], &m); ++ spi_message_add_tail(&t[2], &m); ++ return spi_sync(rb4xx_cpld->spi, &m); ++#endif ++} ++EXPORT_SYMBOL_GPL(rb4xx_cpld_write); ++ ++static int rb4xx_cpld_gpio_get(struct gpio_chip *chip, unsigned offset) ++{ ++ struct rb4xx_cpld *cpld = gpio_to_cpld(chip); ++ int ret; ++ ++ mutex_lock(&cpld->lock); ++ ret = (cpld->config >> offset) & 1; ++ mutex_unlock(&cpld->lock); ++ ++ return ret; ++} ++ ++static void rb4xx_cpld_gpio_set(struct gpio_chip *chip, unsigned offset, ++ int value) ++{ ++ struct rb4xx_cpld *cpld = gpio_to_cpld(chip); ++ ++ mutex_lock(&cpld->lock); ++ __rb4xx_cpld_change_cfg(cpld, (1 << offset), !!value << offset); ++ mutex_unlock(&cpld->lock); ++} ++ ++static int rb4xx_cpld_gpio_direction_input(struct gpio_chip *chip, ++ unsigned offset) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static int rb4xx_cpld_gpio_direction_output(struct gpio_chip *chip, ++ unsigned offset, ++ int value) ++{ ++ struct rb4xx_cpld *cpld = gpio_to_cpld(chip); ++ int ret; ++ ++ mutex_lock(&cpld->lock); ++ ret = __rb4xx_cpld_change_cfg(cpld, (1 << offset), !!value << offset); ++ mutex_unlock(&cpld->lock); ++ ++ return ret; ++} ++ ++static int rb4xx_cpld_gpio_init(struct rb4xx_cpld *cpld, unsigned int base) ++{ ++ int err; ++ ++ /* init config */ ++ cpld->config = CPLD_CFG_nLED1 | CPLD_CFG_nLED2 | CPLD_CFG_nLED3 | ++ CPLD_CFG_nLED4 | CPLD_CFG_nCE; ++ rb4xx_cpld_write_cfg(cpld, cpld->config); ++ ++ /* setup GPIO chip */ ++ cpld->chip.label = DRV_NAME; ++ ++ cpld->chip.get = rb4xx_cpld_gpio_get; ++ cpld->chip.set = rb4xx_cpld_gpio_set; ++ cpld->chip.direction_input = rb4xx_cpld_gpio_direction_input; ++ cpld->chip.direction_output = rb4xx_cpld_gpio_direction_output; ++ ++ cpld->chip.base = base; ++ cpld->chip.ngpio = CPLD_NUM_GPIOS; ++ cpld->chip.can_sleep = 1; ++ cpld->chip.dev = &cpld->spi->dev; ++ cpld->chip.owner = THIS_MODULE; ++ ++ err = gpiochip_add(&cpld->chip); ++ if (err) ++ dev_err(&cpld->spi->dev, "adding GPIO chip failed, err=%d\n", ++ err); ++ ++ return err; ++} ++ ++static int rb4xx_cpld_probe(struct spi_device *spi) ++{ ++ struct rb4xx_cpld *cpld; ++ struct rb4xx_cpld_platform_data *pdata; ++ int err; ++ ++ pdata = spi->dev.platform_data; ++ if (!pdata) { ++ dev_dbg(&spi->dev, "no platform data\n"); ++ return -EINVAL; ++ } ++ ++ cpld = kzalloc(sizeof(*cpld), GFP_KERNEL); ++ if (!cpld) { ++ dev_err(&spi->dev, "no memory for private data\n"); ++ return -ENOMEM; ++ } ++ ++ mutex_init(&cpld->lock); ++ cpld->spi = spi_dev_get(spi); ++ dev_set_drvdata(&spi->dev, cpld); ++ ++ spi->mode = SPI_MODE_0; ++ spi->bits_per_word = 8; ++ err = spi_setup(spi); ++ if (err) { ++ dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); ++ goto err_drvdata; ++ } ++ ++ err = rb4xx_cpld_gpio_init(cpld, pdata->gpio_base); ++ if (err) ++ goto err_drvdata; ++ ++ rb4xx_cpld = cpld; ++ ++ return 0; ++ ++err_drvdata: ++ dev_set_drvdata(&spi->dev, NULL); ++ kfree(cpld); ++ ++ return err; ++} ++ ++static int rb4xx_cpld_remove(struct spi_device *spi) ++{ ++ struct rb4xx_cpld *cpld; ++ ++ rb4xx_cpld = NULL; ++ cpld = dev_get_drvdata(&spi->dev); ++ dev_set_drvdata(&spi->dev, NULL); ++ kfree(cpld); ++ ++ return 0; ++} ++ ++static struct spi_driver rb4xx_cpld_driver = { ++ .driver = { ++ .name = DRV_NAME, ++ .bus = &spi_bus_type, ++ .owner = THIS_MODULE, ++ }, ++ .probe = rb4xx_cpld_probe, ++ .remove = rb4xx_cpld_remove, ++}; ++ ++static int __init rb4xx_cpld_init(void) ++{ ++ return spi_register_driver(&rb4xx_cpld_driver); ++} ++module_init(rb4xx_cpld_init); ++ ++static void __exit rb4xx_cpld_exit(void) ++{ ++ spi_unregister_driver(&rb4xx_cpld_driver); ++} ++module_exit(rb4xx_cpld_exit); ++ ++MODULE_DESCRIPTION(DRV_DESC); ++MODULE_VERSION(DRV_VERSION); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0008-gpio-add-GPIO-latch-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0008-gpio-add-GPIO-latch-driver.patch new file mode 100644 index 000000000..188cec3b2 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0008-gpio-add-GPIO-latch-driver.patch @@ -0,0 +1,290 @@ +From dd93d7e5b6530f1574860776fe6f960c4fd2661d Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:21:54 +0200 +Subject: [PATCH] gpio: add GPIO latch driver + +--- + drivers/gpio/Kconfig | 7 + + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-latch.c | 219 +++++++++++++++++++++++++++++++ + include/linux/platform_data/gpio-latch.h | 14 ++ + 4 files changed, 241 insertions(+) + create mode 100644 drivers/gpio/gpio-latch.c + create mode 100644 include/linux/platform_data/gpio-latch.h + +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 903f24d..905730b 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -834,4 +834,11 @@ config GPIO_VIPERBOARD + River Tech's viperboard.h for detailed meaning + of the module parameters. + ++comment "Other GPIO expanders" ++ ++config GPIO_LATCH ++ tristate "GPIO latch driver" ++ help ++ Say yes here to enable a GPIO latch driver. ++ + endif +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +index 5d50179..7d03524 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -36,6 +36,7 @@ obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o + obj-$(CONFIG_ARCH_KS8695) += gpio-ks8695.o + obj-$(CONFIG_GPIO_INTEL_MID) += gpio-intel-mid.o + obj-$(CONFIG_GPIO_LP3943) += gpio-lp3943.o ++obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o + obj-$(CONFIG_ARCH_LPC32XX) += gpio-lpc32xx.o + obj-$(CONFIG_GPIO_LYNXPOINT) += gpio-lynxpoint.o + obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o +diff --git a/drivers/gpio/gpio-latch.c b/drivers/gpio/gpio-latch.c +new file mode 100644 +index 0000000..1efa1a1 +--- /dev/null ++++ b/drivers/gpio/gpio-latch.c +@@ -0,0 +1,219 @@ ++/* ++ * GPIO latch driver ++ * ++ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * 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 <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/types.h> ++#include <linux/gpio.h> ++#include <linux/slab.h> ++#include <linux/platform_device.h> ++ ++#include <linux/platform_data/gpio-latch.h> ++ ++struct gpio_latch_chip { ++ struct gpio_chip gc; ++ ++ struct mutex mutex; ++ struct mutex latch_mutex; ++ bool latch_enabled; ++ int le_gpio; ++ bool le_active_low; ++ int *gpios; ++}; ++ ++static inline struct gpio_latch_chip *to_gpio_latch_chip(struct gpio_chip *gc) ++{ ++ return container_of(gc, struct gpio_latch_chip, gc); ++} ++ ++static void gpio_latch_lock(struct gpio_latch_chip *glc, bool enable) ++{ ++ mutex_lock(&glc->mutex); ++ ++ if (enable) ++ glc->latch_enabled = true; ++ ++ if (glc->latch_enabled) ++ mutex_lock(&glc->latch_mutex); ++} ++ ++static void gpio_latch_unlock(struct gpio_latch_chip *glc, bool disable) ++{ ++ if (glc->latch_enabled) ++ mutex_unlock(&glc->latch_mutex); ++ ++ if (disable) ++ glc->latch_enabled = true; ++ ++ mutex_unlock(&glc->mutex); ++} ++ ++static int ++gpio_latch_get(struct gpio_chip *gc, unsigned offset) ++{ ++ struct gpio_latch_chip *glc = to_gpio_latch_chip(gc); ++ int ret; ++ ++ gpio_latch_lock(glc, false); ++ ret = gpio_get_value(glc->gpios[offset]); ++ gpio_latch_unlock(glc, false); ++ ++ return ret; ++} ++ ++static void ++gpio_latch_set(struct gpio_chip *gc, unsigned offset, int value) ++{ ++ struct gpio_latch_chip *glc = to_gpio_latch_chip(gc); ++ bool enable_latch = false; ++ bool disable_latch = false; ++ int gpio; ++ ++ gpio = glc->gpios[offset]; ++ ++ if (gpio == glc->le_gpio) { ++ enable_latch = value ^ glc->le_active_low; ++ disable_latch = !enable_latch; ++ } ++ ++ gpio_latch_lock(glc, enable_latch); ++ gpio_set_value(gpio, value); ++ gpio_latch_unlock(glc, disable_latch); ++} ++ ++static int ++gpio_latch_direction_input(struct gpio_chip *gc, unsigned offset) ++{ ++ struct gpio_latch_chip *glc = to_gpio_latch_chip(gc); ++ int ret; ++ ++ gpio_latch_lock(glc, false); ++ ret = gpio_direction_input(glc->gpios[offset]); ++ gpio_latch_unlock(glc, false); ++ ++ return ret; ++} ++ ++static int ++gpio_latch_direction_output(struct gpio_chip *gc, unsigned offset, int value) ++{ ++ struct gpio_latch_chip *glc = to_gpio_latch_chip(gc); ++ bool enable_latch = false; ++ bool disable_latch = false; ++ int gpio; ++ int ret; ++ ++ gpio = glc->gpios[offset]; ++ ++ if (gpio == glc->le_gpio) { ++ enable_latch = value ^ glc->le_active_low; ++ disable_latch = !enable_latch; ++ } ++ ++ gpio_latch_lock(glc, enable_latch); ++ ret = gpio_direction_output(gpio, value); ++ gpio_latch_unlock(glc, disable_latch); ++ ++ return ret; ++} ++ ++static int gpio_latch_probe(struct platform_device *pdev) ++{ ++ struct gpio_latch_chip *glc; ++ struct gpio_latch_platform_data *pdata; ++ struct gpio_chip *gc; ++ int size; ++ int ret; ++ int i; ++ ++ pdata = dev_get_platdata(&pdev->dev); ++ if (!pdata) ++ return -EINVAL; ++ ++ if (pdata->le_gpio_index >= pdata->num_gpios || ++ !pdata->num_gpios || ++ !pdata->gpios) ++ return -EINVAL; ++ ++ for (i = 0; i < pdata->num_gpios; i++) { ++ int gpio = pdata->gpios[i]; ++ ++ ret = devm_gpio_request(&pdev->dev, gpio, ++ GPIO_LATCH_DRIVER_NAME); ++ if (ret) ++ return ret; ++ } ++ ++ glc = devm_kzalloc(&pdev->dev, sizeof(*glc), GFP_KERNEL); ++ if (!glc) ++ return -ENOMEM; ++ ++ mutex_init(&glc->mutex); ++ mutex_init(&glc->latch_mutex); ++ ++ size = pdata->num_gpios * sizeof(glc->gpios[0]); ++ glc->gpios = devm_kzalloc(&pdev->dev, size , GFP_KERNEL); ++ if (!glc->gpios) ++ return -ENOMEM; ++ ++ memcpy(glc->gpios, pdata->gpios, size); ++ ++ glc->le_gpio = glc->gpios[pdata->le_gpio_index]; ++ glc->le_active_low = pdata->le_active_low; ++ ++ gc = &glc->gc; ++ ++ gc->label = GPIO_LATCH_DRIVER_NAME; ++ gc->base = pdata->base; ++ gc->can_sleep = true; ++ gc->ngpio = pdata->num_gpios; ++ gc->get = gpio_latch_get; ++ gc->set = gpio_latch_set; ++ gc->direction_input = gpio_latch_direction_input, ++ gc->direction_output = gpio_latch_direction_output; ++ ++ platform_set_drvdata(pdev, glc); ++ ++ ret = gpiochip_add(&glc->gc); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int gpio_latch_remove(struct platform_device *pdev) ++{ ++ struct gpio_latch_chip *glc = platform_get_drvdata(pdev); ++ ++ return gpiochip_remove(&glc->gc);; ++} ++ ++ ++static struct platform_driver gpio_latch_driver = { ++ .probe = gpio_latch_probe, ++ .remove = gpio_latch_remove, ++ .driver = { ++ .name = GPIO_LATCH_DRIVER_NAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init gpio_latch_init(void) ++{ ++ return platform_driver_register(&gpio_latch_driver); ++} ++ ++postcore_initcall(gpio_latch_init); ++ ++MODULE_DESCRIPTION("GPIO latch driver"); ++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" GPIO_LATCH_DRIVER_NAME); +diff --git a/include/linux/platform_data/gpio-latch.h b/include/linux/platform_data/gpio-latch.h +new file mode 100644 +index 0000000..0450e67 +--- /dev/null ++++ b/include/linux/platform_data/gpio-latch.h +@@ -0,0 +1,14 @@ ++#ifndef _GPIO_LATCH_H_ ++#define _GPIO_LATCH_H_ ++ ++#define GPIO_LATCH_DRIVER_NAME "gpio-latch" ++ ++struct gpio_latch_platform_data { ++ int base; ++ int num_gpios; ++ int *gpios; ++ int le_gpio_index; ++ bool le_active_low; ++}; ++ ++#endif /* _GPIO_LATCH_H_ */ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0009-spi-export-spi_bitbang_bufs-function.patch b/target/mips/dragino-ms14s/patches/3.14.17/0009-spi-export-spi_bitbang_bufs-function.patch new file mode 100644 index 000000000..dc6af0a9d --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0009-spi-export-spi_bitbang_bufs-function.patch @@ -0,0 +1,45 @@ +From ff81dc67568d5393c30352c6075b43afc9de2329 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:22:55 +0200 +Subject: [PATCH] spi: export spi_bitbang_bufs function + +--- + drivers/spi/spi-bitbang.c | 3 ++- + include/linux/spi/spi_bitbang.h | 1 + + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/spi/spi-bitbang.c b/drivers/spi/spi-bitbang.c +index bd222f6..2145d77 100644 +--- a/drivers/spi/spi-bitbang.c ++++ b/drivers/spi/spi-bitbang.c +@@ -234,13 +234,14 @@ void spi_bitbang_cleanup(struct spi_device *spi) + } + EXPORT_SYMBOL_GPL(spi_bitbang_cleanup); + +-static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t) ++int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t) + { + struct spi_bitbang_cs *cs = spi->controller_state; + unsigned nsecs = cs->nsecs; + + return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t); + } ++EXPORT_SYMBOL_GPL(spi_bitbang_bufs); + + /*----------------------------------------------------------------------*/ + +diff --git a/include/linux/spi/spi_bitbang.h b/include/linux/spi/spi_bitbang.h +index daebaba..1631d7a 100644 +--- a/include/linux/spi/spi_bitbang.h ++++ b/include/linux/spi/spi_bitbang.h +@@ -39,6 +39,7 @@ extern int spi_bitbang_setup(struct spi_device *spi); + extern void spi_bitbang_cleanup(struct spi_device *spi); + extern int spi_bitbang_setup_transfer(struct spi_device *spi, + struct spi_transfer *t); ++extern int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t); + + /* start or stop queue processing */ + extern int spi_bitbang_start(struct spi_bitbang *spi); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0010-spi-add-type-field-to-spi_transfer-struct.patch b/target/mips/dragino-ms14s/patches/3.14.17/0010-spi-add-type-field-to-spi_transfer-struct.patch new file mode 100644 index 000000000..2721d3c4e --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0010-spi-add-type-field-to-spi_transfer-struct.patch @@ -0,0 +1,37 @@ +From eaf82ac5fc9272545d4d4fb4582eab69d37e389a Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:23:56 +0200 +Subject: [PATCH] spi: add type field to spi_transfer struct + +--- + include/linux/spi/spi.h | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h +index 4ee1a02..a77d6c6 100644 +--- a/include/linux/spi/spi.h ++++ b/include/linux/spi/spi.h +@@ -475,6 +475,12 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); + + /*---------------------------------------------------------------------------*/ + ++enum spi_transfer_type { ++ SPI_TRANSFER_GENERIC = 0, ++ SPI_TRANSFER_FLASH_READ_CMD, ++ SPI_TRANSFER_FLASH_READ_DATA, ++}; ++ + /* + * I/O INTERFACE between SPI controller and protocol drivers + * +@@ -591,6 +597,7 @@ struct spi_transfer { + u8 bits_per_word; + u16 delay_usecs; + u32 speed_hz; ++ enum spi_transfer_type type; + + struct list_head transfer_list; + }; +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0011-mtd-m25p80-set-SPI-transfer-type.patch b/target/mips/dragino-ms14s/patches/3.14.17/0011-mtd-m25p80-set-SPI-transfer-type.patch new file mode 100644 index 000000000..e2dfad6e0 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0011-mtd-m25p80-set-SPI-transfer-type.patch @@ -0,0 +1,29 @@ +From 531989d989855f673af76ef85300769a8a167405 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:25:59 +0200 +Subject: [PATCH] mtd: m25p80: set SPI transfer type + +--- + drivers/mtd/devices/m25p80.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c +index ad19139..cdabcc0 100644 +--- a/drivers/mtd/devices/m25p80.c ++++ b/drivers/mtd/devices/m25p80.c +@@ -524,10 +524,12 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, + return -EINVAL; + } + ++ t[0].type = SPI_TRANSFER_FLASH_READ_CMD; + t[0].tx_buf = flash->command; + t[0].len = m25p_cmdsz(flash) + dummy; + spi_message_add_tail(&t[0], &m); + ++ t[1].type = SPI_TRANSFER_FLASH_READ_DATA; + t[1].rx_buf = buf; + t[1].rx_nbits = m25p80_rx_nbits(flash); + t[1].len = len; +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0012-mips-ath79-swizzle-PCI-address-for-ar71xx.patch b/target/mips/dragino-ms14s/patches/3.14.17/0012-mips-ath79-swizzle-PCI-address-for-ar71xx.patch new file mode 100644 index 000000000..c63489112 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0012-mips-ath79-swizzle-PCI-address-for-ar71xx.patch @@ -0,0 +1,130 @@ +From 0c139cb15774f3c41a0cf6620727e676c874834a Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:28:24 +0200 +Subject: [PATCH] mips: ath79: swizzle PCI address for ar71xx + +--- + arch/mips/ath79/pci.c | 42 ++++++++++++++++++++++++++ + arch/mips/include/asm/mach-ath79/mangle-port.h | 37 +++++++++++++++++++++++ + 2 files changed, 79 insertions(+) + create mode 100644 arch/mips/include/asm/mach-ath79/mangle-port.h + +diff --git a/arch/mips/ath79/pci.c b/arch/mips/ath79/pci.c +index 730c0b0..47be58c 100644 +--- a/arch/mips/ath79/pci.c ++++ b/arch/mips/ath79/pci.c +@@ -13,6 +13,7 @@ + */ + + #include <linux/init.h> ++#include <linux/export.h> + #include <linux/pci.h> + #include <linux/resource.h> + #include <linux/platform_device.h> +@@ -25,6 +26,9 @@ static int (*ath79_pci_plat_dev_init)(struct pci_dev *dev); + static const struct ath79_pci_irq *ath79_pci_irq_map __initdata; + static unsigned ath79_pci_nr_irqs __initdata; + ++static unsigned long (*__ath79_pci_swizzle_b)(unsigned long port); ++static unsigned long (*__ath79_pci_swizzle_w)(unsigned long port); ++ + static const struct ath79_pci_irq ar71xx_pci_irq_map[] __initconst = { + { + .slot = 17, +@@ -212,12 +216,50 @@ ath79_register_pci_ar724x(int id, + return pdev; + } + ++static inline bool ar71xx_is_pci_addr(unsigned long port) ++{ ++ unsigned long phys = CPHYSADDR(port); ++ ++ return (phys >= AR71XX_PCI_MEM_BASE && ++ phys < AR71XX_PCI_MEM_BASE + AR71XX_PCI_MEM_SIZE); ++} ++ ++static unsigned long ar71xx_pci_swizzle_b(unsigned long port) ++{ ++ return ar71xx_is_pci_addr(port) ? port ^ 3 : port; ++} ++ ++static unsigned long ar71xx_pci_swizzle_w(unsigned long port) ++{ ++ return ar71xx_is_pci_addr(port) ? port ^ 2 : port; ++} ++ ++unsigned long ath79_pci_swizzle_b(unsigned long port) ++{ ++ if (__ath79_pci_swizzle_b) ++ return __ath79_pci_swizzle_b(port); ++ ++ return port; ++} ++EXPORT_SYMBOL(ath79_pci_swizzle_b); ++ ++unsigned long ath79_pci_swizzle_w(unsigned long port) ++{ ++ if (__ath79_pci_swizzle_w) ++ return __ath79_pci_swizzle_w(port); ++ ++ return port; ++} ++EXPORT_SYMBOL(ath79_pci_swizzle_w); ++ + int __init ath79_register_pci(void) + { + struct platform_device *pdev = NULL; + + if (soc_is_ar71xx()) { + pdev = ath79_register_pci_ar71xx(); ++ __ath79_pci_swizzle_b = ar71xx_pci_swizzle_b; ++ __ath79_pci_swizzle_w = ar71xx_pci_swizzle_w; + } else if (soc_is_ar724x()) { + pdev = ath79_register_pci_ar724x(-1, + AR724X_PCI_CFG_BASE, +diff --git a/arch/mips/include/asm/mach-ath79/mangle-port.h b/arch/mips/include/asm/mach-ath79/mangle-port.h +new file mode 100644 +index 0000000..ffd4e20 +--- /dev/null ++++ b/arch/mips/include/asm/mach-ath79/mangle-port.h +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * This file was derived from: inlude/asm-mips/mach-generic/mangle-port.h ++ * Copyright (C) 2003, 2004 Ralf Baechle ++ * ++ * 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. ++ */ ++ ++#ifndef __ASM_MACH_ATH79_MANGLE_PORT_H ++#define __ASM_MACH_ATH79_MANGLE_PORT_H ++ ++#ifdef CONFIG_PCI ++extern unsigned long (ath79_pci_swizzle_b)(unsigned long port); ++extern unsigned long (ath79_pci_swizzle_w)(unsigned long port); ++#else ++#define ath79_pci_swizzle_b(port) (port) ++#define ath79_pci_swizzle_w(port) (port) ++#endif ++ ++#define __swizzle_addr_b(port) ath79_pci_swizzle_b(port) ++#define __swizzle_addr_w(port) ath79_pci_swizzle_w(port) ++#define __swizzle_addr_l(port) (port) ++#define __swizzle_addr_q(port) (port) ++ ++# define ioswabb(a, x) (x) ++# define __mem_ioswabb(a, x) (x) ++# define ioswabw(a, x) (x) ++# define __mem_ioswabw(a, x) cpu_to_le16(x) ++# define ioswabl(a, x) (x) ++# define __mem_ioswabl(a, x) cpu_to_le32(x) ++# define ioswabq(a, x) (x) ++# define __mem_ioswabq(a, x) cpu_to_le64(x) ++ ++#endif /* __ASM_MACH_ATH79_MANGLE_PORT_H */ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0013-net-add-swconfig-support.patch b/target/mips/dragino-ms14s/patches/3.14.17/0013-net-add-swconfig-support.patch new file mode 100644 index 000000000..57c112842 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0013-net-add-swconfig-support.patch @@ -0,0 +1,1859 @@ +From fe40f6aba9ba59000ffa681a23ad59e9347346af Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:36:13 +0200 +Subject: [PATCH] net: add swconfig support + +--- + drivers/net/phy/Kconfig | 10 + + drivers/net/phy/Makefile | 1 + + drivers/net/phy/swconfig.c | 1144 +++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/swconfig_leds.c | 354 ++++++++++++ + include/linux/switch.h | 167 ++++++ + include/uapi/linux/Kbuild | 1 + + include/uapi/linux/switch.h | 103 ++++ + 7 files changed, 1780 insertions(+) + create mode 100644 drivers/net/phy/swconfig.c + create mode 100644 drivers/net/phy/swconfig_leds.c + create mode 100644 include/linux/switch.h + create mode 100644 include/uapi/linux/switch.h + +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 9b5d46c..36a13fc 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -12,6 +12,16 @@ menuconfig PHYLIB + + if PHYLIB + ++config SWCONFIG ++ tristate "Switch configuration API" ++ ---help--- ++ Switch configuration API using netlink. This allows ++ you to configure the VLAN features of certain switches. ++ ++config SWCONFIG_LEDS ++ bool "Switch LED trigger support" ++ depends on (SWCONFIG && LEDS_TRIGGERS) ++ + comment "MII PHY device drivers" + + config AT803X_PHY +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index 9013dfa..b510bd6 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -3,6 +3,7 @@ + libphy-objs := phy.o phy_device.o mdio_bus.o + + obj-$(CONFIG_PHYLIB) += libphy.o ++obj-$(CONFIG_SWCONFIG) += swconfig.o + obj-$(CONFIG_MARVELL_PHY) += marvell.o + obj-$(CONFIG_DAVICOM_PHY) += davicom.o + obj-$(CONFIG_CICADA_PHY) += cicada.o +diff --git a/drivers/net/phy/swconfig.c b/drivers/net/phy/swconfig.c +new file mode 100644 +index 0000000..c043ee4 +--- /dev/null ++++ b/drivers/net/phy/swconfig.c +@@ -0,0 +1,1144 @@ ++/* ++ * swconfig.c: Switch configuration API ++ * ++ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++ ++#include <linux/types.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/list.h> ++#include <linux/if.h> ++#include <linux/if_ether.h> ++#include <linux/capability.h> ++#include <linux/skbuff.h> ++#include <linux/switch.h> ++#include <linux/of.h> ++#include <linux/version.h> ++ ++#define SWCONFIG_DEVNAME "switch%d" ++ ++#include "swconfig_leds.c" ++ ++MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); ++MODULE_LICENSE("GPL"); ++ ++static int swdev_id; ++static struct list_head swdevs; ++static DEFINE_SPINLOCK(swdevs_lock); ++struct swconfig_callback; ++ ++struct swconfig_callback { ++ struct sk_buff *msg; ++ struct genlmsghdr *hdr; ++ struct genl_info *info; ++ int cmd; ++ ++ /* callback for filling in the message data */ ++ int (*fill)(struct swconfig_callback *cb, void *arg); ++ ++ /* callback for closing the message before sending it */ ++ int (*close)(struct swconfig_callback *cb, void *arg); ++ ++ struct nlattr *nest[4]; ++ int args[4]; ++}; ++ ++/* defaults */ ++ ++static int ++swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ int ret; ++ if (val->port_vlan >= dev->vlans) ++ return -EINVAL; ++ ++ if (!dev->ops->get_vlan_ports) ++ return -EOPNOTSUPP; ++ ++ ret = dev->ops->get_vlan_ports(dev, val); ++ return ret; ++} ++ ++static int ++swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct switch_port *ports = val->value.ports; ++ const struct switch_dev_ops *ops = dev->ops; ++ int i; ++ ++ if (val->port_vlan >= dev->vlans) ++ return -EINVAL; ++ ++ /* validate ports */ ++ if (val->len > dev->ports) ++ return -EINVAL; ++ ++ if (!ops->set_vlan_ports) ++ return -EOPNOTSUPP; ++ ++ for (i = 0; i < val->len; i++) { ++ if (ports[i].id >= dev->ports) ++ return -EINVAL; ++ ++ if (ops->set_port_pvid && ++ !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) ++ ops->set_port_pvid(dev, ports[i].id, val->port_vlan); ++ } ++ ++ return ops->set_vlan_ports(dev, val); ++} ++ ++static int ++swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ if (val->port_vlan >= dev->ports) ++ return -EINVAL; ++ ++ if (!dev->ops->set_port_pvid) ++ return -EOPNOTSUPP; ++ ++ return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); ++} ++ ++static int ++swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ if (val->port_vlan >= dev->ports) ++ return -EINVAL; ++ ++ if (!dev->ops->get_port_pvid) ++ return -EOPNOTSUPP; ++ ++ return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); ++} ++ ++static const char * ++swconfig_speed_str(enum switch_port_speed speed) ++{ ++ switch (speed) { ++ case SWITCH_PORT_SPEED_10: ++ return "10baseT"; ++ case SWITCH_PORT_SPEED_100: ++ return "100baseT"; ++ case SWITCH_PORT_SPEED_1000: ++ return "1000baseT"; ++ default: ++ break; ++ } ++ ++ return "unknown"; ++} ++ ++static int ++swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct switch_port_link link; ++ int len; ++ int ret; ++ ++ if (val->port_vlan >= dev->ports) ++ return -EINVAL; ++ ++ if (!dev->ops->get_port_link) ++ return -EOPNOTSUPP; ++ ++ memset(&link, 0, sizeof(link)); ++ ret = dev->ops->get_port_link(dev, val->port_vlan, &link); ++ if (ret) ++ return ret; ++ ++ memset(dev->buf, 0, sizeof(dev->buf)); ++ ++ if (link.link) ++ len = snprintf(dev->buf, sizeof(dev->buf), ++ "port:%d link:up speed:%s %s-duplex %s%s%s", ++ val->port_vlan, ++ swconfig_speed_str(link.speed), ++ link.duplex ? "full" : "half", ++ link.tx_flow ? "txflow " : "", ++ link.rx_flow ? "rxflow " : "", ++ link.aneg ? "auto" : ""); ++ else ++ len = snprintf(dev->buf, sizeof(dev->buf), "port:%d link:down", ++ val->port_vlan); ++ ++ val->value.s = dev->buf; ++ val->len = len; ++ ++ return 0; ++} ++ ++static int ++swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ /* don't complain if not supported by the switch driver */ ++ if (!dev->ops->apply_config) ++ return 0; ++ ++ return dev->ops->apply_config(dev); ++} ++ ++static int ++swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ /* don't complain if not supported by the switch driver */ ++ if (!dev->ops->reset_switch) ++ return 0; ++ ++ return dev->ops->reset_switch(dev); ++} ++ ++enum global_defaults { ++ GLOBAL_APPLY, ++ GLOBAL_RESET, ++}; ++ ++enum vlan_defaults { ++ VLAN_PORTS, ++}; ++ ++enum port_defaults { ++ PORT_PVID, ++ PORT_LINK, ++}; ++ ++static struct switch_attr default_global[] = { ++ [GLOBAL_APPLY] = { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "apply", ++ .description = "Activate changes in the hardware", ++ .set = swconfig_apply_config, ++ }, ++ [GLOBAL_RESET] = { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset", ++ .description = "Reset the switch", ++ .set = swconfig_reset_switch, ++ } ++}; ++ ++static struct switch_attr default_port[] = { ++ [PORT_PVID] = { ++ .type = SWITCH_TYPE_INT, ++ .name = "pvid", ++ .description = "Primary VLAN ID", ++ .set = swconfig_set_pvid, ++ .get = swconfig_get_pvid, ++ }, ++ [PORT_LINK] = { ++ .type = SWITCH_TYPE_STRING, ++ .name = "link", ++ .description = "Get port link information", ++ .set = NULL, ++ .get = swconfig_get_link, ++ } ++}; ++ ++static struct switch_attr default_vlan[] = { ++ [VLAN_PORTS] = { ++ .type = SWITCH_TYPE_PORTS, ++ .name = "ports", ++ .description = "VLAN port mapping", ++ .set = swconfig_set_vlan_ports, ++ .get = swconfig_get_vlan_ports, ++ }, ++}; ++ ++static const struct switch_attr * ++swconfig_find_attr_by_name(const struct switch_attrlist *alist, ++ const char *name) ++{ ++ int i; ++ ++ for (i = 0; i < alist->n_attr; i++) ++ if (strcmp(name, alist->attr[i].name) == 0) ++ return &alist->attr[i]; ++ ++ return NULL; ++} ++ ++static void swconfig_defaults_init(struct switch_dev *dev) ++{ ++ const struct switch_dev_ops *ops = dev->ops; ++ ++ dev->def_global = 0; ++ dev->def_vlan = 0; ++ dev->def_port = 0; ++ ++ if (ops->get_vlan_ports || ops->set_vlan_ports) ++ set_bit(VLAN_PORTS, &dev->def_vlan); ++ ++ if (ops->get_port_pvid || ops->set_port_pvid) ++ set_bit(PORT_PVID, &dev->def_port); ++ ++ if (ops->get_port_link && ++ !swconfig_find_attr_by_name(&ops->attr_port, "link")) ++ set_bit(PORT_LINK, &dev->def_port); ++ ++ /* always present, can be no-op */ ++ set_bit(GLOBAL_APPLY, &dev->def_global); ++ set_bit(GLOBAL_RESET, &dev->def_global); ++} ++ ++ ++static struct genl_family switch_fam = { ++ .id = GENL_ID_GENERATE, ++ .name = "switch", ++ .hdrsize = 0, ++ .version = 1, ++ .maxattr = SWITCH_ATTR_MAX, ++}; ++ ++static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { ++ [SWITCH_ATTR_ID] = { .type = NLA_U32 }, ++ [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, ++ [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, ++ [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, ++ [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, ++ [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, ++ [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, ++ [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, ++}; ++ ++static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { ++ [SWITCH_PORT_ID] = { .type = NLA_U32 }, ++ [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, ++}; ++ ++static inline void ++swconfig_lock(void) ++{ ++ spin_lock(&swdevs_lock); ++} ++ ++static inline void ++swconfig_unlock(void) ++{ ++ spin_unlock(&swdevs_lock); ++} ++ ++static struct switch_dev * ++swconfig_get_dev(struct genl_info *info) ++{ ++ struct switch_dev *dev = NULL; ++ struct switch_dev *p; ++ int id; ++ ++ if (!info->attrs[SWITCH_ATTR_ID]) ++ goto done; ++ ++ id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); ++ swconfig_lock(); ++ list_for_each_entry(p, &swdevs, dev_list) { ++ if (id != p->id) ++ continue; ++ ++ dev = p; ++ break; ++ } ++ if (dev) ++ mutex_lock(&dev->sw_mutex); ++ else ++ pr_debug("device %d not found\n", id); ++ swconfig_unlock(); ++done: ++ return dev; ++} ++ ++static inline void ++swconfig_put_dev(struct switch_dev *dev) ++{ ++ mutex_unlock(&dev->sw_mutex); ++} ++ ++static int ++swconfig_dump_attr(struct swconfig_callback *cb, void *arg) ++{ ++ struct switch_attr *op = arg; ++ struct genl_info *info = cb->info; ++ struct sk_buff *msg = cb->msg; ++ int id = cb->args[0]; ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, ++ NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); ++ if (IS_ERR(hdr)) ++ return -1; ++ ++ if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) ++ goto nla_put_failure; ++ if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) ++ goto nla_put_failure; ++ if (op->description) ++ if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, ++ op->description)) ++ goto nla_put_failure; ++ ++ return genlmsg_end(msg, hdr); ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++/* spread multipart messages across multiple message buffers */ ++static int ++swconfig_send_multipart(struct swconfig_callback *cb, void *arg) ++{ ++ struct genl_info *info = cb->info; ++ int restart = 0; ++ int err; ++ ++ do { ++ if (!cb->msg) { ++ cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); ++ if (cb->msg == NULL) ++ goto error; ++ } ++ ++ if (!(cb->fill(cb, arg) < 0)) ++ break; ++ ++ /* fill failed, check if this was already the second attempt */ ++ if (restart) ++ goto error; ++ ++ /* try again in a new message, send the current one */ ++ restart = 1; ++ if (cb->close) { ++ if (cb->close(cb, arg) < 0) ++ goto error; ++ } ++ err = genlmsg_reply(cb->msg, info); ++ cb->msg = NULL; ++ if (err < 0) ++ goto error; ++ ++ } while (restart); ++ ++ return 0; ++ ++error: ++ if (cb->msg) ++ nlmsg_free(cb->msg); ++ return -1; ++} ++ ++static int ++swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); ++ const struct switch_attrlist *alist; ++ struct switch_dev *dev; ++ struct swconfig_callback cb; ++ int err = -EINVAL; ++ int i; ++ ++ /* defaults */ ++ struct switch_attr *def_list; ++ unsigned long *def_active; ++ int n_def; ++ ++ dev = swconfig_get_dev(info); ++ if (!dev) ++ return -EINVAL; ++ ++ switch (hdr->cmd) { ++ case SWITCH_CMD_LIST_GLOBAL: ++ alist = &dev->ops->attr_global; ++ def_list = default_global; ++ def_active = &dev->def_global; ++ n_def = ARRAY_SIZE(default_global); ++ break; ++ case SWITCH_CMD_LIST_VLAN: ++ alist = &dev->ops->attr_vlan; ++ def_list = default_vlan; ++ def_active = &dev->def_vlan; ++ n_def = ARRAY_SIZE(default_vlan); ++ break; ++ case SWITCH_CMD_LIST_PORT: ++ alist = &dev->ops->attr_port; ++ def_list = default_port; ++ def_active = &dev->def_port; ++ n_def = ARRAY_SIZE(default_port); ++ break; ++ default: ++ WARN_ON(1); ++ goto out; ++ } ++ ++ memset(&cb, 0, sizeof(cb)); ++ cb.info = info; ++ cb.fill = swconfig_dump_attr; ++ for (i = 0; i < alist->n_attr; i++) { ++ if (alist->attr[i].disabled) ++ continue; ++ cb.args[0] = i; ++ err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); ++ if (err < 0) ++ goto error; ++ } ++ ++ /* defaults */ ++ for (i = 0; i < n_def; i++) { ++ if (!test_bit(i, def_active)) ++ continue; ++ cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; ++ err = swconfig_send_multipart(&cb, (void *) &def_list[i]); ++ if (err < 0) ++ goto error; ++ } ++ swconfig_put_dev(dev); ++ ++ if (!cb.msg) ++ return 0; ++ ++ return genlmsg_reply(cb.msg, info); ++ ++error: ++ if (cb.msg) ++ nlmsg_free(cb.msg); ++out: ++ swconfig_put_dev(dev); ++ return err; ++} ++ ++static const struct switch_attr * ++swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, ++ struct switch_val *val) ++{ ++ struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); ++ const struct switch_attrlist *alist; ++ const struct switch_attr *attr = NULL; ++ int attr_id; ++ ++ /* defaults */ ++ struct switch_attr *def_list; ++ unsigned long *def_active; ++ int n_def; ++ ++ if (!info->attrs[SWITCH_ATTR_OP_ID]) ++ goto done; ++ ++ switch (hdr->cmd) { ++ case SWITCH_CMD_SET_GLOBAL: ++ case SWITCH_CMD_GET_GLOBAL: ++ alist = &dev->ops->attr_global; ++ def_list = default_global; ++ def_active = &dev->def_global; ++ n_def = ARRAY_SIZE(default_global); ++ break; ++ case SWITCH_CMD_SET_VLAN: ++ case SWITCH_CMD_GET_VLAN: ++ alist = &dev->ops->attr_vlan; ++ def_list = default_vlan; ++ def_active = &dev->def_vlan; ++ n_def = ARRAY_SIZE(default_vlan); ++ if (!info->attrs[SWITCH_ATTR_OP_VLAN]) ++ goto done; ++ val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); ++ if (val->port_vlan >= dev->vlans) ++ goto done; ++ break; ++ case SWITCH_CMD_SET_PORT: ++ case SWITCH_CMD_GET_PORT: ++ alist = &dev->ops->attr_port; ++ def_list = default_port; ++ def_active = &dev->def_port; ++ n_def = ARRAY_SIZE(default_port); ++ if (!info->attrs[SWITCH_ATTR_OP_PORT]) ++ goto done; ++ val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); ++ if (val->port_vlan >= dev->ports) ++ goto done; ++ break; ++ default: ++ WARN_ON(1); ++ goto done; ++ } ++ ++ if (!alist) ++ goto done; ++ ++ attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); ++ if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { ++ attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; ++ if (attr_id >= n_def) ++ goto done; ++ if (!test_bit(attr_id, def_active)) ++ goto done; ++ attr = &def_list[attr_id]; ++ } else { ++ if (attr_id >= alist->n_attr) ++ goto done; ++ attr = &alist->attr[attr_id]; ++ } ++ ++ if (attr->disabled) ++ attr = NULL; ++ ++done: ++ if (!attr) ++ pr_debug("attribute lookup failed\n"); ++ val->attr = attr; ++ return attr; ++} ++ ++static int ++swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, ++ struct switch_val *val, int max) ++{ ++ struct nlattr *nla; ++ int rem; ++ ++ val->len = 0; ++ nla_for_each_nested(nla, head, rem) { ++ struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; ++ struct switch_port *port = &val->value.ports[val->len]; ++ ++ if (val->len >= max) ++ return -EINVAL; ++ ++ if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, ++ port_policy)) ++ return -EINVAL; ++ ++ if (!tb[SWITCH_PORT_ID]) ++ return -EINVAL; ++ ++ port->id = nla_get_u32(tb[SWITCH_PORT_ID]); ++ if (tb[SWITCH_PORT_FLAG_TAGGED]) ++ port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); ++ val->len++; ++ } ++ ++ return 0; ++} ++ ++static int ++swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) ++{ ++ const struct switch_attr *attr; ++ struct switch_dev *dev; ++ struct switch_val val; ++ int err = -EINVAL; ++ ++ dev = swconfig_get_dev(info); ++ if (!dev) ++ return -EINVAL; ++ ++ memset(&val, 0, sizeof(val)); ++ attr = swconfig_lookup_attr(dev, info, &val); ++ if (!attr || !attr->set) ++ goto error; ++ ++ val.attr = attr; ++ switch (attr->type) { ++ case SWITCH_TYPE_NOVAL: ++ break; ++ case SWITCH_TYPE_INT: ++ if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) ++ goto error; ++ val.value.i = ++ nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); ++ break; ++ case SWITCH_TYPE_STRING: ++ if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) ++ goto error; ++ val.value.s = ++ nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); ++ break; ++ case SWITCH_TYPE_PORTS: ++ val.value.ports = dev->portbuf; ++ memset(dev->portbuf, 0, ++ sizeof(struct switch_port) * dev->ports); ++ ++ /* TODO: implement multipart? */ ++ if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { ++ err = swconfig_parse_ports(skb, ++ info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], ++ &val, dev->ports); ++ if (err < 0) ++ goto error; ++ } else { ++ val.len = 0; ++ err = 0; ++ } ++ break; ++ default: ++ goto error; ++ } ++ ++ err = attr->set(dev, attr, &val); ++error: ++ swconfig_put_dev(dev); ++ return err; ++} ++ ++static int ++swconfig_close_portlist(struct swconfig_callback *cb, void *arg) ++{ ++ if (cb->nest[0]) ++ nla_nest_end(cb->msg, cb->nest[0]); ++ return 0; ++} ++ ++static int ++swconfig_send_port(struct swconfig_callback *cb, void *arg) ++{ ++ const struct switch_port *port = arg; ++ struct nlattr *p = NULL; ++ ++ if (!cb->nest[0]) { ++ cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); ++ if (!cb->nest[0]) ++ return -1; ++ } ++ ++ p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); ++ if (!p) ++ goto error; ++ ++ if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) ++ goto nla_put_failure; ++ if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { ++ if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) ++ goto nla_put_failure; ++ } ++ ++ nla_nest_end(cb->msg, p); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(cb->msg, p); ++error: ++ nla_nest_cancel(cb->msg, cb->nest[0]); ++ return -1; ++} ++ ++static int ++swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, ++ const struct switch_val *val) ++{ ++ struct swconfig_callback cb; ++ int err = 0; ++ int i; ++ ++ if (!val->value.ports) ++ return -EINVAL; ++ ++ memset(&cb, 0, sizeof(cb)); ++ cb.cmd = attr; ++ cb.msg = *msg; ++ cb.info = info; ++ cb.fill = swconfig_send_port; ++ cb.close = swconfig_close_portlist; ++ ++ cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); ++ for (i = 0; i < val->len; i++) { ++ err = swconfig_send_multipart(&cb, &val->value.ports[i]); ++ if (err) ++ goto done; ++ } ++ err = val->len; ++ swconfig_close_portlist(&cb, NULL); ++ *msg = cb.msg; ++ ++done: ++ return err; ++} ++ ++static int ++swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); ++ const struct switch_attr *attr; ++ struct switch_dev *dev; ++ struct sk_buff *msg = NULL; ++ struct switch_val val; ++ int err = -EINVAL; ++ int cmd = hdr->cmd; ++ ++ dev = swconfig_get_dev(info); ++ if (!dev) ++ return -EINVAL; ++ ++ memset(&val, 0, sizeof(val)); ++ attr = swconfig_lookup_attr(dev, info, &val); ++ if (!attr || !attr->get) ++ goto error; ++ ++ if (attr->type == SWITCH_TYPE_PORTS) { ++ val.value.ports = dev->portbuf; ++ memset(dev->portbuf, 0, ++ sizeof(struct switch_port) * dev->ports); ++ } ++ ++ err = attr->get(dev, attr, &val); ++ if (err) ++ goto error; ++ ++ msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); ++ if (!msg) ++ goto error; ++ ++ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, ++ 0, cmd); ++ if (IS_ERR(hdr)) ++ goto nla_put_failure; ++ ++ switch (attr->type) { ++ case SWITCH_TYPE_INT: ++ if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) ++ goto nla_put_failure; ++ break; ++ case SWITCH_TYPE_STRING: ++ if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) ++ goto nla_put_failure; ++ break; ++ case SWITCH_TYPE_PORTS: ++ err = swconfig_send_ports(&msg, info, ++ SWITCH_ATTR_OP_VALUE_PORTS, &val); ++ if (err < 0) ++ goto nla_put_failure; ++ break; ++ default: ++ pr_debug("invalid type in attribute\n"); ++ err = -EINVAL; ++ goto error; ++ } ++ err = genlmsg_end(msg, hdr); ++ if (err < 0) ++ goto nla_put_failure; ++ ++ swconfig_put_dev(dev); ++ return genlmsg_reply(msg, info); ++ ++nla_put_failure: ++ if (msg) ++ nlmsg_free(msg); ++error: ++ swconfig_put_dev(dev); ++ if (!err) ++ err = -ENOMEM; ++ return err; ++} ++ ++static int ++swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, ++ const struct switch_dev *dev) ++{ ++ struct nlattr *p = NULL, *m = NULL; ++ void *hdr; ++ int i; ++ ++ hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, ++ SWITCH_CMD_NEW_ATTR); ++ if (IS_ERR(hdr)) ++ return -1; ++ ++ if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) ++ goto nla_put_failure; ++ if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) ++ goto nla_put_failure; ++ if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) ++ goto nla_put_failure; ++ if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) ++ goto nla_put_failure; ++ ++ m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); ++ if (!m) ++ goto nla_put_failure; ++ for (i = 0; i < dev->ports; i++) { ++ p = nla_nest_start(msg, SWITCH_ATTR_PORTS); ++ if (!p) ++ continue; ++ if (dev->portmap[i].s) { ++ if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, ++ dev->portmap[i].s)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, ++ dev->portmap[i].virt)) ++ goto nla_put_failure; ++ } ++ nla_nest_end(msg, p); ++ } ++ nla_nest_end(msg, m); ++ return genlmsg_end(msg, hdr); ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int swconfig_dump_switches(struct sk_buff *skb, ++ struct netlink_callback *cb) ++{ ++ struct switch_dev *dev; ++ int start = cb->args[0]; ++ int idx = 0; ++ ++ swconfig_lock(); ++ list_for_each_entry(dev, &swdevs, dev_list) { ++ if (++idx <= start) ++ continue; ++ if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, NLM_F_MULTI, ++ dev) < 0) ++ break; ++ } ++ swconfig_unlock(); ++ cb->args[0] = idx; ++ ++ return skb->len; ++} ++ ++static int ++swconfig_done(struct netlink_callback *cb) ++{ ++ return 0; ++} ++ ++static struct genl_ops swconfig_ops[] = { ++ { ++ .cmd = SWITCH_CMD_LIST_GLOBAL, ++ .doit = swconfig_list_attrs, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_LIST_VLAN, ++ .doit = swconfig_list_attrs, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_LIST_PORT, ++ .doit = swconfig_list_attrs, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_GET_GLOBAL, ++ .doit = swconfig_get_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_GET_VLAN, ++ .doit = swconfig_get_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_GET_PORT, ++ .doit = swconfig_get_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_SET_GLOBAL, ++ .doit = swconfig_set_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_SET_VLAN, ++ .doit = swconfig_set_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_SET_PORT, ++ .doit = swconfig_set_attr, ++ .policy = switch_policy, ++ }, ++ { ++ .cmd = SWITCH_CMD_GET_SWITCH, ++ .dumpit = swconfig_dump_switches, ++ .policy = switch_policy, ++ .done = swconfig_done, ++ } ++}; ++ ++#ifdef CONFIG_OF ++void ++of_switch_load_portmap(struct switch_dev *dev) ++{ ++ struct device_node *port; ++ ++ if (!dev->of_node) ++ return; ++ ++ for_each_child_of_node(dev->of_node, port) { ++ const __be32 *prop; ++ const char *segment; ++ int size, phys; ++ ++ if (!of_device_is_compatible(port, "swconfig,port")) ++ continue; ++ ++ if (of_property_read_string(port, "swconfig,segment", &segment)) ++ continue; ++ ++ prop = of_get_property(port, "swconfig,portmap", &size); ++ if (!prop) ++ continue; ++ ++ if (size != (2 * sizeof(*prop))) { ++ pr_err("%s: failed to parse port mapping\n", ++ port->name); ++ continue; ++ } ++ ++ phys = be32_to_cpup(prop++); ++ if ((phys < 0) | (phys >= dev->ports)) { ++ pr_err("%s: physical port index out of range\n", ++ port->name); ++ continue; ++ } ++ ++ dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); ++ dev->portmap[phys].virt = be32_to_cpup(prop); ++ pr_debug("Found port: %s, physical: %d, virtual: %d\n", ++ segment, phys, dev->portmap[phys].virt); ++ } ++} ++#endif ++ ++int ++register_switch(struct switch_dev *dev, struct net_device *netdev) ++{ ++ struct switch_dev *sdev; ++ const int max_switches = 8 * sizeof(unsigned long); ++ unsigned long in_use = 0; ++ int err; ++ int i; ++ ++ INIT_LIST_HEAD(&dev->dev_list); ++ if (netdev) { ++ dev->netdev = netdev; ++ if (!dev->alias) ++ dev->alias = netdev->name; ++ } ++ BUG_ON(!dev->alias); ++ ++ if (dev->ports > 0) { ++ dev->portbuf = kzalloc(sizeof(struct switch_port) * ++ dev->ports, GFP_KERNEL); ++ if (!dev->portbuf) ++ return -ENOMEM; ++ dev->portmap = kzalloc(sizeof(struct switch_portmap) * ++ dev->ports, GFP_KERNEL); ++ if (!dev->portmap) { ++ kfree(dev->portbuf); ++ return -ENOMEM; ++ } ++ } ++ swconfig_defaults_init(dev); ++ mutex_init(&dev->sw_mutex); ++ swconfig_lock(); ++ dev->id = ++swdev_id; ++ ++ list_for_each_entry(sdev, &swdevs, dev_list) { ++ if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) ++ continue; ++ if (i < 0 || i > max_switches) ++ continue; ++ ++ set_bit(i, &in_use); ++ } ++ i = find_first_zero_bit(&in_use, max_switches); ++ ++ if (i == max_switches) { ++ swconfig_unlock(); ++ return -ENFILE; ++ } ++ ++#ifdef CONFIG_OF ++ if (dev->ports) ++ of_switch_load_portmap(dev); ++#endif ++ ++ /* fill device name */ ++ snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); ++ ++ list_add(&dev->dev_list, &swdevs); ++ swconfig_unlock(); ++ ++ err = swconfig_create_led_trigger(dev); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(register_switch); ++ ++void ++unregister_switch(struct switch_dev *dev) ++{ ++ swconfig_destroy_led_trigger(dev); ++ kfree(dev->portbuf); ++ mutex_lock(&dev->sw_mutex); ++ swconfig_lock(); ++ list_del(&dev->dev_list); ++ swconfig_unlock(); ++ mutex_unlock(&dev->sw_mutex); ++} ++EXPORT_SYMBOL_GPL(unregister_switch); ++ ++ ++static int __init ++swconfig_init(void) ++{ ++ int i, err; ++ ++ INIT_LIST_HEAD(&swdevs); ++ ++#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) ++ err = genl_register_family(&switch_fam); ++ if (err) ++ return err; ++ ++ for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { ++ err = genl_register_ops(&switch_fam, &swconfig_ops[i]); ++ if (err) ++ goto unregister; ++ } ++#else ++ err = genl_register_family_with_ops(&switch_fam, swconfig_ops); ++ if (err) ++ return err; ++#endif ++ return 0; ++ ++unregister: ++ genl_unregister_family(&switch_fam); ++ return err; ++} ++ ++static void __exit ++swconfig_exit(void) ++{ ++ genl_unregister_family(&switch_fam); ++} ++ ++module_init(swconfig_init); ++module_exit(swconfig_exit); ++ +diff --git a/drivers/net/phy/swconfig_leds.c b/drivers/net/phy/swconfig_leds.c +new file mode 100644 +index 0000000..abd7bed +--- /dev/null ++++ b/drivers/net/phy/swconfig_leds.c +@@ -0,0 +1,354 @@ ++/* ++ * swconfig_led.c: LED trigger support for the switch configuration API ++ * ++ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * 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. ++ * ++ */ ++ ++#ifdef CONFIG_SWCONFIG_LEDS ++ ++#include <linux/leds.h> ++#include <linux/ctype.h> ++#include <linux/device.h> ++#include <linux/workqueue.h> ++ ++#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) ++#define SWCONFIG_LED_NUM_PORTS 32 ++ ++struct switch_led_trigger { ++ struct led_trigger trig; ++ struct switch_dev *swdev; ++ ++ struct delayed_work sw_led_work; ++ u32 port_mask; ++ u32 port_link; ++ unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; ++}; ++ ++struct swconfig_trig_data { ++ struct led_classdev *led_cdev; ++ struct switch_dev *swdev; ++ ++ rwlock_t lock; ++ u32 port_mask; ++ ++ bool prev_link; ++ unsigned long prev_traffic; ++ enum led_brightness prev_brightness; ++}; ++ ++static void ++swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, ++ enum led_brightness brightness) ++{ ++ led_set_brightness(trig_data->led_cdev, brightness); ++ trig_data->prev_brightness = brightness; ++} ++ ++static void ++swconfig_trig_update_port_mask(struct led_trigger *trigger) ++{ ++ struct list_head *entry; ++ struct switch_led_trigger *sw_trig; ++ u32 port_mask; ++ ++ if (!trigger) ++ return; ++ ++ sw_trig = (void *) trigger; ++ ++ port_mask = 0; ++ read_lock(&trigger->leddev_list_lock); ++ list_for_each(entry, &trigger->led_cdevs) { ++ struct led_classdev *led_cdev; ++ struct swconfig_trig_data *trig_data; ++ ++ led_cdev = list_entry(entry, struct led_classdev, trig_list); ++ trig_data = led_cdev->trigger_data; ++ if (trig_data) { ++ read_lock(&trig_data->lock); ++ port_mask |= trig_data->port_mask; ++ read_unlock(&trig_data->lock); ++ } ++ } ++ read_unlock(&trigger->leddev_list_lock); ++ ++ sw_trig->port_mask = port_mask; ++ ++ if (port_mask) ++ schedule_delayed_work(&sw_trig->sw_led_work, ++ SWCONFIG_LED_TIMER_INTERVAL); ++ else ++ cancel_delayed_work_sync(&sw_trig->sw_led_work); ++} ++ ++static ssize_t ++swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t size) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct swconfig_trig_data *trig_data = led_cdev->trigger_data; ++ unsigned long port_mask; ++ ssize_t ret = -EINVAL; ++ char *after; ++ size_t count; ++ ++ port_mask = simple_strtoul(buf, &after, 16); ++ count = after - buf; ++ ++ if (*after && isspace(*after)) ++ count++; ++ ++ if (count == size) { ++ bool changed; ++ ++ write_lock(&trig_data->lock); ++ ++ changed = (trig_data->port_mask != port_mask); ++ if (changed) { ++ trig_data->port_mask = port_mask; ++ if (port_mask == 0) ++ swconfig_trig_set_brightness(trig_data, LED_OFF); ++ } ++ ++ write_unlock(&trig_data->lock); ++ ++ if (changed) ++ swconfig_trig_update_port_mask(led_cdev->trigger); ++ ++ ret = count; ++ } ++ ++ return ret; ++} ++ ++static ssize_t ++swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct swconfig_trig_data *trig_data = led_cdev->trigger_data; ++ ++ read_lock(&trig_data->lock); ++ sprintf(buf, "%#x\n", trig_data->port_mask); ++ read_unlock(&trig_data->lock); ++ ++ return strlen(buf) + 1; ++} ++ ++static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, ++ swconfig_trig_port_mask_store); ++ ++static void ++swconfig_trig_activate(struct led_classdev *led_cdev) ++{ ++ struct switch_led_trigger *sw_trig; ++ struct swconfig_trig_data *trig_data; ++ int err; ++ ++ if (led_cdev->trigger->activate != swconfig_trig_activate) ++ return; ++ ++ trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); ++ if (!trig_data) ++ return; ++ ++ sw_trig = (void *) led_cdev->trigger; ++ ++ rwlock_init(&trig_data->lock); ++ trig_data->led_cdev = led_cdev; ++ trig_data->swdev = sw_trig->swdev; ++ led_cdev->trigger_data = trig_data; ++ ++ err = device_create_file(led_cdev->dev, &dev_attr_port_mask); ++ if (err) ++ goto err_free; ++ ++ return; ++ ++err_free: ++ led_cdev->trigger_data = NULL; ++ kfree(trig_data); ++} ++ ++static void ++swconfig_trig_deactivate(struct led_classdev *led_cdev) ++{ ++ struct swconfig_trig_data *trig_data; ++ ++ swconfig_trig_update_port_mask(led_cdev->trigger); ++ ++ trig_data = (void *) led_cdev->trigger_data; ++ if (trig_data) { ++ device_remove_file(led_cdev->dev, &dev_attr_port_mask); ++ kfree(trig_data); ++ } ++} ++ ++static void ++swconfig_trig_led_event(struct switch_led_trigger *sw_trig, ++ struct led_classdev *led_cdev) ++{ ++ struct swconfig_trig_data *trig_data; ++ u32 port_mask; ++ bool link; ++ ++ trig_data = led_cdev->trigger_data; ++ if (!trig_data) ++ return; ++ ++ read_lock(&trig_data->lock); ++ port_mask = trig_data->port_mask; ++ read_unlock(&trig_data->lock); ++ ++ link = !!(sw_trig->port_link & port_mask); ++ if (!link) { ++ if (link != trig_data->prev_link) ++ swconfig_trig_set_brightness(trig_data, LED_OFF); ++ } else { ++ unsigned long traffic; ++ int i; ++ ++ traffic = 0; ++ for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { ++ if (port_mask & (1 << i)) ++ traffic += sw_trig->port_traffic[i]; ++ } ++ ++ if (trig_data->prev_brightness != LED_FULL) ++ swconfig_trig_set_brightness(trig_data, LED_FULL); ++ else if (traffic != trig_data->prev_traffic) ++ swconfig_trig_set_brightness(trig_data, LED_OFF); ++ ++ trig_data->prev_traffic = traffic; ++ } ++ ++ trig_data->prev_link = link; ++} ++ ++static void ++swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) ++{ ++ struct list_head *entry; ++ struct led_trigger *trigger; ++ ++ trigger = &sw_trig->trig; ++ read_lock(&trigger->leddev_list_lock); ++ list_for_each(entry, &trigger->led_cdevs) { ++ struct led_classdev *led_cdev; ++ ++ led_cdev = list_entry(entry, struct led_classdev, trig_list); ++ swconfig_trig_led_event(sw_trig, led_cdev); ++ } ++ read_unlock(&trigger->leddev_list_lock); ++} ++ ++static void ++swconfig_led_work_func(struct work_struct *work) ++{ ++ struct switch_led_trigger *sw_trig; ++ struct switch_dev *swdev; ++ u32 port_mask; ++ u32 link; ++ int i; ++ ++ sw_trig = container_of(work, struct switch_led_trigger, ++ sw_led_work.work); ++ ++ port_mask = sw_trig->port_mask; ++ swdev = sw_trig->swdev; ++ ++ link = 0; ++ for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { ++ u32 port_bit; ++ ++ port_bit = BIT(i); ++ if ((port_mask & port_bit) == 0) ++ continue; ++ ++ if (swdev->ops->get_port_link) { ++ struct switch_port_link port_link; ++ ++ memset(&port_link, '\0', sizeof(port_link)); ++ swdev->ops->get_port_link(swdev, i, &port_link); ++ ++ if (port_link.link) ++ link |= port_bit; ++ } ++ ++ if (swdev->ops->get_port_stats) { ++ struct switch_port_stats port_stats; ++ ++ memset(&port_stats, '\0', sizeof(port_stats)); ++ swdev->ops->get_port_stats(swdev, i, &port_stats); ++ sw_trig->port_traffic[i] = port_stats.tx_bytes + ++ port_stats.rx_bytes; ++ } ++ } ++ ++ sw_trig->port_link = link; ++ ++ swconfig_trig_update_leds(sw_trig); ++ ++ schedule_delayed_work(&sw_trig->sw_led_work, ++ SWCONFIG_LED_TIMER_INTERVAL); ++} ++ ++static int ++swconfig_create_led_trigger(struct switch_dev *swdev) ++{ ++ struct switch_led_trigger *sw_trig; ++ int err; ++ ++ if (!swdev->ops->get_port_link) ++ return 0; ++ ++ sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); ++ if (!sw_trig) ++ return -ENOMEM; ++ ++ sw_trig->swdev = swdev; ++ sw_trig->trig.name = swdev->devname; ++ sw_trig->trig.activate = swconfig_trig_activate; ++ sw_trig->trig.deactivate = swconfig_trig_deactivate; ++ ++ INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); ++ ++ err = led_trigger_register(&sw_trig->trig); ++ if (err) ++ goto err_free; ++ ++ swdev->led_trigger = sw_trig; ++ ++ return 0; ++ ++err_free: ++ kfree(sw_trig); ++ return err; ++} ++ ++static void ++swconfig_destroy_led_trigger(struct switch_dev *swdev) ++{ ++ struct switch_led_trigger *sw_trig; ++ ++ sw_trig = swdev->led_trigger; ++ if (sw_trig) { ++ cancel_delayed_work_sync(&sw_trig->sw_led_work); ++ led_trigger_unregister(&sw_trig->trig); ++ kfree(sw_trig); ++ } ++} ++ ++#else /* SWCONFIG_LEDS */ ++static inline int ++swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } ++ ++static inline void ++swconfig_destroy_led_trigger(struct switch_dev *swdev) { } ++#endif /* CONFIG_SWCONFIG_LEDS */ +diff --git a/include/linux/switch.h b/include/linux/switch.h +new file mode 100644 +index 0000000..b53431e +--- /dev/null ++++ b/include/linux/switch.h +@@ -0,0 +1,167 @@ ++/* ++ * switch.h: Switch configuration API ++ * ++ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++#ifndef _LINUX_SWITCH_H ++#define _LINUX_SWITCH_H ++ ++#include <net/genetlink.h> ++#include <uapi/linux/switch.h> ++ ++struct switch_dev; ++struct switch_op; ++struct switch_val; ++struct switch_attr; ++struct switch_attrlist; ++struct switch_led_trigger; ++ ++int register_switch(struct switch_dev *dev, struct net_device *netdev); ++void unregister_switch(struct switch_dev *dev); ++ ++/** ++ * struct switch_attrlist - attribute list ++ * ++ * @n_attr: number of attributes ++ * @attr: pointer to the attributes array ++ */ ++struct switch_attrlist { ++ int n_attr; ++ const struct switch_attr *attr; ++}; ++ ++enum switch_port_speed { ++ SWITCH_PORT_SPEED_UNKNOWN = 0, ++ SWITCH_PORT_SPEED_10 = 10, ++ SWITCH_PORT_SPEED_100 = 100, ++ SWITCH_PORT_SPEED_1000 = 1000, ++}; ++ ++struct switch_port_link { ++ bool link; ++ bool duplex; ++ bool aneg; ++ bool tx_flow; ++ bool rx_flow; ++ enum switch_port_speed speed; ++}; ++ ++struct switch_port_stats { ++ unsigned long tx_bytes; ++ unsigned long rx_bytes; ++}; ++ ++/** ++ * struct switch_dev_ops - switch driver operations ++ * ++ * @attr_global: global switch attribute list ++ * @attr_port: port attribute list ++ * @attr_vlan: vlan attribute list ++ * ++ * Callbacks: ++ * ++ * @get_vlan_ports: read the port list of a VLAN ++ * @set_vlan_ports: set the port list of a VLAN ++ * ++ * @get_port_pvid: get the primary VLAN ID of a port ++ * @set_port_pvid: set the primary VLAN ID of a port ++ * ++ * @apply_config: apply all changed settings to the switch ++ * @reset_switch: resetting the switch ++ */ ++struct switch_dev_ops { ++ struct switch_attrlist attr_global, attr_port, attr_vlan; ++ ++ int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); ++ int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); ++ ++ int (*get_port_pvid)(struct switch_dev *dev, int port, int *val); ++ int (*set_port_pvid)(struct switch_dev *dev, int port, int val); ++ ++ int (*apply_config)(struct switch_dev *dev); ++ int (*reset_switch)(struct switch_dev *dev); ++ ++ int (*get_port_link)(struct switch_dev *dev, int port, ++ struct switch_port_link *link); ++ int (*get_port_stats)(struct switch_dev *dev, int port, ++ struct switch_port_stats *stats); ++}; ++ ++struct switch_dev { ++ struct device_node *of_node; ++ const struct switch_dev_ops *ops; ++ /* will be automatically filled */ ++ char devname[IFNAMSIZ]; ++ ++ const char *name; ++ /* NB: either alias or netdev must be set */ ++ const char *alias; ++ struct net_device *netdev; ++ ++ int ports; ++ int vlans; ++ int cpu_port; ++ ++ /* the following fields are internal for swconfig */ ++ int id; ++ struct list_head dev_list; ++ unsigned long def_global, def_port, def_vlan; ++ ++ struct mutex sw_mutex; ++ struct switch_port *portbuf; ++ struct switch_portmap *portmap; ++ ++ char buf[128]; ++ ++#ifdef CONFIG_SWCONFIG_LEDS ++ struct switch_led_trigger *led_trigger; ++#endif ++}; ++ ++struct switch_port { ++ u32 id; ++ u32 flags; ++}; ++ ++struct switch_portmap { ++ u32 virt; ++ const char *s; ++}; ++ ++struct switch_val { ++ const struct switch_attr *attr; ++ int port_vlan; ++ int len; ++ union { ++ const char *s; ++ u32 i; ++ struct switch_port *ports; ++ } value; ++}; ++ ++struct switch_attr { ++ int disabled; ++ int type; ++ const char *name; ++ const char *description; ++ ++ int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); ++ int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); ++ ++ /* for driver internal use */ ++ int id; ++ int ofs; ++ int max; ++}; ++ ++#endif /* _LINUX_SWITCH_H */ +diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild +index 3ce25b5..b9565df 100644 +--- a/include/uapi/linux/Kbuild ++++ b/include/uapi/linux/Kbuild +@@ -365,6 +365,7 @@ header-y += stddef.h + header-y += string.h + header-y += suspend_ioctls.h + header-y += swab.h ++header-y += switch.h + header-y += synclink.h + header-y += sysctl.h + header-y += sysinfo.h +diff --git a/include/uapi/linux/switch.h b/include/uapi/linux/switch.h +new file mode 100644 +index 0000000..a59b239 +--- /dev/null ++++ b/include/uapi/linux/switch.h +@@ -0,0 +1,103 @@ ++/* ++ * switch.h: Switch configuration API ++ * ++ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++ ++#ifndef _UAPI_LINUX_SWITCH_H ++#define _UAPI_LINUX_SWITCH_H ++ ++#include <linux/types.h> ++#include <linux/netdevice.h> ++#include <linux/netlink.h> ++#include <linux/genetlink.h> ++#ifndef __KERNEL__ ++#include <netlink/netlink.h> ++#include <netlink/genl/genl.h> ++#include <netlink/genl/ctrl.h> ++#endif ++ ++/* main attributes */ ++enum { ++ SWITCH_ATTR_UNSPEC, ++ /* global */ ++ SWITCH_ATTR_TYPE, ++ /* device */ ++ SWITCH_ATTR_ID, ++ SWITCH_ATTR_DEV_NAME, ++ SWITCH_ATTR_ALIAS, ++ SWITCH_ATTR_NAME, ++ SWITCH_ATTR_VLANS, ++ SWITCH_ATTR_PORTS, ++ SWITCH_ATTR_PORTMAP, ++ SWITCH_ATTR_CPU_PORT, ++ /* attributes */ ++ SWITCH_ATTR_OP_ID, ++ SWITCH_ATTR_OP_TYPE, ++ SWITCH_ATTR_OP_NAME, ++ SWITCH_ATTR_OP_PORT, ++ SWITCH_ATTR_OP_VLAN, ++ SWITCH_ATTR_OP_VALUE_INT, ++ SWITCH_ATTR_OP_VALUE_STR, ++ SWITCH_ATTR_OP_VALUE_PORTS, ++ SWITCH_ATTR_OP_DESCRIPTION, ++ /* port lists */ ++ SWITCH_ATTR_PORT, ++ SWITCH_ATTR_MAX ++}; ++ ++enum { ++ /* port map */ ++ SWITCH_PORTMAP_PORTS, ++ SWITCH_PORTMAP_SEGMENT, ++ SWITCH_PORTMAP_VIRT, ++ SWITCH_PORTMAP_MAX ++}; ++ ++/* commands */ ++enum { ++ SWITCH_CMD_UNSPEC, ++ SWITCH_CMD_GET_SWITCH, ++ SWITCH_CMD_NEW_ATTR, ++ SWITCH_CMD_LIST_GLOBAL, ++ SWITCH_CMD_GET_GLOBAL, ++ SWITCH_CMD_SET_GLOBAL, ++ SWITCH_CMD_LIST_PORT, ++ SWITCH_CMD_GET_PORT, ++ SWITCH_CMD_SET_PORT, ++ SWITCH_CMD_LIST_VLAN, ++ SWITCH_CMD_GET_VLAN, ++ SWITCH_CMD_SET_VLAN ++}; ++ ++/* data types */ ++enum switch_val_type { ++ SWITCH_TYPE_UNSPEC, ++ SWITCH_TYPE_INT, ++ SWITCH_TYPE_STRING, ++ SWITCH_TYPE_PORTS, ++ SWITCH_TYPE_NOVAL, ++}; ++ ++/* port nested attributes */ ++enum { ++ SWITCH_PORT_UNSPEC, ++ SWITCH_PORT_ID, ++ SWITCH_PORT_FLAG_TAGGED, ++ SWITCH_PORT_ATTR_MAX ++}; ++ ++#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000 ++ ++ ++#endif /* _UAPI_LINUX_SWITCH_H */ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0014-phy-add-detach-callback-to-struct-phy_driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0014-phy-add-detach-callback-to-struct-phy_driver.patch new file mode 100644 index 000000000..43ca7ce9b --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0014-phy-add-detach-callback-to-struct-phy_driver.patch @@ -0,0 +1,46 @@ +From 76e9965a69ff97c3ca973ca54f145a96023bdeca Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Wed, 14 May 2014 03:24:00 +0200 +Subject: [PATCH] phy: add detach callback to struct phy_driver + +This is used by ar8216 driver. +--- + drivers/net/phy/phy_device.c | 4 ++++ + include/linux/phy.h | 6 ++++++ + 2 files changed, 10 insertions(+) + +diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c +index 3653754..b35ece2 100644 +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -662,6 +662,10 @@ EXPORT_SYMBOL(phy_attach); + void phy_detach(struct phy_device *phydev) + { + int i; ++ ++ if (phydev->drv && phydev->drv->detach) ++ phydev->drv->detach(phydev); ++ + phydev->attached_dev->phydev = NULL; + phydev->attached_dev = NULL; + phy_suspend(phydev); +diff --git a/include/linux/phy.h b/include/linux/phy.h +index 9ab0d79..f1441b4 100644 +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -432,6 +432,12 @@ struct phy_driver { + */ + int (*did_interrupt)(struct phy_device *phydev); + ++ /* ++ * Called before an ethernet device is detached ++ * from the PHY. ++ */ ++ void (*detach)(struct phy_device *phydev); ++ + /* Clears up any memory if needed */ + void (*remove)(struct phy_device *phydev); + +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0015-phy-add-ar8216-PHY-support.patch b/target/mips/dragino-ms14s/patches/3.14.17/0015-phy-add-ar8216-PHY-support.patch new file mode 100644 index 000000000..46b2ba467 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0015-phy-add-ar8216-PHY-support.patch @@ -0,0 +1,3671 @@ +From 6137bedf972f576765c6e5d4373a488951371609 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 00:37:30 +0200 +Subject: [PATCH] phy: add ar8216 PHY support + +--- + drivers/net/phy/Kconfig | 9 + + drivers/net/phy/Makefile | 1 + + drivers/net/phy/ar8216.c | 2978 +++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/ar8216.h | 492 +++++++ + include/linux/ar8216_platform.h | 131 ++ + 5 files changed, 3611 insertions(+) + create mode 100644 drivers/net/phy/ar8216.c + create mode 100644 drivers/net/phy/ar8216.h + create mode 100644 include/linux/ar8216_platform.h + +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 36a13fc..0414889 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -116,6 +116,15 @@ config MICREL_PHY + ---help--- + Supports the KSZ9021, VSC8201, KS8001 PHYs. + ++config AR8216_PHY ++ tristate "Driver for Atheros AR8216 switches" ++ select ETHERNET_PACKET_MANGLE ++ select SWCONFIG ++ ++config AR8216_PHY_LEDS ++ bool "Atheros AR8216 switch LED support" ++ depends on (AR8216_PHY && LEDS_CLASS) ++ + config FIXED_PHY + bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs" + depends on PHYLIB=y +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index b510bd6..3c76ff8 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -16,6 +16,7 @@ obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o + obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o + obj-$(CONFIG_ICPLUS_PHY) += icplus.o + obj-$(CONFIG_REALTEK_PHY) += realtek.o ++obj-$(CONFIG_AR8216_PHY) += ar8216.o + obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o + obj-$(CONFIG_FIXED_PHY) += fixed.o + obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o +diff --git a/drivers/net/phy/ar8216.c b/drivers/net/phy/ar8216.c +new file mode 100644 +index 0000000..3f60878 +--- /dev/null ++++ b/drivers/net/phy/ar8216.c +@@ -0,0 +1,2978 @@ ++/* ++ * ar8216.c: AR8216 switch driver ++ * ++ * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> ++ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++ ++#include <linux/if.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/list.h> ++#include <linux/if_ether.h> ++#include <linux/skbuff.h> ++#include <linux/netdevice.h> ++#include <linux/netlink.h> ++#include <linux/bitops.h> ++#include <net/genetlink.h> ++#include <linux/switch.h> ++#include <linux/delay.h> ++#include <linux/phy.h> ++#include <linux/netdevice.h> ++#include <linux/etherdevice.h> ++#include <linux/lockdep.h> ++#include <linux/ar8216_platform.h> ++#include <linux/workqueue.h> ++#include <linux/of_device.h> ++#include <linux/leds.h> ++#include <linux/gpio.h> ++ ++#include "ar8216.h" ++ ++/* size of the vlan table */ ++#define AR8X16_MAX_VLANS 128 ++#define AR8X16_PROBE_RETRIES 10 ++#define AR8X16_MAX_PORTS 8 ++ ++#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ ++ ++struct ar8xxx_priv; ++ ++#define AR8XXX_CAP_GIGE BIT(0) ++#define AR8XXX_CAP_MIB_COUNTERS BIT(1) ++ ++enum { ++ AR8XXX_VER_AR8216 = 0x01, ++ AR8XXX_VER_AR8236 = 0x03, ++ AR8XXX_VER_AR8316 = 0x10, ++ AR8XXX_VER_AR8327 = 0x12, ++ AR8XXX_VER_AR8337 = 0x13, ++}; ++ ++struct ar8xxx_mib_desc { ++ unsigned int size; ++ unsigned int offset; ++ const char *name; ++}; ++ ++struct ar8xxx_chip { ++ unsigned long caps; ++ ++ int (*hw_init)(struct ar8xxx_priv *priv); ++ void (*cleanup)(struct ar8xxx_priv *priv); ++ ++ void (*init_globals)(struct ar8xxx_priv *priv); ++ void (*init_port)(struct ar8xxx_priv *priv, int port); ++ void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 egress, ++ u32 ingress, u32 members, u32 pvid); ++ u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); ++ int (*atu_flush)(struct ar8xxx_priv *priv); ++ void (*vtu_flush)(struct ar8xxx_priv *priv); ++ void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); ++ ++ const struct ar8xxx_mib_desc *mib_decs; ++ unsigned num_mibs; ++}; ++ ++enum ar8327_led_pattern { ++ AR8327_LED_PATTERN_OFF = 0, ++ AR8327_LED_PATTERN_BLINK, ++ AR8327_LED_PATTERN_ON, ++ AR8327_LED_PATTERN_RULE, ++}; ++ ++struct ar8327_led_entry { ++ unsigned reg; ++ unsigned shift; ++}; ++ ++struct ar8327_led { ++ struct led_classdev cdev; ++ struct ar8xxx_priv *sw_priv; ++ ++ char *name; ++ bool active_low; ++ u8 led_num; ++ enum ar8327_led_mode mode; ++ ++ struct mutex mutex; ++ spinlock_t lock; ++ struct work_struct led_work; ++ bool enable_hw_mode; ++ enum ar8327_led_pattern pattern; ++}; ++ ++struct ar8327_data { ++ u32 port0_status; ++ u32 port6_status; ++ ++ struct ar8327_led **leds; ++ unsigned int num_leds; ++}; ++ ++struct ar8xxx_priv { ++ struct switch_dev dev; ++ struct mii_bus *mii_bus; ++ struct phy_device *phy; ++ ++ u32 (*read)(struct ar8xxx_priv *priv, int reg); ++ void (*write)(struct ar8xxx_priv *priv, int reg, u32 val); ++ u32 (*rmw)(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); ++ ++ int (*get_port_link)(unsigned port); ++ ++ const struct net_device_ops *ndo_old; ++ struct net_device_ops ndo; ++ struct mutex reg_mutex; ++ u8 chip_ver; ++ u8 chip_rev; ++ const struct ar8xxx_chip *chip; ++ union { ++ struct ar8327_data ar8327; ++ } chip_data; ++ bool initialized; ++ bool port4_phy; ++ char buf[2048]; ++ ++ bool init; ++ bool mii_lo_first; ++ ++ struct mutex mib_lock; ++ struct delayed_work mib_work; ++ int mib_next_port; ++ u64 *mib_stats; ++ ++ struct list_head list; ++ unsigned int use_count; ++ ++ /* all fields below are cleared on reset */ ++ bool vlan; ++ u16 vlan_id[AR8X16_MAX_VLANS]; ++ u8 vlan_table[AR8X16_MAX_VLANS]; ++ u8 vlan_tagged; ++ u16 pvid[AR8X16_MAX_PORTS]; ++ ++ /* mirroring */ ++ bool mirror_rx; ++ bool mirror_tx; ++ int source_port; ++ int monitor_port; ++}; ++ ++#define MIB_DESC(_s , _o, _n) \ ++ { \ ++ .size = (_s), \ ++ .offset = (_o), \ ++ .name = (_n), \ ++ } ++ ++static const struct ar8xxx_mib_desc ar8216_mibs[] = { ++ MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), ++ MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), ++ MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), ++ MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), ++ MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), ++ MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), ++ MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), ++ MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), ++ MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), ++ MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), ++ MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), ++ MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), ++ MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), ++ MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), ++ MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), ++ MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), ++ MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), ++ MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), ++ MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), ++ MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), ++ MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), ++ MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), ++ MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), ++ MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), ++ MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), ++ MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), ++ MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), ++ MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), ++ MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), ++ MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), ++ MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), ++ MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), ++ MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), ++ MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), ++ MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), ++ MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), ++ MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), ++}; ++ ++static const struct ar8xxx_mib_desc ar8236_mibs[] = { ++ MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), ++ MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), ++ MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), ++ MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), ++ MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), ++ MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), ++ MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), ++ MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), ++ MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), ++ MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), ++ MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), ++ MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), ++ MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), ++ MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), ++ MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), ++ MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), ++ MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), ++ MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), ++ MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), ++ MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), ++ MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), ++ MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), ++ MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), ++ MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), ++ MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), ++ MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), ++ MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), ++ MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), ++ MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), ++ MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), ++ MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), ++ MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), ++ MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), ++ MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), ++ MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), ++ MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), ++ MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), ++ MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), ++ MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), ++}; ++ ++static DEFINE_MUTEX(ar8xxx_dev_list_lock); ++static LIST_HEAD(ar8xxx_dev_list); ++ ++static inline struct ar8xxx_priv * ++swdev_to_ar8xxx(struct switch_dev *swdev) ++{ ++ return container_of(swdev, struct ar8xxx_priv, dev); ++} ++ ++static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) ++{ ++ return priv->chip->caps & AR8XXX_CAP_GIGE; ++} ++ ++static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) ++{ ++ return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; ++} ++ ++static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) ++{ ++ return priv->chip_ver == AR8XXX_VER_AR8216; ++} ++ ++static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) ++{ ++ return priv->chip_ver == AR8XXX_VER_AR8236; ++} ++ ++static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) ++{ ++ return priv->chip_ver == AR8XXX_VER_AR8316; ++} ++ ++static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) ++{ ++ return priv->chip_ver == AR8XXX_VER_AR8327; ++} ++ ++static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) ++{ ++ return priv->chip_ver == AR8XXX_VER_AR8337; ++} ++ ++static inline void ++split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) ++{ ++ regaddr >>= 1; ++ *r1 = regaddr & 0x1e; ++ ++ regaddr >>= 5; ++ *r2 = regaddr & 0x7; ++ ++ regaddr >>= 3; ++ *page = regaddr & 0x1ff; ++} ++ ++static u32 ++ar8xxx_mii_read(struct ar8xxx_priv *priv, int reg) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ u16 r1, r2, page; ++ u16 lo, hi; ++ ++ split_addr((u32) reg, &r1, &r2, &page); ++ ++ mutex_lock(&bus->mdio_lock); ++ ++ bus->write(bus, 0x18, 0, page); ++ usleep_range(1000, 2000); /* wait for the page switch to propagate */ ++ lo = bus->read(bus, 0x10 | r2, r1); ++ hi = bus->read(bus, 0x10 | r2, r1 + 1); ++ ++ mutex_unlock(&bus->mdio_lock); ++ ++ return (hi << 16) | lo; ++} ++ ++static void ++ar8xxx_mii_write(struct ar8xxx_priv *priv, int reg, u32 val) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ u16 r1, r2, r3; ++ u16 lo, hi; ++ ++ split_addr((u32) reg, &r1, &r2, &r3); ++ lo = val & 0xffff; ++ hi = (u16) (val >> 16); ++ ++ mutex_lock(&bus->mdio_lock); ++ ++ bus->write(bus, 0x18, 0, r3); ++ usleep_range(1000, 2000); /* wait for the page switch to propagate */ ++ if (priv->mii_lo_first) { ++ bus->write(bus, 0x10 | r2, r1, lo); ++ bus->write(bus, 0x10 | r2, r1 + 1, hi); ++ } else { ++ bus->write(bus, 0x10 | r2, r1 + 1, hi); ++ bus->write(bus, 0x10 | r2, r1, lo); ++ } ++ ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static u32 ++ar8xxx_mii_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ u16 r1, r2, page; ++ u16 lo, hi; ++ u32 ret; ++ ++ split_addr((u32) reg, &r1, &r2, &page); ++ ++ mutex_lock(&bus->mdio_lock); ++ ++ bus->write(bus, 0x18, 0, page); ++ usleep_range(1000, 2000); /* wait for the page switch to propagate */ ++ ++ lo = bus->read(bus, 0x10 | r2, r1); ++ hi = bus->read(bus, 0x10 | r2, r1 + 1); ++ ++ ret = hi << 16 | lo; ++ ret &= ~mask; ++ ret |= val; ++ ++ lo = ret & 0xffff; ++ hi = (u16) (ret >> 16); ++ ++ if (priv->mii_lo_first) { ++ bus->write(bus, 0x10 | r2, r1, lo); ++ bus->write(bus, 0x10 | r2, r1 + 1, hi); ++ } else { ++ bus->write(bus, 0x10 | r2, r1 + 1, hi); ++ bus->write(bus, 0x10 | r2, r1, lo); ++ } ++ ++ mutex_unlock(&bus->mdio_lock); ++ ++ return ret; ++} ++ ++ ++static void ++ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, ++ u16 dbg_addr, u16 dbg_data) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); ++ bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static void ++ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 data) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); ++ bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static inline u32 ++ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) ++{ ++ return priv->rmw(priv, reg, mask, val); ++} ++ ++static inline void ++ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) ++{ ++ priv->rmw(priv, reg, 0, val); ++} ++ ++static int ++ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, ++ unsigned timeout) ++{ ++ int i; ++ ++ for (i = 0; i < timeout; i++) { ++ u32 t; ++ ++ t = priv->read(priv, reg); ++ if ((t & mask) == val) ++ return 0; ++ ++ usleep_range(1000, 2000); ++ } ++ ++ return -ETIMEDOUT; ++} ++ ++static int ++ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) ++{ ++ unsigned mib_func; ++ int ret; ++ ++ lockdep_assert_held(&priv->mib_lock); ++ ++ if (chip_is_ar8327(priv) || chip_is_ar8337(priv)) ++ mib_func = AR8327_REG_MIB_FUNC; ++ else ++ mib_func = AR8216_REG_MIB_FUNC; ++ ++ /* Capture the hardware statistics for all ports */ ++ ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); ++ ++ /* Wait for the capturing to complete. */ ++ ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); ++ if (ret) ++ goto out; ++ ++ ret = 0; ++ ++out: ++ return ret; ++} ++ ++static int ++ar8xxx_mib_capture(struct ar8xxx_priv *priv) ++{ ++ return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); ++} ++ ++static int ++ar8xxx_mib_flush(struct ar8xxx_priv *priv) ++{ ++ return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); ++} ++ ++static void ++ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) ++{ ++ unsigned int base; ++ u64 *mib_stats; ++ int i; ++ ++ WARN_ON(port >= priv->dev.ports); ++ ++ lockdep_assert_held(&priv->mib_lock); ++ ++ if (chip_is_ar8327(priv) || chip_is_ar8337(priv)) ++ base = AR8327_REG_PORT_STATS_BASE(port); ++ else if (chip_is_ar8236(priv) || ++ chip_is_ar8316(priv)) ++ base = AR8236_REG_PORT_STATS_BASE(port); ++ else ++ base = AR8216_REG_PORT_STATS_BASE(port); ++ ++ mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; ++ for (i = 0; i < priv->chip->num_mibs; i++) { ++ const struct ar8xxx_mib_desc *mib; ++ u64 t; ++ ++ mib = &priv->chip->mib_decs[i]; ++ t = priv->read(priv, base + mib->offset); ++ if (mib->size == 2) { ++ u64 hi; ++ ++ hi = priv->read(priv, base + mib->offset + 4); ++ t |= hi << 32; ++ } ++ ++ if (flush) ++ mib_stats[i] = 0; ++ else ++ mib_stats[i] += t; ++ } ++} ++ ++static void ++ar8216_read_port_link(struct ar8xxx_priv *priv, int port, ++ struct switch_port_link *link) ++{ ++ u32 status; ++ u32 speed; ++ ++ memset(link, '\0', sizeof(*link)); ++ ++ status = priv->chip->read_port_status(priv, port); ++ ++ link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); ++ if (link->aneg) { ++ link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); ++ } else { ++ link->link = true; ++ ++ if (priv->get_port_link) { ++ int err; ++ ++ err = priv->get_port_link(port); ++ if (err >= 0) ++ link->link = !!err; ++ } ++ } ++ ++ if (!link->link) ++ return; ++ ++ link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); ++ link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); ++ link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); ++ ++ speed = (status & AR8216_PORT_STATUS_SPEED) >> ++ AR8216_PORT_STATUS_SPEED_S; ++ ++ switch (speed) { ++ case AR8216_PORT_SPEED_10M: ++ link->speed = SWITCH_PORT_SPEED_10; ++ break; ++ case AR8216_PORT_SPEED_100M: ++ link->speed = SWITCH_PORT_SPEED_100; ++ break; ++ case AR8216_PORT_SPEED_1000M: ++ link->speed = SWITCH_PORT_SPEED_1000; ++ break; ++ default: ++ link->speed = SWITCH_PORT_SPEED_UNKNOWN; ++ break; ++ } ++} ++ ++static struct sk_buff * ++ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) ++{ ++ struct ar8xxx_priv *priv = dev->phy_ptr; ++ unsigned char *buf; ++ ++ if (unlikely(!priv)) ++ goto error; ++ ++ if (!priv->vlan) ++ goto send; ++ ++ if (unlikely(skb_headroom(skb) < 2)) { ++ if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) ++ goto error; ++ } ++ ++ buf = skb_push(skb, 2); ++ buf[0] = 0x10; ++ buf[1] = 0x80; ++ ++send: ++ return skb; ++ ++error: ++ dev_kfree_skb_any(skb); ++ return NULL; ++} ++ ++static void ++ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) ++{ ++ struct ar8xxx_priv *priv; ++ unsigned char *buf; ++ int port, vlan; ++ ++ priv = dev->phy_ptr; ++ if (!priv) ++ return; ++ ++ /* don't strip the header if vlan mode is disabled */ ++ if (!priv->vlan) ++ return; ++ ++ /* strip header, get vlan id */ ++ buf = skb->data; ++ skb_pull(skb, 2); ++ ++ /* check for vlan header presence */ ++ if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) ++ return; ++ ++ port = buf[0] & 0xf; ++ ++ /* no need to fix up packets coming from a tagged source */ ++ if (priv->vlan_tagged & (1 << port)) ++ return; ++ ++ /* lookup port vid from local table, the switch passes an invalid vlan id */ ++ vlan = priv->vlan_id[priv->pvid[port]]; ++ ++ buf[14 + 2] &= 0xf0; ++ buf[14 + 2] |= vlan >> 8; ++ buf[15 + 2] = vlan & 0xff; ++} ++ ++static int ++ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) ++{ ++ int timeout = 20; ++ u32 t = 0; ++ ++ while (1) { ++ t = priv->read(priv, reg); ++ if ((t & mask) == val) ++ return 0; ++ ++ if (timeout-- <= 0) ++ break; ++ ++ udelay(10); ++ } ++ ++ pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", ++ (unsigned int) reg, t, mask, val); ++ return -ETIMEDOUT; ++} ++ ++static void ++ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) ++{ ++ if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) ++ return; ++ if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { ++ val &= AR8216_VTUDATA_MEMBER; ++ val |= AR8216_VTUDATA_VALID; ++ priv->write(priv, AR8216_REG_VTU_DATA, val); ++ } ++ op |= AR8216_VTU_ACTIVE; ++ priv->write(priv, AR8216_REG_VTU, op); ++} ++ ++static void ++ar8216_vtu_flush(struct ar8xxx_priv *priv) ++{ ++ ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); ++} ++ ++static void ++ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) ++{ ++ u32 op; ++ ++ op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); ++ ar8216_vtu_op(priv, op, port_mask); ++} ++ ++static int ++ar8216_atu_flush(struct ar8xxx_priv *priv) ++{ ++ int ret; ++ ++ ret = ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0); ++ if (!ret) ++ priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH); ++ ++ return ret; ++} ++ ++static u32 ++ar8216_read_port_status(struct ar8xxx_priv *priv, int port) ++{ ++ return priv->read(priv, AR8216_REG_PORT_STATUS(port)); ++} ++ ++static void ++ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 egress, u32 ingress, ++ u32 members, u32 pvid) ++{ ++ u32 header; ++ ++ if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) ++ header = AR8216_PORT_CTRL_HEADER; ++ else ++ header = 0; ++ ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), ++ AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | ++ AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | ++ AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, ++ AR8216_PORT_CTRL_LEARN | header | ++ (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | ++ (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); ++ ++ ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), ++ AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | ++ AR8216_PORT_VLAN_DEFAULT_ID, ++ (members << AR8216_PORT_VLAN_DEST_PORTS_S) | ++ (ingress << AR8216_PORT_VLAN_MODE_S) | ++ (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); ++} ++ ++static int ++ar8216_hw_init(struct ar8xxx_priv *priv) ++{ ++ return 0; ++} ++ ++static void ++ar8216_init_globals(struct ar8xxx_priv *priv) ++{ ++ /* standard atheros magic */ ++ priv->write(priv, 0x38, 0xc000050e); ++ ++ ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, ++ AR8216_GCTRL_MTU, 1518 + 8 + 2); ++} ++ ++static void ++ar8216_init_port(struct ar8xxx_priv *priv, int port) ++{ ++ /* Enable port learning and tx */ ++ priv->write(priv, AR8216_REG_PORT_CTRL(port), ++ AR8216_PORT_CTRL_LEARN | ++ (4 << AR8216_PORT_CTRL_STATE_S)); ++ ++ priv->write(priv, AR8216_REG_PORT_VLAN(port), 0); ++ ++ if (port == AR8216_PORT_CPU) { ++ priv->write(priv, AR8216_REG_PORT_STATUS(port), ++ AR8216_PORT_STATUS_LINK_UP | ++ (ar8xxx_has_gige(priv) ? ++ AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | ++ AR8216_PORT_STATUS_TXMAC | ++ AR8216_PORT_STATUS_RXMAC | ++ (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | ++ (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | ++ AR8216_PORT_STATUS_DUPLEX); ++ } else { ++ priv->write(priv, AR8216_REG_PORT_STATUS(port), ++ AR8216_PORT_STATUS_LINK_AUTO); ++ } ++} ++ ++static const struct ar8xxx_chip ar8216_chip = { ++ .caps = AR8XXX_CAP_MIB_COUNTERS, ++ ++ .hw_init = ar8216_hw_init, ++ .init_globals = ar8216_init_globals, ++ .init_port = ar8216_init_port, ++ .setup_port = ar8216_setup_port, ++ .read_port_status = ar8216_read_port_status, ++ .atu_flush = ar8216_atu_flush, ++ .vtu_flush = ar8216_vtu_flush, ++ .vtu_load_vlan = ar8216_vtu_load_vlan, ++ ++ .num_mibs = ARRAY_SIZE(ar8216_mibs), ++ .mib_decs = ar8216_mibs, ++}; ++ ++static void ++ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 egress, u32 ingress, ++ u32 members, u32 pvid) ++{ ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), ++ AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | ++ AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | ++ AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, ++ AR8216_PORT_CTRL_LEARN | ++ (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | ++ (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); ++ ++ ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), ++ AR8236_PORT_VLAN_DEFAULT_ID, ++ (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); ++ ++ ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), ++ AR8236_PORT_VLAN2_VLAN_MODE | ++ AR8236_PORT_VLAN2_MEMBER, ++ (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | ++ (members << AR8236_PORT_VLAN2_MEMBER_S)); ++} ++ ++static int ++ar8236_hw_init(struct ar8xxx_priv *priv) ++{ ++ int i; ++ struct mii_bus *bus; ++ ++ if (priv->initialized) ++ return 0; ++ ++ /* Initialize the PHYs */ ++ bus = priv->mii_bus; ++ for (i = 0; i < 5; i++) { ++ mdiobus_write(bus, i, MII_ADVERTISE, ++ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ++ ADVERTISE_PAUSE_ASYM); ++ mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); ++ } ++ msleep(1000); ++ ++ priv->initialized = true; ++ return 0; ++} ++ ++static void ++ar8236_init_globals(struct ar8xxx_priv *priv) ++{ ++ /* enable jumbo frames */ ++ ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, ++ AR8316_GCTRL_MTU, 9018 + 8 + 2); ++ ++ /* Enable MIB counters */ ++ ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, ++ (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | ++ AR8236_MIB_EN); ++} ++ ++static const struct ar8xxx_chip ar8236_chip = { ++ .caps = AR8XXX_CAP_MIB_COUNTERS, ++ .hw_init = ar8236_hw_init, ++ .init_globals = ar8236_init_globals, ++ .init_port = ar8216_init_port, ++ .setup_port = ar8236_setup_port, ++ .read_port_status = ar8216_read_port_status, ++ .atu_flush = ar8216_atu_flush, ++ .vtu_flush = ar8216_vtu_flush, ++ .vtu_load_vlan = ar8216_vtu_load_vlan, ++ ++ .num_mibs = ARRAY_SIZE(ar8236_mibs), ++ .mib_decs = ar8236_mibs, ++}; ++ ++static int ++ar8316_hw_init(struct ar8xxx_priv *priv) ++{ ++ int i; ++ u32 val, newval; ++ struct mii_bus *bus; ++ ++ val = priv->read(priv, AR8316_REG_POSTRIP); ++ ++ if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { ++ if (priv->port4_phy) { ++ /* value taken from Ubiquiti RouterStation Pro */ ++ newval = 0x81461bea; ++ pr_info("ar8316: Using port 4 as PHY\n"); ++ } else { ++ newval = 0x01261be2; ++ pr_info("ar8316: Using port 4 as switch port\n"); ++ } ++ } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { ++ /* value taken from AVM Fritz!Box 7390 sources */ ++ newval = 0x010e5b71; ++ } else { ++ /* no known value for phy interface */ ++ pr_err("ar8316: unsupported mii mode: %d.\n", ++ priv->phy->interface); ++ return -EINVAL; ++ } ++ ++ if (val == newval) ++ goto out; ++ ++ priv->write(priv, AR8316_REG_POSTRIP, newval); ++ ++ if (priv->port4_phy && ++ priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { ++ /* work around for phy4 rgmii mode */ ++ ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); ++ /* rx delay */ ++ ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); ++ /* tx delay */ ++ ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); ++ msleep(1000); ++ } ++ ++ /* Initialize the ports */ ++ bus = priv->mii_bus; ++ for (i = 0; i < 5; i++) { ++ /* initialize the port itself */ ++ mdiobus_write(bus, i, MII_ADVERTISE, ++ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); ++ mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); ++ mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); ++ } ++ ++ msleep(1000); ++ ++out: ++ priv->initialized = true; ++ return 0; ++} ++ ++static void ++ar8316_init_globals(struct ar8xxx_priv *priv) ++{ ++ /* standard atheros magic */ ++ priv->write(priv, 0x38, 0xc000050e); ++ ++ /* enable cpu port to receive multicast and broadcast frames */ ++ priv->write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); ++ ++ /* enable jumbo frames */ ++ ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, ++ AR8316_GCTRL_MTU, 9018 + 8 + 2); ++ ++ /* Enable MIB counters */ ++ ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, ++ (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | ++ AR8236_MIB_EN); ++} ++ ++static const struct ar8xxx_chip ar8316_chip = { ++ .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, ++ .hw_init = ar8316_hw_init, ++ .init_globals = ar8316_init_globals, ++ .init_port = ar8216_init_port, ++ .setup_port = ar8216_setup_port, ++ .read_port_status = ar8216_read_port_status, ++ .atu_flush = ar8216_atu_flush, ++ .vtu_flush = ar8216_vtu_flush, ++ .vtu_load_vlan = ar8216_vtu_load_vlan, ++ ++ .num_mibs = ARRAY_SIZE(ar8236_mibs), ++ .mib_decs = ar8236_mibs, ++}; ++ ++static u32 ++ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) ++{ ++ u32 t; ++ ++ if (!cfg) ++ return 0; ++ ++ t = 0; ++ switch (cfg->mode) { ++ case AR8327_PAD_NC: ++ break; ++ ++ case AR8327_PAD_MAC2MAC_MII: ++ t = AR8327_PAD_MAC_MII_EN; ++ if (cfg->rxclk_sel) ++ t |= AR8327_PAD_MAC_MII_RXCLK_SEL; ++ if (cfg->txclk_sel) ++ t |= AR8327_PAD_MAC_MII_TXCLK_SEL; ++ break; ++ ++ case AR8327_PAD_MAC2MAC_GMII: ++ t = AR8327_PAD_MAC_GMII_EN; ++ if (cfg->rxclk_sel) ++ t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; ++ if (cfg->txclk_sel) ++ t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; ++ break; ++ ++ case AR8327_PAD_MAC_SGMII: ++ t = AR8327_PAD_SGMII_EN; ++ ++ /* ++ * WAR for the QUalcomm Atheros AP136 board. ++ * It seems that RGMII TX/RX delay settings needs to be ++ * applied for SGMII mode as well, The ethernet is not ++ * reliable without this. ++ */ ++ t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; ++ t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; ++ if (cfg->rxclk_delay_en) ++ t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; ++ if (cfg->txclk_delay_en) ++ t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; ++ ++ if (cfg->sgmii_delay_en) ++ t |= AR8327_PAD_SGMII_DELAY_EN; ++ ++ break; ++ ++ case AR8327_PAD_MAC2PHY_MII: ++ t = AR8327_PAD_PHY_MII_EN; ++ if (cfg->rxclk_sel) ++ t |= AR8327_PAD_PHY_MII_RXCLK_SEL; ++ if (cfg->txclk_sel) ++ t |= AR8327_PAD_PHY_MII_TXCLK_SEL; ++ break; ++ ++ case AR8327_PAD_MAC2PHY_GMII: ++ t = AR8327_PAD_PHY_GMII_EN; ++ if (cfg->pipe_rxclk_sel) ++ t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; ++ if (cfg->rxclk_sel) ++ t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; ++ if (cfg->txclk_sel) ++ t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; ++ break; ++ ++ case AR8327_PAD_MAC_RGMII: ++ t = AR8327_PAD_RGMII_EN; ++ t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; ++ t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; ++ if (cfg->rxclk_delay_en) ++ t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; ++ if (cfg->txclk_delay_en) ++ t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; ++ break; ++ ++ case AR8327_PAD_PHY_GMII: ++ t = AR8327_PAD_PHYX_GMII_EN; ++ break; ++ ++ case AR8327_PAD_PHY_RGMII: ++ t = AR8327_PAD_PHYX_RGMII_EN; ++ break; ++ ++ case AR8327_PAD_PHY_MII: ++ t = AR8327_PAD_PHYX_MII_EN; ++ break; ++ } ++ ++ return t; ++} ++ ++static void ++ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) ++{ ++ switch (priv->chip_rev) { ++ case 1: ++ /* For 100M waveform */ ++ ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); ++ /* Turn on Gigabit clock */ ++ ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); ++ break; ++ ++ case 2: ++ ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c); ++ ar8xxx_phy_mmd_write(priv, phy, 0x4007, 0x0); ++ /* fallthrough */ ++ case 4: ++ ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d); ++ ar8xxx_phy_mmd_write(priv, phy, 0x4003, 0x803f); ++ ++ ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); ++ ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); ++ ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); ++ break; ++ } ++} ++ ++static u32 ++ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) ++{ ++ u32 t; ++ ++ if (!cfg->force_link) ++ return AR8216_PORT_STATUS_LINK_AUTO; ++ ++ t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; ++ t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; ++ t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; ++ t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; ++ ++ switch (cfg->speed) { ++ case AR8327_PORT_SPEED_10: ++ t |= AR8216_PORT_SPEED_10M; ++ break; ++ case AR8327_PORT_SPEED_100: ++ t |= AR8216_PORT_SPEED_100M; ++ break; ++ case AR8327_PORT_SPEED_1000: ++ t |= AR8216_PORT_SPEED_1000M; ++ break; ++ } ++ ++ return t; ++} ++ ++#define AR8327_LED_ENTRY(_num, _reg, _shift) \ ++ [_num] = { .reg = (_reg), .shift = (_shift) } ++ ++static const struct ar8327_led_entry ++ar8327_led_map[AR8327_NUM_LEDS] = { ++ AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), ++ AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), ++ AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), ++ ++ AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), ++ AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), ++ AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), ++ ++ AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), ++ AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), ++ AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), ++ ++ AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), ++ AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), ++ AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), ++ ++ AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), ++ AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), ++ AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), ++}; ++ ++static void ++ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, ++ enum ar8327_led_pattern pattern) ++{ ++ const struct ar8327_led_entry *entry; ++ ++ entry = &ar8327_led_map[led_num]; ++ ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), ++ (3 << entry->shift), pattern << entry->shift); ++} ++ ++static void ++ar8327_led_work_func(struct work_struct *work) ++{ ++ struct ar8327_led *aled; ++ u8 pattern; ++ ++ aled = container_of(work, struct ar8327_led, led_work); ++ ++ spin_lock(&aled->lock); ++ pattern = aled->pattern; ++ spin_unlock(&aled->lock); ++ ++ ar8327_set_led_pattern(aled->sw_priv, aled->led_num, ++ pattern); ++} ++ ++static void ++ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) ++{ ++ if (aled->pattern == pattern) ++ return; ++ ++ aled->pattern = pattern; ++ schedule_work(&aled->led_work); ++} ++ ++static inline struct ar8327_led * ++led_cdev_to_ar8327_led(struct led_classdev *led_cdev) ++{ ++ return container_of(led_cdev, struct ar8327_led, cdev); ++} ++ ++static int ++ar8327_led_blink_set(struct led_classdev *led_cdev, ++ unsigned long *delay_on, ++ unsigned long *delay_off) ++{ ++ struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); ++ ++ if (*delay_on == 0 && *delay_off == 0) { ++ *delay_on = 125; ++ *delay_off = 125; ++ } ++ ++ if (*delay_on != 125 || *delay_off != 125) { ++ /* ++ * The hardware only supports blinking at 4Hz. Fall back ++ * to software implementation in other cases. ++ */ ++ return -EINVAL; ++ } ++ ++ spin_lock(&aled->lock); ++ ++ aled->enable_hw_mode = false; ++ ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); ++ ++ spin_unlock(&aled->lock); ++ ++ return 0; ++} ++ ++static void ++ar8327_led_set_brightness(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); ++ u8 pattern; ++ bool active; ++ ++ active = (brightness != LED_OFF); ++ active ^= aled->active_low; ++ ++ pattern = (active) ? AR8327_LED_PATTERN_ON : ++ AR8327_LED_PATTERN_OFF; ++ ++ spin_lock(&aled->lock); ++ ++ aled->enable_hw_mode = false; ++ ar8327_led_schedule_change(aled, pattern); ++ ++ spin_unlock(&aled->lock); ++} ++ ++static ssize_t ++ar8327_led_enable_hw_mode_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); ++ ssize_t ret = 0; ++ ++ spin_lock(&aled->lock); ++ ret += sprintf(buf, "%d\n", aled->enable_hw_mode); ++ spin_unlock(&aled->lock); ++ ++ return ret; ++} ++ ++static ssize_t ++ar8327_led_enable_hw_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t size) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); ++ u8 pattern; ++ u8 value; ++ int ret; ++ ++ ret = kstrtou8(buf, 10, &value); ++ if (ret < 0) ++ return -EINVAL; ++ ++ spin_lock(&aled->lock); ++ ++ aled->enable_hw_mode = !!value; ++ if (aled->enable_hw_mode) ++ pattern = AR8327_LED_PATTERN_RULE; ++ else ++ pattern = AR8327_LED_PATTERN_OFF; ++ ++ ar8327_led_schedule_change(aled, pattern); ++ ++ spin_unlock(&aled->lock); ++ ++ return size; ++} ++ ++static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, ++ ar8327_led_enable_hw_mode_show, ++ ar8327_led_enable_hw_mode_store); ++ ++static int ++ar8327_led_register(struct ar8xxx_priv *priv, struct ar8327_led *aled) ++{ ++ int ret; ++ ++ ret = led_classdev_register(NULL, &aled->cdev); ++ if (ret < 0) ++ return ret; ++ ++ if (aled->mode == AR8327_LED_MODE_HW) { ++ ret = device_create_file(aled->cdev.dev, ++ &dev_attr_enable_hw_mode); ++ if (ret) ++ goto err_unregister; ++ } ++ ++ return 0; ++ ++err_unregister: ++ led_classdev_unregister(&aled->cdev); ++ return ret; ++} ++ ++static void ++ar8327_led_unregister(struct ar8327_led *aled) ++{ ++ if (aled->mode == AR8327_LED_MODE_HW) ++ device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); ++ ++ led_classdev_unregister(&aled->cdev); ++ cancel_work_sync(&aled->led_work); ++} ++ ++static int ++ar8327_led_create(struct ar8xxx_priv *priv, ++ const struct ar8327_led_info *led_info) ++{ ++ struct ar8327_data *data = &priv->chip_data.ar8327; ++ struct ar8327_led *aled; ++ int ret; ++ ++ if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) ++ return 0; ++ ++ if (!led_info->name) ++ return -EINVAL; ++ ++ if (led_info->led_num >= AR8327_NUM_LEDS) ++ return -EINVAL; ++ ++ aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, ++ GFP_KERNEL); ++ if (!aled) ++ return -ENOMEM; ++ ++ aled->sw_priv = priv; ++ aled->led_num = led_info->led_num; ++ aled->active_low = led_info->active_low; ++ aled->mode = led_info->mode; ++ ++ if (aled->mode == AR8327_LED_MODE_HW) ++ aled->enable_hw_mode = true; ++ ++ aled->name = (char *)(aled + 1); ++ strcpy(aled->name, led_info->name); ++ ++ aled->cdev.name = aled->name; ++ aled->cdev.brightness_set = ar8327_led_set_brightness; ++ aled->cdev.blink_set = ar8327_led_blink_set; ++ aled->cdev.default_trigger = led_info->default_trigger; ++ ++ spin_lock_init(&aled->lock); ++ mutex_init(&aled->mutex); ++ INIT_WORK(&aled->led_work, ar8327_led_work_func); ++ ++ ret = ar8327_led_register(priv, aled); ++ if (ret) ++ goto err_free; ++ ++ data->leds[data->num_leds++] = aled; ++ ++ return 0; ++ ++err_free: ++ kfree(aled); ++ return ret; ++} ++ ++static void ++ar8327_led_destroy(struct ar8327_led *aled) ++{ ++ ar8327_led_unregister(aled); ++ kfree(aled); ++} ++ ++static void ++ar8327_leds_init(struct ar8xxx_priv *priv) ++{ ++ struct ar8327_data *data; ++ unsigned i; ++ ++ if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) ++ return; ++ ++ data = &priv->chip_data.ar8327; ++ ++ for (i = 0; i < data->num_leds; i++) { ++ struct ar8327_led *aled; ++ ++ aled = data->leds[i]; ++ ++ if (aled->enable_hw_mode) ++ aled->pattern = AR8327_LED_PATTERN_RULE; ++ else ++ aled->pattern = AR8327_LED_PATTERN_OFF; ++ ++ ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); ++ } ++} ++ ++static void ++ar8327_leds_cleanup(struct ar8xxx_priv *priv) ++{ ++ struct ar8327_data *data = &priv->chip_data.ar8327; ++ unsigned i; ++ ++ if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) ++ return; ++ ++ for (i = 0; i < data->num_leds; i++) { ++ struct ar8327_led *aled; ++ ++ aled = data->leds[i]; ++ ar8327_led_destroy(aled); ++ } ++ ++ kfree(data->leds); ++} ++ ++static int ++ar8327_hw_config_pdata(struct ar8xxx_priv *priv, ++ struct ar8327_platform_data *pdata) ++{ ++ struct ar8327_led_cfg *led_cfg; ++ struct ar8327_data *data; ++ u32 pos, new_pos; ++ u32 t; ++ ++ if (!pdata) ++ return -EINVAL; ++ ++ priv->get_port_link = pdata->get_port_link; ++ ++ data = &priv->chip_data.ar8327; ++ ++ data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); ++ data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); ++ ++ t = ar8327_get_pad_cfg(pdata->pad0_cfg); ++ if (chip_is_ar8337(priv)) ++ t |= AR8337_PAD_MAC06_EXCHANGE_EN; ++ ++ priv->write(priv, AR8327_REG_PAD0_MODE, t); ++ t = ar8327_get_pad_cfg(pdata->pad5_cfg); ++ priv->write(priv, AR8327_REG_PAD5_MODE, t); ++ t = ar8327_get_pad_cfg(pdata->pad6_cfg); ++ priv->write(priv, AR8327_REG_PAD6_MODE, t); ++ ++ pos = priv->read(priv, AR8327_REG_POWER_ON_STRIP); ++ new_pos = pos; ++ ++ led_cfg = pdata->led_cfg; ++ if (led_cfg) { ++ if (led_cfg->open_drain) ++ new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; ++ else ++ new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; ++ ++ priv->write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); ++ priv->write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); ++ priv->write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); ++ priv->write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); ++ ++ if (new_pos != pos) ++ new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; ++ } ++ ++ if (pdata->sgmii_cfg) { ++ t = pdata->sgmii_cfg->sgmii_ctrl; ++ if (priv->chip_rev == 1) ++ t |= AR8327_SGMII_CTRL_EN_PLL | ++ AR8327_SGMII_CTRL_EN_RX | ++ AR8327_SGMII_CTRL_EN_TX; ++ else ++ t &= ~(AR8327_SGMII_CTRL_EN_PLL | ++ AR8327_SGMII_CTRL_EN_RX | ++ AR8327_SGMII_CTRL_EN_TX); ++ ++ priv->write(priv, AR8327_REG_SGMII_CTRL, t); ++ ++ if (pdata->sgmii_cfg->serdes_aen) ++ new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; ++ else ++ new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; ++ } ++ ++ priv->write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); ++ ++ if (pdata->leds && pdata->num_leds) { ++ int i; ++ ++ data->leds = kzalloc(pdata->num_leds * sizeof(void *), ++ GFP_KERNEL); ++ if (!data->leds) ++ return -ENOMEM; ++ ++ for (i = 0; i < pdata->num_leds; i++) ++ ar8327_led_create(priv, &pdata->leds[i]); ++ } ++ ++ return 0; ++} ++ ++#ifdef CONFIG_OF ++static int ++ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) ++{ ++ const __be32 *paddr; ++ int len; ++ int i; ++ ++ paddr = of_get_property(np, "qca,ar8327-initvals", &len); ++ if (!paddr || len < (2 * sizeof(*paddr))) ++ return -EINVAL; ++ ++ len /= sizeof(*paddr); ++ ++ for (i = 0; i < len - 1; i += 2) { ++ u32 reg; ++ u32 val; ++ ++ reg = be32_to_cpup(paddr + i); ++ val = be32_to_cpup(paddr + i + 1); ++ ++ switch (reg) { ++ case AR8327_REG_PORT_STATUS(0): ++ priv->chip_data.ar8327.port0_status = val; ++ break; ++ case AR8327_REG_PORT_STATUS(6): ++ priv->chip_data.ar8327.port6_status = val; ++ break; ++ default: ++ priv->write(priv, reg, val); ++ break; ++ } ++ } ++ ++ return 0; ++} ++#else ++static inline int ++ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) ++{ ++ return -EINVAL; ++} ++#endif ++ ++static int ++ar8327_hw_init(struct ar8xxx_priv *priv) ++{ ++ struct mii_bus *bus; ++ int ret; ++ int i; ++ ++ if (priv->phy->dev.of_node) ++ ret = ar8327_hw_config_of(priv, priv->phy->dev.of_node); ++ else ++ ret = ar8327_hw_config_pdata(priv, ++ priv->phy->dev.platform_data); ++ ++ if (ret) ++ return ret; ++ ++ ar8327_leds_init(priv); ++ ++ bus = priv->mii_bus; ++ for (i = 0; i < AR8327_NUM_PHYS; i++) { ++ ar8327_phy_fixup(priv, i); ++ ++ /* start aneg on the PHY */ ++ mdiobus_write(bus, i, MII_ADVERTISE, ADVERTISE_ALL | ++ ADVERTISE_PAUSE_CAP | ++ ADVERTISE_PAUSE_ASYM); ++ mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); ++ mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); ++ } ++ ++ msleep(1000); ++ ++ return 0; ++} ++ ++static void ++ar8327_cleanup(struct ar8xxx_priv *priv) ++{ ++ ar8327_leds_cleanup(priv); ++} ++ ++static void ++ar8327_init_globals(struct ar8xxx_priv *priv) ++{ ++ u32 t; ++ ++ /* enable CPU port and disable mirror port */ ++ t = AR8327_FWD_CTRL0_CPU_PORT_EN | ++ AR8327_FWD_CTRL0_MIRROR_PORT; ++ priv->write(priv, AR8327_REG_FWD_CTRL0, t); ++ ++ /* forward multicast and broadcast frames to CPU */ ++ t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | ++ (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | ++ (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); ++ priv->write(priv, AR8327_REG_FWD_CTRL1, t); ++ ++ /* enable jumbo frames */ ++ ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, ++ AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); ++ ++ /* Enable MIB counters */ ++ ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, ++ AR8327_MODULE_EN_MIB); ++} ++ ++static void ++ar8327_init_port(struct ar8xxx_priv *priv, int port) ++{ ++ u32 t; ++ ++ if (port == AR8216_PORT_CPU) ++ t = priv->chip_data.ar8327.port0_status; ++ else if (port == 6) ++ t = priv->chip_data.ar8327.port6_status; ++ else ++ t = AR8216_PORT_STATUS_LINK_AUTO; ++ ++ priv->write(priv, AR8327_REG_PORT_STATUS(port), t); ++ priv->write(priv, AR8327_REG_PORT_HEADER(port), 0); ++ ++ t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S; ++ t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S; ++ priv->write(priv, AR8327_REG_PORT_VLAN0(port), t); ++ ++ t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; ++ priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); ++ ++ t = AR8327_PORT_LOOKUP_LEARN; ++ t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; ++ priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); ++} ++ ++static u32 ++ar8327_read_port_status(struct ar8xxx_priv *priv, int port) ++{ ++ return priv->read(priv, AR8327_REG_PORT_STATUS(port)); ++} ++ ++static int ++ar8327_atu_flush(struct ar8xxx_priv *priv) ++{ ++ int ret; ++ ++ ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, ++ AR8327_ATU_FUNC_BUSY, 0); ++ if (!ret) ++ priv->write(priv, AR8327_REG_ATU_FUNC, ++ AR8327_ATU_FUNC_OP_FLUSH); ++ ++ return ret; ++} ++ ++static void ++ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) ++{ ++ if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, ++ AR8327_VTU_FUNC1_BUSY, 0)) ++ return; ++ ++ if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) ++ priv->write(priv, AR8327_REG_VTU_FUNC0, val); ++ ++ op |= AR8327_VTU_FUNC1_BUSY; ++ priv->write(priv, AR8327_REG_VTU_FUNC1, op); ++} ++ ++static void ++ar8327_vtu_flush(struct ar8xxx_priv *priv) ++{ ++ ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); ++} ++ ++static void ++ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) ++{ ++ u32 op; ++ u32 val; ++ int i; ++ ++ op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); ++ val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; ++ for (i = 0; i < AR8327_NUM_PORTS; i++) { ++ u32 mode; ++ ++ if ((port_mask & BIT(i)) == 0) ++ mode = AR8327_VTU_FUNC0_EG_MODE_NOT; ++ else if (priv->vlan == 0) ++ mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; ++ else if (priv->vlan_tagged & BIT(i)) ++ mode = AR8327_VTU_FUNC0_EG_MODE_TAG; ++ else ++ mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; ++ ++ val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); ++ } ++ ar8327_vtu_op(priv, op, val); ++} ++ ++static void ++ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 egress, u32 ingress, ++ u32 members, u32 pvid) ++{ ++ u32 t; ++ u32 mode; ++ ++ t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; ++ t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; ++ priv->write(priv, AR8327_REG_PORT_VLAN0(port), t); ++ ++ mode = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; ++ switch (egress) { ++ case AR8216_OUT_KEEP: ++ mode = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; ++ break; ++ case AR8216_OUT_STRIP_VLAN: ++ mode = AR8327_PORT_VLAN1_OUT_MODE_UNTAG; ++ break; ++ case AR8216_OUT_ADD_VLAN: ++ mode = AR8327_PORT_VLAN1_OUT_MODE_TAG; ++ break; ++ } ++ ++ t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; ++ t |= mode << AR8327_PORT_VLAN1_OUT_MODE_S; ++ priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); ++ ++ t = members; ++ t |= AR8327_PORT_LOOKUP_LEARN; ++ t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; ++ t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; ++ priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); ++} ++ ++static const struct ar8xxx_chip ar8327_chip = { ++ .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, ++ .hw_init = ar8327_hw_init, ++ .cleanup = ar8327_cleanup, ++ .init_globals = ar8327_init_globals, ++ .init_port = ar8327_init_port, ++ .setup_port = ar8327_setup_port, ++ .read_port_status = ar8327_read_port_status, ++ .atu_flush = ar8327_atu_flush, ++ .vtu_flush = ar8327_vtu_flush, ++ .vtu_load_vlan = ar8327_vtu_load_vlan, ++ ++ .num_mibs = ARRAY_SIZE(ar8236_mibs), ++ .mib_decs = ar8236_mibs, ++}; ++ ++static int ++ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ priv->vlan = !!val->value.i; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->vlan; ++ return 0; ++} ++ ++ ++static int ++ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ /* make sure no invalid PVIDs get set */ ++ ++ if (vlan >= dev->vlans) ++ return -EINVAL; ++ ++ priv->pvid[port] = vlan; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ *vlan = priv->pvid[port]; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ priv->vlan_id[val->port_vlan] = val->value.i; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->vlan_id[val->port_vlan]; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, ++ struct switch_port_link *link) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ ar8216_read_port_link(priv, port, link); ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ u8 ports = priv->vlan_table[val->port_vlan]; ++ int i; ++ ++ val->len = 0; ++ for (i = 0; i < dev->ports; i++) { ++ struct switch_port *p; ++ ++ if (!(ports & (1 << i))) ++ continue; ++ ++ p = &val->value.ports[val->len++]; ++ p->id = i; ++ if (priv->vlan_tagged & (1 << i)) ++ p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); ++ else ++ p->flags = 0; ++ } ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ u8 *vt = &priv->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)) { ++ priv->vlan_tagged |= (1 << p->id); ++ } else { ++ priv->vlan_tagged &= ~(1 << p->id); ++ priv->pvid[p->id] = val->port_vlan; ++ ++ /* make sure that an untagged port does not ++ * appear in other vlans */ ++ for (j = 0; j < AR8X16_MAX_VLANS; j++) { ++ if (j == val->port_vlan) ++ continue; ++ priv->vlan_table[j] &= ~(1 << p->id); ++ } ++ } ++ ++ *vt |= 1 << p->id; ++ } ++ return 0; ++} ++ ++static void ++ar8327_set_mirror_regs(struct ar8xxx_priv *priv) ++{ ++ int port; ++ ++ /* reset all mirror registers */ ++ ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, ++ AR8327_FWD_CTRL0_MIRROR_PORT, ++ (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); ++ for (port = 0; port < AR8327_NUM_PORTS; port++) { ++ ar8xxx_rmw(priv, AR8327_REG_PORT_LOOKUP(port), ++ AR8327_PORT_LOOKUP_ING_MIRROR_EN, ++ 0); ++ ++ ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(port), ++ AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN, ++ 0); ++ } ++ ++ /* now enable mirroring if necessary */ ++ if (priv->source_port >= AR8327_NUM_PORTS || ++ priv->monitor_port >= AR8327_NUM_PORTS || ++ priv->source_port == priv->monitor_port) { ++ return; ++ } ++ ++ ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, ++ AR8327_FWD_CTRL0_MIRROR_PORT, ++ (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); ++ ++ if (priv->mirror_rx) ++ ar8xxx_rmw(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), ++ AR8327_PORT_LOOKUP_ING_MIRROR_EN, ++ AR8327_PORT_LOOKUP_ING_MIRROR_EN); ++ ++ if (priv->mirror_tx) ++ ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), ++ AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN, ++ AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); ++} ++ ++static void ++ar8216_set_mirror_regs(struct ar8xxx_priv *priv) ++{ ++ int port; ++ ++ /* reset all mirror registers */ ++ ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, ++ AR8216_GLOBAL_CPUPORT_MIRROR_PORT, ++ (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); ++ for (port = 0; port < AR8216_NUM_PORTS; port++) { ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), ++ AR8216_PORT_CTRL_MIRROR_RX, ++ 0); ++ ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), ++ AR8216_PORT_CTRL_MIRROR_TX, ++ 0); ++ } ++ ++ /* now enable mirroring if necessary */ ++ if (priv->source_port >= AR8216_NUM_PORTS || ++ priv->monitor_port >= AR8216_NUM_PORTS || ++ priv->source_port == priv->monitor_port) { ++ return; ++ } ++ ++ ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, ++ AR8216_GLOBAL_CPUPORT_MIRROR_PORT, ++ (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); ++ ++ if (priv->mirror_rx) ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(priv->source_port), ++ AR8216_PORT_CTRL_MIRROR_RX, ++ AR8216_PORT_CTRL_MIRROR_RX); ++ ++ if (priv->mirror_tx) ++ ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(priv->source_port), ++ AR8216_PORT_CTRL_MIRROR_TX, ++ AR8216_PORT_CTRL_MIRROR_TX); ++} ++ ++static void ++ar8xxx_set_mirror_regs(struct ar8xxx_priv *priv) ++{ ++ if (chip_is_ar8327(priv) || chip_is_ar8337(priv)) { ++ ar8327_set_mirror_regs(priv); ++ } else { ++ ar8216_set_mirror_regs(priv); ++ } ++} ++ ++static int ++ar8xxx_sw_hw_apply(struct switch_dev *dev) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ u8 portmask[AR8X16_MAX_PORTS]; ++ int i, j; ++ ++ mutex_lock(&priv->reg_mutex); ++ /* flush all vlan translation unit entries */ ++ priv->chip->vtu_flush(priv); ++ ++ memset(portmask, 0, sizeof(portmask)); ++ if (!priv->init) { ++ /* calculate the port destination masks and load vlans ++ * into the vlan translation unit */ ++ for (j = 0; j < AR8X16_MAX_VLANS; j++) { ++ u8 vp = priv->vlan_table[j]; ++ ++ if (!vp) ++ continue; ++ ++ for (i = 0; i < dev->ports; i++) { ++ u8 mask = (1 << i); ++ if (vp & mask) ++ portmask[i] |= vp & ~mask; ++ } ++ ++ priv->chip->vtu_load_vlan(priv, priv->vlan_id[j], ++ priv->vlan_table[j]); ++ } ++ } else { ++ /* vlan disabled: ++ * isolate all ports, but connect them to the cpu port */ ++ for (i = 0; i < dev->ports; i++) { ++ if (i == AR8216_PORT_CPU) ++ continue; ++ ++ portmask[i] = 1 << AR8216_PORT_CPU; ++ portmask[AR8216_PORT_CPU] |= (1 << i); ++ } ++ } ++ ++ /* update the port destination mask registers and tag settings */ ++ for (i = 0; i < dev->ports; i++) { ++ int egress, ingress; ++ int pvid; ++ ++ if (priv->vlan) { ++ pvid = priv->vlan_id[priv->pvid[i]]; ++ if (priv->vlan_tagged & (1 << i)) ++ egress = AR8216_OUT_ADD_VLAN; ++ else ++ egress = AR8216_OUT_STRIP_VLAN; ++ ingress = AR8216_IN_SECURE; ++ } else { ++ pvid = i; ++ egress = AR8216_OUT_KEEP; ++ ingress = AR8216_IN_PORT_ONLY; ++ } ++ ++ priv->chip->setup_port(priv, i, egress, ingress, portmask[i], ++ pvid); ++ } ++ ++ ar8xxx_set_mirror_regs(priv); ++ ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar8xxx_sw_reset_switch(struct switch_dev *dev) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ int i; ++ ++ mutex_lock(&priv->reg_mutex); ++ memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - ++ offsetof(struct ar8xxx_priv, vlan)); ++ ++ for (i = 0; i < AR8X16_MAX_VLANS; i++) ++ priv->vlan_id[i] = i; ++ ++ /* Configure all ports */ ++ for (i = 0; i < dev->ports; i++) ++ priv->chip->init_port(priv, i); ++ ++ priv->mirror_rx = false; ++ priv->mirror_tx = false; ++ priv->source_port = 0; ++ priv->monitor_port = 0; ++ ++ priv->chip->init_globals(priv); ++ ++ mutex_unlock(&priv->reg_mutex); ++ ++ return ar8xxx_sw_hw_apply(dev); ++} ++ ++static int ++ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ unsigned int len; ++ int ret; ++ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&priv->mib_lock); ++ ++ len = priv->dev.ports * priv->chip->num_mibs * ++ sizeof(*priv->mib_stats); ++ memset(priv->mib_stats, '\0', len); ++ ret = ar8xxx_mib_flush(priv); ++ if (ret) ++ goto unlock; ++ ++ ret = 0; ++ ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static int ++ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->mirror_rx = !!val->value.i; ++ ar8xxx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->mirror_rx; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->mirror_tx = !!val->value.i; ++ ar8xxx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->mirror_tx; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->monitor_port = val->value.i; ++ ar8xxx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->monitor_port; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->source_port = val->value.i; ++ ar8xxx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ val->value.i = priv->source_port; ++ return 0; ++} ++ ++static int ++ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ int port; ++ int ret; ++ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return -EOPNOTSUPP; ++ ++ port = val->port_vlan; ++ if (port >= dev->ports) ++ return -EINVAL; ++ ++ mutex_lock(&priv->mib_lock); ++ ret = ar8xxx_mib_capture(priv); ++ if (ret) ++ goto unlock; ++ ++ ar8xxx_mib_fetch_port_stat(priv, port, true); ++ ++ ret = 0; ++ ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static int ++ar8xxx_sw_get_port_mib(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); ++ const struct ar8xxx_chip *chip = priv->chip; ++ u64 *mib_stats; ++ int port; ++ int ret; ++ char *buf = priv->buf; ++ int i, len = 0; ++ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return -EOPNOTSUPP; ++ ++ port = val->port_vlan; ++ if (port >= dev->ports) ++ return -EINVAL; ++ ++ mutex_lock(&priv->mib_lock); ++ ret = ar8xxx_mib_capture(priv); ++ if (ret) ++ goto unlock; ++ ++ ar8xxx_mib_fetch_port_stat(priv, port, false); ++ ++ len += snprintf(buf + len, sizeof(priv->buf) - len, ++ "Port %d MIB counters\n", ++ port); ++ ++ mib_stats = &priv->mib_stats[port * chip->num_mibs]; ++ for (i = 0; i < chip->num_mibs; i++) ++ len += snprintf(buf + len, sizeof(priv->buf) - len, ++ "%-12s: %llu\n", ++ chip->mib_decs[i].name, ++ mib_stats[i]); ++ ++ val->value.s = buf; ++ val->len = len; ++ ++ ret = 0; ++ ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static struct switch_attr ar8xxx_sw_attr_globals[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = ar8xxx_sw_set_vlan, ++ .get = ar8xxx_sw_get_vlan, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset_mibs", ++ .description = "Reset all MIB counters", ++ .set = ar8xxx_sw_set_reset_mibs, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_rx", ++ .description = "Enable mirroring of RX packets", ++ .set = ar8xxx_sw_set_mirror_rx_enable, ++ .get = ar8xxx_sw_get_mirror_rx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_tx", ++ .description = "Enable mirroring of TX packets", ++ .set = ar8xxx_sw_set_mirror_tx_enable, ++ .get = ar8xxx_sw_get_mirror_tx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_monitor_port", ++ .description = "Mirror monitor port", ++ .set = ar8xxx_sw_set_mirror_monitor_port, ++ .get = ar8xxx_sw_get_mirror_monitor_port, ++ .max = AR8216_NUM_PORTS - 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_source_port", ++ .description = "Mirror source port", ++ .set = ar8xxx_sw_set_mirror_source_port, ++ .get = ar8xxx_sw_get_mirror_source_port, ++ .max = AR8216_NUM_PORTS - 1 ++ }, ++}; ++ ++static struct switch_attr ar8327_sw_attr_globals[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = ar8xxx_sw_set_vlan, ++ .get = ar8xxx_sw_get_vlan, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset_mibs", ++ .description = "Reset all MIB counters", ++ .set = ar8xxx_sw_set_reset_mibs, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_rx", ++ .description = "Enable mirroring of RX packets", ++ .set = ar8xxx_sw_set_mirror_rx_enable, ++ .get = ar8xxx_sw_get_mirror_rx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_tx", ++ .description = "Enable mirroring of TX packets", ++ .set = ar8xxx_sw_set_mirror_tx_enable, ++ .get = ar8xxx_sw_get_mirror_tx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_monitor_port", ++ .description = "Mirror monitor port", ++ .set = ar8xxx_sw_set_mirror_monitor_port, ++ .get = ar8xxx_sw_get_mirror_monitor_port, ++ .max = AR8327_NUM_PORTS - 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_source_port", ++ .description = "Mirror source port", ++ .set = ar8xxx_sw_set_mirror_source_port, ++ .get = ar8xxx_sw_get_mirror_source_port, ++ .max = AR8327_NUM_PORTS - 1 ++ }, ++}; ++ ++static struct switch_attr ar8xxx_sw_attr_port[] = { ++ { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset_mib", ++ .description = "Reset single port MIB counters", ++ .set = ar8xxx_sw_set_port_reset_mib, ++ }, ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "mib", ++ .description = "Get port's MIB counters", ++ .set = NULL, ++ .get = ar8xxx_sw_get_port_mib, ++ }, ++}; ++ ++static struct switch_attr ar8xxx_sw_attr_vlan[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "vid", ++ .description = "VLAN ID (0-4094)", ++ .set = ar8xxx_sw_set_vid, ++ .get = ar8xxx_sw_get_vid, ++ .max = 4094, ++ }, ++}; ++ ++static const struct switch_dev_ops ar8xxx_sw_ops = { ++ .attr_global = { ++ .attr = ar8xxx_sw_attr_globals, ++ .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), ++ }, ++ .attr_port = { ++ .attr = ar8xxx_sw_attr_port, ++ .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), ++ }, ++ .attr_vlan = { ++ .attr = ar8xxx_sw_attr_vlan, ++ .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), ++ }, ++ .get_port_pvid = ar8xxx_sw_get_pvid, ++ .set_port_pvid = ar8xxx_sw_set_pvid, ++ .get_vlan_ports = ar8xxx_sw_get_ports, ++ .set_vlan_ports = ar8xxx_sw_set_ports, ++ .apply_config = ar8xxx_sw_hw_apply, ++ .reset_switch = ar8xxx_sw_reset_switch, ++ .get_port_link = ar8xxx_sw_get_port_link, ++}; ++ ++static const struct switch_dev_ops ar8327_sw_ops = { ++ .attr_global = { ++ .attr = ar8327_sw_attr_globals, ++ .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), ++ }, ++ .attr_port = { ++ .attr = ar8xxx_sw_attr_port, ++ .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), ++ }, ++ .attr_vlan = { ++ .attr = ar8xxx_sw_attr_vlan, ++ .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), ++ }, ++ .get_port_pvid = ar8xxx_sw_get_pvid, ++ .set_port_pvid = ar8xxx_sw_set_pvid, ++ .get_vlan_ports = ar8xxx_sw_get_ports, ++ .set_vlan_ports = ar8xxx_sw_set_ports, ++ .apply_config = ar8xxx_sw_hw_apply, ++ .reset_switch = ar8xxx_sw_reset_switch, ++ .get_port_link = ar8xxx_sw_get_port_link, ++}; ++ ++static int ++ar8xxx_id_chip(struct ar8xxx_priv *priv) ++{ ++ u32 val; ++ u16 id; ++ int i; ++ ++ val = priv->read(priv, AR8216_REG_CTRL); ++ if (val == ~0) ++ return -ENODEV; ++ ++ id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); ++ for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { ++ u16 t; ++ ++ val = priv->read(priv, AR8216_REG_CTRL); ++ if (val == ~0) ++ return -ENODEV; ++ ++ t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); ++ if (t != id) ++ return -ENODEV; ++ } ++ ++ priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; ++ priv->chip_rev = (id & AR8216_CTRL_REVISION); ++ ++ switch (priv->chip_ver) { ++ case AR8XXX_VER_AR8216: ++ priv->chip = &ar8216_chip; ++ break; ++ case AR8XXX_VER_AR8236: ++ priv->chip = &ar8236_chip; ++ break; ++ case AR8XXX_VER_AR8316: ++ priv->chip = &ar8316_chip; ++ break; ++ case AR8XXX_VER_AR8327: ++ priv->mii_lo_first = true; ++ priv->chip = &ar8327_chip; ++ break; ++ case AR8XXX_VER_AR8337: ++ priv->mii_lo_first = true; ++ priv->chip = &ar8327_chip; ++ break; ++ default: ++ pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", ++ priv->chip_ver, priv->chip_rev); ++ ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static void ++ar8xxx_mib_work_func(struct work_struct *work) ++{ ++ struct ar8xxx_priv *priv; ++ int err; ++ ++ priv = container_of(work, struct ar8xxx_priv, mib_work.work); ++ ++ mutex_lock(&priv->mib_lock); ++ ++ err = ar8xxx_mib_capture(priv); ++ if (err) ++ goto next_port; ++ ++ ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); ++ ++next_port: ++ priv->mib_next_port++; ++ if (priv->mib_next_port >= priv->dev.ports) ++ priv->mib_next_port = 0; ++ ++ mutex_unlock(&priv->mib_lock); ++ schedule_delayed_work(&priv->mib_work, ++ msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); ++} ++ ++static int ++ar8xxx_mib_init(struct ar8xxx_priv *priv) ++{ ++ unsigned int len; ++ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return 0; ++ ++ BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); ++ ++ len = priv->dev.ports * priv->chip->num_mibs * ++ sizeof(*priv->mib_stats); ++ priv->mib_stats = kzalloc(len, GFP_KERNEL); ++ ++ if (!priv->mib_stats) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void ++ar8xxx_mib_start(struct ar8xxx_priv *priv) ++{ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return; ++ ++ schedule_delayed_work(&priv->mib_work, ++ msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); ++} ++ ++static void ++ar8xxx_mib_stop(struct ar8xxx_priv *priv) ++{ ++ if (!ar8xxx_has_mib_counters(priv)) ++ return; ++ ++ cancel_delayed_work(&priv->mib_work); ++} ++ ++static struct ar8xxx_priv * ++ar8xxx_create(void) ++{ ++ struct ar8xxx_priv *priv; ++ ++ priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); ++ if (priv == NULL) ++ return NULL; ++ ++ mutex_init(&priv->reg_mutex); ++ mutex_init(&priv->mib_lock); ++ INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); ++ ++ return priv; ++} ++ ++static void ++ar8xxx_free(struct ar8xxx_priv *priv) ++{ ++ if (priv->chip && priv->chip->cleanup) ++ priv->chip->cleanup(priv); ++ ++ kfree(priv->mib_stats); ++ kfree(priv); ++} ++ ++static struct ar8xxx_priv * ++ar8xxx_create_mii(struct mii_bus *bus) ++{ ++ struct ar8xxx_priv *priv; ++ ++ priv = ar8xxx_create(); ++ if (priv) { ++ priv->mii_bus = bus; ++ priv->read = ar8xxx_mii_read; ++ priv->write = ar8xxx_mii_write; ++ priv->rmw = ar8xxx_mii_rmw; ++ } ++ ++ return priv; ++} ++ ++static int ++ar8xxx_probe_switch(struct ar8xxx_priv *priv) ++{ ++ struct switch_dev *swdev; ++ int ret; ++ ++ ret = ar8xxx_id_chip(priv); ++ if (ret) ++ return ret; ++ ++ swdev = &priv->dev; ++ swdev->cpu_port = AR8216_PORT_CPU; ++ swdev->ops = &ar8xxx_sw_ops; ++ ++ if (chip_is_ar8316(priv)) { ++ swdev->name = "Atheros AR8316"; ++ swdev->vlans = AR8X16_MAX_VLANS; ++ swdev->ports = AR8216_NUM_PORTS; ++ } else if (chip_is_ar8236(priv)) { ++ swdev->name = "Atheros AR8236"; ++ swdev->vlans = AR8216_NUM_VLANS; ++ swdev->ports = AR8216_NUM_PORTS; ++ } else if (chip_is_ar8327(priv)) { ++ swdev->name = "Atheros AR8327"; ++ swdev->vlans = AR8X16_MAX_VLANS; ++ swdev->ports = AR8327_NUM_PORTS; ++ swdev->ops = &ar8327_sw_ops; ++ } else if (chip_is_ar8337(priv)) { ++ swdev->name = "Atheros AR8337"; ++ swdev->vlans = AR8X16_MAX_VLANS; ++ swdev->ports = AR8327_NUM_PORTS; ++ swdev->ops = &ar8327_sw_ops; ++ } else { ++ swdev->name = "Atheros AR8216"; ++ swdev->vlans = AR8216_NUM_VLANS; ++ swdev->ports = AR8216_NUM_PORTS; ++ } ++ ++ ret = ar8xxx_mib_init(priv); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int ++ar8xxx_start(struct ar8xxx_priv *priv) ++{ ++ int ret; ++ ++ priv->init = true; ++ ++ ret = priv->chip->hw_init(priv); ++ if (ret) ++ return ret; ++ ++ ret = ar8xxx_sw_reset_switch(&priv->dev); ++ if (ret) ++ return ret; ++ ++ priv->init = false; ++ ++ ar8xxx_mib_start(priv); ++ ++ return 0; ++} ++ ++static int ++ar8xxx_phy_config_init(struct phy_device *phydev) ++{ ++ struct ar8xxx_priv *priv = phydev->priv; ++ struct net_device *dev = phydev->attached_dev; ++ int ret; ++ ++ if (WARN_ON(!priv)) ++ return -ENODEV; ++ ++ if (chip_is_ar8327(priv) || chip_is_ar8337(priv)) ++ return 0; ++ ++ priv->phy = phydev; ++ ++ if (phydev->addr != 0) { ++ if (chip_is_ar8316(priv)) { ++ /* switch device has been initialized, reinit */ ++ priv->dev.ports = (AR8216_NUM_PORTS - 1); ++ priv->initialized = false; ++ priv->port4_phy = true; ++ ar8316_hw_init(priv); ++ return 0; ++ } ++ ++ return 0; ++ } ++ ++ ret = ar8xxx_start(priv); ++ if (ret) ++ return ret; ++ ++ /* VID fixup only needed on ar8216 */ ++ if (chip_is_ar8216(priv)) { ++ dev->phy_ptr = priv; ++ dev->priv_flags |= IFF_NO_IP_ALIGN; ++ dev->eth_mangle_rx = ar8216_mangle_rx; ++ dev->eth_mangle_tx = ar8216_mangle_tx; ++ } ++ ++ return 0; ++} ++ ++static int ++ar8xxx_phy_read_status(struct phy_device *phydev) ++{ ++ struct ar8xxx_priv *priv = phydev->priv; ++ struct switch_port_link link; ++ int ret; ++ ++ if (phydev->addr != 0) ++ return genphy_read_status(phydev); ++ ++ ar8216_read_port_link(priv, phydev->addr, &link); ++ phydev->link = !!link.link; ++ if (!phydev->link) ++ return 0; ++ ++ switch (link.speed) { ++ case SWITCH_PORT_SPEED_10: ++ phydev->speed = SPEED_10; ++ break; ++ case SWITCH_PORT_SPEED_100: ++ phydev->speed = SPEED_100; ++ break; ++ case SWITCH_PORT_SPEED_1000: ++ phydev->speed = SPEED_1000; ++ break; ++ default: ++ phydev->speed = 0; ++ } ++ phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; ++ ++ /* flush the address translation unit */ ++ mutex_lock(&priv->reg_mutex); ++ ret = priv->chip->atu_flush(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ phydev->state = PHY_RUNNING; ++ netif_carrier_on(phydev->attached_dev); ++ phydev->adjust_link(phydev->attached_dev); ++ ++ return ret; ++} ++ ++static int ++ar8xxx_phy_config_aneg(struct phy_device *phydev) ++{ ++ if (phydev->addr == 0) ++ return 0; ++ ++ return genphy_config_aneg(phydev); ++} ++ ++static const u32 ar8xxx_phy_ids[] = { ++ 0x004dd033, ++ 0x004dd034, /* AR8327 */ ++ 0x004dd036, /* AR8337 */ ++ 0x004dd041, ++ 0x004dd042, ++}; ++ ++static bool ++ar8xxx_phy_match(u32 phy_id) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) ++ if (phy_id == ar8xxx_phy_ids[i]) ++ return true; ++ ++ return false; ++} ++ ++static bool ++ar8xxx_is_possible(struct mii_bus *bus) ++{ ++ unsigned i; ++ ++ for (i = 0; i < 4; i++) { ++ u32 phy_id; ++ ++ phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; ++ phy_id |= mdiobus_read(bus, i, MII_PHYSID2); ++ if (!ar8xxx_phy_match(phy_id)) { ++ pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", ++ dev_name(&bus->dev), i, phy_id); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++static int ++ar8xxx_phy_probe(struct phy_device *phydev) ++{ ++ struct ar8xxx_priv *priv; ++ struct switch_dev *swdev; ++ int ret; ++ ++ /* skip PHYs at unused adresses */ ++ if (phydev->addr != 0 && phydev->addr != 4) ++ return -ENODEV; ++ ++ if (!ar8xxx_is_possible(phydev->bus)) ++ return -ENODEV; ++ ++ mutex_lock(&ar8xxx_dev_list_lock); ++ list_for_each_entry(priv, &ar8xxx_dev_list, list) ++ if (priv->mii_bus == phydev->bus) ++ goto found; ++ ++ priv = ar8xxx_create_mii(phydev->bus); ++ if (priv == NULL) { ++ ret = -ENOMEM; ++ goto unlock; ++ } ++ ++ ret = ar8xxx_probe_switch(priv); ++ if (ret) ++ goto free_priv; ++ ++ swdev = &priv->dev; ++ swdev->alias = dev_name(&priv->mii_bus->dev); ++ ret = register_switch(swdev, NULL); ++ if (ret) ++ goto free_priv; ++ ++ pr_info("%s: %s rev. %u switch registered on %s\n", ++ swdev->devname, swdev->name, priv->chip_rev, ++ dev_name(&priv->mii_bus->dev)); ++ ++found: ++ priv->use_count++; ++ ++ if (phydev->addr == 0) { ++ if (ar8xxx_has_gige(priv)) { ++ phydev->supported = SUPPORTED_1000baseT_Full; ++ phydev->advertising = ADVERTISED_1000baseT_Full; ++ } else { ++ phydev->supported = SUPPORTED_100baseT_Full; ++ phydev->advertising = ADVERTISED_100baseT_Full; ++ } ++ ++ if (chip_is_ar8327(priv) || chip_is_ar8337(priv)) { ++ priv->phy = phydev; ++ ++ ret = ar8xxx_start(priv); ++ if (ret) ++ goto err_unregister_switch; ++ } ++ } else { ++ if (ar8xxx_has_gige(priv)) { ++ phydev->supported |= SUPPORTED_1000baseT_Full; ++ phydev->advertising |= ADVERTISED_1000baseT_Full; ++ } ++ } ++ ++ phydev->priv = priv; ++ ++ list_add(&priv->list, &ar8xxx_dev_list); ++ ++ mutex_unlock(&ar8xxx_dev_list_lock); ++ ++ return 0; ++ ++err_unregister_switch: ++ if (--priv->use_count) ++ goto unlock; ++ ++ unregister_switch(&priv->dev); ++ ++free_priv: ++ ar8xxx_free(priv); ++unlock: ++ mutex_unlock(&ar8xxx_dev_list_lock); ++ return ret; ++} ++ ++static void ++ar8xxx_phy_detach(struct phy_device *phydev) ++{ ++ struct net_device *dev = phydev->attached_dev; ++ ++ if (!dev) ++ return; ++ ++ dev->phy_ptr = NULL; ++ dev->priv_flags &= ~IFF_NO_IP_ALIGN; ++ dev->eth_mangle_rx = NULL; ++ dev->eth_mangle_tx = NULL; ++} ++ ++static void ++ar8xxx_phy_remove(struct phy_device *phydev) ++{ ++ struct ar8xxx_priv *priv = phydev->priv; ++ ++ if (WARN_ON(!priv)) ++ return; ++ ++ phydev->priv = NULL; ++ if (--priv->use_count > 0) ++ return; ++ ++ mutex_lock(&ar8xxx_dev_list_lock); ++ list_del(&priv->list); ++ mutex_unlock(&ar8xxx_dev_list_lock); ++ ++ unregister_switch(&priv->dev); ++ ar8xxx_mib_stop(priv); ++ ar8xxx_free(priv); ++} ++ ++static struct phy_driver ar8xxx_phy_driver = { ++ .phy_id = 0x004d0000, ++ .name = "Atheros AR8216/AR8236/AR8316", ++ .phy_id_mask = 0xffff0000, ++ .features = PHY_BASIC_FEATURES, ++ .probe = ar8xxx_phy_probe, ++ .remove = ar8xxx_phy_remove, ++ .detach = ar8xxx_phy_detach, ++ .config_init = ar8xxx_phy_config_init, ++ .config_aneg = ar8xxx_phy_config_aneg, ++ .read_status = ar8xxx_phy_read_status, ++ .driver = { .owner = THIS_MODULE }, ++}; ++ ++int __init ++ar8xxx_init(void) ++{ ++ return phy_driver_register(&ar8xxx_phy_driver); ++} ++ ++void __exit ++ar8xxx_exit(void) ++{ ++ phy_driver_unregister(&ar8xxx_phy_driver); ++} ++ ++module_init(ar8xxx_init); ++module_exit(ar8xxx_exit); ++MODULE_LICENSE("GPL"); ++ +diff --git a/drivers/net/phy/ar8216.h b/drivers/net/phy/ar8216.h +new file mode 100644 +index 0000000..00d6d7f +--- /dev/null ++++ b/drivers/net/phy/ar8216.h +@@ -0,0 +1,492 @@ ++/* ++ * ar8216.h: AR8216 switch driver ++ * ++ * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++ ++#ifndef __AR8216_H ++#define __AR8216_H ++ ++#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) ++ ++#define AR8216_PORT_CPU 0 ++#define AR8216_NUM_PORTS 6 ++#define AR8216_NUM_VLANS 16 ++#define AR8316_NUM_VLANS 4096 ++ ++/* Atheros specific MII registers */ ++#define MII_ATH_MMD_ADDR 0x0d ++#define MII_ATH_MMD_DATA 0x0e ++#define MII_ATH_DBG_ADDR 0x1d ++#define MII_ATH_DBG_DATA 0x1e ++ ++#define AR8216_REG_CTRL 0x0000 ++#define AR8216_CTRL_REVISION BITS(0, 8) ++#define AR8216_CTRL_REVISION_S 0 ++#define AR8216_CTRL_VERSION BITS(8, 8) ++#define AR8216_CTRL_VERSION_S 8 ++#define AR8216_CTRL_RESET BIT(31) ++ ++#define AR8216_REG_FLOOD_MASK 0x002C ++#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) ++#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) ++ ++#define AR8216_REG_GLOBAL_CTRL 0x0030 ++#define AR8216_GCTRL_MTU BITS(0, 11) ++#define AR8236_GCTRL_MTU BITS(0, 14) ++#define AR8316_GCTRL_MTU BITS(0, 14) ++ ++#define AR8216_REG_VTU 0x0040 ++#define AR8216_VTU_OP BITS(0, 3) ++#define AR8216_VTU_OP_NOOP 0x0 ++#define AR8216_VTU_OP_FLUSH 0x1 ++#define AR8216_VTU_OP_LOAD 0x2 ++#define AR8216_VTU_OP_PURGE 0x3 ++#define AR8216_VTU_OP_REMOVE_PORT 0x4 ++#define AR8216_VTU_ACTIVE BIT(3) ++#define AR8216_VTU_FULL BIT(4) ++#define AR8216_VTU_PORT BITS(8, 4) ++#define AR8216_VTU_PORT_S 8 ++#define AR8216_VTU_VID BITS(16, 12) ++#define AR8216_VTU_VID_S 16 ++#define AR8216_VTU_PRIO BITS(28, 3) ++#define AR8216_VTU_PRIO_S 28 ++#define AR8216_VTU_PRIO_EN BIT(31) ++ ++#define AR8216_REG_VTU_DATA 0x0044 ++#define AR8216_VTUDATA_MEMBER BITS(0, 10) ++#define AR8236_VTUDATA_MEMBER BITS(0, 7) ++#define AR8216_VTUDATA_VALID BIT(11) ++ ++#define AR8216_REG_ATU 0x0050 ++#define AR8216_ATU_OP BITS(0, 3) ++#define AR8216_ATU_OP_NOOP 0x0 ++#define AR8216_ATU_OP_FLUSH 0x1 ++#define AR8216_ATU_OP_LOAD 0x2 ++#define AR8216_ATU_OP_PURGE 0x3 ++#define AR8216_ATU_OP_FLUSH_LOCKED 0x4 ++#define AR8216_ATU_OP_FLUSH_UNICAST 0x5 ++#define AR8216_ATU_OP_GET_NEXT 0x6 ++#define AR8216_ATU_ACTIVE BIT(3) ++#define AR8216_ATU_PORT_NUM BITS(8, 4) ++#define AR8216_ATU_FULL_VIO BIT(12) ++#define AR8216_ATU_ADDR4 BITS(16, 8) ++#define AR8216_ATU_ADDR5 BITS(24, 8) ++ ++#define AR8216_REG_ATU_DATA 0x0054 ++#define AR8216_ATU_ADDR3 BITS(0, 8) ++#define AR8216_ATU_ADDR2 BITS(8, 8) ++#define AR8216_ATU_ADDR1 BITS(16, 8) ++#define AR8216_ATU_ADDR0 BITS(24, 8) ++ ++#define AR8216_REG_ATU_CTRL 0x005C ++#define AR8216_ATU_CTRL_AGE_EN BIT(17) ++#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) ++#define AR8216_ATU_CTRL_AGE_TIME_S 0 ++ ++#define AR8216_REG_MIB_FUNC 0x0080 ++#define AR8216_MIB_TIMER BITS(0, 16) ++#define AR8216_MIB_AT_HALF_EN BIT(16) ++#define AR8216_MIB_BUSY BIT(17) ++#define AR8216_MIB_FUNC BITS(24, 3) ++#define AR8216_MIB_FUNC_S 24 ++#define AR8216_MIB_FUNC_NO_OP 0x0 ++#define AR8216_MIB_FUNC_FLUSH 0x1 ++#define AR8216_MIB_FUNC_CAPTURE 0x3 ++#define AR8236_MIB_EN BIT(30) ++ ++#define AR8216_REG_GLOBAL_CPUPORT 0x0078 ++#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) ++#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 ++ ++#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) ++#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) ++#define AR8216_PORT_STATUS_SPEED BITS(0,2) ++#define AR8216_PORT_STATUS_SPEED_S 0 ++#define AR8216_PORT_STATUS_TXMAC BIT(2) ++#define AR8216_PORT_STATUS_RXMAC BIT(3) ++#define AR8216_PORT_STATUS_TXFLOW BIT(4) ++#define AR8216_PORT_STATUS_RXFLOW BIT(5) ++#define AR8216_PORT_STATUS_DUPLEX BIT(6) ++#define AR8216_PORT_STATUS_LINK_UP BIT(8) ++#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) ++#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) ++ ++#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) ++ ++/* port forwarding state */ ++#define AR8216_PORT_CTRL_STATE BITS(0, 3) ++#define AR8216_PORT_CTRL_STATE_S 0 ++ ++#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) ++ ++/* egress 802.1q mode */ ++#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) ++#define AR8216_PORT_CTRL_VLAN_MODE_S 8 ++ ++#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) ++#define AR8216_PORT_CTRL_HEADER BIT(11) ++#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) ++#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) ++#define AR8216_PORT_CTRL_LEARN BIT(14) ++#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) ++#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) ++ ++#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) ++ ++#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) ++#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 ++ ++#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) ++#define AR8216_PORT_VLAN_DEST_PORTS_S 16 ++ ++/* bit0 added to the priority field of egress frames */ ++#define AR8216_PORT_VLAN_TX_PRIO BIT(27) ++ ++/* port default priority */ ++#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) ++#define AR8216_PORT_VLAN_PRIORITY_S 28 ++ ++/* ingress 802.1q mode */ ++#define AR8216_PORT_VLAN_MODE BITS(30, 2) ++#define AR8216_PORT_VLAN_MODE_S 30 ++ ++#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) ++#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) ++ ++#define AR8216_REG_PORT_STATS_BASE(_i) (0x19000 + (_i) * 0xa0) ++ ++#define AR8216_STATS_RXBROAD 0x00 ++#define AR8216_STATS_RXPAUSE 0x04 ++#define AR8216_STATS_RXMULTI 0x08 ++#define AR8216_STATS_RXFCSERR 0x0c ++#define AR8216_STATS_RXALIGNERR 0x10 ++#define AR8216_STATS_RXRUNT 0x14 ++#define AR8216_STATS_RXFRAGMENT 0x18 ++#define AR8216_STATS_RX64BYTE 0x1c ++#define AR8216_STATS_RX128BYTE 0x20 ++#define AR8216_STATS_RX256BYTE 0x24 ++#define AR8216_STATS_RX512BYTE 0x28 ++#define AR8216_STATS_RX1024BYTE 0x2c ++#define AR8216_STATS_RXMAXBYTE 0x30 ++#define AR8216_STATS_RXTOOLONG 0x34 ++#define AR8216_STATS_RXGOODBYTE 0x38 ++#define AR8216_STATS_RXBADBYTE 0x40 ++#define AR8216_STATS_RXOVERFLOW 0x48 ++#define AR8216_STATS_FILTERED 0x4c ++#define AR8216_STATS_TXBROAD 0x50 ++#define AR8216_STATS_TXPAUSE 0x54 ++#define AR8216_STATS_TXMULTI 0x58 ++#define AR8216_STATS_TXUNDERRUN 0x5c ++#define AR8216_STATS_TX64BYTE 0x60 ++#define AR8216_STATS_TX128BYTE 0x64 ++#define AR8216_STATS_TX256BYTE 0x68 ++#define AR8216_STATS_TX512BYTE 0x6c ++#define AR8216_STATS_TX1024BYTE 0x70 ++#define AR8216_STATS_TXMAXBYTE 0x74 ++#define AR8216_STATS_TXOVERSIZE 0x78 ++#define AR8216_STATS_TXBYTE 0x7c ++#define AR8216_STATS_TXCOLLISION 0x84 ++#define AR8216_STATS_TXABORTCOL 0x88 ++#define AR8216_STATS_TXMULTICOL 0x8c ++#define AR8216_STATS_TXSINGLECOL 0x90 ++#define AR8216_STATS_TXEXCDEFER 0x94 ++#define AR8216_STATS_TXDEFER 0x98 ++#define AR8216_STATS_TXLATECOL 0x9c ++ ++#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) ++#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) ++#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 ++#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) ++#define AR8236_PORT_VLAN_PRIORITY_S 28 ++ ++#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) ++#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) ++#define AR8236_PORT_VLAN2_MEMBER_S 16 ++#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) ++#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) ++#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 ++ ++#define AR8236_REG_PORT_STATS_BASE(_i) (0x20000 + (_i) * 0x100) ++ ++#define AR8236_STATS_RXBROAD 0x00 ++#define AR8236_STATS_RXPAUSE 0x04 ++#define AR8236_STATS_RXMULTI 0x08 ++#define AR8236_STATS_RXFCSERR 0x0c ++#define AR8236_STATS_RXALIGNERR 0x10 ++#define AR8236_STATS_RXRUNT 0x14 ++#define AR8236_STATS_RXFRAGMENT 0x18 ++#define AR8236_STATS_RX64BYTE 0x1c ++#define AR8236_STATS_RX128BYTE 0x20 ++#define AR8236_STATS_RX256BYTE 0x24 ++#define AR8236_STATS_RX512BYTE 0x28 ++#define AR8236_STATS_RX1024BYTE 0x2c ++#define AR8236_STATS_RX1518BYTE 0x30 ++#define AR8236_STATS_RXMAXBYTE 0x34 ++#define AR8236_STATS_RXTOOLONG 0x38 ++#define AR8236_STATS_RXGOODBYTE 0x3c ++#define AR8236_STATS_RXBADBYTE 0x44 ++#define AR8236_STATS_RXOVERFLOW 0x4c ++#define AR8236_STATS_FILTERED 0x50 ++#define AR8236_STATS_TXBROAD 0x54 ++#define AR8236_STATS_TXPAUSE 0x58 ++#define AR8236_STATS_TXMULTI 0x5c ++#define AR8236_STATS_TXUNDERRUN 0x60 ++#define AR8236_STATS_TX64BYTE 0x64 ++#define AR8236_STATS_TX128BYTE 0x68 ++#define AR8236_STATS_TX256BYTE 0x6c ++#define AR8236_STATS_TX512BYTE 0x70 ++#define AR8236_STATS_TX1024BYTE 0x74 ++#define AR8236_STATS_TX1518BYTE 0x78 ++#define AR8236_STATS_TXMAXBYTE 0x7c ++#define AR8236_STATS_TXOVERSIZE 0x80 ++#define AR8236_STATS_TXBYTE 0x84 ++#define AR8236_STATS_TXCOLLISION 0x8c ++#define AR8236_STATS_TXABORTCOL 0x90 ++#define AR8236_STATS_TXMULTICOL 0x94 ++#define AR8236_STATS_TXSINGLECOL 0x98 ++#define AR8236_STATS_TXEXCDEFER 0x9c ++#define AR8236_STATS_TXDEFER 0xa0 ++#define AR8236_STATS_TXLATECOL 0xa4 ++ ++#define AR8316_REG_POSTRIP 0x0008 ++#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) ++#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) ++#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) ++#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) ++#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) ++#define AR8316_POSTRIP_RTL_MODE BIT(5) ++#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) ++#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) ++#define AR8316_POSTRIP_SERDES_EN BIT(8) ++#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) ++#define AR8316_POSTRIP_GATE_25M_EN BIT(10) ++#define AR8316_POSTRIP_SEL_CLK25M BIT(11) ++#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) ++#define AR8316_POSTRIP_DBG_MODE_I BIT(13) ++#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) ++#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) ++#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) ++#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) ++#define AR8316_POSTRIP_MAN_EN BIT(18) ++#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) ++#define AR8316_POSTRIP_LPW_EXIT BIT(20) ++#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) ++#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) ++#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) ++#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) ++#define AR8316_POSTRIP_SPI_EN BIT(25) ++#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) ++#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) ++ ++#define AR8327_NUM_PORTS 7 ++#define AR8327_NUM_LEDS 15 ++#define AR8327_NUM_PHYS 5 ++#define AR8327_PORTS_ALL 0x7f ++#define AR8327_NUM_LED_CTRL_REGS 4 ++ ++#define AR8327_REG_MASK 0x000 ++ ++#define AR8327_REG_PAD0_MODE 0x004 ++#define AR8327_REG_PAD5_MODE 0x008 ++#define AR8327_REG_PAD6_MODE 0x00c ++#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) ++#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) ++#define AR8327_PAD_MAC_MII_EN BIT(2) ++#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) ++#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) ++#define AR8327_PAD_MAC_GMII_EN BIT(6) ++#define AR8327_PAD_SGMII_EN BIT(7) ++#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) ++#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) ++#define AR8327_PAD_PHY_MII_EN BIT(10) ++#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) ++#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) ++#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) ++#define AR8327_PAD_PHY_GMII_EN BIT(14) ++#define AR8327_PAD_PHYX_GMII_EN BIT(16) ++#define AR8327_PAD_PHYX_RGMII_EN BIT(17) ++#define AR8327_PAD_PHYX_MII_EN BIT(18) ++#define AR8327_PAD_SGMII_DELAY_EN BIT(19) ++#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) ++#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 ++#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) ++#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 ++#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) ++#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) ++#define AR8327_PAD_RGMII_EN BIT(26) ++ ++#define AR8327_REG_POWER_ON_STRIP 0x010 ++#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) ++#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) ++#define AR8327_POWER_ON_STRIP_SERDES_AEN BIT(7) ++ ++#define AR8327_REG_INT_STATUS0 0x020 ++#define AR8327_INT0_VT_DONE BIT(20) ++ ++#define AR8327_REG_INT_STATUS1 0x024 ++#define AR8327_REG_INT_MASK0 0x028 ++#define AR8327_REG_INT_MASK1 0x02c ++ ++#define AR8327_REG_MODULE_EN 0x030 ++#define AR8327_MODULE_EN_MIB BIT(0) ++ ++#define AR8327_REG_MIB_FUNC 0x034 ++#define AR8327_MIB_CPU_KEEP BIT(20) ++ ++#define AR8327_REG_SERVICE_TAG 0x048 ++#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4) ++#define AR8327_REG_LED_CTRL0 0x050 ++#define AR8327_REG_LED_CTRL1 0x054 ++#define AR8327_REG_LED_CTRL2 0x058 ++#define AR8327_REG_LED_CTRL3 0x05c ++#define AR8327_REG_MAC_ADDR0 0x060 ++#define AR8327_REG_MAC_ADDR1 0x064 ++ ++#define AR8327_REG_MAX_FRAME_SIZE 0x078 ++#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) ++ ++#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) ++ ++#define AR8327_REG_HEADER_CTRL 0x098 ++#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) ++ ++#define AR8327_REG_SGMII_CTRL 0x0e0 ++#define AR8327_SGMII_CTRL_EN_PLL BIT(1) ++#define AR8327_SGMII_CTRL_EN_RX BIT(2) ++#define AR8327_SGMII_CTRL_EN_TX BIT(3) ++ ++#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) ++#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) ++#define AR8327_PORT_VLAN0_DEF_SVID_S 0 ++#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) ++#define AR8327_PORT_VLAN0_DEF_CVID_S 16 ++ ++#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) ++#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) ++#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) ++#define AR8327_PORT_VLAN1_OUT_MODE_S 12 ++#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 ++#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 ++#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 ++#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 ++ ++#define AR8327_REG_ATU_DATA0 0x600 ++#define AR8327_REG_ATU_DATA1 0x604 ++#define AR8327_REG_ATU_DATA2 0x608 ++ ++#define AR8327_REG_ATU_FUNC 0x60c ++#define AR8327_ATU_FUNC_OP BITS(0, 4) ++#define AR8327_ATU_FUNC_OP_NOOP 0x0 ++#define AR8327_ATU_FUNC_OP_FLUSH 0x1 ++#define AR8327_ATU_FUNC_OP_LOAD 0x2 ++#define AR8327_ATU_FUNC_OP_PURGE 0x3 ++#define AR8327_ATU_FUNC_OP_FLUSH_LOCKED 0x4 ++#define AR8327_ATU_FUNC_OP_FLUSH_UNICAST 0x5 ++#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 ++#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 ++#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 ++#define AR8327_ATU_FUNC_BUSY BIT(31) ++ ++#define AR8327_REG_VTU_FUNC0 0x0610 ++#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) ++#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) ++#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 ++#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 ++#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 ++#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 ++#define AR8327_VTU_FUNC0_IVL BIT(19) ++#define AR8327_VTU_FUNC0_VALID BIT(20) ++ ++#define AR8327_REG_VTU_FUNC1 0x0614 ++#define AR8327_VTU_FUNC1_OP BITS(0, 3) ++#define AR8327_VTU_FUNC1_OP_NOOP 0 ++#define AR8327_VTU_FUNC1_OP_FLUSH 1 ++#define AR8327_VTU_FUNC1_OP_LOAD 2 ++#define AR8327_VTU_FUNC1_OP_PURGE 3 ++#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 ++#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 ++#define AR8327_VTU_FUNC1_OP_GET_ONE 6 ++#define AR8327_VTU_FUNC1_FULL BIT(4) ++#define AR8327_VTU_FUNC1_PORT BIT(8, 4) ++#define AR8327_VTU_FUNC1_PORT_S 8 ++#define AR8327_VTU_FUNC1_VID BIT(16, 12) ++#define AR8327_VTU_FUNC1_VID_S 16 ++#define AR8327_VTU_FUNC1_BUSY BIT(31) ++ ++#define AR8327_REG_FWD_CTRL0 0x620 ++#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) ++#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) ++#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 ++ ++#define AR8327_REG_FWD_CTRL1 0x624 ++#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) ++#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 ++#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) ++#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 ++#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) ++#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 ++#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) ++#define AR8327_FWD_CTRL1_IGMP_S 24 ++ ++#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) ++#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) ++#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) ++#define AR8327_PORT_LOOKUP_IN_MODE_S 8 ++#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) ++#define AR8327_PORT_LOOKUP_STATE_S 16 ++#define AR8327_PORT_LOOKUP_LEARN BIT(20) ++#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25) ++ ++#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) ++ ++#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) ++#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) ++ ++#define AR8327_REG_PORT_STATS_BASE(_i) (0x1000 + (_i) * 0x100) ++ ++#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31) ++ ++/* port speed */ ++enum { ++ AR8216_PORT_SPEED_10M = 0, ++ AR8216_PORT_SPEED_100M = 1, ++ AR8216_PORT_SPEED_1000M = 2, ++ AR8216_PORT_SPEED_ERR = 3, ++}; ++ ++/* ingress 802.1q mode */ ++enum { ++ AR8216_IN_PORT_ONLY = 0, ++ AR8216_IN_PORT_FALLBACK = 1, ++ AR8216_IN_VLAN_ONLY = 2, ++ AR8216_IN_SECURE = 3 ++}; ++ ++/* egress 802.1q mode */ ++enum { ++ AR8216_OUT_KEEP = 0, ++ AR8216_OUT_STRIP_VLAN = 1, ++ AR8216_OUT_ADD_VLAN = 2 ++}; ++ ++/* port forwarding state */ ++enum { ++ AR8216_PORT_STATE_DISABLED = 0, ++ AR8216_PORT_STATE_BLOCK = 1, ++ AR8216_PORT_STATE_LISTEN = 2, ++ AR8216_PORT_STATE_LEARN = 3, ++ AR8216_PORT_STATE_FORWARD = 4 ++}; ++ ++#endif +diff --git a/include/linux/ar8216_platform.h b/include/linux/ar8216_platform.h +new file mode 100644 +index 0000000..4935ad3 +--- /dev/null ++++ b/include/linux/ar8216_platform.h +@@ -0,0 +1,131 @@ ++/* ++ * AR8216 switch driver platform data ++ * ++ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org> ++ * ++ * 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. ++ * ++ * 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. ++ */ ++ ++#ifndef AR8216_PLATFORM_H ++#define AR8216_PLATFORM_H ++ ++enum ar8327_pad_mode { ++ AR8327_PAD_NC = 0, ++ AR8327_PAD_MAC2MAC_MII, ++ AR8327_PAD_MAC2MAC_GMII, ++ AR8327_PAD_MAC_SGMII, ++ AR8327_PAD_MAC2PHY_MII, ++ AR8327_PAD_MAC2PHY_GMII, ++ AR8327_PAD_MAC_RGMII, ++ AR8327_PAD_PHY_GMII, ++ AR8327_PAD_PHY_RGMII, ++ AR8327_PAD_PHY_MII, ++}; ++ ++enum ar8327_clk_delay_sel { ++ AR8327_CLK_DELAY_SEL0 = 0, ++ AR8327_CLK_DELAY_SEL1, ++ AR8327_CLK_DELAY_SEL2, ++ AR8327_CLK_DELAY_SEL3, ++}; ++ ++struct ar8327_pad_cfg { ++ enum ar8327_pad_mode mode; ++ bool rxclk_sel; ++ bool txclk_sel; ++ bool pipe_rxclk_sel; ++ bool txclk_delay_en; ++ bool rxclk_delay_en; ++ bool sgmii_delay_en; ++ enum ar8327_clk_delay_sel txclk_delay_sel; ++ enum ar8327_clk_delay_sel rxclk_delay_sel; ++}; ++ ++enum ar8327_port_speed { ++ AR8327_PORT_SPEED_10 = 0, ++ AR8327_PORT_SPEED_100, ++ AR8327_PORT_SPEED_1000, ++}; ++ ++struct ar8327_port_cfg { ++ int force_link:1; ++ enum ar8327_port_speed speed; ++ int txpause:1; ++ int rxpause:1; ++ int duplex:1; ++}; ++ ++struct ar8327_sgmii_cfg { ++ u32 sgmii_ctrl; ++ bool serdes_aen; ++}; ++ ++struct ar8327_led_cfg { ++ u32 led_ctrl0; ++ u32 led_ctrl1; ++ u32 led_ctrl2; ++ u32 led_ctrl3; ++ bool open_drain; ++}; ++ ++enum ar8327_led_num { ++ AR8327_LED_PHY0_0 = 0, ++ AR8327_LED_PHY0_1, ++ AR8327_LED_PHY0_2, ++ AR8327_LED_PHY1_0, ++ AR8327_LED_PHY1_1, ++ AR8327_LED_PHY1_2, ++ AR8327_LED_PHY2_0, ++ AR8327_LED_PHY2_1, ++ AR8327_LED_PHY2_2, ++ AR8327_LED_PHY3_0, ++ AR8327_LED_PHY3_1, ++ AR8327_LED_PHY3_2, ++ AR8327_LED_PHY4_0, ++ AR8327_LED_PHY4_1, ++ AR8327_LED_PHY4_2, ++}; ++ ++enum ar8327_led_mode { ++ AR8327_LED_MODE_HW = 0, ++ AR8327_LED_MODE_SW, ++}; ++ ++struct ar8327_led_info { ++ const char *name; ++ const char *default_trigger; ++ bool active_low; ++ enum ar8327_led_num led_num; ++ enum ar8327_led_mode mode; ++}; ++ ++#define AR8327_LED_INFO(_led, _mode, _name) { \ ++ .name = (_name), \ ++ .led_num = AR8327_LED_ ## _led, \ ++ .mode = AR8327_LED_MODE_ ## _mode \ ++} ++ ++struct ar8327_platform_data { ++ struct ar8327_pad_cfg *pad0_cfg; ++ struct ar8327_pad_cfg *pad5_cfg; ++ struct ar8327_pad_cfg *pad6_cfg; ++ struct ar8327_sgmii_cfg *sgmii_cfg; ++ struct ar8327_port_cfg port0_cfg; ++ struct ar8327_port_cfg port6_cfg; ++ struct ar8327_led_cfg *led_cfg; ++ ++ int (*get_port_link)(unsigned port); ++ ++ unsigned num_leds; ++ const struct ar8327_led_info *leds; ++}; ++ ++#endif /* AR8216_PLATFORM_H */ +\ No newline at end of file +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0016-phy-mdio-bitbang-ignore-TA-value.patch b/target/mips/dragino-ms14s/patches/3.14.17/0016-phy-mdio-bitbang-ignore-TA-value.patch new file mode 100644 index 000000000..fe23f4912 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0016-phy-mdio-bitbang-ignore-TA-value.patch @@ -0,0 +1,44 @@ +From e73f7d9a658c7fc693a9b9c45a1f65c014dd6e40 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 01:17:38 +0200 +Subject: [PATCH] phy: mdio-bitbang: ignore TA value + +This is necessary on rb493g to make the kernel detect the second switch. +--- + drivers/net/phy/mdio-bitbang.c | 13 ++----------- + 1 file changed, 2 insertions(+), 11 deletions(-) + +diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c +index daec9b0..4fa2be0 100644 +--- a/drivers/net/phy/mdio-bitbang.c ++++ b/drivers/net/phy/mdio-bitbang.c +@@ -155,7 +155,7 @@ static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr) + static int mdiobb_read(struct mii_bus *bus, int phy, int reg) + { + struct mdiobb_ctrl *ctrl = bus->priv; +- int ret, i; ++ int ret; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); +@@ -165,16 +165,7 @@ static int mdiobb_read(struct mii_bus *bus, int phy, int reg) + + ctrl->ops->set_mdio_dir(ctrl, 0); + +- /* check the turnaround bit: the PHY should be driving it to zero */ +- if (mdiobb_get_bit(ctrl) != 0) { +- /* PHY didn't drive TA low -- flush any bits it +- * may be trying to send. +- */ +- for (i = 0; i < 32; i++) +- mdiobb_get_bit(ctrl); +- +- return 0xffff; +- } ++ mdiobb_get_bit(ctrl); + + ret = mdiobb_get_num(ctrl, 16); + mdiobb_get_bit(ctrl); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0017-MIPS-ath79-fix-maximum-timeout.patch b/target/mips/dragino-ms14s/patches/3.14.17/0017-MIPS-ath79-fix-maximum-timeout.patch new file mode 100644 index 000000000..3ca02783d --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0017-MIPS-ath79-fix-maximum-timeout.patch @@ -0,0 +1,37 @@ +From 54d01581baa903adb8515625d98652ed43efba36 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 01:21:59 +0200 +Subject: [PATCH] MIPS: ath79: fix maximum timeout + +If the userland tries to set a timeout higher than the max_timeout, then +we should fallback to max_timeout. + +Signed-off-by: John Crispin <blogic@openwrt.org> +--- + drivers/watchdog/ath79_wdt.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c +index 9fa1f69..bf26baf 100644 +--- a/drivers/watchdog/ath79_wdt.c ++++ b/drivers/watchdog/ath79_wdt.c +@@ -105,10 +105,14 @@ static inline void ath79_wdt_disable(void) + + static int ath79_wdt_set_timeout(int val) + { +- if (val < 1 || val > max_timeout) ++ if (val < 1) + return -EINVAL; + +- timeout = val; ++ if (val > max_timeout) ++ timeout = max_timeout; ++ else ++ timeout = val; ++ + ath79_wdt_keepalive(); + + return 0; +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0018-net-allow-PHY-drivers-to-insert-packet-mangle-hooks.patch b/target/mips/dragino-ms14s/patches/3.14.17/0018-net-allow-PHY-drivers-to-insert-packet-mangle-hooks.patch new file mode 100644 index 000000000..6a372c8f1 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0018-net-allow-PHY-drivers-to-insert-packet-mangle-hooks.patch @@ -0,0 +1,211 @@ +From ebca842041d737b7441748a17ffd535aab851fce Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 01:32:11 +0200 +Subject: [PATCH] net: allow PHY drivers to insert packet mangle hooks + +--- + include/linux/netdevice.h | 8 ++++++++ + include/linux/skbuff.h | 14 ++++---------- + include/uapi/linux/if.h | 1 + + net/Kconfig | 6 ++++++ + net/core/dev.c | 36 ++++++++++++++++++++++++++++-------- + net/core/skbuff.c | 17 +++++++++++++++++ + net/ethernet/eth.c | 6 ++++++ + 7 files changed, 70 insertions(+), 18 deletions(-) + +diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h +index 911718f..8e8dd46 100644 +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -1245,6 +1245,11 @@ struct net_device { + const struct ethtool_ops *ethtool_ops; + const struct forwarding_accel_ops *fwd_ops; + ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ void (*eth_mangle_rx)(struct net_device *dev, struct sk_buff *skb); ++ struct sk_buff *(*eth_mangle_tx)(struct net_device *dev, struct sk_buff *skb); ++#endif ++ + /* Hardware header description */ + const struct header_ops *header_ops; + +@@ -1313,6 +1318,9 @@ struct net_device { + void *ax25_ptr; /* AX.25 specific data */ + struct wireless_dev *ieee80211_ptr; /* IEEE 802.11 specific data, + assign before registering */ ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ void *phy_ptr; /* PHY device specific data */ ++#endif + + /* + * Cache lines mostly used on receive path (including eth_type_trans()) +diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h +index 15ede6a..5530766 100644 +--- a/include/linux/skbuff.h ++++ b/include/linux/skbuff.h +@@ -1858,6 +1858,10 @@ static inline int pskb_trim(struct sk_buff *skb, unsigned int len) + return (len < skb->len) ? __pskb_trim(skb, len) : 0; + } + ++extern struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev, ++ unsigned int length, gfp_t gfp); ++ ++ + /** + * pskb_trim_unique - remove end from a paged unique (not cloned) buffer + * @skb: buffer to alter +@@ -1966,16 +1970,6 @@ static inline struct sk_buff *dev_alloc_skb(unsigned int length) + } + + +-static inline struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev, +- unsigned int length, gfp_t gfp) +-{ +- struct sk_buff *skb = __netdev_alloc_skb(dev, length + NET_IP_ALIGN, gfp); +- +- if (NET_IP_ALIGN && skb) +- skb_reserve(skb, NET_IP_ALIGN); +- return skb; +-} +- + static inline struct sk_buff *netdev_alloc_skb_ip_align(struct net_device *dev, + unsigned int length) + { +diff --git a/include/uapi/linux/if.h b/include/uapi/linux/if.h +index d758163..7ffa548 100644 +--- a/include/uapi/linux/if.h ++++ b/include/uapi/linux/if.h +@@ -84,6 +84,7 @@ + #define IFF_LIVE_ADDR_CHANGE 0x100000 /* device supports hardware address + * change when it's running */ + #define IFF_MACVLAN 0x200000 /* Macvlan device */ ++#define IFF_NO_IP_ALIGN 0x400000 /* do not ip-align allocated rx pkts */ + + + #define IF_GET_IFACE 0x0001 /* for querying only */ +diff --git a/net/Kconfig b/net/Kconfig +index e411046..970c52a 100644 +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -24,6 +24,12 @@ menuconfig NET + + if NET + ++config ETHERNET_PACKET_MANGLE ++ bool ++ help ++ This option can be selected by phy drivers that need to mangle ++ packets going in or out of an ethernet device. ++ + config WANT_COMPAT_NETLINK_MESSAGES + bool + help +diff --git a/net/core/dev.c b/net/core/dev.c +index fccc195..2e0ba23 100644 +--- a/net/core/dev.c ++++ b/net/core/dev.c +@@ -2607,10 +2607,20 @@ int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, + if (!list_empty(&ptype_all)) + dev_queue_xmit_nit(skb, dev); + +- skb_len = skb->len; +- trace_net_dev_start_xmit(skb, dev); +- rc = ops->ndo_start_xmit(skb, dev); +- trace_net_dev_xmit(skb, rc, dev, skb_len); ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ if (!dev->eth_mangle_tx || ++ (skb = dev->eth_mangle_tx(dev, skb)) != NULL) ++#else ++ if (1) ++#endif ++ { ++ skb_len = skb->len; ++ trace_net_dev_start_xmit(skb, dev); ++ rc = ops->ndo_start_xmit(skb, dev); ++ trace_net_dev_xmit(skb, rc, dev, skb_len); ++ } else { ++ rc = NETDEV_TX_OK; ++ } + if (rc == NETDEV_TX_OK) + txq_trans_update(txq); + return rc; +@@ -2626,10 +2636,20 @@ gso: + if (!list_empty(&ptype_all)) + dev_queue_xmit_nit(nskb, dev); + +- skb_len = nskb->len; +- trace_net_dev_start_xmit(nskb, dev); +- rc = ops->ndo_start_xmit(nskb, dev); +- trace_net_dev_xmit(nskb, rc, dev, skb_len); ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ if (!dev->eth_mangle_tx || ++ (nskb = dev->eth_mangle_tx(dev, nskb)) != NULL) ++#else ++ if (1) ++#endif ++ { ++ skb_len = nskb->len; ++ trace_net_dev_start_xmit(nskb, dev); ++ rc = ops->ndo_start_xmit(nskb, dev); ++ trace_net_dev_xmit(nskb, rc, dev, skb_len); ++ } else { ++ rc = NETDEV_TX_OK; ++ } + if (unlikely(rc != NETDEV_TX_OK)) { + if (rc & ~NETDEV_TX_MASK) + goto out_kfree_gso_skb; +diff --git a/net/core/skbuff.c b/net/core/skbuff.c +index e5ae776e..400ff2a 100644 +--- a/net/core/skbuff.c ++++ b/net/core/skbuff.c +@@ -62,6 +62,7 @@ + #include <linux/scatterlist.h> + #include <linux/errqueue.h> + #include <linux/prefetch.h> ++#include <uapi/linux/if.h> + + #include <net/protocol.h> + #include <net/dst.h> +@@ -439,6 +440,22 @@ struct sk_buff *__netdev_alloc_skb(struct net_device *dev, + } + EXPORT_SYMBOL(__netdev_alloc_skb); + ++struct sk_buff *__netdev_alloc_skb_ip_align(struct net_device *dev, ++ unsigned int length, gfp_t gfp) ++{ ++ struct sk_buff *skb = __netdev_alloc_skb(dev, length + NET_IP_ALIGN, gfp); ++ ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ if (dev && (dev->priv_flags & IFF_NO_IP_ALIGN)) ++ return skb; ++#endif ++ ++ if (NET_IP_ALIGN && skb) ++ skb_reserve(skb, NET_IP_ALIGN); ++ return skb; ++} ++EXPORT_SYMBOL(__netdev_alloc_skb_ip_align); ++ + void skb_add_rx_frag(struct sk_buff *skb, int i, struct page *page, int off, + int size, unsigned int truesize) + { +diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c +index 5dc638c..f4fd124 100644 +--- a/net/ethernet/eth.c ++++ b/net/ethernet/eth.c +@@ -161,6 +161,12 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) + const struct ethhdr *eth; + + skb->dev = dev; ++ ++#ifdef CONFIG_ETHERNET_PACKET_MANGLE ++ if (dev->eth_mangle_rx) ++ dev->eth_mangle_rx(dev, skb); ++#endif ++ + skb_reset_mac_header(skb); + skb_pull_inline(skb, ETH_HLEN); + eth = eth_hdr(skb); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0019-MIPS-ath79-process-board-cmdline-option.patch b/target/mips/dragino-ms14s/patches/3.14.17/0019-MIPS-ath79-process-board-cmdline-option.patch new file mode 100644 index 000000000..13eae3b8c --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0019-MIPS-ath79-process-board-cmdline-option.patch @@ -0,0 +1,26 @@ +From 4c84b317734842765cb1c52624fc569efd9222dc Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 02:11:59 +0200 +Subject: [PATCH] MIPS: ath79: process board cmdline option + +This is necessary to correctly identify the running machine. +--- + arch/mips/ath79/setup.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/arch/mips/ath79/setup.c b/arch/mips/ath79/setup.c +index 64807a4..0c95758 100644 +--- a/arch/mips/ath79/setup.c ++++ b/arch/mips/ath79/setup.c +@@ -229,6 +229,8 @@ void __init plat_time_init(void) + mips_hpt_frequency = cpu_clk_rate / 2; + } + ++__setup("board=", mips_machtype_setup); ++ + static int __init ath79_setup(void) + { + ath79_gpio_init(); +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0020-spi-ath79-add-fast-flash-read-support.patch b/target/mips/dragino-ms14s/patches/3.14.17/0020-spi-ath79-add-fast-flash-read-support.patch new file mode 100644 index 000000000..8fd174448 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0020-spi-ath79-add-fast-flash-read-support.patch @@ -0,0 +1,202 @@ +From c4388a57860440e23c9654f4de2f515433e685a1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 13 May 2014 04:12:35 +0200 +Subject: [PATCH] spi-ath79: add fast flash read support + +--- + .../include/asm/mach-ath79/ath79_spi_platform.h | 1 + + drivers/spi/spi-ath79.c | 124 ++++++++++++++++++++- + 2 files changed, 120 insertions(+), 5 deletions(-) + +diff --git a/arch/mips/include/asm/mach-ath79/ath79_spi_platform.h b/arch/mips/include/asm/mach-ath79/ath79_spi_platform.h +index aa2283e..65369fe 100644 +--- a/arch/mips/include/asm/mach-ath79/ath79_spi_platform.h ++++ b/arch/mips/include/asm/mach-ath79/ath79_spi_platform.h +@@ -18,6 +18,7 @@ struct ath79_spi_platform_data { + + struct ath79_spi_controller_data { + unsigned gpio; ++ bool is_flash; + }; + + #endif /* _ATH79_SPI_PLATFORM_H */ +diff --git a/drivers/spi/spi-ath79.c b/drivers/spi/spi-ath79.c +index c3b2fb9..a26a6a4 100644 +--- a/drivers/spi/spi-ath79.c ++++ b/drivers/spi/spi-ath79.c +@@ -35,6 +35,11 @@ + #define ATH79_SPI_RRW_DELAY_FACTOR 12000 + #define MHZ (1000 * 1000) + ++enum ath79_spi_state { ++ ATH79_SPI_STATE_WAIT_CMD = 0, ++ ATH79_SPI_STATE_WAIT_READ, ++}; ++ + struct ath79_spi { + struct spi_bitbang bitbang; + u32 ioc_base; +@@ -42,6 +47,11 @@ struct ath79_spi { + void __iomem *base; + struct clk *clk; + unsigned rrw_delay; ++ ++ enum ath79_spi_state state; ++ u32 clk_div; ++ unsigned long read_addr; ++ unsigned long ahb_rate; + }; + + static inline u32 ath79_spi_rr(struct ath79_spi *sp, unsigned reg) +@@ -104,9 +114,6 @@ static void ath79_spi_enable(struct ath79_spi *sp) + /* save CTRL register */ + sp->reg_ctrl = ath79_spi_rr(sp, AR71XX_SPI_REG_CTRL); + sp->ioc_base = ath79_spi_rr(sp, AR71XX_SPI_REG_IOC); +- +- /* TODO: setup speed? */ +- ath79_spi_wr(sp, AR71XX_SPI_REG_CTRL, 0x43); + } + + static void ath79_spi_disable(struct ath79_spi *sp) +@@ -203,6 +210,110 @@ static u32 ath79_spi_txrx_mode0(struct spi_device *spi, unsigned nsecs, + return ath79_spi_rr(sp, AR71XX_SPI_REG_RDS); + } + ++static int ath79_spi_do_read_flash_data(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ struct ath79_spi *sp = ath79_spidev_to_sp(spi); ++ ++ /* disable GPIO mode */ ++ ath79_spi_wr(sp, AR71XX_SPI_REG_FS, 0); ++ ++ memcpy_fromio(t->rx_buf, sp->base + sp->read_addr, t->len); ++ ++ /* enable GPIO mode */ ++ ath79_spi_wr(sp, AR71XX_SPI_REG_FS, AR71XX_SPI_FS_GPIO); ++ ++ /* restore IOC register */ ++ ath79_spi_wr(sp, AR71XX_SPI_REG_IOC, sp->ioc_base); ++ ++ return t->len; ++} ++ ++static int ath79_spi_do_read_flash_cmd(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ struct ath79_spi *sp = ath79_spidev_to_sp(spi); ++ int len; ++ const u8 *p; ++ ++ sp->read_addr = 0; ++ ++ len = t->len - 1; ++ p = t->tx_buf; ++ ++ while (len--) { ++ p++; ++ sp->read_addr <<= 8; ++ sp->read_addr |= *p; ++ } ++ ++ return t->len; ++} ++ ++static bool ath79_spi_is_read_cmd(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ return t->type == SPI_TRANSFER_FLASH_READ_CMD; ++} ++ ++static bool ath79_spi_is_data_read(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ return t->type == SPI_TRANSFER_FLASH_READ_DATA; ++} ++ ++static int ath79_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) ++{ ++ struct ath79_spi *sp = ath79_spidev_to_sp(spi); ++ int ret; ++ ++ switch (sp->state) { ++ case ATH79_SPI_STATE_WAIT_CMD: ++ if (ath79_spi_is_read_cmd(spi, t)) { ++ ret = ath79_spi_do_read_flash_cmd(spi, t); ++ sp->state = ATH79_SPI_STATE_WAIT_READ; ++ } else { ++ ret = spi_bitbang_bufs(spi, t); ++ } ++ break; ++ ++ case ATH79_SPI_STATE_WAIT_READ: ++ if (ath79_spi_is_data_read(spi, t)) { ++ ret = ath79_spi_do_read_flash_data(spi, t); ++ } else { ++ dev_warn(&spi->dev, "flash data read expected\n"); ++ ret = -EIO; ++ } ++ sp->state = ATH79_SPI_STATE_WAIT_CMD; ++ break; ++ ++ default: ++ BUG(); ++ } ++ ++ return ret; ++} ++ ++static int ath79_spi_setup_transfer(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ struct ath79_spi *sp = ath79_spidev_to_sp(spi); ++ struct ath79_spi_controller_data *cdata; ++ int ret; ++ ++ ret = spi_bitbang_setup_transfer(spi, t); ++ if (ret) ++ return ret; ++ ++ cdata = spi->controller_data; ++ if (cdata->is_flash) ++ sp->bitbang.txrx_bufs = ath79_spi_txrx_bufs; ++ else ++ sp->bitbang.txrx_bufs = spi_bitbang_bufs; ++ ++ return ret; ++} ++ + static int ath79_spi_probe(struct platform_device *pdev) + { + struct spi_master *master; +@@ -223,6 +334,8 @@ static int ath79_spi_probe(struct platform_device *pdev) + + pdata = dev_get_platdata(&pdev->dev); + ++ sp->state = ATH79_SPI_STATE_WAIT_CMD; ++ + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); + master->setup = ath79_spi_setup; + master->cleanup = ath79_spi_cleanup; +@@ -234,7 +347,7 @@ static int ath79_spi_probe(struct platform_device *pdev) + sp->bitbang.master = master; + sp->bitbang.chipselect = ath79_spi_chipselect; + sp->bitbang.txrx_word[SPI_MODE_0] = ath79_spi_txrx_mode0; +- sp->bitbang.setup_transfer = spi_bitbang_setup_transfer; ++ sp->bitbang.setup_transfer = ath79_spi_setup_transfer; + sp->bitbang.flags = SPI_CS_HIGH; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +@@ -259,7 +372,8 @@ static int ath79_spi_probe(struct platform_device *pdev) + if (ret) + goto err_put_master; + +- rate = DIV_ROUND_UP(clk_get_rate(sp->clk), MHZ); ++ sp->ahb_rate = clk_get_rate(sp->clk); ++ rate = DIV_ROUND_UP(sp->ahb_rate, MHZ); + if (!rate) { + ret = -EINVAL; + goto err_clk_disable; +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0021-phy-add-mdio-boardinfo.patch b/target/mips/dragino-ms14s/patches/3.14.17/0021-phy-add-mdio-boardinfo.patch new file mode 100644 index 000000000..3ec15e171 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0021-phy-add-mdio-boardinfo.patch @@ -0,0 +1,227 @@ +From b8d5957374dc0e6ec8687c6e1b154ea066d27a5b Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Wed, 14 May 2014 02:44:43 +0200 +Subject: [PATCH] phy: add mdio boardinfo + +--- + drivers/net/Makefile | 2 +- + drivers/net/phy/Kconfig | 4 +++ + drivers/net/phy/Makefile | 2 ++ + drivers/net/phy/mdio-boardinfo.c | 58 ++++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/mdio-boardinfo.h | 22 +++++++++++++++ + drivers/net/phy/mdio_bus.c | 20 ++++++++++++++ + include/linux/phy.h | 18 +++++++++++++ + 7 files changed, 125 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/phy/mdio-boardinfo.c + create mode 100644 drivers/net/phy/mdio-boardinfo.h + +diff --git a/drivers/net/Makefile b/drivers/net/Makefile +index 3fef8a8..70b736b 100644 +--- a/drivers/net/Makefile ++++ b/drivers/net/Makefile +@@ -15,7 +15,7 @@ obj-$(CONFIG_MII) += mii.o + obj-$(CONFIG_MDIO) += mdio.o + obj-$(CONFIG_NET) += Space.o loopback.o + obj-$(CONFIG_NETCONSOLE) += netconsole.o +-obj-$(CONFIG_PHYLIB) += phy/ ++obj-y += phy/ + obj-$(CONFIG_RIONET) += rionet.o + obj-$(CONFIG_NET_TEAM) += team/ + obj-$(CONFIG_TUN) += tun.o +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 0414889..97ca8ec 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -12,6 +12,10 @@ menuconfig PHYLIB + + if PHYLIB + ++config MDIO_BOARDINFO ++ bool ++ default y ++ + config SWCONFIG + tristate "Switch configuration API" + ---help--- +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index 3c76ff8..0c990a4 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -2,6 +2,8 @@ + + libphy-objs := phy.o phy_device.o mdio_bus.o + ++obj-$(CONFIG_MDIO_BOARDINFO) += mdio-boardinfo.o ++ + obj-$(CONFIG_PHYLIB) += libphy.o + obj-$(CONFIG_SWCONFIG) += swconfig.o + obj-$(CONFIG_MARVELL_PHY) += marvell.o +diff --git a/drivers/net/phy/mdio-boardinfo.c b/drivers/net/phy/mdio-boardinfo.c +new file mode 100644 +index 0000000..9b8aaed +--- /dev/null ++++ b/drivers/net/phy/mdio-boardinfo.c +@@ -0,0 +1,58 @@ ++/* ++ * mdio-boardinfo.c - collect pre-declarations of PHY devices ++ * ++ * 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/phy.h> ++#include <linux/slab.h> ++#include <linux/export.h> ++#include <linux/mutex.h> ++#include <linux/phy.h> ++ ++#include "mdio-boardinfo.h" ++ ++/* ++ * These symbols are exported ONLY FOR the mdio_bus component. ++ * No other users will be supported. ++ */ ++ ++LIST_HEAD(__mdio_board_list); ++EXPORT_SYMBOL_GPL(__mdio_board_list); ++ ++DEFINE_MUTEX(__mdio_board_lock); ++EXPORT_SYMBOL_GPL(__mdio_board_lock); ++ ++/** ++ * mdio_register_board_info - register PHY devices for a given board ++ * @info: array of chip descriptors ++ * @n: how many descriptors are provided ++ * Context: can sleep ++ * ++ * The board info passed can safely be __initdata ... but be careful of ++ * any embedded pointers (platform_data, etc), they're copied as-is. ++ */ ++int __init ++mdiobus_register_board_info(struct mdio_board_info const *info, unsigned n) ++{ ++ struct mdio_board_entry *be; ++ int i; ++ ++ be = kzalloc(n * sizeof(*be), GFP_KERNEL); ++ if (!be) ++ return -ENOMEM; ++ ++ for (i = 0; i < n; i++, be++, info++) { ++ memcpy(&be->board_info, info, sizeof(*info)); ++ mutex_lock(&__mdio_board_lock); ++ list_add_tail(&be->list, &__mdio_board_list); ++ mutex_unlock(&__mdio_board_lock); ++ } ++ ++ return 0; ++} +diff --git a/drivers/net/phy/mdio-boardinfo.h b/drivers/net/phy/mdio-boardinfo.h +new file mode 100644 +index 0000000..28fbc0d +--- /dev/null ++++ b/drivers/net/phy/mdio-boardinfo.h +@@ -0,0 +1,22 @@ ++/* ++ * mdio-boardinfo.h - boardinfo interface internal to the mdio_bus component ++ * ++ * 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/mutex.h> ++ ++struct mdio_board_entry { ++ struct list_head list; ++ struct mdio_board_info board_info; ++}; ++ ++/* __mdio_board_lock protects __mdio_board_list ++ * only mdio_bus components are allowed to use these symbols. ++ */ ++extern struct mutex __mdio_board_lock; ++extern struct list_head __mdio_board_list; +diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c +index 71e4900..50fbe35 100644 +--- a/drivers/net/phy/mdio_bus.c ++++ b/drivers/net/phy/mdio_bus.c +@@ -38,6 +38,8 @@ + + #include <asm/irq.h> + ++#include "mdio-boardinfo.h" ++ + /** + * mdiobus_alloc_size - allocate a mii_bus structure + * @size: extra amount of memory to allocate for private storage. +@@ -224,15 +226,33 @@ void mdiobus_free(struct mii_bus *bus) + } + EXPORT_SYMBOL(mdiobus_free); + ++static void mdiobus_setup_phydev_from_boardinfo(struct mii_bus *bus, ++ struct phy_device *phydev, ++ struct mdio_board_info *bi) ++{ ++ if (strcmp(bus->id, bi->bus_id) || ++ bi->phy_addr != phydev->addr) ++ return; ++ ++ phydev->dev.platform_data = (void *) bi->platform_data; ++} ++ + struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr) + { + struct phy_device *phydev; ++ struct mdio_board_entry *be; + int err; + + phydev = get_phy_device(bus, addr, false); + if (IS_ERR(phydev) || phydev == NULL) + return phydev; + ++ mutex_lock(&__mdio_board_lock); ++ list_for_each_entry(be, &__mdio_board_list, list) ++ mdiobus_setup_phydev_from_boardinfo(bus, phydev, ++ &be->board_info); ++ mutex_unlock(&__mdio_board_lock); ++ + err = phy_device_register(phydev); + if (err) { + phy_device_free(phydev); +diff --git a/include/linux/phy.h b/include/linux/phy.h +index f1441b4..9dca415 100644 +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -658,4 +658,22 @@ int __init mdio_bus_init(void); + void mdio_bus_exit(void); + + extern struct bus_type mdio_bus_type; ++ ++struct mdio_board_info { ++ const char *bus_id; ++ int phy_addr; ++ ++ const void *platform_data; ++}; ++ ++#ifdef CONFIG_MDIO_BOARDINFO ++int mdiobus_register_board_info(const struct mdio_board_info *info, unsigned n); ++#else ++static inline int ++mdiobus_register_board_info(const struct mdio_board_info *info, unsigned n) ++{ ++ return 0; ++} ++#endif ++ + #endif /* __PHY_H */ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0022-mips-ath79-add-ath79-ethernet-driver.patch b/target/mips/dragino-ms14s/patches/3.14.17/0022-mips-ath79-add-ath79-ethernet-driver.patch new file mode 100644 index 000000000..a7eff47b1 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0022-mips-ath79-add-ath79-ethernet-driver.patch @@ -0,0 +1,1429 @@ +From 0c6bdad5f210f5f2fe28dc197ab77a36402bb36e Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Wed, 14 May 2014 03:08:37 +0200 +Subject: [PATCH] mips: ath79: add ath79 ethernet driver + +--- + arch/mips/ath79/Kconfig | 3 + + arch/mips/ath79/Makefile | 1 + + arch/mips/ath79/dev-eth.c | 1151 ++++++++++++++++++++++++ + arch/mips/ath79/dev-eth.h | 51 ++ + arch/mips/include/asm/mach-ath79/ar71xx_regs.h | 81 ++ + 5 files changed, 1287 insertions(+) + create mode 100644 arch/mips/ath79/dev-eth.c + create mode 100644 arch/mips/ath79/dev-eth.h + +diff --git a/arch/mips/ath79/Kconfig b/arch/mips/ath79/Kconfig +index 3995e31..52cefd7 100644 +--- a/arch/mips/ath79/Kconfig ++++ b/arch/mips/ath79/Kconfig +@@ -109,6 +109,9 @@ config SOC_QCA955X + config PCI_AR724X + def_bool n + ++config ATH79_DEV_ETH ++ def_bool n ++ + config ATH79_DEV_GPIO_BUTTONS + def_bool n + +diff --git a/arch/mips/ath79/Makefile b/arch/mips/ath79/Makefile +index 5c9ff69..05485da 100644 +--- a/arch/mips/ath79/Makefile ++++ b/arch/mips/ath79/Makefile +@@ -17,6 +17,7 @@ obj-$(CONFIG_PCI) += pci.o + # Devices + # + obj-y += dev-common.o ++obj-$(CONFIG_ATH79_DEV_ETH) += dev-eth.o + obj-$(CONFIG_ATH79_DEV_GPIO_BUTTONS) += dev-gpio-buttons.o + obj-$(CONFIG_ATH79_DEV_LEDS_GPIO) += dev-leds-gpio.o + obj-$(CONFIG_ATH79_DEV_SPI) += dev-spi.o +diff --git a/arch/mips/ath79/dev-eth.c b/arch/mips/ath79/dev-eth.c +new file mode 100644 +index 0000000..21feeb9 +--- /dev/null ++++ b/arch/mips/ath79/dev-eth.c +@@ -0,0 +1,1151 @@ ++/* ++ * Atheros AR71xx SoC platform devices ++ * ++ * Copyright (C) 2010-2011 Jaiganesh Narayanan <jnarayanan@atheros.com> ++ * Copyright (C) 2008-2012 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * Parts of this file are based on Atheros 2.6.15 BSP ++ * Parts of this file are based on Atheros 2.6.31 BSP ++ * ++ * 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 <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++#include <linux/etherdevice.h> ++#include <linux/platform_device.h> ++#include <linux/serial_8250.h> ++#include <linux/clk.h> ++#include <linux/sizes.h> ++ ++#include <asm/mach-ath79/ath79.h> ++#include <asm/mach-ath79/ar71xx_regs.h> ++#include <asm/mach-ath79/irq.h> ++ ++#include "common.h" ++#include "dev-eth.h" ++ ++unsigned char ath79_mac_base[ETH_ALEN] __initdata; ++ ++static struct resource ath79_mdio0_resources[] = { ++ { ++ .name = "mdio_base", ++ .flags = IORESOURCE_MEM, ++ .start = AR71XX_GE0_BASE, ++ .end = AR71XX_GE0_BASE + 0x200 - 1, ++ } ++}; ++ ++struct ag71xx_mdio_platform_data ath79_mdio0_data; ++ ++struct platform_device ath79_mdio0_device = { ++ .name = "ag71xx-mdio", ++ .id = 0, ++ .resource = ath79_mdio0_resources, ++ .num_resources = ARRAY_SIZE(ath79_mdio0_resources), ++ .dev = { ++ .platform_data = &ath79_mdio0_data, ++ }, ++}; ++ ++static struct resource ath79_mdio1_resources[] = { ++ { ++ .name = "mdio_base", ++ .flags = IORESOURCE_MEM, ++ .start = AR71XX_GE1_BASE, ++ .end = AR71XX_GE1_BASE + 0x200 - 1, ++ } ++}; ++ ++struct ag71xx_mdio_platform_data ath79_mdio1_data; ++ ++struct platform_device ath79_mdio1_device = { ++ .name = "ag71xx-mdio", ++ .id = 1, ++ .resource = ath79_mdio1_resources, ++ .num_resources = ARRAY_SIZE(ath79_mdio1_resources), ++ .dev = { ++ .platform_data = &ath79_mdio1_data, ++ }, ++}; ++ ++static void ath79_set_pll(u32 cfg_reg, u32 pll_reg, u32 pll_val, u32 shift) ++{ ++ void __iomem *base; ++ u32 t; ++ ++ base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ ++ t = __raw_readl(base + cfg_reg); ++ t &= ~(3 << shift); ++ t |= (2 << shift); ++ __raw_writel(t, base + cfg_reg); ++ udelay(100); ++ ++ __raw_writel(pll_val, base + pll_reg); ++ ++ t |= (3 << shift); ++ __raw_writel(t, base + cfg_reg); ++ udelay(100); ++ ++ t &= ~(3 << shift); ++ __raw_writel(t, base + cfg_reg); ++ udelay(100); ++ ++ printk(KERN_DEBUG "ar71xx: pll_reg %#x: %#x\n", ++ (unsigned int)(base + pll_reg), __raw_readl(base + pll_reg)); ++ ++ iounmap(base); ++} ++ ++static void __init ath79_mii_ctrl_set_if(unsigned int reg, ++ unsigned int mii_if) ++{ ++ void __iomem *base; ++ u32 t; ++ ++ base = ioremap(AR71XX_MII_BASE, AR71XX_MII_SIZE); ++ ++ t = __raw_readl(base + reg); ++ t &= ~(AR71XX_MII_CTRL_IF_MASK); ++ t |= (mii_if & AR71XX_MII_CTRL_IF_MASK); ++ __raw_writel(t, base + reg); ++ ++ iounmap(base); ++} ++ ++static void ath79_mii_ctrl_set_speed(unsigned int reg, unsigned int speed) ++{ ++ void __iomem *base; ++ unsigned int mii_speed; ++ u32 t; ++ ++ switch (speed) { ++ case SPEED_10: ++ mii_speed = AR71XX_MII_CTRL_SPEED_10; ++ break; ++ case SPEED_100: ++ mii_speed = AR71XX_MII_CTRL_SPEED_100; ++ break; ++ case SPEED_1000: ++ mii_speed = AR71XX_MII_CTRL_SPEED_1000; ++ break; ++ default: ++ BUG(); ++ } ++ ++ base = ioremap(AR71XX_MII_BASE, AR71XX_MII_SIZE); ++ ++ t = __raw_readl(base + reg); ++ t &= ~(AR71XX_MII_CTRL_SPEED_MASK << AR71XX_MII_CTRL_SPEED_SHIFT); ++ t |= mii_speed << AR71XX_MII_CTRL_SPEED_SHIFT; ++ __raw_writel(t, base + reg); ++ ++ iounmap(base); ++} ++ ++static unsigned long ar934x_get_mdio_ref_clock(void) ++{ ++ void __iomem *base; ++ unsigned long ret; ++ u32 t; ++ ++ base = ioremap(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ ++ ret = 0; ++ t = __raw_readl(base + AR934X_PLL_SWITCH_CLOCK_CONTROL_REG); ++ if (t & AR934X_PLL_SWITCH_CLOCK_CONTROL_MDIO_CLK_SEL) { ++ ret = 100 * 1000 * 1000; ++ } else { ++ struct clk *clk; ++ ++ clk = clk_get(NULL, "ref"); ++ if (!IS_ERR(clk)) ++ ret = clk_get_rate(clk); ++ } ++ ++ iounmap(base); ++ ++ return ret; ++} ++ ++void __init ath79_register_mdio(unsigned int id, u32 phy_mask) ++{ ++ struct platform_device *mdio_dev; ++ struct ag71xx_mdio_platform_data *mdio_data; ++ unsigned int max_id; ++ ++ if (ath79_soc == ATH79_SOC_AR9341 || ++ ath79_soc == ATH79_SOC_AR9342 || ++ ath79_soc == ATH79_SOC_AR9344 || ++ ath79_soc == ATH79_SOC_QCA9556 || ++ ath79_soc == ATH79_SOC_QCA9558) ++ max_id = 1; ++ else ++ max_id = 0; ++ ++ if (id > max_id) { ++ printk(KERN_ERR "ar71xx: invalid MDIO id %u\n", id); ++ return; ++ } ++ ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7241: ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ mdio_dev = &ath79_mdio1_device; ++ mdio_data = &ath79_mdio1_data; ++ break; ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ if (id == 0) { ++ mdio_dev = &ath79_mdio0_device; ++ mdio_data = &ath79_mdio0_data; ++ } else { ++ mdio_dev = &ath79_mdio1_device; ++ mdio_data = &ath79_mdio1_data; ++ } ++ break; ++ ++ case ATH79_SOC_AR7242: ++ ath79_set_pll(AR71XX_PLL_REG_SEC_CONFIG, ++ AR7242_PLL_REG_ETH0_INT_CLOCK, 0x62000000, ++ AR71XX_ETH0_PLL_SHIFT); ++ /* fall through */ ++ default: ++ mdio_dev = &ath79_mdio0_device; ++ mdio_data = &ath79_mdio0_data; ++ break; ++ } ++ ++ mdio_data->phy_mask = phy_mask; ++ ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7240: ++ mdio_data->is_ar7240 = 1; ++ /* fall through */ ++ case ATH79_SOC_AR7241: ++ mdio_data->builtin_switch = 1; ++ break; ++ ++ case ATH79_SOC_AR9330: ++ mdio_data->is_ar9330 = 1; ++ /* fall through */ ++ case ATH79_SOC_AR9331: ++ mdio_data->builtin_switch = 1; ++ break; ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ if (id == 1) { ++ mdio_data->builtin_switch = 1; ++ mdio_data->ref_clock = ar934x_get_mdio_ref_clock(); ++ mdio_data->mdio_clock = 6250000; ++ } ++ mdio_data->is_ar934x = 1; ++ break; ++ ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ mdio_data->is_ar934x = 1; ++ break; ++ ++ default: ++ break; ++ } ++ ++ platform_device_register(mdio_dev); ++} ++ ++struct ath79_eth_pll_data ath79_eth0_pll_data; ++struct ath79_eth_pll_data ath79_eth1_pll_data; ++ ++static u32 ath79_get_eth_pll(unsigned int mac, int speed) ++{ ++ struct ath79_eth_pll_data *pll_data; ++ u32 pll_val; ++ ++ switch (mac) { ++ case 0: ++ pll_data = &ath79_eth0_pll_data; ++ break; ++ case 1: ++ pll_data = &ath79_eth1_pll_data; ++ break; ++ default: ++ BUG(); ++ } ++ ++ switch (speed) { ++ case SPEED_10: ++ pll_val = pll_data->pll_10; ++ break; ++ case SPEED_100: ++ pll_val = pll_data->pll_100; ++ break; ++ case SPEED_1000: ++ pll_val = pll_data->pll_1000; ++ break; ++ default: ++ BUG(); ++ } ++ ++ return pll_val; ++} ++ ++static void ath79_set_speed_ge0(int speed) ++{ ++ u32 val = ath79_get_eth_pll(0, speed); ++ ++ ath79_set_pll(AR71XX_PLL_REG_SEC_CONFIG, AR71XX_PLL_REG_ETH0_INT_CLOCK, ++ val, AR71XX_ETH0_PLL_SHIFT); ++ ath79_mii_ctrl_set_speed(AR71XX_MII_REG_MII0_CTRL, speed); ++} ++ ++static void ath79_set_speed_ge1(int speed) ++{ ++ u32 val = ath79_get_eth_pll(1, speed); ++ ++ ath79_set_pll(AR71XX_PLL_REG_SEC_CONFIG, AR71XX_PLL_REG_ETH1_INT_CLOCK, ++ val, AR71XX_ETH1_PLL_SHIFT); ++ ath79_mii_ctrl_set_speed(AR71XX_MII_REG_MII1_CTRL, speed); ++} ++ ++static void ar7242_set_speed_ge0(int speed) ++{ ++ u32 val = ath79_get_eth_pll(0, speed); ++ void __iomem *base; ++ ++ base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ __raw_writel(val, base + AR7242_PLL_REG_ETH0_INT_CLOCK); ++ iounmap(base); ++} ++ ++static void ar91xx_set_speed_ge0(int speed) ++{ ++ u32 val = ath79_get_eth_pll(0, speed); ++ ++ ath79_set_pll(AR913X_PLL_REG_ETH_CONFIG, AR913X_PLL_REG_ETH0_INT_CLOCK, ++ val, AR913X_ETH0_PLL_SHIFT); ++ ath79_mii_ctrl_set_speed(AR71XX_MII_REG_MII0_CTRL, speed); ++} ++ ++static void ar91xx_set_speed_ge1(int speed) ++{ ++ u32 val = ath79_get_eth_pll(1, speed); ++ ++ ath79_set_pll(AR913X_PLL_REG_ETH_CONFIG, AR913X_PLL_REG_ETH1_INT_CLOCK, ++ val, AR913X_ETH1_PLL_SHIFT); ++ ath79_mii_ctrl_set_speed(AR71XX_MII_REG_MII1_CTRL, speed); ++} ++ ++static void ar934x_set_speed_ge0(int speed) ++{ ++ void __iomem *base; ++ u32 val = ath79_get_eth_pll(0, speed); ++ ++ base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ __raw_writel(val, base + AR934X_PLL_ETH_XMII_CONTROL_REG); ++ iounmap(base); ++} ++ ++static void qca955x_set_speed_xmii(int speed) ++{ ++ void __iomem *base; ++ u32 val = ath79_get_eth_pll(0, speed); ++ ++ base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ __raw_writel(val, base + QCA955X_PLL_ETH_XMII_CONTROL_REG); ++ iounmap(base); ++} ++ ++static void qca955x_set_speed_sgmii(int speed) ++{ ++ void __iomem *base; ++ u32 val = ath79_get_eth_pll(1, speed); ++ ++ base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); ++ __raw_writel(val, base + QCA955X_PLL_ETH_SGMII_CONTROL_REG); ++ iounmap(base); ++} ++ ++static void ath79_set_speed_dummy(int speed) ++{ ++} ++ ++static void ath79_ddr_no_flush(void) ++{ ++} ++ ++static void ath79_ddr_flush_ge0(void) ++{ ++ ath79_ddr_wb_flush(AR71XX_DDR_REG_FLUSH_GE0); ++} ++ ++static void ath79_ddr_flush_ge1(void) ++{ ++ ath79_ddr_wb_flush(AR71XX_DDR_REG_FLUSH_GE1); ++} ++ ++static void ar724x_ddr_flush_ge0(void) ++{ ++ ath79_ddr_wb_flush(AR724X_DDR_REG_FLUSH_GE0); ++} ++ ++static void ar724x_ddr_flush_ge1(void) ++{ ++ ath79_ddr_wb_flush(AR724X_DDR_REG_FLUSH_GE1); ++} ++ ++static void ar91xx_ddr_flush_ge0(void) ++{ ++ ath79_ddr_wb_flush(AR913X_DDR_REG_FLUSH_GE0); ++} ++ ++static void ar91xx_ddr_flush_ge1(void) ++{ ++ ath79_ddr_wb_flush(AR913X_DDR_REG_FLUSH_GE1); ++} ++ ++static void ar933x_ddr_flush_ge0(void) ++{ ++ ath79_ddr_wb_flush(AR933X_DDR_REG_FLUSH_GE0); ++} ++ ++static void ar933x_ddr_flush_ge1(void) ++{ ++ ath79_ddr_wb_flush(AR933X_DDR_REG_FLUSH_GE1); ++} ++ ++static struct resource ath79_eth0_resources[] = { ++ { ++ .name = "mac_base", ++ .flags = IORESOURCE_MEM, ++ .start = AR71XX_GE0_BASE, ++ .end = AR71XX_GE0_BASE + 0x200 - 1, ++ }, { ++ .name = "mac_irq", ++ .flags = IORESOURCE_IRQ, ++ .start = ATH79_CPU_IRQ(4), ++ .end = ATH79_CPU_IRQ(4), ++ }, ++}; ++ ++struct ag71xx_platform_data ath79_eth0_data = { ++ .reset_bit = AR71XX_RESET_GE0_MAC, ++}; ++ ++struct platform_device ath79_eth0_device = { ++ .name = "ag71xx", ++ .id = 0, ++ .resource = ath79_eth0_resources, ++ .num_resources = ARRAY_SIZE(ath79_eth0_resources), ++ .dev = { ++ .platform_data = &ath79_eth0_data, ++ }, ++}; ++ ++static struct resource ath79_eth1_resources[] = { ++ { ++ .name = "mac_base", ++ .flags = IORESOURCE_MEM, ++ .start = AR71XX_GE1_BASE, ++ .end = AR71XX_GE1_BASE + 0x200 - 1, ++ }, { ++ .name = "mac_irq", ++ .flags = IORESOURCE_IRQ, ++ .start = ATH79_CPU_IRQ(5), ++ .end = ATH79_CPU_IRQ(5), ++ }, ++}; ++ ++struct ag71xx_platform_data ath79_eth1_data = { ++ .reset_bit = AR71XX_RESET_GE1_MAC, ++}; ++ ++struct platform_device ath79_eth1_device = { ++ .name = "ag71xx", ++ .id = 1, ++ .resource = ath79_eth1_resources, ++ .num_resources = ARRAY_SIZE(ath79_eth1_resources), ++ .dev = { ++ .platform_data = &ath79_eth1_data, ++ }, ++}; ++ ++struct ag71xx_switch_platform_data ath79_switch_data; ++ ++#define AR71XX_PLL_VAL_1000 0x00110000 ++#define AR71XX_PLL_VAL_100 0x00001099 ++#define AR71XX_PLL_VAL_10 0x00991099 ++ ++#define AR724X_PLL_VAL_1000 0x00110000 ++#define AR724X_PLL_VAL_100 0x00001099 ++#define AR724X_PLL_VAL_10 0x00991099 ++ ++#define AR7242_PLL_VAL_1000 0x16000000 ++#define AR7242_PLL_VAL_100 0x00000101 ++#define AR7242_PLL_VAL_10 0x00001616 ++ ++#define AR913X_PLL_VAL_1000 0x1a000000 ++#define AR913X_PLL_VAL_100 0x13000a44 ++#define AR913X_PLL_VAL_10 0x00441099 ++ ++#define AR933X_PLL_VAL_1000 0x00110000 ++#define AR933X_PLL_VAL_100 0x00001099 ++#define AR933X_PLL_VAL_10 0x00991099 ++ ++#define AR934X_PLL_VAL_1000 0x16000000 ++#define AR934X_PLL_VAL_100 0x00000101 ++#define AR934X_PLL_VAL_10 0x00001616 ++ ++static void __init ath79_init_eth_pll_data(unsigned int id) ++{ ++ struct ath79_eth_pll_data *pll_data; ++ u32 pll_10, pll_100, pll_1000; ++ ++ switch (id) { ++ case 0: ++ pll_data = &ath79_eth0_pll_data; ++ break; ++ case 1: ++ pll_data = &ath79_eth1_pll_data; ++ break; ++ default: ++ BUG(); ++ } ++ ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7130: ++ case ATH79_SOC_AR7141: ++ case ATH79_SOC_AR7161: ++ pll_10 = AR71XX_PLL_VAL_10; ++ pll_100 = AR71XX_PLL_VAL_100; ++ pll_1000 = AR71XX_PLL_VAL_1000; ++ break; ++ ++ case ATH79_SOC_AR7240: ++ case ATH79_SOC_AR7241: ++ pll_10 = AR724X_PLL_VAL_10; ++ pll_100 = AR724X_PLL_VAL_100; ++ pll_1000 = AR724X_PLL_VAL_1000; ++ break; ++ ++ case ATH79_SOC_AR7242: ++ pll_10 = AR7242_PLL_VAL_10; ++ pll_100 = AR7242_PLL_VAL_100; ++ pll_1000 = AR7242_PLL_VAL_1000; ++ break; ++ ++ case ATH79_SOC_AR9130: ++ case ATH79_SOC_AR9132: ++ pll_10 = AR913X_PLL_VAL_10; ++ pll_100 = AR913X_PLL_VAL_100; ++ pll_1000 = AR913X_PLL_VAL_1000; ++ break; ++ ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ pll_10 = AR933X_PLL_VAL_10; ++ pll_100 = AR933X_PLL_VAL_100; ++ pll_1000 = AR933X_PLL_VAL_1000; ++ break; ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ pll_10 = AR934X_PLL_VAL_10; ++ pll_100 = AR934X_PLL_VAL_100; ++ pll_1000 = AR934X_PLL_VAL_1000; ++ break; ++ ++ default: ++ BUG(); ++ } ++ ++ if (!pll_data->pll_10) ++ pll_data->pll_10 = pll_10; ++ ++ if (!pll_data->pll_100) ++ pll_data->pll_100 = pll_100; ++ ++ if (!pll_data->pll_1000) ++ pll_data->pll_1000 = pll_1000; ++} ++ ++static int __init ath79_setup_phy_if_mode(unsigned int id, ++ struct ag71xx_platform_data *pdata) ++{ ++ unsigned int mii_if; ++ ++ switch (id) { ++ case 0: ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7130: ++ case ATH79_SOC_AR7141: ++ case ATH79_SOC_AR7161: ++ case ATH79_SOC_AR9130: ++ case ATH79_SOC_AR9132: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_MII: ++ mii_if = AR71XX_MII0_CTRL_IF_MII; ++ break; ++ case PHY_INTERFACE_MODE_GMII: ++ mii_if = AR71XX_MII0_CTRL_IF_GMII; ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ mii_if = AR71XX_MII0_CTRL_IF_RGMII; ++ break; ++ case PHY_INTERFACE_MODE_RMII: ++ mii_if = AR71XX_MII0_CTRL_IF_RMII; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ath79_mii_ctrl_set_if(AR71XX_MII_REG_MII0_CTRL, mii_if); ++ break; ++ ++ case ATH79_SOC_AR7240: ++ case ATH79_SOC_AR7241: ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ pdata->phy_if_mode = PHY_INTERFACE_MODE_MII; ++ break; ++ ++ case ATH79_SOC_AR7242: ++ /* FIXME */ ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_MII: ++ case PHY_INTERFACE_MODE_GMII: ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_RMII: ++ break; ++ default: ++ return -EINVAL; ++ } ++ break; ++ ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_MII: ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_SGMII: ++ break; ++ default: ++ return -EINVAL; ++ } ++ break; ++ ++ default: ++ BUG(); ++ } ++ break; ++ case 1: ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7130: ++ case ATH79_SOC_AR7141: ++ case ATH79_SOC_AR7161: ++ case ATH79_SOC_AR9130: ++ case ATH79_SOC_AR9132: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_RMII: ++ mii_if = AR71XX_MII1_CTRL_IF_RMII; ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ mii_if = AR71XX_MII1_CTRL_IF_RGMII; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ath79_mii_ctrl_set_if(AR71XX_MII_REG_MII1_CTRL, mii_if); ++ break; ++ ++ case ATH79_SOC_AR7240: ++ case ATH79_SOC_AR7241: ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ pdata->phy_if_mode = PHY_INTERFACE_MODE_GMII; ++ break; ++ ++ case ATH79_SOC_AR7242: ++ /* FIXME */ ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_MII: ++ case PHY_INTERFACE_MODE_GMII: ++ break; ++ default: ++ return -EINVAL; ++ } ++ break; ++ ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_MII: ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_SGMII: ++ break; ++ default: ++ return -EINVAL; ++ } ++ break; ++ ++ default: ++ BUG(); ++ } ++ break; ++ } ++ ++ return 0; ++} ++ ++void __init ath79_setup_ar933x_phy4_switch(bool mac, bool mdio) ++{ ++ void __iomem *base; ++ u32 t; ++ ++ base = ioremap(AR933X_GMAC_BASE, AR933X_GMAC_SIZE); ++ ++ t = __raw_readl(base + AR933X_GMAC_REG_ETH_CFG); ++ t &= ~(AR933X_ETH_CFG_SW_PHY_SWAP | AR933X_ETH_CFG_SW_PHY_ADDR_SWAP); ++ if (mac) ++ t |= AR933X_ETH_CFG_SW_PHY_SWAP; ++ if (mdio) ++ t |= AR933X_ETH_CFG_SW_PHY_ADDR_SWAP; ++ __raw_writel(t, base + AR933X_GMAC_REG_ETH_CFG); ++ ++ iounmap(base); ++} ++ ++void __init ath79_setup_ar934x_eth_cfg(u32 mask) ++{ ++ void __iomem *base; ++ u32 t; ++ ++ base = ioremap(AR934X_GMAC_BASE, AR934X_GMAC_SIZE); ++ ++ t = __raw_readl(base + AR934X_GMAC_REG_ETH_CFG); ++ ++ t &= ~(AR934X_ETH_CFG_RGMII_GMAC0 | ++ AR934X_ETH_CFG_MII_GMAC0 | ++ AR934X_ETH_CFG_GMII_GMAC0 | ++ AR934X_ETH_CFG_SW_ONLY_MODE | ++ AR934X_ETH_CFG_SW_PHY_SWAP); ++ ++ t |= mask; ++ ++ __raw_writel(t, base + AR934X_GMAC_REG_ETH_CFG); ++ /* flush write */ ++ __raw_readl(base + AR934X_GMAC_REG_ETH_CFG); ++ ++ iounmap(base); ++} ++ ++static int ath79_eth_instance __initdata; ++void __init ath79_register_eth(unsigned int id) ++{ ++ struct platform_device *pdev; ++ struct ag71xx_platform_data *pdata; ++ int err; ++ ++ if (id > 1) { ++ printk(KERN_ERR "ar71xx: invalid ethernet id %d\n", id); ++ return; ++ } ++ ++ ath79_init_eth_pll_data(id); ++ ++ if (id == 0) ++ pdev = &ath79_eth0_device; ++ else ++ pdev = &ath79_eth1_device; ++ ++ pdata = pdev->dev.platform_data; ++ ++ pdata->max_frame_len = 1540; ++ pdata->desc_pktlen_mask = 0xfff; ++ ++ err = ath79_setup_phy_if_mode(id, pdata); ++ if (err) { ++ printk(KERN_ERR ++ "ar71xx: invalid PHY interface mode for GE%u\n", id); ++ return; ++ } ++ ++ switch (ath79_soc) { ++ case ATH79_SOC_AR7130: ++ if (id == 0) { ++ pdata->ddr_flush = ath79_ddr_flush_ge0; ++ pdata->set_speed = ath79_set_speed_ge0; ++ } else { ++ pdata->ddr_flush = ath79_ddr_flush_ge1; ++ pdata->set_speed = ath79_set_speed_ge1; ++ } ++ break; ++ ++ case ATH79_SOC_AR7141: ++ case ATH79_SOC_AR7161: ++ if (id == 0) { ++ pdata->ddr_flush = ath79_ddr_flush_ge0; ++ pdata->set_speed = ath79_set_speed_ge0; ++ } else { ++ pdata->ddr_flush = ath79_ddr_flush_ge1; ++ pdata->set_speed = ath79_set_speed_ge1; ++ } ++ pdata->has_gbit = 1; ++ break; ++ ++ case ATH79_SOC_AR7242: ++ if (id == 0) { ++ pdata->reset_bit |= AR724X_RESET_GE0_MDIO | ++ AR71XX_RESET_GE0_PHY; ++ pdata->ddr_flush = ar724x_ddr_flush_ge0; ++ pdata->set_speed = ar7242_set_speed_ge0; ++ } else { ++ pdata->reset_bit |= AR724X_RESET_GE1_MDIO | ++ AR71XX_RESET_GE1_PHY; ++ pdata->ddr_flush = ar724x_ddr_flush_ge1; ++ pdata->set_speed = ath79_set_speed_dummy; ++ } ++ pdata->has_gbit = 1; ++ pdata->is_ar724x = 1; ++ ++ if (!pdata->fifo_cfg1) ++ pdata->fifo_cfg1 = 0x0010ffff; ++ if (!pdata->fifo_cfg2) ++ pdata->fifo_cfg2 = 0x015500aa; ++ if (!pdata->fifo_cfg3) ++ pdata->fifo_cfg3 = 0x01f00140; ++ break; ++ ++ case ATH79_SOC_AR7241: ++ if (id == 0) ++ pdata->reset_bit |= AR724X_RESET_GE0_MDIO; ++ else ++ pdata->reset_bit |= AR724X_RESET_GE1_MDIO; ++ /* fall through */ ++ case ATH79_SOC_AR7240: ++ if (id == 0) { ++ pdata->reset_bit |= AR71XX_RESET_GE0_PHY; ++ pdata->ddr_flush = ar724x_ddr_flush_ge0; ++ pdata->set_speed = ath79_set_speed_dummy; ++ ++ pdata->phy_mask = BIT(4); ++ } else { ++ pdata->reset_bit |= AR71XX_RESET_GE1_PHY; ++ pdata->ddr_flush = ar724x_ddr_flush_ge1; ++ pdata->set_speed = ath79_set_speed_dummy; ++ ++ pdata->speed = SPEED_1000; ++ pdata->duplex = DUPLEX_FULL; ++ pdata->switch_data = &ath79_switch_data; ++ ++ ath79_switch_data.phy_poll_mask |= BIT(4); ++ } ++ pdata->has_gbit = 1; ++ pdata->is_ar724x = 1; ++ if (ath79_soc == ATH79_SOC_AR7240) ++ pdata->is_ar7240 = 1; ++ ++ if (!pdata->fifo_cfg1) ++ pdata->fifo_cfg1 = 0x0010ffff; ++ if (!pdata->fifo_cfg2) ++ pdata->fifo_cfg2 = 0x015500aa; ++ if (!pdata->fifo_cfg3) ++ pdata->fifo_cfg3 = 0x01f00140; ++ break; ++ ++ case ATH79_SOC_AR9130: ++ if (id == 0) { ++ pdata->ddr_flush = ar91xx_ddr_flush_ge0; ++ pdata->set_speed = ar91xx_set_speed_ge0; ++ } else { ++ pdata->ddr_flush = ar91xx_ddr_flush_ge1; ++ pdata->set_speed = ar91xx_set_speed_ge1; ++ } ++ pdata->is_ar91xx = 1; ++ break; ++ ++ case ATH79_SOC_AR9132: ++ if (id == 0) { ++ pdata->ddr_flush = ar91xx_ddr_flush_ge0; ++ pdata->set_speed = ar91xx_set_speed_ge0; ++ } else { ++ pdata->ddr_flush = ar91xx_ddr_flush_ge1; ++ pdata->set_speed = ar91xx_set_speed_ge1; ++ } ++ pdata->is_ar91xx = 1; ++ pdata->has_gbit = 1; ++ break; ++ ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ if (id == 0) { ++ pdata->reset_bit = AR933X_RESET_GE0_MAC | ++ AR933X_RESET_GE0_MDIO; ++ pdata->ddr_flush = ar933x_ddr_flush_ge0; ++ pdata->set_speed = ath79_set_speed_dummy; ++ ++ pdata->phy_mask = BIT(4); ++ } else { ++ pdata->reset_bit = AR933X_RESET_GE1_MAC | ++ AR933X_RESET_GE1_MDIO; ++ pdata->ddr_flush = ar933x_ddr_flush_ge1; ++ pdata->set_speed = ath79_set_speed_dummy; ++ ++ pdata->speed = SPEED_1000; ++ pdata->duplex = DUPLEX_FULL; ++ pdata->switch_data = &ath79_switch_data; ++ ++ ath79_switch_data.phy_poll_mask |= BIT(4); ++ } ++ ++ pdata->has_gbit = 1; ++ pdata->is_ar724x = 1; ++ ++ if (!pdata->fifo_cfg1) ++ pdata->fifo_cfg1 = 0x0010ffff; ++ if (!pdata->fifo_cfg2) ++ pdata->fifo_cfg2 = 0x015500aa; ++ if (!pdata->fifo_cfg3) ++ pdata->fifo_cfg3 = 0x01f00140; ++ break; ++ ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ if (id == 0) { ++ pdata->reset_bit = AR934X_RESET_GE0_MAC | ++ AR934X_RESET_GE0_MDIO; ++ pdata->set_speed = ar934x_set_speed_ge0; ++ } else { ++ pdata->reset_bit = AR934X_RESET_GE1_MAC | ++ AR934X_RESET_GE1_MDIO; ++ pdata->set_speed = ath79_set_speed_dummy; ++ ++ pdata->switch_data = &ath79_switch_data; ++ ++ /* reset the built-in switch */ ++ ath79_device_reset_set(AR934X_RESET_ETH_SWITCH); ++ ath79_device_reset_clear(AR934X_RESET_ETH_SWITCH); ++ } ++ ++ pdata->ddr_flush = ath79_ddr_no_flush; ++ pdata->has_gbit = 1; ++ pdata->is_ar724x = 1; ++ ++ pdata->max_frame_len = SZ_16K - 1; ++ pdata->desc_pktlen_mask = SZ_16K - 1; ++ ++ if (!pdata->fifo_cfg1) ++ pdata->fifo_cfg1 = 0x0010ffff; ++ if (!pdata->fifo_cfg2) ++ pdata->fifo_cfg2 = 0x015500aa; ++ if (!pdata->fifo_cfg3) ++ pdata->fifo_cfg3 = 0x01f00140; ++ break; ++ ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ if (id == 0) { ++ pdata->reset_bit = QCA955X_RESET_GE0_MAC | ++ QCA955X_RESET_GE0_MDIO; ++ pdata->set_speed = qca955x_set_speed_xmii; ++ } else { ++ pdata->reset_bit = QCA955X_RESET_GE1_MAC | ++ QCA955X_RESET_GE1_MDIO; ++ pdata->set_speed = qca955x_set_speed_sgmii; ++ } ++ ++ pdata->ddr_flush = ath79_ddr_no_flush; ++ pdata->has_gbit = 1; ++ pdata->is_ar724x = 1; ++ ++ /* ++ * Limit the maximum frame length to 4095 bytes. ++ * Although the documentation says that the hardware ++ * limit is 16383 bytes but that does not work in ++ * practice. It seems that the hardware only updates ++ * the lowest 12 bits of the packet length field ++ * in the RX descriptor. ++ */ ++ pdata->max_frame_len = SZ_4K - 1; ++ pdata->desc_pktlen_mask = SZ_16K - 1; ++ ++ if (!pdata->fifo_cfg1) ++ pdata->fifo_cfg1 = 0x0010ffff; ++ if (!pdata->fifo_cfg2) ++ pdata->fifo_cfg2 = 0x015500aa; ++ if (!pdata->fifo_cfg3) ++ pdata->fifo_cfg3 = 0x01f00140; ++ break; ++ ++ default: ++ BUG(); ++ } ++ ++ switch (pdata->phy_if_mode) { ++ case PHY_INTERFACE_MODE_GMII: ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_SGMII: ++ if (!pdata->has_gbit) { ++ printk(KERN_ERR "ar71xx: no gbit available on eth%d\n", ++ id); ++ return; ++ } ++ /* fallthrough */ ++ default: ++ break; ++ } ++ ++ if (!is_valid_ether_addr(pdata->mac_addr)) { ++ random_ether_addr(pdata->mac_addr); ++ printk(KERN_DEBUG ++ "ar71xx: using random MAC address for eth%d\n", ++ ath79_eth_instance); ++ } ++ ++ if (pdata->mii_bus_dev == NULL) { ++ switch (ath79_soc) { ++ case ATH79_SOC_AR9341: ++ case ATH79_SOC_AR9342: ++ case ATH79_SOC_AR9344: ++ if (id == 0) ++ pdata->mii_bus_dev = &ath79_mdio0_device.dev; ++ else ++ pdata->mii_bus_dev = &ath79_mdio1_device.dev; ++ break; ++ ++ case ATH79_SOC_AR7241: ++ case ATH79_SOC_AR9330: ++ case ATH79_SOC_AR9331: ++ pdata->mii_bus_dev = &ath79_mdio1_device.dev; ++ break; ++ ++ case ATH79_SOC_QCA9556: ++ case ATH79_SOC_QCA9558: ++ /* don't assign any MDIO device by default */ ++ break; ++ ++ default: ++ pdata->mii_bus_dev = &ath79_mdio0_device.dev; ++ break; ++ } ++ } ++ ++ /* Reset the device */ ++ ath79_device_reset_set(pdata->reset_bit); ++ mdelay(100); ++ ++ ath79_device_reset_clear(pdata->reset_bit); ++ mdelay(100); ++ ++ platform_device_register(pdev); ++ ath79_eth_instance++; ++} ++ ++void __init ath79_set_mac_base(unsigned char *mac) ++{ ++ memcpy(ath79_mac_base, mac, ETH_ALEN); ++} ++ ++void __init ath79_parse_ascii_mac(char *mac_str, u8 *mac) ++{ ++ int t; ++ ++ t = sscanf(mac_str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", ++ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); ++ ++ if (t != ETH_ALEN) ++ t = sscanf(mac_str, "%02hhx.%02hhx.%02hhx.%02hhx.%02hhx.%02hhx", ++ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); ++ ++ if (t != ETH_ALEN || !is_valid_ether_addr(mac)) { ++ memset(mac, 0, ETH_ALEN); ++ printk(KERN_DEBUG "ar71xx: invalid mac address \"%s\"\n", ++ mac_str); ++ } ++} ++ ++static void __init ath79_set_mac_base_ascii(char *str) ++{ ++ u8 mac[ETH_ALEN]; ++ ++ ath79_parse_ascii_mac(str, mac); ++ ath79_set_mac_base(mac); ++} ++ ++static int __init ath79_ethaddr_setup(char *str) ++{ ++ ath79_set_mac_base_ascii(str); ++ return 1; ++} ++__setup("ethaddr=", ath79_ethaddr_setup); ++ ++static int __init ath79_kmac_setup(char *str) ++{ ++ ath79_set_mac_base_ascii(str); ++ return 1; ++} ++__setup("kmac=", ath79_kmac_setup); ++ ++void __init ath79_init_mac(unsigned char *dst, const unsigned char *src, ++ int offset) ++{ ++ int t; ++ ++ if (!dst) ++ return; ++ ++ if (!src || !is_valid_ether_addr(src)) { ++ memset(dst, '\0', ETH_ALEN); ++ return; ++ } ++ ++ t = (((u32) src[3]) << 16) + (((u32) src[4]) << 8) + ((u32) src[5]); ++ t += offset; ++ ++ dst[0] = src[0]; ++ dst[1] = src[1]; ++ dst[2] = src[2]; ++ dst[3] = (t >> 16) & 0xff; ++ dst[4] = (t >> 8) & 0xff; ++ dst[5] = t & 0xff; ++} ++ ++void __init ath79_init_local_mac(unsigned char *dst, const unsigned char *src) ++{ ++ int i; ++ ++ if (!dst) ++ return; ++ ++ if (!src || !is_valid_ether_addr(src)) { ++ memset(dst, '\0', ETH_ALEN); ++ return; ++ } ++ ++ for (i = 0; i < ETH_ALEN; i++) ++ dst[i] = src[i]; ++ dst[0] |= 0x02; ++} +diff --git a/arch/mips/ath79/dev-eth.h b/arch/mips/ath79/dev-eth.h +new file mode 100644 +index 0000000..ff26ec4 +--- /dev/null ++++ b/arch/mips/ath79/dev-eth.h +@@ -0,0 +1,51 @@ ++/* ++ * Atheros AR71xx SoC device definitions ++ * ++ * Copyright (C) 2008-2012 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * 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. ++ */ ++ ++#ifndef _ATH79_DEV_ETH_H ++#define _ATH79_DEV_ETH_H ++ ++#include <asm/mach-ath79/ag71xx_platform.h> ++ ++struct platform_device; ++ ++extern unsigned char ath79_mac_base[] __initdata; ++void ath79_parse_ascii_mac(char *mac_str, u8 *mac); ++void ath79_init_mac(unsigned char *dst, const unsigned char *src, ++ int offset); ++void ath79_init_local_mac(unsigned char *dst, const unsigned char *src); ++ ++struct ath79_eth_pll_data { ++ u32 pll_10; ++ u32 pll_100; ++ u32 pll_1000; ++}; ++ ++extern struct ath79_eth_pll_data ath79_eth0_pll_data; ++extern struct ath79_eth_pll_data ath79_eth1_pll_data; ++ ++extern struct ag71xx_platform_data ath79_eth0_data; ++extern struct ag71xx_platform_data ath79_eth1_data; ++extern struct platform_device ath79_eth0_device; ++extern struct platform_device ath79_eth1_device; ++void ath79_register_eth(unsigned int id); ++ ++extern struct ag71xx_switch_platform_data ath79_switch_data; ++ ++extern struct ag71xx_mdio_platform_data ath79_mdio0_data; ++extern struct ag71xx_mdio_platform_data ath79_mdio1_data; ++extern struct platform_device ath79_mdio0_device; ++extern struct platform_device ath79_mdio1_device; ++void ath79_register_mdio(unsigned int id, u32 phy_mask); ++ ++void ath79_setup_ar933x_phy4_switch(bool mac, bool mdio); ++void ath79_setup_ar934x_eth_cfg(u32 mask); ++ ++#endif /* _ATH79_DEV_ETH_H */ +diff --git a/arch/mips/include/asm/mach-ath79/ar71xx_regs.h b/arch/mips/include/asm/mach-ath79/ar71xx_regs.h +index cd41e93..3e6b2ed 100644 +--- a/arch/mips/include/asm/mach-ath79/ar71xx_regs.h ++++ b/arch/mips/include/asm/mach-ath79/ar71xx_regs.h +@@ -20,6 +20,10 @@ + #include <linux/bitops.h> + + #define AR71XX_APB_BASE 0x18000000 ++#define AR71XX_GE0_BASE 0x19000000 ++#define AR71XX_GE0_SIZE 0x10000 ++#define AR71XX_GE1_BASE 0x1a000000 ++#define AR71XX_GE1_SIZE 0x10000 + #define AR71XX_EHCI_BASE 0x1b000000 + #define AR71XX_EHCI_SIZE 0x1000 + #define AR71XX_OHCI_BASE 0x1c000000 +@@ -39,6 +43,8 @@ + #define AR71XX_PLL_SIZE 0x100 + #define AR71XX_RESET_BASE (AR71XX_APB_BASE + 0x00060000) + #define AR71XX_RESET_SIZE 0x100 ++#define AR71XX_MII_BASE (AR71XX_APB_BASE + 0x00070000) ++#define AR71XX_MII_SIZE 0x100 + + #define AR71XX_PCI_MEM_BASE 0x10000000 + #define AR71XX_PCI_MEM_SIZE 0x07000000 +@@ -81,11 +87,15 @@ + + #define AR933X_UART_BASE (AR71XX_APB_BASE + 0x00020000) + #define AR933X_UART_SIZE 0x14 ++#define AR933X_GMAC_BASE (AR71XX_APB_BASE + 0x00070000) ++#define AR933X_GMAC_SIZE 0x04 + #define AR933X_WMAC_BASE (AR71XX_APB_BASE + 0x00100000) + #define AR933X_WMAC_SIZE 0x20000 + #define AR933X_EHCI_BASE 0x1b000000 + #define AR933X_EHCI_SIZE 0x1000 + ++#define AR934X_GMAC_BASE (AR71XX_APB_BASE + 0x00070000) ++#define AR934X_GMAC_SIZE 0x14 + #define AR934X_WMAC_BASE (AR71XX_APB_BASE + 0x00100000) + #define AR934X_WMAC_SIZE 0x20000 + #define AR934X_EHCI_BASE 0x1b000000 +@@ -166,6 +176,9 @@ + #define AR71XX_AHB_DIV_SHIFT 20 + #define AR71XX_AHB_DIV_MASK 0x7 + ++#define AR71XX_ETH0_PLL_SHIFT 17 ++#define AR71XX_ETH1_PLL_SHIFT 19 ++ + #define AR724X_PLL_REG_CPU_CONFIG 0x00 + #define AR724X_PLL_REG_PCIE_CONFIG 0x18 + +@@ -178,6 +191,8 @@ + #define AR724X_DDR_DIV_SHIFT 22 + #define AR724X_DDR_DIV_MASK 0x3 + ++#define AR7242_PLL_REG_ETH0_INT_CLOCK 0x2c ++ + #define AR913X_PLL_REG_CPU_CONFIG 0x00 + #define AR913X_PLL_REG_ETH_CONFIG 0x04 + #define AR913X_PLL_REG_ETH0_INT_CLOCK 0x14 +@@ -190,6 +205,9 @@ + #define AR913X_AHB_DIV_SHIFT 19 + #define AR913X_AHB_DIV_MASK 0x1 + ++#define AR913X_ETH0_PLL_SHIFT 20 ++#define AR913X_ETH1_PLL_SHIFT 22 ++ + #define AR933X_PLL_CPU_CONFIG_REG 0x00 + #define AR933X_PLL_CLOCK_CTRL_REG 0x08 + +@@ -211,6 +229,8 @@ + #define AR934X_PLL_CPU_CONFIG_REG 0x00 + #define AR934X_PLL_DDR_CONFIG_REG 0x04 + #define AR934X_PLL_CPU_DDR_CLK_CTRL_REG 0x08 ++#define AR934X_PLL_SWITCH_CLOCK_CONTROL_REG 0x24 ++#define AR934X_PLL_ETH_XMII_CONTROL_REG 0x2c + + #define AR934X_PLL_CPU_CONFIG_NFRAC_SHIFT 0 + #define AR934X_PLL_CPU_CONFIG_NFRAC_MASK 0x3f +@@ -243,9 +263,13 @@ + #define AR934X_PLL_CPU_DDR_CLK_CTRL_DDRCLK_FROM_DDRPLL BIT(21) + #define AR934X_PLL_CPU_DDR_CLK_CTRL_AHBCLK_FROM_DDRPLL BIT(24) + ++#define AR934X_PLL_SWITCH_CLOCK_CONTROL_MDIO_CLK_SEL BIT(6) ++ + #define QCA955X_PLL_CPU_CONFIG_REG 0x00 + #define QCA955X_PLL_DDR_CONFIG_REG 0x04 + #define QCA955X_PLL_CLK_CTRL_REG 0x08 ++#define QCA955X_PLL_ETH_XMII_CONTROL_REG 0x28 ++#define QCA955X_PLL_ETH_SGMII_CONTROL_REG 0x48 + + #define QCA955X_PLL_CPU_CONFIG_NFRAC_SHIFT 0 + #define QCA955X_PLL_CPU_CONFIG_NFRAC_MASK 0x3f +@@ -370,16 +394,30 @@ + #define AR913X_RESET_USB_HOST BIT(5) + #define AR913X_RESET_USB_PHY BIT(4) + ++#define AR933X_RESET_GE1_MDIO BIT(23) ++#define AR933X_RESET_GE0_MDIO BIT(22) ++#define AR933X_RESET_GE1_MAC BIT(13) + #define AR933X_RESET_WMAC BIT(11) ++#define AR933X_RESET_GE0_MAC BIT(9) + #define AR933X_RESET_USB_HOST BIT(5) + #define AR933X_RESET_USB_PHY BIT(4) + #define AR933X_RESET_USBSUS_OVERRIDE BIT(3) + ++#define AR934X_RESET_GE1_MDIO BIT(23) ++#define AR934X_RESET_GE0_MDIO BIT(22) ++#define AR934X_RESET_GE1_MAC BIT(13) + #define AR934X_RESET_USB_PHY_ANALOG BIT(11) ++#define AR934X_RESET_GE0_MAC BIT(9) ++#define AR934X_RESET_ETH_SWITCH BIT(8) + #define AR934X_RESET_USB_HOST BIT(5) + #define AR934X_RESET_USB_PHY BIT(4) + #define AR934X_RESET_USBSUS_OVERRIDE BIT(3) + ++#define QCA955X_RESET_GE1_MDIO BIT(23) ++#define QCA955X_RESET_GE0_MDIO BIT(22) ++#define QCA955X_RESET_GE1_MAC BIT(13) ++#define QCA955X_RESET_GE0_MAC BIT(9) ++ + #define AR933X_BOOTSTRAP_REF_CLK_40 BIT(0) + + #define AR934X_BOOTSTRAP_SW_OPTION8 BIT(23) +@@ -552,4 +590,47 @@ + #define AR934X_SRIF_DPLL2_OUTDIV_SHIFT 13 + #define AR934X_SRIF_DPLL2_OUTDIV_MASK 0x7 + ++#define AR71XX_GPIO_FUNC_SPI_CS2_EN BIT(13) ++#define AR71XX_GPIO_FUNC_SPI_CS1_EN BIT(12) ++ ++/* ++ * MII_CTRL block ++ */ ++#define AR71XX_MII_REG_MII0_CTRL 0x00 ++#define AR71XX_MII_REG_MII1_CTRL 0x04 ++ ++#define AR71XX_MII_CTRL_IF_MASK 3 ++#define AR71XX_MII_CTRL_SPEED_SHIFT 4 ++#define AR71XX_MII_CTRL_SPEED_MASK 3 ++#define AR71XX_MII_CTRL_SPEED_10 0 ++#define AR71XX_MII_CTRL_SPEED_100 1 ++#define AR71XX_MII_CTRL_SPEED_1000 2 ++ ++#define AR71XX_MII0_CTRL_IF_GMII 0 ++#define AR71XX_MII0_CTRL_IF_MII 1 ++#define AR71XX_MII0_CTRL_IF_RGMII 2 ++#define AR71XX_MII0_CTRL_IF_RMII 3 ++ ++#define AR71XX_MII1_CTRL_IF_RGMII 0 ++#define AR71XX_MII1_CTRL_IF_RMII 1 ++ ++/* ++ * AR933X GMAC interface ++ */ ++#define AR933X_GMAC_REG_ETH_CFG 0x00 ++ ++#define AR933X_ETH_CFG_SW_PHY_SWAP BIT(7) ++#define AR933X_ETH_CFG_SW_PHY_ADDR_SWAP BIT(8) ++ ++/* ++ * AR934X GMAC Interface ++ */ ++#define AR934X_GMAC_REG_ETH_CFG 0x00 ++ ++#define AR934X_ETH_CFG_RGMII_GMAC0 BIT(0) ++#define AR934X_ETH_CFG_MII_GMAC0 BIT(1) ++#define AR934X_ETH_CFG_GMII_GMAC0 BIT(2) ++#define AR934X_ETH_CFG_SW_ONLY_MODE BIT(6) ++#define AR934X_ETH_CFG_SW_PHY_SWAP BIT(7) ++ + #endif /* __ASM_MACH_AR71XX_REGS_H */ +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0023-MIPS-ath79-add-Mikrotik-rb4xx-device-support.patch b/target/mips/dragino-ms14s/patches/3.14.17/0023-MIPS-ath79-add-Mikrotik-rb4xx-device-support.patch new file mode 100644 index 000000000..67d390432 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0023-MIPS-ath79-add-Mikrotik-rb4xx-device-support.patch @@ -0,0 +1,536 @@ +From 7f5193750c4fb525ab7bd0610d05631b1dfbd8bb Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Wed, 14 May 2014 03:10:28 +0200 +Subject: [PATCH] MIPS: ath79: add Mikrotik rb4xx device support + +--- + arch/mips/ath79/Kconfig | 8 + + arch/mips/ath79/Makefile | 1 + + arch/mips/ath79/mach-rb4xx.c | 465 +++++++++++++++++++++++++++++++++++++++++++ + arch/mips/ath79/machtypes.h | 9 + + 4 files changed, 483 insertions(+) + create mode 100644 arch/mips/ath79/mach-rb4xx.c + +diff --git a/arch/mips/ath79/Kconfig b/arch/mips/ath79/Kconfig +index 52cefd7..7863079 100644 +--- a/arch/mips/ath79/Kconfig ++++ b/arch/mips/ath79/Kconfig +@@ -61,6 +61,14 @@ config ATH79_MACH_PB44 + Say 'Y' here if you want your kernel to support the + Atheros PB44 reference board. + ++config ATH79_MACH_RB4XX ++ bool "MikroTik RouterBOARD 4xx series support" ++ select SOC_AR71XX ++ select ATH79_DEV_ETH ++ select ATH79_DEV_GPIO_BUTTONS ++ select ATH79_DEV_LEDS_GPIO ++ select ATH79_DEV_USB ++ + config ATH79_MACH_UBNT_XM + bool "Ubiquiti Networks XM (rev 1.0) board" + select SOC_AR724X +diff --git a/arch/mips/ath79/Makefile b/arch/mips/ath79/Makefile +index 05485da..2b0e01b 100644 +--- a/arch/mips/ath79/Makefile ++++ b/arch/mips/ath79/Makefile +@@ -32,4 +32,5 @@ obj-$(CONFIG_ATH79_MACH_AP136) += mach-ap136.o + obj-$(CONFIG_ATH79_MACH_AP81) += mach-ap81.o + obj-$(CONFIG_ATH79_MACH_DB120) += mach-db120.o + obj-$(CONFIG_ATH79_MACH_PB44) += mach-pb44.o ++obj-$(CONFIG_ATH79_MACH_RB4XX) += mach-rb4xx.o + obj-$(CONFIG_ATH79_MACH_UBNT_XM) += mach-ubnt-xm.o +diff --git a/arch/mips/ath79/mach-rb4xx.c b/arch/mips/ath79/mach-rb4xx.c +new file mode 100644 +index 0000000..1a61b45 +--- /dev/null ++++ b/arch/mips/ath79/mach-rb4xx.c +@@ -0,0 +1,465 @@ ++/* ++ * MikroTik RouterBOARD 4xx series support ++ * ++ * Copyright (C) 2008-2012 Gabor Juhos <juhosg@openwrt.org> ++ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> ++ * ++ * 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 <linux/platform_device.h> ++#include <linux/irq.h> ++#include <linux/mdio-gpio.h> ++#include <linux/mmc/host.h> ++#include <linux/spi/spi.h> ++#include <linux/spi/flash.h> ++#include <linux/spi/mmc_spi.h> ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/partitions.h> ++ ++#include <asm/mach-ath79/ar71xx_regs.h> ++#include <asm/mach-ath79/ath79.h> ++#include <asm/mach-ath79/rb4xx_cpld.h> ++ ++#include "common.h" ++#include "dev-eth.h" ++#include "dev-gpio-buttons.h" ++#include "dev-leds-gpio.h" ++#include "dev-usb.h" ++#include "machtypes.h" ++#include "pci.h" ++ ++#define RB4XX_GPIO_USER_LED 4 ++#define RB4XX_GPIO_RESET_SWITCH 7 ++ ++#define RB4XX_GPIO_CPLD_BASE 32 ++#define RB4XX_GPIO_CPLD_LED1 (RB4XX_GPIO_CPLD_BASE + CPLD_GPIO_nLED1) ++#define RB4XX_GPIO_CPLD_LED2 (RB4XX_GPIO_CPLD_BASE + CPLD_GPIO_nLED2) ++#define RB4XX_GPIO_CPLD_LED3 (RB4XX_GPIO_CPLD_BASE + CPLD_GPIO_nLED3) ++#define RB4XX_GPIO_CPLD_LED4 (RB4XX_GPIO_CPLD_BASE + CPLD_GPIO_nLED4) ++#define RB4XX_GPIO_CPLD_LED5 (RB4XX_GPIO_CPLD_BASE + CPLD_GPIO_nLED5) ++ ++#define RB4XX_KEYS_POLL_INTERVAL 20 /* msecs */ ++#define RB4XX_KEYS_DEBOUNCE_INTERVAL (3 * RB4XX_KEYS_POLL_INTERVAL) ++ ++static struct gpio_led rb4xx_leds_gpio[] __initdata = { ++ { ++ .name = "rb4xx:yellow:user", ++ .gpio = RB4XX_GPIO_USER_LED, ++ .active_low = 0, ++ }, { ++ .name = "rb4xx:green:led1", ++ .gpio = RB4XX_GPIO_CPLD_LED1, ++ .active_low = 1, ++ }, { ++ .name = "rb4xx:green:led2", ++ .gpio = RB4XX_GPIO_CPLD_LED2, ++ .active_low = 1, ++ }, { ++ .name = "rb4xx:green:led3", ++ .gpio = RB4XX_GPIO_CPLD_LED3, ++ .active_low = 1, ++ }, { ++ .name = "rb4xx:green:led4", ++ .gpio = RB4XX_GPIO_CPLD_LED4, ++ .active_low = 1, ++ }, { ++ .name = "rb4xx:green:led5", ++ .gpio = RB4XX_GPIO_CPLD_LED5, ++ .active_low = 0, ++ }, ++}; ++ ++static struct gpio_keys_button rb4xx_gpio_keys[] __initdata = { ++ { ++ .desc = "reset_switch", ++ .type = EV_KEY, ++ .code = KEY_RESTART, ++ .debounce_interval = RB4XX_KEYS_DEBOUNCE_INTERVAL, ++ .gpio = RB4XX_GPIO_RESET_SWITCH, ++ .active_low = 1, ++ } ++}; ++ ++static struct platform_device rb4xx_nand_device = { ++ .name = "rb4xx-nand", ++ .id = -1, ++}; ++ ++static struct ath79_pci_irq rb4xx_pci_irqs[] __initdata = { ++ { ++ .slot = 17, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(2), ++ }, { ++ .slot = 18, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(0), ++ }, { ++ .slot = 18, ++ .pin = 2, ++ .irq = ATH79_PCI_IRQ(1), ++ }, { ++ .slot = 19, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(1), ++ }, { ++ .slot = 19, ++ .pin = 2, ++ .irq = ATH79_PCI_IRQ(2), ++ }, { ++ .slot = 20, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(2), ++ }, { ++ .slot = 20, ++ .pin = 2, ++ .irq = ATH79_PCI_IRQ(0), ++ }, { ++ .slot = 21, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(0), ++ }, { ++ .slot = 22, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(1), ++ }, { ++ .slot = 22, ++ .pin = 2, ++ .irq = ATH79_PCI_IRQ(2), ++ }, { ++ .slot = 23, ++ .pin = 1, ++ .irq = ATH79_PCI_IRQ(2), ++ }, { ++ .slot = 23, ++ .pin = 2, ++ .irq = ATH79_PCI_IRQ(0), ++ } ++}; ++ ++static struct mtd_partition rb4xx_partitions[] = { ++ { ++ .name = "routerboot", ++ .offset = 0, ++ .size = 0x0b000, ++ .mask_flags = MTD_WRITEABLE, ++ }, { ++ .name = "hard_config", ++ .offset = 0x0b000, ++ .size = 0x01000, ++ .mask_flags = MTD_WRITEABLE, ++ }, { ++ .name = "bios", ++ .offset = 0x0d000, ++ .size = 0x02000, ++ .mask_flags = MTD_WRITEABLE, ++ }, { ++ .name = "soft_config", ++ .offset = 0x0f000, ++ .size = 0x01000, ++ } ++}; ++ ++static struct flash_platform_data rb4xx_flash_data = { ++ .type = "pm25lv512", ++ .parts = rb4xx_partitions, ++ .nr_parts = ARRAY_SIZE(rb4xx_partitions), ++}; ++ ++static struct rb4xx_cpld_platform_data rb4xx_cpld_data = { ++ .gpio_base = RB4XX_GPIO_CPLD_BASE, ++}; ++ ++static struct mmc_spi_platform_data rb4xx_mmc_data = { ++ .ocr_mask = MMC_VDD_32_33 | MMC_VDD_33_34, ++}; ++ ++static struct spi_board_info rb4xx_spi_info[] = { ++ { ++ .bus_num = 0, ++ .chip_select = 0, ++ .max_speed_hz = 25000000, ++ .modalias = "m25p80", ++ .platform_data = &rb4xx_flash_data, ++ }, { ++ .bus_num = 0, ++ .chip_select = 1, ++ .max_speed_hz = 25000000, ++ .modalias = "spi-rb4xx-cpld", ++ .platform_data = &rb4xx_cpld_data, ++ } ++}; ++ ++static struct spi_board_info rb4xx_microsd_info[] = { ++ { ++ .bus_num = 0, ++ .chip_select = 2, ++ .max_speed_hz = 25000000, ++ .modalias = "mmc_spi", ++ .platform_data = &rb4xx_mmc_data, ++ } ++}; ++ ++ ++static struct resource rb4xx_spi_resources[] = { ++ { ++ .start = AR71XX_SPI_BASE, ++ .end = AR71XX_SPI_BASE + AR71XX_SPI_SIZE - 1, ++ .flags = IORESOURCE_MEM, ++ }, ++}; ++ ++static struct platform_device rb4xx_spi_device = { ++ .name = "rb4xx-spi", ++ .id = -1, ++ .resource = rb4xx_spi_resources, ++ .num_resources = ARRAY_SIZE(rb4xx_spi_resources), ++}; ++ ++static void __init rb4xx_generic_setup(void) ++{ ++ ath79_gpio_function_enable(AR71XX_GPIO_FUNC_SPI_CS1_EN | ++ AR71XX_GPIO_FUNC_SPI_CS2_EN); ++ ++ ath79_register_leds_gpio(-1, ARRAY_SIZE(rb4xx_leds_gpio), ++ rb4xx_leds_gpio); ++ ++ ath79_register_gpio_keys_polled(-1, RB4XX_KEYS_POLL_INTERVAL, ++ ARRAY_SIZE(rb4xx_gpio_keys), ++ rb4xx_gpio_keys); ++ ++ spi_register_board_info(rb4xx_spi_info, ARRAY_SIZE(rb4xx_spi_info)); ++ platform_device_register(&rb4xx_spi_device); ++ platform_device_register(&rb4xx_nand_device); ++} ++ ++static void __init rb411_setup(void) ++{ ++ rb4xx_generic_setup(); ++ spi_register_board_info(rb4xx_microsd_info, ++ ARRAY_SIZE(rb4xx_microsd_info)); ++ ++ ath79_register_mdio(0, 0xfffffffc); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_MII; ++ ath79_eth0_data.phy_mask = 0x00000003; ++ ++ ath79_register_eth(0); ++ ++ ath79_pci_set_irq_map(ARRAY_SIZE(rb4xx_pci_irqs), rb4xx_pci_irqs); ++ ath79_register_pci(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_411, "411", "MikroTik RouterBOARD 411/A/AH", ++ rb411_setup); ++ ++static void __init rb411u_setup(void) ++{ ++ rb411_setup(); ++ ath79_register_usb(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_411U, "411U", "MikroTik RouterBOARD 411U", ++ rb411u_setup); ++ ++#define RB433_LAN_PHYMASK BIT(0) ++#define RB433_WAN_PHYMASK BIT(4) ++#define RB433_MDIO_PHYMASK (RB433_LAN_PHYMASK | RB433_WAN_PHYMASK) ++ ++static void __init rb433_setup(void) ++{ ++ rb4xx_generic_setup(); ++ spi_register_board_info(rb4xx_microsd_info, ++ ARRAY_SIZE(rb4xx_microsd_info)); ++ ++ ath79_register_mdio(0, ~RB433_MDIO_PHYMASK); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 1); ++ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_MII; ++ ath79_eth0_data.phy_mask = RB433_LAN_PHYMASK; ++ ++ ath79_init_mac(ath79_eth1_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; ++ ath79_eth1_data.phy_mask = RB433_WAN_PHYMASK; ++ ++ ath79_register_eth(1); ++ ath79_register_eth(0); ++ ++ ath79_pci_set_irq_map(ARRAY_SIZE(rb4xx_pci_irqs), rb4xx_pci_irqs); ++ ath79_register_pci(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_433, "433", "MikroTik RouterBOARD 433/AH", ++ rb433_setup); ++ ++static void __init rb433u_setup(void) ++{ ++ rb433_setup(); ++ ath79_register_usb(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_433U, "433U", "MikroTik RouterBOARD 433UAH", ++ rb433u_setup); ++ ++static void __init rb435g_setup(void) ++{ ++ rb4xx_generic_setup(); ++ ++ spi_register_board_info(rb4xx_microsd_info, ++ ARRAY_SIZE(rb4xx_microsd_info)); ++ ++ ath79_register_mdio(0, ~RB433_MDIO_PHYMASK); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 1); ++ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ++ ath79_eth0_data.phy_mask = RB433_LAN_PHYMASK; ++ ++ ath79_init_mac(ath79_eth1_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ++ ath79_eth1_data.phy_mask = RB433_WAN_PHYMASK; ++ ++ ath79_register_eth(1); ++ ath79_register_eth(0); ++ ++ ath79_pci_set_irq_map(ARRAY_SIZE(rb4xx_pci_irqs), rb4xx_pci_irqs); ++ ath79_register_pci(); ++ ++ ath79_register_usb(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_435G, "435G", "MikroTik RouterBOARD 435G", ++ rb435g_setup); ++ ++#define RB450_LAN_PHYMASK BIT(0) ++#define RB450_WAN_PHYMASK BIT(4) ++#define RB450_MDIO_PHYMASK (RB450_LAN_PHYMASK | RB450_WAN_PHYMASK) ++ ++static void __init rb450_generic_setup(int gige) ++{ ++ rb4xx_generic_setup(); ++ ath79_register_mdio(0, ~RB450_MDIO_PHYMASK); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 1); ++ ath79_eth0_data.phy_if_mode = (gige) ? ++ PHY_INTERFACE_MODE_RGMII : PHY_INTERFACE_MODE_MII; ++ ath79_eth0_data.phy_mask = RB450_LAN_PHYMASK; ++ ++ ath79_init_mac(ath79_eth1_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth1_data.phy_if_mode = (gige) ? ++ PHY_INTERFACE_MODE_RGMII : PHY_INTERFACE_MODE_RMII; ++ ath79_eth1_data.phy_mask = RB450_WAN_PHYMASK; ++ ++ ath79_register_eth(1); ++ ath79_register_eth(0); ++} ++ ++static void __init rb450_setup(void) ++{ ++ rb450_generic_setup(0); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_450, "450", "MikroTik RouterBOARD 450", ++ rb450_setup); ++ ++static void __init rb450g_setup(void) ++{ ++ rb450_generic_setup(1); ++ spi_register_board_info(rb4xx_microsd_info, ++ ARRAY_SIZE(rb4xx_microsd_info)); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_450G, "450G", "MikroTik RouterBOARD 450G", ++ rb450g_setup); ++ ++static void __init rb493_setup(void) ++{ ++ rb4xx_generic_setup(); ++ ++ ath79_register_mdio(0, 0x3fffff00); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_MII; ++ ath79_eth0_data.speed = SPEED_100; ++ ath79_eth0_data.duplex = DUPLEX_FULL; ++ ++ ath79_init_mac(ath79_eth1_data.mac_addr, ath79_mac_base, 1); ++ ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; ++ ath79_eth1_data.phy_mask = 0x00000001; ++ ++ ath79_register_eth(0); ++ ath79_register_eth(1); ++ ++ ath79_pci_set_irq_map(ARRAY_SIZE(rb4xx_pci_irqs), rb4xx_pci_irqs); ++ ath79_register_pci(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_493, "493", "MikroTik RouterBOARD 493/AH", ++ rb493_setup); ++ ++#define RB493G_GPIO_MDIO_MDC 7 ++#define RB493G_GPIO_MDIO_DATA 8 ++ ++#define RB493G_MDIO_PHYMASK BIT(0) ++ ++static struct mdio_gpio_platform_data rb493g_mdio_data = { ++ .mdc = RB493G_GPIO_MDIO_MDC, ++ .mdio = RB493G_GPIO_MDIO_DATA, ++ ++ .phy_mask = ~RB493G_MDIO_PHYMASK, ++}; ++ ++static struct platform_device rb493g_mdio_device = { ++ .name = "mdio-gpio", ++ .id = -1, ++ .dev = { ++ .platform_data = &rb493g_mdio_data, ++ }, ++}; ++ ++static void __init rb493g_setup(void) ++{ ++ ath79_gpio_function_enable(AR71XX_GPIO_FUNC_SPI_CS1_EN | ++ AR71XX_GPIO_FUNC_SPI_CS2_EN); ++ ++ ath79_register_leds_gpio(-1, ARRAY_SIZE(rb4xx_leds_gpio), ++ rb4xx_leds_gpio); ++ ++ spi_register_board_info(rb4xx_spi_info, ARRAY_SIZE(rb4xx_spi_info)); ++ spi_register_board_info(rb4xx_microsd_info, ++ ARRAY_SIZE(rb4xx_microsd_info)); ++ ++ platform_device_register(&rb4xx_spi_device); ++ platform_device_register(&rb4xx_nand_device); ++ ++ ath79_register_mdio(0, ~RB493G_MDIO_PHYMASK); ++ ++ ath79_init_mac(ath79_eth0_data.mac_addr, ath79_mac_base, 0); ++ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ++ ath79_eth0_data.phy_mask = RB493G_MDIO_PHYMASK; ++ ath79_eth0_data.speed = SPEED_1000; ++ ath79_eth0_data.duplex = DUPLEX_FULL; ++ ++ ath79_init_mac(ath79_eth1_data.mac_addr, ath79_mac_base, 1); ++ ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ++ ath79_eth1_data.mii_bus_dev = &rb493g_mdio_device.dev; ++ ath79_eth1_data.phy_mask = RB493G_MDIO_PHYMASK; ++ ath79_eth1_data.speed = SPEED_1000; ++ ath79_eth1_data.duplex = DUPLEX_FULL; ++ ++ platform_device_register(&rb493g_mdio_device); ++ ++ ath79_register_eth(1); ++ ath79_register_eth(0); ++ ++ ath79_register_usb(); ++ ++ ath79_pci_set_irq_map(ARRAY_SIZE(rb4xx_pci_irqs), rb4xx_pci_irqs); ++ ath79_register_pci(); ++} ++ ++MIPS_MACHINE(ATH79_MACH_RB_493G, "493G", "MikroTik RouterBOARD 493G", ++ rb493g_setup); +diff --git a/arch/mips/ath79/machtypes.h b/arch/mips/ath79/machtypes.h +index 2625405..7630954 100644 +--- a/arch/mips/ath79/machtypes.h ++++ b/arch/mips/ath79/machtypes.h +@@ -21,6 +21,15 @@ enum ath79_mach_type { + ATH79_MACH_AP81, /* Atheros AP81 reference board */ + ATH79_MACH_DB120, /* Atheros DB120 reference board */ + ATH79_MACH_PB44, /* Atheros PB44 reference board */ ++ ATH79_MACH_RB_411, /* MikroTik RouterBOARD 411/411A/411AH */ ++ ATH79_MACH_RB_411U, /* MikroTik RouterBOARD 411U */ ++ ATH79_MACH_RB_433, /* MikroTik RouterBOARD 433/433AH */ ++ ATH79_MACH_RB_433U, /* MikroTik RouterBOARD 433UAH */ ++ ATH79_MACH_RB_435G, /* MikroTik RouterBOARD 435G */ ++ ATH79_MACH_RB_450G, /* MikroTik RouterBOARD 450G */ ++ ATH79_MACH_RB_450, /* MikroTik RouterBOARD 450 */ ++ ATH79_MACH_RB_493, /* Mikrotik RouterBOARD 493/493AH */ ++ ATH79_MACH_RB_493G, /* Mikrotik RouterBOARD 493G */ + ATH79_MACH_UBNT_XM, /* Ubiquiti Networks XM board rev 1.0 */ + }; + +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0024-various-fixups-for-Werror.patch b/target/mips/dragino-ms14s/patches/3.14.17/0024-various-fixups-for-Werror.patch new file mode 100644 index 000000000..77883846d --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0024-various-fixups-for-Werror.patch @@ -0,0 +1,105 @@ +From 45bdbeaf12f96a95bda6016a2aa943ae2dfceb96 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Fri, 16 May 2014 04:37:17 +0200 +Subject: [PATCH] various fixups for -Werror + +--- + arch/mips/ath79/common.c | 4 ++-- + arch/mips/ath79/dev-eth.c | 8 ++++---- + drivers/net/phy/swconfig.c | 18 +----------------- + 3 files changed, 7 insertions(+), 23 deletions(-) + +diff --git a/arch/mips/ath79/common.c b/arch/mips/ath79/common.c +index eb3966c..def54c2 100644 +--- a/arch/mips/ath79/common.c ++++ b/arch/mips/ath79/common.c +@@ -59,7 +59,7 @@ EXPORT_SYMBOL_GPL(ath79_ddr_wb_flush); + void ath79_device_reset_set(u32 mask) + { + unsigned long flags; +- u32 reg; ++ u32 reg = 0; + u32 t; + + if (soc_is_ar71xx()) +@@ -87,7 +87,7 @@ EXPORT_SYMBOL_GPL(ath79_device_reset_set); + void ath79_device_reset_clear(u32 mask) + { + unsigned long flags; +- u32 reg; ++ u32 reg = 0; + u32 t; + + if (soc_is_ar71xx()) +diff --git a/arch/mips/ath79/dev-eth.c b/arch/mips/ath79/dev-eth.c +index 21feeb9..879f1cd 100644 +--- a/arch/mips/ath79/dev-eth.c ++++ b/arch/mips/ath79/dev-eth.c +@@ -121,7 +121,7 @@ static void __init ath79_mii_ctrl_set_if(unsigned int reg, + static void ath79_mii_ctrl_set_speed(unsigned int reg, unsigned int speed) + { + void __iomem *base; +- unsigned int mii_speed; ++ unsigned int mii_speed = 0; + u32 t; + + switch (speed) { +@@ -271,8 +271,8 @@ struct ath79_eth_pll_data ath79_eth1_pll_data; + + static u32 ath79_get_eth_pll(unsigned int mac, int speed) + { +- struct ath79_eth_pll_data *pll_data; +- u32 pll_val; ++ struct ath79_eth_pll_data *pll_data = NULL; ++ u32 pll_val = 0; + + switch (mac) { + case 0: +@@ -511,7 +511,7 @@ struct ag71xx_switch_platform_data ath79_switch_data; + static void __init ath79_init_eth_pll_data(unsigned int id) + { + struct ath79_eth_pll_data *pll_data; +- u32 pll_10, pll_100, pll_1000; ++ u32 pll_10 = 0, pll_100 = 0, pll_1000 = 0; + + switch (id) { + case 0: +diff --git a/drivers/net/phy/swconfig.c b/drivers/net/phy/swconfig.c +index c043ee4..c4d7689 100644 +--- a/drivers/net/phy/swconfig.c ++++ b/drivers/net/phy/swconfig.c +@@ -1107,30 +1107,14 @@ EXPORT_SYMBOL_GPL(unregister_switch); + static int __init + swconfig_init(void) + { +- int i, err; ++ int err; + + INIT_LIST_HEAD(&swdevs); +- +-#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) +- err = genl_register_family(&switch_fam); +- if (err) +- return err; + +- for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { +- err = genl_register_ops(&switch_fam, &swconfig_ops[i]); +- if (err) +- goto unregister; +- } +-#else + err = genl_register_family_with_ops(&switch_fam, swconfig_ops); + if (err) + return err; +-#endif + return 0; +- +-unregister: +- genl_unregister_family(&switch_fam); +- return err; + } + + static void __exit +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0025-rb4xx_nand-add-partition-for-cfgfs.patch b/target/mips/dragino-ms14s/patches/3.14.17/0025-rb4xx_nand-add-partition-for-cfgfs.patch new file mode 100644 index 000000000..7d9d85f62 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0025-rb4xx_nand-add-partition-for-cfgfs.patch @@ -0,0 +1,28 @@ +From 8cbc2ee92ec6dbed4a806cedffc6919b6b90275b Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Tue, 3 Jun 2014 00:32:22 +0200 +Subject: [PATCH] rb4xx_nand: add partition for cfgfs + +--- + drivers/mtd/nand/rb4xx_nand.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/drivers/mtd/nand/rb4xx_nand.c b/drivers/mtd/nand/rb4xx_nand.c +index 5b9841b..603d001 100644 +--- a/drivers/mtd/nand/rb4xx_nand.c ++++ b/drivers/mtd/nand/rb4xx_nand.c +@@ -65,6 +65,11 @@ static struct mtd_partition rb4xx_nand_partitions[] = { + .size = (4 * 1024 * 1024) - (256 * 1024), + }, + { ++ .name = "cfgfs", ++ .offset = MTDPART_OFS_NXTBLK, ++ .size = 0x400000, ++ }, ++ { + .name = "rootfs", + .offset = MTDPART_OFS_NXTBLK, + .size = MTDPART_SIZ_FULL, +-- +1.8.5.3 + diff --git a/target/mips/dragino-ms14s/patches/3.14.17/0026-various-fixups-for-ath5k-fixing-system-freezes.patch b/target/mips/dragino-ms14s/patches/3.14.17/0026-various-fixups-for-ath5k-fixing-system-freezes.patch new file mode 100644 index 000000000..4b17700d8 --- /dev/null +++ b/target/mips/dragino-ms14s/patches/3.14.17/0026-various-fixups-for-ath5k-fixing-system-freezes.patch @@ -0,0 +1,108 @@ +From 95945fe79069ee6b7ccce2b14fb9f8b93db33918 Mon Sep 17 00:00:00 2001 +From: Phil Sutter <phil@nwl.cc> +Date: Sun, 15 Jun 2014 18:29:27 +0200 +Subject: [PATCH] various fixups for ath5k, fixing system freezes + +--- + drivers/net/wireless/ath/ath5k/base.c | 3 +++ + drivers/net/wireless/ath/ath5k/dma.c | 9 +++++++++ + drivers/net/wireless/ath/ath5k/initvals.c | 6 ++++++ + drivers/net/wireless/ath/ath5k/phy.c | 4 ++-- + drivers/net/wireless/ath/ath5k/reset.c | 2 ++ + 5 files changed, 22 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c +index ef35da8..4b18434 100644 +--- a/drivers/net/wireless/ath/ath5k/base.c ++++ b/drivers/net/wireless/ath/ath5k/base.c +@@ -751,6 +751,9 @@ ath5k_txbuf_setup(struct ath5k_hw *ah, struct ath5k_buf *bf, + bf->skbaddr = dma_map_single(ah->dev, skb->data, skb->len, + DMA_TO_DEVICE); + ++ if (dma_mapping_error(ah->dev, bf->skbaddr)) ++ return -ENOSPC; ++ + ieee80211_get_tx_rates(info->control.vif, (control) ? control->sta : NULL, skb, bf->rates, + ARRAY_SIZE(bf->rates)); + +diff --git a/drivers/net/wireless/ath/ath5k/dma.c b/drivers/net/wireless/ath/ath5k/dma.c +index e6c52f7..72bf600 100644 +--- a/drivers/net/wireless/ath/ath5k/dma.c ++++ b/drivers/net/wireless/ath/ath5k/dma.c +@@ -869,10 +869,19 @@ ath5k_hw_dma_init(struct ath5k_hw *ah) + * guess we can tweak it and see how it goes ;-) + */ + if (ah->ah_version != AR5K_AR5210) { ++#if !defined(CONFIG_ATHEROS_AR71XX) && !defined(CONFIG_ATH79) + AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_SDMAMR, AR5K_DMASIZE_128B); + AR5K_REG_WRITE_BITS(ah, AR5K_RXCFG, + AR5K_RXCFG_SDMAMW, AR5K_DMASIZE_128B); ++#else ++ /* WAR for AR71xx PCI bug */ ++ AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, ++ AR5K_TXCFG_SDMAMR, AR5K_DMASIZE_128B); ++ AR5K_REG_WRITE_BITS(ah, AR5K_RXCFG, ++ AR5K_RXCFG_SDMAMW, AR5K_DMASIZE_4B); ++#endif ++ + } + + /* Pre-enable interrupts on 5211/5212*/ +diff --git a/drivers/net/wireless/ath/ath5k/initvals.c b/drivers/net/wireless/ath/ath5k/initvals.c +index ee1c2fa..ba84ab5 100644 +--- a/drivers/net/wireless/ath/ath5k/initvals.c ++++ b/drivers/net/wireless/ath/ath5k/initvals.c +@@ -62,8 +62,14 @@ static const struct ath5k_ini ar5210_ini[] = { + { AR5K_IMR, 0 }, + { AR5K_IER, AR5K_IER_DISABLE }, + { AR5K_BSR, 0, AR5K_INI_READ }, ++#if !defined(CONFIG_ATHEROS_AR71XX) && !defined(CONFIG_ATH79) + { AR5K_TXCFG, AR5K_DMASIZE_128B }, + { AR5K_RXCFG, AR5K_DMASIZE_128B }, ++#else ++ /* WAR for AR71xx PCI bug */ ++ { AR5K_TXCFG, AR5K_DMASIZE_128B }, ++ { AR5K_RXCFG, AR5K_DMASIZE_4B }, ++#endif + { AR5K_CFG, AR5K_INIT_CFG }, + { AR5K_TOPS, 8 }, + { AR5K_RXNOFRM, 8 }, +diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c +index 1a2973b..0fce1c7 100644 +--- a/drivers/net/wireless/ath/ath5k/phy.c ++++ b/drivers/net/wireless/ath/ath5k/phy.c +@@ -3709,8 +3709,8 @@ ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, + AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_CHIRP), + AR5K_TPC); + } else { +- ath5k_hw_reg_write(ah, AR5K_PHY_TXPOWER_RATE_MAX | +- AR5K_TUNE_MAX_TXPOWER, AR5K_PHY_TXPOWER_RATE_MAX); ++ ath5k_hw_reg_write(ah, AR5K_TUNE_MAX_TXPOWER, ++ AR5K_PHY_TXPOWER_RATE_MAX); + } + + return 0; +diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c +index a3399c4..66d0ecc 100644 +--- a/drivers/net/wireless/ath/ath5k/reset.c ++++ b/drivers/net/wireless/ath/ath5k/reset.c +@@ -1154,6 +1154,7 @@ ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, + tsf_lo = 0; + mode = 0; + ++#if 0 + /* + * Sanity check for fast flag + * Fast channel change only available +@@ -1161,6 +1162,7 @@ ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, + */ + if (fast && (ah->ah_radio != AR5K_RF2413) && + (ah->ah_radio != AR5K_RF5413)) ++#endif + fast = false; + + /* Disable sleep clock operation +-- +1.8.5.3 + diff --git a/target/mips/kernel/dragino-ms14s b/target/mips/kernel/dragino-ms14s new file mode 100644 index 000000000..cce44918e --- /dev/null +++ b/target/mips/kernel/dragino-ms14s @@ -0,0 +1,12 @@ +CONFIG_MIPS=y +CONFIG_ATH79=y +CONFIG_ATH79_MACH_Linino=y +CONFIG_NET_VENDOR_ATHEROS=y +CONFIG_AG71XX=y +CONFIG_AG71XX_AR8216_SUPPORT=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=1 +CONFIG_SERIAL_8250_RUNTIME_UARTS=1 +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y diff --git a/target/mips/systems/dragino-ms14s b/target/mips/systems/dragino-ms14s new file mode 100644 index 000000000..6b1dd0368 --- /dev/null +++ b/target/mips/systems/dragino-ms14s @@ -0,0 +1,11 @@ +config ADK_TARGET_SYSTEM_DRAGINO_MS14S + bool "Dragino2 ms14-s" + select ADK_mips + select ADK_big + select ADK_dragino_ms14s + select ADK_CPU_MIPS32 + select ADK_TARGET_WITH_WATCHDOG + select ADK_TARGET_WITH_NAND + help + Support for Dragino2 ms14-s. + |