This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "IPFire 2.x development tree".
The branch, master has been updated via 7726e3a66bf397742a346512922ef67184d8f483 (commit) from f8cdaa1084caefafca126ca7e50600f8fbc4a1bb (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 7726e3a66bf397742a346512922ef67184d8f483 Author: Arne Fitzenreiter arne_f@ipfire.org Date: Tue Jan 27 19:11:57 2015 +0100
kernel: add support for lamobo-r1.
-----------------------------------------------------------------------
Summary of changes: config/kernel/kernel.config.armv5tel-ipfire-multi | 13 +- lfs/linux | 3 + src/patches/linux-3.14.x-lamobo-r1.patch | 5732 +++++++++++++++++++++ 3 files changed, 5745 insertions(+), 3 deletions(-) create mode 100644 src/patches/linux-3.14.x-lamobo-r1.patch
Difference in files: diff --git a/config/kernel/kernel.config.armv5tel-ipfire-multi b/config/kernel/kernel.config.armv5tel-ipfire-multi index caf4c44..6c387b3 100644 --- a/config/kernel/kernel.config.armv5tel-ipfire-multi +++ b/config/kernel/kernel.config.armv5tel-ipfire-multi @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/arm 3.14.27 Kernel Configuration +# Linux/arm 3.14.29 Kernel Configuration # CONFIG_ARM=y CONFIG_MIGHT_HAVE_PCI=y @@ -2053,7 +2053,7 @@ CONFIG_IP1000=m CONFIG_JME=m CONFIG_NET_VENDOR_MARVELL=y CONFIG_MV643XX_ETH=m -CONFIG_MVMDIO=m +CONFIG_MVMDIO=y CONFIG_MVNETA=y CONFIG_SKGE=m # CONFIG_SKGE_DEBUG is not set @@ -2155,6 +2155,8 @@ CONFIG_XILINX_EMACLITE=m # CONFIG_FDDI is not set # CONFIG_HIPPI is not set CONFIG_PHYLIB=y +CONFIG_SWCONFIG=m +# CONFIG_SWCONFIG_LEDS is not set
# # MII PHY device drivers @@ -2178,11 +2180,16 @@ CONFIG_LSI_ET1011C_PHY=m CONFIG_MICREL_PHY=m CONFIG_FIXED_PHY=y CONFIG_MDIO_BITBANG=m -# CONFIG_MDIO_GPIO is not set +CONFIG_MDIO_GPIO=m CONFIG_MDIO_SUN4I=m CONFIG_MDIO_BUS_MUX=m CONFIG_MDIO_BUS_MUX_GPIO=m CONFIG_MDIO_BUS_MUX_MMIOREG=m +CONFIG_B53=m +CONFIG_B53_PHY_DRIVER=m +# CONFIG_B53_MMAP_DRIVER is not set +# CONFIG_B53_SRAB_DRIVER is not set +CONFIG_B53_PHY_FIXUP=y CONFIG_GATEWORKS_GW16083=m # CONFIG_PLIP is not set CONFIG_PPP=m diff --git a/lfs/linux b/lfs/linux index aad84bd..0086bfe 100644 --- a/lfs/linux +++ b/lfs/linux @@ -171,6 +171,9 @@ ifeq "$(KCFG)" "-multi"
# Apply Arm7-multiarch kernel patches. cd $(DIR_APP) && xzcat $(DIR_DL)/arm7-multi-patches-$(A7M_PATCHES).patch.xz | patch -Np1 + + # After next kernel update this patch will included to arm7-multi patchset. + cd $(DIR_APP) && patch -Np1 < $(DIR_SRC)/src/patches/linux-3.14.x-lamobo-r1.patch endif
ifeq "$(KCFG)" "-rpi" diff --git a/src/patches/linux-3.14.x-lamobo-r1.patch b/src/patches/linux-3.14.x-lamobo-r1.patch new file mode 100644 index 0000000..4e604df --- /dev/null +++ b/src/patches/linux-3.14.x-lamobo-r1.patch @@ -0,0 +1,5732 @@ +diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile +index e7cba29..b695ccd 100644 +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -293,6 +293,7 @@ dtb-$(CONFIG_ARCH_SUNXI) += \ + sun7i-a20-bananapi.dtb \ + sun7i-a20-cubieboard2.dtb \ + sun7i-a20-cubietruck.dtb \ ++ sun7i-a20-lamobo-r1.dtb \ + sun7i-a20-olinuxino-micro.dtb \ + sun7i-a20-pcduino3.dtb + dtb-$(CONFIG_ARCH_TEGRA) += tegra20-harmony.dtb \ +diff --git a/arch/arm/boot/dts/sun7i-a20-lamobo-r1.dts b/arch/arm/boot/dts/sun7i-a20-lamobo-r1.dts +new file mode 100644 +index 0000000..615b77f +--- /dev/null ++++ b/arch/arm/boot/dts/sun7i-a20-lamobo-r1.dts +@@ -0,0 +1,207 @@ ++/* ++ * Copyright 2014 Zoltan HERPAI ++ * Zoltan HERPAI wigyori@uid0.hu ++ * Arne Fitzenreiter arne_f@ipfire.org ++ * ++ * The code contained herein is licensed under the GNU General Public ++ * License. You may obtain a copy of the GNU General Public License ++ * Version 2 or later at the following locations: ++ * ++ * http://www.opensource.org/licenses/gpl-license.html ++ * http://www.gnu.org/copyleft/gpl.html ++ */ ++ ++/dts-v1/; ++/include/ "sun7i-a20.dtsi" ++/include/ "sunxi-common-regulators.dtsi" ++#include <dt-bindings/input/input.h> ++ ++/ { ++ model = "Lamobo-R1"; ++ compatible = "lamobo,lamobo-r1", "allwinner,sun7i-a20"; ++ ++ aliases { ++ spi0 = &spi1; ++ spi1 = &spi2; ++ }; ++ ++ soc@01c00000 { ++ spi1: spi@01c06000 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi1_pins_a>; ++ status = "okay"; ++ }; ++ ++ spi2: spi@01c17000 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi2_pins_a>; ++ status = "okay"; ++ }; ++ ++ mmc0: mmc@01c0f000 { ++ pinctrl-names = "default", "default"; ++ pinctrl-0 = <&mmc0_pins_a>; ++ pinctrl-1 = <&mmc0_cd_pin_lamobo>; ++ cd-gpios = <&pio 7 10 0>; /* PH10 */ ++ status = "okay"; ++ }; ++ ++ usbphy: phy@01c13400 { ++ usb1_vbus-supply = <®_usb1_vbus>; ++ usb2_vbus-supply = <®_usb2_vbus>; ++ status = "okay"; ++ }; ++ ++ ehci0: usb@01c14000 { ++ status = "okay"; ++ }; ++ ++ ohci0: usb@01c14400 { ++ status = "okay"; ++ }; ++ ++ ahci: sata@01c18000 { ++ target-supply = <®_ahci_5v>; ++ status = "okay"; ++ }; ++ ++ ehci1: usb@01c1c000 { ++ status = "okay"; ++ }; ++ ++ ohci1: usb@01c1c400 { ++ status = "okay"; ++ }; ++ ++ pinctrl@01c20800 { ++ led_pins_lamobo: led_pins@0 { ++ allwinner,pins = "PH24"; ++ allwinner,function = "gpio_out"; ++ allwinner,drive = <0>; ++ allwinner,pull = <0>; ++ }; ++ ++ mmc0_cd_pin_lamobo: mmc0_cd_pin@0 { ++ allwinner,pins = "PH10"; ++ allwinner,function = "gpio_in"; ++ allwinner,drive = <0>; ++ allwinner,pull = <1>; ++ }; ++ ++ gmac_power_pin_lamobo: gmac_power_pin@0 { ++ allwinner,pins = "PH23"; ++ allwinner,function = "gpio_out"; ++ allwinner,drive = <0>; ++ allwinner,pull = <0>; ++ }; ++ }; ++ ++ lradc: lradc@01c22800 { ++ allwinner,chan0-step = <200>; ++ linux,chan0-keycodes = <KEY_VOLUMEUP KEY_VOLUMEDOWN ++ KEY_MENU KEY_SEARCH KEY_HOME ++ KEY_ESC KEY_ENTER>; ++ status = "okay"; ++ }; ++ ++ ir0: ir@01c21800 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&ir0_pins_a>; ++ status = "okay"; ++ }; ++ ++ uart0: serial@01c28000 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_pins_a>; ++ status = "okay"; ++ }; ++ ++ uart6: serial@01c29800 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart6_pins_a>; ++ status = "okay"; ++ }; ++ ++ uart7: serial@01c29c00 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart7_pins_a>; ++ status = "okay"; ++ }; ++ ++ i2c0: i2c@01c2ac00 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c0_pins_a>; ++ status = "okay"; ++ ++ axp: axp20x@34 { ++ reg = <0x34>; ++ interrupt-parent = <&nmi_intc>; ++ interrupts = <0 8>; ++ axp,system-power-controller; ++ /include/ "x-powers-axp209.dtsi" ++ }; ++ }; ++ ++ i2c1: i2c@01c2b000 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c1_pins_a>; ++ status = "okay"; ++ }; ++ ++ i2c2: i2c@01c2b400 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c2_pins_a>; ++ status = "okay"; ++ }; ++ ++ gmac: ethernet@01c50000 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&gmac_pins_rgmii_a>; ++ phy = <&phy1>; ++ phy-mode = "rgmii"; ++ phy-supply = <®_gmac_3v3>; ++ status = "okay"; ++ ++ phy1: ethernet-phy@1 { ++ reg = <1>; ++ }; ++ }; ++ }; ++ ++ leds { ++ compatible = "gpio-leds"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&led_pins_lamobo>; ++ ++ green { ++ label = "lamobo:green:usr"; ++ gpios = <&pio 7 24 0>; ++ linux,default-trigger = "heartbeat"; ++ }; ++ }; ++ ++ reg_ahci_5v: ahci-5v { ++ status = "okay"; ++ }; ++ ++ reg_usb1_vbus: usb1-vbus { ++ status = "okay"; ++ }; ++ ++ reg_usb2_vbus: usb2-vbus { ++ status = "okay"; ++ }; ++ ++ reg_gmac_3v3: gmac-3v3 { ++ compatible = "regulator-fixed"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&gmac_power_pin_lamobo>; ++ regulator-name = "gmac-3v3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ startup-delay-us = <50000>; ++ enable-active-high; ++ gpio = <&pio 7 23 0>; ++ status = "okay"; ++ }; ++}; +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 9b5d46c..3fe9c4d 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 +@@ -193,6 +203,8 @@ config MDIO_BUS_MUX_MMIOREG + + Currently, only 8-bit registers are supported. + ++source "drivers/net/phy/b53/Kconfig" ++ + endif # PHYLIB + + config MICREL_KS8995MA +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index 9013dfa..d5c5c50 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 +@@ -16,6 +17,7 @@ obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o + obj-$(CONFIG_ICPLUS_PHY) += icplus.o + obj-$(CONFIG_REALTEK_PHY) += realtek.o + obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o ++obj-$(CONFIG_B53) += b53/ + obj-$(CONFIG_FIXED_PHY) += fixed.o + obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o + obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o +diff --git a/drivers/net/phy/b53/Kconfig b/drivers/net/phy/b53/Kconfig +new file mode 100644 +index 0000000..67e053e +--- /dev/null ++++ b/drivers/net/phy/b53/Kconfig +@@ -0,0 +1,37 @@ ++menuconfig B53 ++ tristate "Broadcom bcm53xx managed switch support" ++ depends on SWCONFIG ++ help ++ This driver adds support for Broadcom managed switch chips. It supports ++ BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX ++ integrated switches. ++ ++config B53_SPI_DRIVER ++ tristate "B53 SPI connected switch driver" ++ depends on B53 && SPI ++ help ++ Select to enable support for registering switches configured through SPI. ++ ++config B53_PHY_DRIVER ++ tristate "B53 MDIO connected switch driver" ++ depends on B53 ++ select B53_PHY_FIXUP ++ help ++ Select to enable support for registering switches configured through MDIO. ++ ++config B53_MMAP_DRIVER ++ tristate "B53 MMAP connected switch driver" ++ depends on B53 ++ help ++ Select to enable support for memory-mapped switches like the BCM63XX ++ integrated switches. ++ ++config B53_SRAB_DRIVER ++ tristate "B53 SRAB connected switch driver" ++ depends on B53 ++ help ++ Select to enable support for memory-mapped Switch Register Access ++ Bridge Registers (SRAB) like it is found on the BCM53010 ++ ++config B53_PHY_FIXUP ++ bool +diff --git a/drivers/net/phy/b53/Makefile b/drivers/net/phy/b53/Makefile +new file mode 100644 +index 0000000..7cc39c7 +--- /dev/null ++++ b/drivers/net/phy/b53/Makefile +@@ -0,0 +1,10 @@ ++obj-$(CONFIG_B53) += b53_common.o ++ ++obj-$(CONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o ++ ++obj-$(CONFIG_B53_MMAP_DRIVER) += b53_mmap.o ++obj-$(CONFIG_B53_SRAB_DRIVER) += b53_srab.o ++obj-$(CONFIG_B53_PHY_DRIVER) += b53_mdio.o ++obj-$(CONFIG_B53_SPI_DRIVER) += b53_spi.o ++ ++ccflags-y += -Werror +diff --git a/drivers/net/phy/b53/b53_common.c b/drivers/net/phy/b53/b53_common.c +new file mode 100644 +index 0000000..b82bc93 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_common.c +@@ -0,0 +1,1428 @@ ++/* ++ * B53 switch driver main logic ++ * ++ * Copyright (C) 2011-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include <linux/delay.h> ++#include <linux/export.h> ++#include <linux/gpio.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/switch.h> ++#include <linux/platform_data/b53.h> ++ ++#include "b53_regs.h" ++#include "b53_priv.h" ++ ++/* buffer size needed for displaying all MIBs with max'd values */ ++#define B53_BUF_SIZE 1188 ++ ++struct b53_mib_desc { ++ u8 size; ++ u8 offset; ++ const char *name; ++}; ++ ++ ++/* BCM5365 MIB counters */ ++static const struct b53_mib_desc b53_mibs_65[] = { ++ { 8, 0x00, "TxOctets" }, ++ { 4, 0x08, "TxDropPkts" }, ++ { 4, 0x10, "TxBroadcastPkts" }, ++ { 4, 0x14, "TxMulticastPkts" }, ++ { 4, 0x18, "TxUnicastPkts" }, ++ { 4, 0x1c, "TxCollisions" }, ++ { 4, 0x20, "TxSingleCollision" }, ++ { 4, 0x24, "TxMultipleCollision" }, ++ { 4, 0x28, "TxDeferredTransmit" }, ++ { 4, 0x2c, "TxLateCollision" }, ++ { 4, 0x30, "TxExcessiveCollision" }, ++ { 4, 0x38, "TxPausePkts" }, ++ { 8, 0x44, "RxOctets" }, ++ { 4, 0x4c, "RxUndersizePkts" }, ++ { 4, 0x50, "RxPausePkts" }, ++ { 4, 0x54, "Pkts64Octets" }, ++ { 4, 0x58, "Pkts65to127Octets" }, ++ { 4, 0x5c, "Pkts128to255Octets" }, ++ { 4, 0x60, "Pkts256to511Octets" }, ++ { 4, 0x64, "Pkts512to1023Octets" }, ++ { 4, 0x68, "Pkts1024to1522Octets" }, ++ { 4, 0x6c, "RxOversizePkts" }, ++ { 4, 0x70, "RxJabbers" }, ++ { 4, 0x74, "RxAlignmentErrors" }, ++ { 4, 0x78, "RxFCSErrors" }, ++ { 8, 0x7c, "RxGoodOctets" }, ++ { 4, 0x84, "RxDropPkts" }, ++ { 4, 0x88, "RxUnicastPkts" }, ++ { 4, 0x8c, "RxMulticastPkts" }, ++ { 4, 0x90, "RxBroadcastPkts" }, ++ { 4, 0x94, "RxSAChanges" }, ++ { 4, 0x98, "RxFragments" }, ++ { }, ++}; ++ ++/* BCM63xx MIB counters */ ++static const struct b53_mib_desc b53_mibs_63xx[] = { ++ { 8, 0x00, "TxOctets" }, ++ { 4, 0x08, "TxDropPkts" }, ++ { 4, 0x0c, "TxQoSPkts" }, ++ { 4, 0x10, "TxBroadcastPkts" }, ++ { 4, 0x14, "TxMulticastPkts" }, ++ { 4, 0x18, "TxUnicastPkts" }, ++ { 4, 0x1c, "TxCollisions" }, ++ { 4, 0x20, "TxSingleCollision" }, ++ { 4, 0x24, "TxMultipleCollision" }, ++ { 4, 0x28, "TxDeferredTransmit" }, ++ { 4, 0x2c, "TxLateCollision" }, ++ { 4, 0x30, "TxExcessiveCollision" }, ++ { 4, 0x38, "TxPausePkts" }, ++ { 8, 0x3c, "TxQoSOctets" }, ++ { 8, 0x44, "RxOctets" }, ++ { 4, 0x4c, "RxUndersizePkts" }, ++ { 4, 0x50, "RxPausePkts" }, ++ { 4, 0x54, "Pkts64Octets" }, ++ { 4, 0x58, "Pkts65to127Octets" }, ++ { 4, 0x5c, "Pkts128to255Octets" }, ++ { 4, 0x60, "Pkts256to511Octets" }, ++ { 4, 0x64, "Pkts512to1023Octets" }, ++ { 4, 0x68, "Pkts1024to1522Octets" }, ++ { 4, 0x6c, "RxOversizePkts" }, ++ { 4, 0x70, "RxJabbers" }, ++ { 4, 0x74, "RxAlignmentErrors" }, ++ { 4, 0x78, "RxFCSErrors" }, ++ { 8, 0x7c, "RxGoodOctets" }, ++ { 4, 0x84, "RxDropPkts" }, ++ { 4, 0x88, "RxUnicastPkts" }, ++ { 4, 0x8c, "RxMulticastPkts" }, ++ { 4, 0x90, "RxBroadcastPkts" }, ++ { 4, 0x94, "RxSAChanges" }, ++ { 4, 0x98, "RxFragments" }, ++ { 4, 0xa0, "RxSymbolErrors" }, ++ { 4, 0xa4, "RxQoSPkts" }, ++ { 8, 0xa8, "RxQoSOctets" }, ++ { 4, 0xb0, "Pkts1523to2047Octets" }, ++ { 4, 0xb4, "Pkts2048to4095Octets" }, ++ { 4, 0xb8, "Pkts4096to8191Octets" }, ++ { 4, 0xbc, "Pkts8192to9728Octets" }, ++ { 4, 0xc0, "RxDiscarded" }, ++ { } ++}; ++ ++/* MIB counters */ ++static const struct b53_mib_desc b53_mibs[] = { ++ { 8, 0x00, "TxOctets" }, ++ { 4, 0x08, "TxDropPkts" }, ++ { 4, 0x10, "TxBroadcastPkts" }, ++ { 4, 0x14, "TxMulticastPkts" }, ++ { 4, 0x18, "TxUnicastPkts" }, ++ { 4, 0x1c, "TxCollisions" }, ++ { 4, 0x20, "TxSingleCollision" }, ++ { 4, 0x24, "TxMultipleCollision" }, ++ { 4, 0x28, "TxDeferredTransmit" }, ++ { 4, 0x2c, "TxLateCollision" }, ++ { 4, 0x30, "TxExcessiveCollision" }, ++ { 4, 0x38, "TxPausePkts" }, ++ { 8, 0x50, "RxOctets" }, ++ { 4, 0x58, "RxUndersizePkts" }, ++ { 4, 0x5c, "RxPausePkts" }, ++ { 4, 0x60, "Pkts64Octets" }, ++ { 4, 0x64, "Pkts65to127Octets" }, ++ { 4, 0x68, "Pkts128to255Octets" }, ++ { 4, 0x6c, "Pkts256to511Octets" }, ++ { 4, 0x70, "Pkts512to1023Octets" }, ++ { 4, 0x74, "Pkts1024to1522Octets" }, ++ { 4, 0x78, "RxOversizePkts" }, ++ { 4, 0x7c, "RxJabbers" }, ++ { 4, 0x80, "RxAlignmentErrors" }, ++ { 4, 0x84, "RxFCSErrors" }, ++ { 8, 0x88, "RxGoodOctets" }, ++ { 4, 0x90, "RxDropPkts" }, ++ { 4, 0x94, "RxUnicastPkts" }, ++ { 4, 0x98, "RxMulticastPkts" }, ++ { 4, 0x9c, "RxBroadcastPkts" }, ++ { 4, 0xa0, "RxSAChanges" }, ++ { 4, 0xa4, "RxFragments" }, ++ { 4, 0xa8, "RxJumboPkts" }, ++ { 4, 0xac, "RxSymbolErrors" }, ++ { 4, 0xc0, "RxDiscarded" }, ++ { } ++}; ++ ++static int b53_do_vlan_op(struct b53_device *dev, u8 op) ++{ ++ unsigned int i; ++ ++ b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); ++ ++ for (i = 0; i < 10; i++) { ++ u8 vta; ++ ++ b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); ++ if (!(vta & VTA_START_CMD)) ++ return 0; ++ ++ usleep_range(100, 200); ++ } ++ ++ return -EIO; ++} ++ ++static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, ++ u16 untag) ++{ ++ if (is5325(dev)) { ++ u32 entry = 0; ++ ++ if (members) { ++ entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | ++ members; ++ if (dev->core_rev >= 3) ++ entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; ++ else ++ entry |= VA_VALID_25; ++ } ++ ++ b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | ++ VTA_RW_STATE_WR | VTA_RW_OP_EN); ++ } else if (is5365(dev)) { ++ u16 entry = 0; ++ ++ if (members) ++ entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | ++ members | VA_VALID_65; ++ ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | ++ VTA_RW_STATE_WR | VTA_RW_OP_EN); ++ } else { ++ b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); ++ b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], ++ (untag << VTE_UNTAG_S) | members); ++ ++ b53_do_vlan_op(dev, VTA_CMD_WRITE); ++ } ++} ++ ++void b53_set_forwarding(struct b53_device *dev, int enable) ++{ ++ u8 mgmt; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); ++ ++ if (enable) ++ mgmt |= SM_SW_FWD_EN; ++ else ++ mgmt &= ~SM_SW_FWD_EN; ++ ++ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); ++} ++ ++static void b53_enable_vlan(struct b53_device *dev, int enable) ++{ ++ u8 mgmt, vc0, vc1, vc4 = 0, vc5; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); ++ ++ if (is5325(dev) || is5365(dev)) { ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); ++ } else if (is63xx(dev)) { ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); ++ } else { ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); ++ } ++ ++ mgmt &= ~SM_SW_FWD_MODE; ++ ++ if (enable) { ++ vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; ++ vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; ++ vc4 &= ~VC4_ING_VID_CHECK_MASK; ++ vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; ++ vc5 |= VC5_DROP_VTABLE_MISS; ++ ++ if (is5325(dev)) ++ vc0 &= ~VC0_RESERVED_1; ++ ++ if (is5325(dev) || is5365(dev)) ++ vc1 |= VC1_RX_MCST_TAG_EN; ++ ++ if (!is5325(dev) && !is5365(dev)) { ++ if (dev->allow_vid_4095) ++ vc5 |= VC5_VID_FFF_EN; ++ else ++ vc5 &= ~VC5_VID_FFF_EN; ++ } ++ } else { ++ vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); ++ vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); ++ vc4 &= ~VC4_ING_VID_CHECK_MASK; ++ vc5 &= ~VC5_DROP_VTABLE_MISS; ++ ++ if (is5325(dev) || is5365(dev)) ++ vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; ++ else ++ vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; ++ ++ if (is5325(dev) || is5365(dev)) ++ vc1 &= ~VC1_RX_MCST_TAG_EN; ++ ++ if (!is5325(dev) && !is5365(dev)) ++ vc5 &= ~VC5_VID_FFF_EN; ++ } ++ ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); ++ ++ if (is5325(dev) || is5365(dev)) { ++ /* enable the high 8 bit vid check on 5325 */ ++ if (is5325(dev) && enable) ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, ++ VC3_HIGH_8BIT_EN); ++ else ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); ++ ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); ++ } else if (is63xx(dev)) { ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); ++ } else { ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); ++ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); ++ } ++ ++ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); ++} ++ ++static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) ++{ ++ u32 port_mask = 0; ++ u16 max_size = JMS_MIN_SIZE; ++ ++ if (is5325(dev) || is5365(dev)) ++ return -EINVAL; ++ ++ if (enable) { ++ port_mask = dev->enabled_ports; ++ max_size = JMS_MAX_SIZE; ++ if (allow_10_100) ++ port_mask |= JPM_10_100_JUMBO_EN; ++ } ++ ++ b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); ++ return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); ++} ++ ++static int b53_flush_arl(struct b53_device *dev) ++{ ++ unsigned int i; ++ ++ b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, ++ FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); ++ ++ for (i = 0; i < 10; i++) { ++ u8 fast_age_ctrl; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, ++ &fast_age_ctrl); ++ ++ if (!(fast_age_ctrl & FAST_AGE_DONE)) ++ return 0; ++ ++ mdelay(1); ++ } ++ ++ pr_warn("time out while flushing ARL\n"); ++ ++ return -EINVAL; ++} ++ ++static void b53_enable_ports(struct b53_device *dev) ++{ ++ unsigned i; ++ ++ b53_for_each_port(dev, i) { ++ u8 port_ctrl; ++ u16 pvlan_mask; ++ ++ /* ++ * prevent leaking packets between wan and lan in unmanaged ++ * mode through port vlans. ++ */ ++ if (dev->enable_vlan || is_cpu_port(dev, i)) ++ pvlan_mask = 0x1ff; ++ else if (is531x5(dev) || is5301x(dev)) ++ /* BCM53115 may use a different port as cpu port */ ++ pvlan_mask = BIT(dev->sw_dev.cpu_port); ++ else ++ pvlan_mask = BIT(B53_CPU_PORT); ++ ++ /* BCM5325 CPU port is at 8 */ ++ if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) ++ i = B53_CPU_PORT; ++ ++ if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) ++ /* disable unused ports 6 & 7 */ ++ port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; ++ else if (i == B53_CPU_PORT) ++ port_ctrl = PORT_CTRL_RX_BCST_EN | ++ PORT_CTRL_RX_MCST_EN | ++ PORT_CTRL_RX_UCST_EN; ++ else ++ port_ctrl = 0; ++ ++ b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), ++ pvlan_mask); ++ ++ /* port state is handled by bcm63xx_enet driver */ ++ if (!is63xx(dev) && !(is5301x(dev) && i == 6)) ++ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), ++ port_ctrl); ++ } ++} ++ ++static void b53_enable_mib(struct b53_device *dev) ++{ ++ u8 gc; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_GLOBAL_CONFIG, &gc); ++ ++ gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); ++ ++ b53_write8(dev, B53_CTRL_PAGE, B53_GLOBAL_CONFIG, gc); ++} ++ ++static int b53_apply(struct b53_device *dev) ++{ ++ int i; ++ ++ /* clear all vlan entries */ ++ if (is5325(dev) || is5365(dev)) { ++ for (i = 1; i < dev->sw_dev.vlans; i++) ++ b53_set_vlan_entry(dev, i, 0, 0); ++ } else { ++ b53_do_vlan_op(dev, VTA_CMD_CLEAR); ++ } ++ ++ b53_enable_vlan(dev, dev->enable_vlan); ++ ++ /* fill VLAN table */ ++ if (dev->enable_vlan) { ++ for (i = 0; i < dev->sw_dev.vlans; i++) { ++ struct b53_vlan *vlan = &dev->vlans[i]; ++ ++ if (!vlan->members) ++ continue; ++ ++ b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); ++ } ++ ++ b53_for_each_port(dev, i) ++ b53_write16(dev, B53_VLAN_PAGE, ++ B53_VLAN_PORT_DEF_TAG(i), ++ dev->ports[i].pvid); ++ } else { ++ b53_for_each_port(dev, i) ++ b53_write16(dev, B53_VLAN_PAGE, ++ B53_VLAN_PORT_DEF_TAG(i), 1); ++ ++ } ++ ++ b53_enable_ports(dev); ++ ++ if (!is5325(dev) && !is5365(dev)) ++ b53_set_jumbo(dev, dev->enable_jumbo, 1); ++ ++ return 0; ++} ++ ++static void b53_switch_reset_gpio(struct b53_device *dev) ++{ ++ int gpio = dev->reset_gpio; ++ ++ if (gpio < 0) ++ return; ++ ++ /* ++ * Reset sequence: RESET low(50ms)->high(20ms) ++ */ ++ gpio_set_value(gpio, 0); ++ mdelay(50); ++ ++ gpio_set_value(gpio, 1); ++ mdelay(20); ++ ++ dev->current_page = 0xff; ++} ++ ++static int b53_switch_reset(struct b53_device *dev) ++{ ++ u8 mgmt; ++ ++ b53_switch_reset_gpio(dev); ++ ++ if (is539x(dev)) { ++ b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); ++ b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); ++ } ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); ++ ++ if (!(mgmt & SM_SW_FWD_EN)) { ++ mgmt &= ~SM_SW_FWD_MODE; ++ mgmt |= SM_SW_FWD_EN; ++ ++ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); ++ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); ++ ++ if (!(mgmt & SM_SW_FWD_EN)) { ++ pr_err("Failed to enable switch!\n"); ++ return -EINVAL; ++ } ++ } ++ ++ /* enable all ports */ ++ b53_enable_ports(dev); ++ ++ /* configure MII port if necessary */ ++ if (is5325(dev)) { ++ u8 mii_port_override; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, ++ &mii_port_override); ++ /* reverse mii needs to be enabled */ ++ if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { ++ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, ++ mii_port_override | PORT_OVERRIDE_RV_MII_25); ++ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, ++ &mii_port_override); ++ ++ if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { ++ pr_err("Failed to enable reverse MII mode\n"); ++ return -EINVAL; ++ } ++ } ++ } else if ((is531x5(dev) || is5301x(dev)) && dev->sw_dev.cpu_port == B53_CPU_PORT) { ++ u8 mii_port_override; ++ ++ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, ++ &mii_port_override); ++ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, ++ mii_port_override | PORT_OVERRIDE_EN | ++ PORT_OVERRIDE_LINK); ++ } ++ ++ b53_enable_mib(dev); ++ ++ return b53_flush_arl(dev); ++} ++ ++/* ++ * Swconfig glue functions ++ */ ++ ++static int b53_global_get_vlan_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ val->value.i = priv->enable_vlan; ++ ++ return 0; ++} ++ ++static int b53_global_set_vlan_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ priv->enable_vlan = val->value.i; ++ ++ return 0; ++} ++ ++static int b53_global_get_jumbo_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ val->value.i = priv->enable_jumbo; ++ ++ return 0; ++} ++ ++static int b53_global_set_jumbo_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ priv->enable_jumbo = val->value.i; ++ ++ return 0; ++} ++ ++static int b53_global_get_4095_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ val->value.i = priv->allow_vid_4095; ++ ++ return 0; ++} ++ ++static int b53_global_set_4095_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ priv->allow_vid_4095 = val->value.i; ++ ++ return 0; ++} ++ ++static int b53_global_get_ports(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", ++ priv->enabled_ports); ++ val->value.s = priv->buf; ++ ++ return 0; ++} ++ ++static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ *val = priv->ports[port].pvid; ++ ++ return 0; ++} ++ ++static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ if (val > 15 && is5325(priv)) ++ return -EINVAL; ++ if (val == 4095 && !priv->allow_vid_4095) ++ return -EINVAL; ++ ++ priv->ports[port].pvid = val; ++ ++ return 0; ++} ++ ++static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ struct switch_port *port = &val->value.ports[0]; ++ struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; ++ int i; ++ ++ val->len = 0; ++ ++ if (!vlan->members) ++ return 0; ++ ++ for (i = 0; i < dev->ports; i++) { ++ if (!(vlan->members & BIT(i))) ++ continue; ++ ++ ++ if (!(vlan->untag & BIT(i))) ++ port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); ++ else ++ port->flags = 0; ++ ++ port->id = i; ++ val->len++; ++ port++; ++ } ++ ++ return 0; ++} ++ ++static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ struct switch_port *port; ++ struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; ++ int i; ++ ++ /* only BCM5325 and BCM5365 supports VID 0 */ ++ if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) ++ return -EINVAL; ++ ++ /* VLAN 4095 needs special handling */ ++ if (val->port_vlan == 4095 && !priv->allow_vid_4095) ++ return -EINVAL; ++ ++ port = &val->value.ports[0]; ++ vlan->members = 0; ++ vlan->untag = 0; ++ for (i = 0; i < val->len; i++, port++) { ++ vlan->members |= BIT(port->id); ++ ++ if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { ++ vlan->untag |= BIT(port->id); ++ priv->ports[port->id].pvid = val->port_vlan; ++ }; ++ } ++ ++ /* ignore disabled ports */ ++ vlan->members &= priv->enabled_ports; ++ vlan->untag &= priv->enabled_ports; ++ ++ return 0; ++} ++ ++static int b53_port_get_link(struct switch_dev *dev, int port, ++ struct switch_port_link *link) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ if (is_cpu_port(priv, port)) { ++ link->link = 1; ++ link->duplex = 1; ++ link->speed = is5325(priv) || is5365(priv) ? ++ SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; ++ link->aneg = 0; ++ } else if (priv->enabled_ports & BIT(port)) { ++ u32 speed; ++ u16 lnk, duplex; ++ ++ b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); ++ b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); ++ ++ lnk = (lnk >> port) & 1; ++ duplex = (duplex >> port) & 1; ++ ++ if (is5325(priv) || is5365(priv)) { ++ u16 tmp; ++ ++ b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); ++ speed = SPEED_PORT_FE(tmp, port); ++ } else { ++ b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); ++ speed = SPEED_PORT_GE(speed, port); ++ } ++ ++ link->link = lnk; ++ if (lnk) { ++ link->duplex = duplex; ++ switch (speed) { ++ case SPEED_STAT_10M: ++ link->speed = SWITCH_PORT_SPEED_10; ++ break; ++ case SPEED_STAT_100M: ++ link->speed = SWITCH_PORT_SPEED_100; ++ break; ++ case SPEED_STAT_1000M: ++ link->speed = SWITCH_PORT_SPEED_1000; ++ break; ++ } ++ } ++ ++ link->aneg = 1; ++ } else { ++ link->link = 0; ++ } ++ ++ return 0; ++ ++} ++ ++static int b53_global_reset_switch(struct switch_dev *dev) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ /* reset vlans */ ++ priv->enable_vlan = 0; ++ priv->enable_jumbo = 0; ++ priv->allow_vid_4095 = 0; ++ ++ memset(priv->vlans, 0, sizeof(priv->vlans) * dev->vlans); ++ memset(priv->ports, 0, sizeof(priv->ports) * dev->ports); ++ ++ return b53_switch_reset(priv); ++} ++ ++static int b53_global_apply_config(struct switch_dev *dev) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ ++ /* disable switching */ ++ b53_set_forwarding(priv, 0); ++ ++ b53_apply(priv); ++ ++ /* enable switching */ ++ b53_set_forwarding(priv, 1); ++ ++ return 0; ++} ++ ++ ++static int b53_global_reset_mib(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *priv = sw_to_b53(dev); ++ u8 gc; ++ ++ b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); ++ ++ b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); ++ mdelay(1); ++ b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); ++ mdelay(1); ++ ++ return 0; ++} ++ ++static int b53_port_get_mib(struct switch_dev *sw_dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct b53_device *dev = sw_to_b53(sw_dev); ++ const struct b53_mib_desc *mibs; ++ int port = val->port_vlan; ++ int len = 0; ++ ++ if (!(BIT(port) & dev->enabled_ports)) ++ return -1; ++ ++ if (is5365(dev)) { ++ if (port == 5) ++ port = 8; ++ ++ mibs = b53_mibs_65; ++ } else if (is63xx(dev)) { ++ mibs = b53_mibs_63xx; ++ } else { ++ mibs = b53_mibs; ++ } ++ ++ dev->buf[0] = 0; ++ ++ for (; mibs->size > 0; mibs++) { ++ u64 val; ++ ++ if (mibs->size == 8) { ++ b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); ++ } else { ++ u32 val32; ++ ++ b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, ++ &val32); ++ val = val32; ++ } ++ ++ len += snprintf(dev->buf + len, B53_BUF_SIZE - len, ++ "%-20s: %llu\n", mibs->name, val); ++ } ++ ++ val->len = len; ++ val->value.s = dev->buf; ++ ++ return 0; ++} ++ ++static struct switch_attr b53_global_ops_25[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = b53_global_set_vlan_enable, ++ .get = b53_global_get_vlan_enable, ++ .max = 1, ++ }, ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "ports", ++ .description = "Available ports (as bitmask)", ++ .get = b53_global_get_ports, ++ }, ++}; ++ ++static struct switch_attr b53_global_ops_65[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = b53_global_set_vlan_enable, ++ .get = b53_global_get_vlan_enable, ++ .max = 1, ++ }, ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "ports", ++ .description = "Available ports (as bitmask)", ++ .get = b53_global_get_ports, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "reset_mib", ++ .description = "Reset MIB counters", ++ .set = b53_global_reset_mib, ++ }, ++}; ++ ++static struct switch_attr b53_global_ops[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = b53_global_set_vlan_enable, ++ .get = b53_global_get_vlan_enable, ++ .max = 1, ++ }, ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "ports", ++ .description = "Available Ports (as bitmask)", ++ .get = b53_global_get_ports, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "reset_mib", ++ .description = "Reset MIB counters", ++ .set = b53_global_reset_mib, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_jumbo", ++ .description = "Enable Jumbo Frames", ++ .set = b53_global_set_jumbo_enable, ++ .get = b53_global_get_jumbo_enable, ++ .max = 1, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "allow_vid_4095", ++ .description = "Allow VID 4095", ++ .set = b53_global_set_4095_enable, ++ .get = b53_global_get_4095_enable, ++ .max = 1, ++ }, ++}; ++ ++static struct switch_attr b53_port_ops[] = { ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "mib", ++ .description = "Get port's MIB counters", ++ .get = b53_port_get_mib, ++ }, ++}; ++ ++static struct switch_attr b53_no_ops[] = { ++}; ++ ++static const struct switch_dev_ops b53_switch_ops_25 = { ++ .attr_global = { ++ .attr = b53_global_ops_25, ++ .n_attr = ARRAY_SIZE(b53_global_ops_25), ++ }, ++ .attr_port = { ++ .attr = b53_no_ops, ++ .n_attr = ARRAY_SIZE(b53_no_ops), ++ }, ++ .attr_vlan = { ++ .attr = b53_no_ops, ++ .n_attr = ARRAY_SIZE(b53_no_ops), ++ }, ++ ++ .get_vlan_ports = b53_vlan_get_ports, ++ .set_vlan_ports = b53_vlan_set_ports, ++ .get_port_pvid = b53_port_get_pvid, ++ .set_port_pvid = b53_port_set_pvid, ++ .apply_config = b53_global_apply_config, ++ .reset_switch = b53_global_reset_switch, ++ .get_port_link = b53_port_get_link, ++}; ++ ++static const struct switch_dev_ops b53_switch_ops_65 = { ++ .attr_global = { ++ .attr = b53_global_ops_65, ++ .n_attr = ARRAY_SIZE(b53_global_ops_65), ++ }, ++ .attr_port = { ++ .attr = b53_port_ops, ++ .n_attr = ARRAY_SIZE(b53_port_ops), ++ }, ++ .attr_vlan = { ++ .attr = b53_no_ops, ++ .n_attr = ARRAY_SIZE(b53_no_ops), ++ }, ++ ++ .get_vlan_ports = b53_vlan_get_ports, ++ .set_vlan_ports = b53_vlan_set_ports, ++ .get_port_pvid = b53_port_get_pvid, ++ .set_port_pvid = b53_port_set_pvid, ++ .apply_config = b53_global_apply_config, ++ .reset_switch = b53_global_reset_switch, ++ .get_port_link = b53_port_get_link, ++}; ++ ++static const struct switch_dev_ops b53_switch_ops = { ++ .attr_global = { ++ .attr = b53_global_ops, ++ .n_attr = ARRAY_SIZE(b53_global_ops), ++ }, ++ .attr_port = { ++ .attr = b53_port_ops, ++ .n_attr = ARRAY_SIZE(b53_port_ops), ++ }, ++ .attr_vlan = { ++ .attr = b53_no_ops, ++ .n_attr = ARRAY_SIZE(b53_no_ops), ++ }, ++ ++ .get_vlan_ports = b53_vlan_get_ports, ++ .set_vlan_ports = b53_vlan_set_ports, ++ .get_port_pvid = b53_port_get_pvid, ++ .set_port_pvid = b53_port_set_pvid, ++ .apply_config = b53_global_apply_config, ++ .reset_switch = b53_global_reset_switch, ++ .get_port_link = b53_port_get_link, ++}; ++ ++struct b53_chip_data { ++ u32 chip_id; ++ const char *dev_name; ++ const char *alias; ++ u16 vlans; ++ u16 enabled_ports; ++ u8 cpu_port; ++ u8 vta_regs[3]; ++ u8 duplex_reg; ++ u8 jumbo_pm_reg; ++ u8 jumbo_size_reg; ++ const struct switch_dev_ops *sw_ops; ++}; ++ ++#define B53_VTA_REGS \ ++ { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } ++#define B53_VTA_REGS_9798 \ ++ { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } ++#define B53_VTA_REGS_63XX \ ++ { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } ++ ++static const struct b53_chip_data b53_switch_chips[] = { ++ { ++ .chip_id = BCM5325_DEVICE_ID, ++ .dev_name = "BCM5325", ++ .alias = "bcm5325", ++ .vlans = 16, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, ++ .duplex_reg = B53_DUPLEX_STAT_FE, ++ .sw_ops = &b53_switch_ops_25, ++ }, ++ { ++ .chip_id = BCM5365_DEVICE_ID, ++ .dev_name = "BCM5365", ++ .alias = "bcm5365", ++ .vlans = 256, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, ++ .duplex_reg = B53_DUPLEX_STAT_FE, ++ .sw_ops = &b53_switch_ops_65, ++ }, ++ { ++ .chip_id = BCM5395_DEVICE_ID, ++ .dev_name = "BCM5395", ++ .alias = "bcm5395", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM5397_DEVICE_ID, ++ .dev_name = "BCM5397", ++ .alias = "bcm5397", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS_9798, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM5398_DEVICE_ID, ++ .dev_name = "BCM5398", ++ .alias = "bcm5398", ++ .vlans = 4096, ++ .enabled_ports = 0x7f, ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS_9798, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53115_DEVICE_ID, ++ .dev_name = "BCM53115", ++ .alias = "bcm53115", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .vta_regs = B53_VTA_REGS, ++ .cpu_port = B53_CPU_PORT, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53125_DEVICE_ID, ++ .dev_name = "BCM53125", ++ .alias = "bcm53125", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53128_DEVICE_ID, ++ .dev_name = "BCM53128", ++ .alias = "bcm53128", ++ .vlans = 4096, ++ .enabled_ports = 0x1ff, ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM63XX_DEVICE_ID, ++ .dev_name = "BCM63xx", ++ .alias = "bcm63xx", ++ .vlans = 4096, ++ .enabled_ports = 0, /* pdata must provide them */ ++ .cpu_port = B53_CPU_PORT, ++ .vta_regs = B53_VTA_REGS_63XX, ++ .duplex_reg = B53_DUPLEX_STAT_63XX, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53010_DEVICE_ID, ++ .dev_name = "BCM53010", ++ .alias = "bcm53011", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53011_DEVICE_ID, ++ .dev_name = "BCM53011", ++ .alias = "bcm53011", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53012_DEVICE_ID, ++ .dev_name = "BCM53012", ++ .alias = "bcm53011", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53018_DEVICE_ID, ++ .dev_name = "BCM53018", ++ .alias = "bcm53018", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++ { ++ .chip_id = BCM53019_DEVICE_ID, ++ .dev_name = "BCM53019", ++ .alias = "bcm53019", ++ .vlans = 4096, ++ .enabled_ports = 0x1f, ++ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect ++ .vta_regs = B53_VTA_REGS, ++ .duplex_reg = B53_DUPLEX_STAT_GE, ++ .jumbo_pm_reg = B53_JUMBO_PORT_MASK, ++ .jumbo_size_reg = B53_JUMBO_MAX_SIZE, ++ .sw_ops = &b53_switch_ops, ++ }, ++}; ++ ++static int b53_switch_init(struct b53_device *dev) ++{ ++ struct switch_dev *sw_dev = &dev->sw_dev; ++ unsigned i; ++ int ret; ++ ++ for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { ++ const struct b53_chip_data *chip = &b53_switch_chips[i]; ++ ++ if (chip->chip_id == dev->chip_id) { ++ sw_dev->name = chip->dev_name; ++ if (!sw_dev->alias) ++ sw_dev->alias = chip->alias; ++ if (!dev->enabled_ports) ++ dev->enabled_ports = chip->enabled_ports; ++ dev->duplex_reg = chip->duplex_reg; ++ dev->vta_regs[0] = chip->vta_regs[0]; ++ dev->vta_regs[1] = chip->vta_regs[1]; ++ dev->vta_regs[2] = chip->vta_regs[2]; ++ dev->jumbo_pm_reg = chip->jumbo_pm_reg; ++ sw_dev->ops = chip->sw_ops; ++ sw_dev->cpu_port = chip->cpu_port; ++ sw_dev->vlans = chip->vlans; ++ break; ++ } ++ } ++ ++ if (!sw_dev->name) ++ return -EINVAL; ++ ++ /* check which BCM5325x version we have */ ++ if (is5325(dev)) { ++ u8 vc4; ++ ++ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); ++ ++ /* check reserved bits */ ++ switch (vc4 & 3) { ++ case 1: ++ /* BCM5325E */ ++ break; ++ case 3: ++ /* BCM5325F - do not use port 4 */ ++ dev->enabled_ports &= ~BIT(4); ++ break; ++ default: ++/* On the BCM47XX SoCs this is the supported internal switch.*/ ++#ifndef CONFIG_BCM47XX ++ /* BCM5325M */ ++ return -EINVAL; ++#else ++ break; ++#endif ++ } ++ } else if (dev->chip_id == BCM53115_DEVICE_ID) { ++ u64 strap_value; ++ ++ b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); ++ /* use second IMP port if GMII is enabled */ ++ if (strap_value & SV_GMII_CTRL_115) ++ sw_dev->cpu_port = 5; ++ } ++ ++ /* cpu port is always last */ ++ sw_dev->ports = sw_dev->cpu_port + 1; ++ dev->enabled_ports |= BIT(sw_dev->cpu_port); ++ ++ dev->ports = devm_kzalloc(dev->dev, ++ sizeof(struct b53_port) * sw_dev->ports, ++ GFP_KERNEL); ++ if (!dev->ports) ++ return -ENOMEM; ++ ++ dev->vlans = devm_kzalloc(dev->dev, ++ sizeof(struct b53_vlan) * sw_dev->vlans, ++ GFP_KERNEL); ++ if (!dev->vlans) ++ return -ENOMEM; ++ ++ dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); ++ if (!dev->buf) ++ return -ENOMEM; ++ ++ dev->reset_gpio = b53_switch_get_reset_gpio(dev); ++ if (dev->reset_gpio >= 0) { ++ ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, GPIOF_OUT_INIT_HIGH, "robo_reset"); ++ if (ret) ++ return ret; ++ } ++ ++ return b53_switch_reset(dev); ++} ++ ++struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, ++ void *priv) ++{ ++ struct b53_device *dev; ++ ++ dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return NULL; ++ ++ dev->dev = base; ++ dev->ops = ops; ++ dev->priv = priv; ++ mutex_init(&dev->reg_mutex); ++ ++ return dev; ++} ++EXPORT_SYMBOL(b53_switch_alloc); ++ ++int b53_switch_detect(struct b53_device *dev) ++{ ++ u32 id32; ++ u16 tmp; ++ u8 id8; ++ int ret; ++ ++ ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); ++ if (ret) ++ return ret; ++ ++ switch (id8) { ++ case 0: ++ /* ++ * BCM5325 and BCM5365 do not have this register so reads ++ * return 0. But the read operation did succeed, so assume ++ * this is one of them. ++ * ++ * Next check if we can write to the 5325's VTA register; for ++ * 5365 it is read only. ++ */ ++ ++ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); ++ b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); ++ ++ if (tmp == 0xf) ++ dev->chip_id = BCM5325_DEVICE_ID; ++ else ++ dev->chip_id = BCM5365_DEVICE_ID; ++ break; ++ case BCM5395_DEVICE_ID: ++ case BCM5397_DEVICE_ID: ++ case BCM5398_DEVICE_ID: ++ dev->chip_id = id8; ++ break; ++ default: ++ ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); ++ if (ret) ++ return ret; ++ ++ switch (id32) { ++ case BCM53115_DEVICE_ID: ++ case BCM53125_DEVICE_ID: ++ case BCM53128_DEVICE_ID: ++ case BCM53010_DEVICE_ID: ++ case BCM53011_DEVICE_ID: ++ case BCM53012_DEVICE_ID: ++ case BCM53018_DEVICE_ID: ++ case BCM53019_DEVICE_ID: ++ dev->chip_id = id32; ++ break; ++ default: ++ pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", ++ id8, id32); ++ return -ENODEV; ++ } ++ } ++ ++ if (dev->chip_id == BCM5325_DEVICE_ID) ++ return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, ++ &dev->core_rev); ++ else ++ return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, ++ &dev->core_rev); ++} ++EXPORT_SYMBOL(b53_switch_detect); ++ ++int b53_switch_register(struct b53_device *dev) ++{ ++ int ret; ++ ++ if (dev->pdata) { ++ dev->chip_id = dev->pdata->chip_id; ++ dev->enabled_ports = dev->pdata->enabled_ports; ++ dev->sw_dev.alias = dev->pdata->alias; ++ } ++ ++ if (!dev->chip_id && b53_switch_detect(dev)) ++ return -EINVAL; ++ ++ ret = b53_switch_init(dev); ++ if (ret) ++ return ret; ++ ++ pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); ++ ++ return register_switch(&dev->sw_dev, NULL); ++} ++EXPORT_SYMBOL(b53_switch_register); ++ ++MODULE_AUTHOR("Jonas Gorski jogo@openwrt.org"); ++MODULE_DESCRIPTION("B53 switch library"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/phy/b53/b53_mdio.c b/drivers/net/phy/b53/b53_mdio.c +new file mode 100644 +index 0000000..9062115 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_mdio.c +@@ -0,0 +1,434 @@ ++/* ++ * B53 register access through MII registers ++ * ++ * Copyright (C) 2011-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/phy.h> ++#include <linux/module.h> ++ ++#include "b53_priv.h" ++ ++#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ ++ ++/* MII registers */ ++#define REG_MII_PAGE 0x10 /* MII Page register */ ++#define REG_MII_ADDR 0x11 /* MII Address register */ ++#define REG_MII_DATA0 0x18 /* MII Data register 0 */ ++#define REG_MII_DATA1 0x19 /* MII Data register 1 */ ++#define REG_MII_DATA2 0x1a /* MII Data register 2 */ ++#define REG_MII_DATA3 0x1b /* MII Data register 3 */ ++ ++#define REG_MII_PAGE_ENABLE BIT(0) ++#define REG_MII_ADDR_WRITE BIT(0) ++#define REG_MII_ADDR_READ BIT(1) ++ ++static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) ++{ ++ int i; ++ u16 v; ++ int ret; ++ struct mii_bus *bus = dev->priv; ++ ++ if (dev->current_page != page) { ++ /* set page number */ ++ v = (page << 8) | REG_MII_PAGE_ENABLE; ++ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); ++ if (ret) ++ return ret; ++ dev->current_page = page; ++ } ++ ++ /* set register address */ ++ v = (reg << 8) | op; ++ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); ++ if (ret) ++ return ret; ++ ++ /* check if operation completed */ ++ for (i = 0; i < 5; ++i) { ++ v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); ++ if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) ++ break; ++ usleep_range(10, 100); ++ } ++ ++ if (WARN_ON(i == 5)) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) ++{ ++ struct mii_bus *bus = dev->priv; ++ int ret; ++ ++ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); ++ if (ret) ++ return ret; ++ ++ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; ++ ++ return 0; ++} ++ ++static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) ++{ ++ struct mii_bus *bus = dev->priv; ++ int ret; ++ ++ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); ++ if (ret) ++ return ret; ++ ++ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); ++ ++ return 0; ++} ++ ++static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) ++{ ++ struct mii_bus *bus = dev->priv; ++ int ret; ++ ++ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); ++ if (ret) ++ return ret; ++ ++ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); ++ *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; ++ ++ return 0; ++} ++ ++static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ struct mii_bus *bus = dev->priv; ++ u64 temp = 0; ++ int i; ++ int ret; ++ ++ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); ++ if (ret) ++ return ret; ++ ++ for (i = 2; i >= 0; i--) { ++ temp <<= 16; ++ temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); ++ } ++ ++ *val = temp; ++ ++ return 0; ++} ++ ++static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ struct mii_bus *bus = dev->priv; ++ u64 temp = 0; ++ int i; ++ int ret; ++ ++ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); ++ if (ret) ++ return ret; ++ ++ for (i = 3; i >= 0; i--) { ++ temp <<= 16; ++ temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); ++ } ++ ++ *val = temp; ++ ++ return 0; ++} ++ ++static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) ++{ ++ struct mii_bus *bus = dev->priv; ++ int ret; ++ ++ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); ++ if (ret) ++ return ret; ++ ++ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); ++} ++ ++static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, ++ u16 value) ++{ ++ struct mii_bus *bus = dev->priv; ++ int ret; ++ ++ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); ++ if (ret) ++ return ret; ++ ++ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); ++} ++ ++static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, ++ u32 value) ++{ ++ struct mii_bus *bus = dev->priv; ++ unsigned int i; ++ u32 temp = value; ++ ++ for (i = 0; i < 2; i++) { ++ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, ++ temp & 0xffff); ++ if (ret) ++ return ret; ++ temp >>= 16; ++ } ++ ++ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); ++ ++} ++ ++static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ struct mii_bus *bus = dev->priv; ++ unsigned i; ++ u64 temp = value; ++ ++ for (i = 0; i < 3; i++) { ++ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, ++ temp & 0xffff); ++ if (ret) ++ return ret; ++ temp >>= 16; ++ } ++ ++ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); ++ ++} ++ ++static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ struct mii_bus *bus = dev->priv; ++ unsigned i; ++ u64 temp = value; ++ ++ for (i = 0; i < 4; i++) { ++ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, ++ temp & 0xffff); ++ if (ret) ++ return ret; ++ temp >>= 16; ++ } ++ ++ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); ++} ++ ++static struct b53_io_ops b53_mdio_ops = { ++ .read8 = b53_mdio_read8, ++ .read16 = b53_mdio_read16, ++ .read32 = b53_mdio_read32, ++ .read48 = b53_mdio_read48, ++ .read64 = b53_mdio_read64, ++ .write8 = b53_mdio_write8, ++ .write16 = b53_mdio_write16, ++ .write32 = b53_mdio_write32, ++ .write48 = b53_mdio_write48, ++ .write64 = b53_mdio_write64, ++}; ++ ++static int b53_phy_probe(struct phy_device *phydev) ++{ ++ struct b53_device dev; ++ int ret; ++ ++ /* allow the generic phy driver to take over */ ++ if (phydev->addr != B53_PSEUDO_PHY && phydev->addr != 0) ++ return -ENODEV; ++ ++ dev.current_page = 0xff; ++ dev.priv = phydev->bus; ++ dev.ops = &b53_mdio_ops; ++ dev.pdata = NULL; ++ mutex_init(&dev.reg_mutex); ++ ++ ret = b53_switch_detect(&dev); ++ if (ret) ++ return ret; ++ ++ if (is5325(&dev) || is5365(&dev)) ++ phydev->supported = SUPPORTED_100baseT_Full; ++ else ++ phydev->supported = SUPPORTED_1000baseT_Full; ++ ++ phydev->advertising = phydev->supported; ++ ++ return 0; ++} ++ ++static int b53_phy_config_init(struct phy_device *phydev) ++{ ++ struct b53_device *dev; ++ int ret; ++ ++ dev = b53_switch_alloc(&phydev->dev, &b53_mdio_ops, phydev->bus); ++ if (!dev) ++ return -ENOMEM; ++ ++ /* we don't use page 0xff, so force a page set */ ++ dev->current_page = 0xff; ++ /* force the ethX as alias */ ++ dev->sw_dev.alias = phydev->attached_dev->name; ++ ++ ret = b53_switch_register(dev); ++ if (ret) { ++ dev_err(dev->dev, "failed to register switch: %i\n", ret); ++ return ret; ++ } ++ ++ phydev->priv = dev; ++ ++ return 0; ++} ++ ++static void b53_phy_remove(struct phy_device *phydev) ++{ ++ struct b53_device *priv = phydev->priv; ++ ++ if (!priv) ++ return; ++ ++ b53_switch_remove(priv); ++ ++ phydev->priv = NULL; ++} ++ ++static int b53_phy_config_aneg(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int b53_phy_read_status(struct phy_device *phydev) ++{ ++ struct b53_device *priv = phydev->priv; ++ ++ if (is5325(priv) || is5365(priv)) ++ phydev->speed = 100; ++ else ++ phydev->speed = 1000; ++ ++ phydev->duplex = DUPLEX_FULL; ++ phydev->link = 1; ++ phydev->state = PHY_RUNNING; ++ ++ netif_carrier_on(phydev->attached_dev); ++ phydev->adjust_link(phydev->attached_dev); ++ ++ return 0; ++} ++ ++/* BCM5325, BCM539x */ ++static struct phy_driver b53_phy_driver_id1 = { ++ .phy_id = 0x0143bc00, ++ .name = "Broadcom B53 (1)", ++ .phy_id_mask = 0x1ffffc00, ++ .features = 0, ++ .probe = b53_phy_probe, ++ .remove = b53_phy_remove, ++ .config_aneg = b53_phy_config_aneg, ++ .config_init = b53_phy_config_init, ++ .read_status = b53_phy_read_status, ++ .driver = { ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++/* BCM53125, BCM53128 */ ++static struct phy_driver b53_phy_driver_id2 = { ++ .phy_id = 0x03625c00, ++ .name = "Broadcom B53 (2)", ++ .phy_id_mask = 0x1ffffc00, ++ .features = 0, ++ .probe = b53_phy_probe, ++ .remove = b53_phy_remove, ++ .config_aneg = b53_phy_config_aneg, ++ .config_init = b53_phy_config_init, ++ .read_status = b53_phy_read_status, ++ .driver = { ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++/* BCM5365 */ ++static struct phy_driver b53_phy_driver_id3 = { ++ .phy_id = 0x00406000, ++ .name = "Broadcom B53 (3)", ++ .phy_id_mask = 0x1ffffc00, ++ .features = 0, ++ .probe = b53_phy_probe, ++ .remove = b53_phy_remove, ++ .config_aneg = b53_phy_config_aneg, ++ .config_init = b53_phy_config_init, ++ .read_status = b53_phy_read_status, ++ .driver = { ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++int __init b53_phy_driver_register(void) ++{ ++ int ret; ++ ++ ret = phy_driver_register(&b53_phy_driver_id1); ++ if (ret) ++ return ret; ++ ++ ret = phy_driver_register(&b53_phy_driver_id2); ++ if (ret) ++ goto err1; ++ ++ ret = phy_driver_register(&b53_phy_driver_id3); ++ if (!ret) ++ return 0; ++ ++ phy_driver_unregister(&b53_phy_driver_id2); ++err1: ++ phy_driver_unregister(&b53_phy_driver_id1); ++ return ret; ++} ++ ++void __exit b53_phy_driver_unregister(void) ++{ ++ phy_driver_unregister(&b53_phy_driver_id3); ++ phy_driver_unregister(&b53_phy_driver_id2); ++ phy_driver_unregister(&b53_phy_driver_id1); ++} ++ ++module_init(b53_phy_driver_register); ++module_exit(b53_phy_driver_unregister); ++ ++static struct mdio_device_id __maybe_unused b53_mdio_tbl[] = { ++ { 0x0143bc00, 0x1ffffc00 }, ++ { 0x03625c00, 0x1ffffc00 }, ++ { 0x00406000, 0x1ffffc00 }, ++ { } ++}; ++ ++ ++MODULE_DESCRIPTION("B53 MDIO access driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DEVICE_TABLE(mdio, b53_mdio_tbl); +diff --git a/drivers/net/phy/b53/b53_mmap.c b/drivers/net/phy/b53/b53_mmap.c +new file mode 100644 +index 0000000..272360f +--- /dev/null ++++ b/drivers/net/phy/b53/b53_mmap.c +@@ -0,0 +1,240 @@ ++/* ++ * B53 register access through memory mapped registers ++ * ++ * Copyright (C) 2012-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/platform_data/b53.h> ++ ++#include "b53_priv.h" ++ ++static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ *val = readb(regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 2)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) ++ *val = readw_be(regs + (page << 8) + reg); ++ else ++ *val = readw(regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) ++ *val = readl_be(regs + (page << 8) + reg); ++ else ++ *val = readl(regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) { ++ *val = readl_be(regs + (page << 8) + reg); ++ *val <<= 16; ++ *val |= readw_be(regs + (page << 8) + reg + 4); ++ } else { ++ *val |= readw(regs + (page << 8) + reg + 4); ++ *val <<= 32; ++ *val = readl(regs + (page << 8) + reg); ++ } ++ ++ return 0; ++} ++ ++static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ u32 hi, lo; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) { ++ lo = readl_be(regs + (page << 8) + reg); ++ hi = readl_be(regs + (page << 8) + reg + 4); ++ } else { ++ lo = readl(regs + (page << 8) + reg); ++ hi = readl(regs + (page << 8) + reg + 4); ++ } ++ ++ *val = ((u64)hi << 32) | lo; ++ ++ return 0; ++} ++ ++static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ writeb(value, regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, ++ u16 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 2)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) ++ writew_be(value, regs + (page << 8) + reg); ++ else ++ writew(value, regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, ++ u32 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) ++ writel_be(value, regs + (page << 8) + reg); ++ else ++ writel(value, regs + (page << 8) + reg); ++ ++ return 0; ++} ++ ++static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) { ++ writel_be((u32)(value >> 16), regs + (page << 8) + reg); ++ writew_be((u16)value, regs + (page << 8) + reg + 4); ++ } else { ++ writel((u32)value, regs + (page << 8) + reg); ++ writew((u16)(value >> 32), regs + (page << 8) + reg + 4); ++ } ++ ++ return 0; ++} ++ ++static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ ++ if (WARN_ON(reg % 4)) ++ return -EINVAL; ++ ++ if (dev->pdata && dev->pdata->big_endian) { ++ writel_be((u32)(value >> 32), regs + (page << 8) + reg); ++ writel_be((u32)value, regs + (page << 8) + reg + 4); ++ } else { ++ writel((u32)value, regs + (page << 8) + reg); ++ writel((u32)(value >> 32), regs + (page << 8) + reg + 4); ++ } ++ ++ return 0; ++} ++ ++static struct b53_io_ops b53_mmap_ops = { ++ .read8 = b53_mmap_read8, ++ .read16 = b53_mmap_read16, ++ .read32 = b53_mmap_read32, ++ .read48 = b53_mmap_read48, ++ .read64 = b53_mmap_read64, ++ .write8 = b53_mmap_write8, ++ .write16 = b53_mmap_write16, ++ .write32 = b53_mmap_write32, ++ .write48 = b53_mmap_write48, ++ .write64 = b53_mmap_write64, ++}; ++ ++static int b53_mmap_probe(struct platform_device *pdev) ++{ ++ struct b53_platform_data *pdata = pdev->dev.platform_data; ++ struct b53_device *dev; ++ ++ if (!pdata) ++ return -EINVAL; ++ ++ dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); ++ if (!dev) ++ return -ENOMEM; ++ ++ if (pdata) ++ dev->pdata = pdata; ++ ++ platform_set_drvdata(pdev, dev); ++ ++ return b53_switch_register(dev); ++} ++ ++static int b53_mmap_remove(struct platform_device *pdev) ++{ ++ struct b53_device *dev = platform_get_drvdata(pdev); ++ ++ if (dev) { ++ b53_switch_remove(dev); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver b53_mmap_driver = { ++ .probe = b53_mmap_probe, ++ .remove = b53_mmap_remove, ++ .driver = { ++ .name = "b53-switch", ++ }, ++}; ++ ++module_platform_driver(b53_mmap_driver); ++MODULE_AUTHOR("Jonas Gorski jogo@openwrt.org"); ++MODULE_DESCRIPTION("B53 MMAP access driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/phy/b53/b53_phy_fixup.c b/drivers/net/phy/b53/b53_phy_fixup.c +new file mode 100644 +index 0000000..72d1373 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_phy_fixup.c +@@ -0,0 +1,55 @@ ++/* ++ * B53 PHY Fixup call ++ * ++ * Copyright (C) 2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/phy.h> ++ ++#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ ++ ++#define B53_BRCM_OUI_1 0x0143bc00 ++#define B53_BRCM_OUI_2 0x03625c00 ++#define B53_BRCM_OUI_3 0x00406000 ++ ++static int b53_phy_fixup(struct phy_device *dev) ++{ ++ u32 phy_id; ++ struct mii_bus *bus = dev->bus; ++ ++ if (dev->addr != B53_PSEUDO_PHY) ++ return 0; ++ ++ /* read the first port's id */ ++ phy_id = mdiobus_read(bus, 0, 2) << 16; ++ phy_id |= mdiobus_read(bus, 0, 3); ++ ++ if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 || ++ (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 || ++ (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) { ++ dev->phy_id = phy_id; ++ } ++ ++ return 0; ++} ++ ++int __init b53_phy_fixup_register(void) ++{ ++ return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup); ++} ++ ++subsys_initcall(b53_phy_fixup_register); +diff --git a/drivers/net/phy/b53/b53_priv.h b/drivers/net/phy/b53/b53_priv.h +new file mode 100644 +index 0000000..bc9b533 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_priv.h +@@ -0,0 +1,324 @@ ++/* ++ * B53 common definitions ++ * ++ * Copyright (C) 2011-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#ifndef __B53_PRIV_H ++#define __B53_PRIV_H ++ ++#include <linux/kernel.h> ++#include <linux/mutex.h> ++#include <linux/switch.h> ++ ++struct b53_device; ++ ++struct b53_io_ops { ++ int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); ++ int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); ++ int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); ++ int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); ++ int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); ++ int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); ++ int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); ++ int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); ++ int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); ++ int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); ++}; ++ ++enum { ++ BCM5325_DEVICE_ID = 0x25, ++ BCM5365_DEVICE_ID = 0x65, ++ BCM5395_DEVICE_ID = 0x95, ++ BCM5397_DEVICE_ID = 0x97, ++ BCM5398_DEVICE_ID = 0x98, ++ BCM53115_DEVICE_ID = 0x53115, ++ BCM53125_DEVICE_ID = 0x53125, ++ BCM53128_DEVICE_ID = 0x53128, ++ BCM63XX_DEVICE_ID = 0x6300, ++ BCM53010_DEVICE_ID = 0x53010, ++ BCM53011_DEVICE_ID = 0x53011, ++ BCM53012_DEVICE_ID = 0x53012, ++ BCM53018_DEVICE_ID = 0x53018, ++ BCM53019_DEVICE_ID = 0x53019, ++}; ++ ++#define B53_N_PORTS 9 ++#define B53_N_PORTS_25 6 ++ ++struct b53_vlan { ++ unsigned int members:B53_N_PORTS; ++ unsigned int untag:B53_N_PORTS; ++}; ++ ++struct b53_port { ++ unsigned int pvid:12; ++}; ++ ++struct b53_device { ++ struct switch_dev sw_dev; ++ struct b53_platform_data *pdata; ++ ++ struct mutex reg_mutex; ++ const struct b53_io_ops *ops; ++ ++ /* chip specific data */ ++ u32 chip_id; ++ u8 core_rev; ++ u8 vta_regs[3]; ++ u8 duplex_reg; ++ u8 jumbo_pm_reg; ++ u8 jumbo_size_reg; ++ int reset_gpio; ++ ++ /* used ports mask */ ++ u16 enabled_ports; ++ ++ /* connect specific data */ ++ u8 current_page; ++ struct device *dev; ++ void *priv; ++ ++ /* run time configuration */ ++ unsigned enable_vlan:1; ++ unsigned enable_jumbo:1; ++ unsigned allow_vid_4095:1; ++ ++ struct b53_port *ports; ++ struct b53_vlan *vlans; ++ ++ char *buf; ++}; ++ ++#define b53_for_each_port(dev, i) \ ++ for (i = 0; i < B53_N_PORTS; i++) \ ++ if (dev->enabled_ports & BIT(i)) ++ ++ ++ ++static inline int is5325(struct b53_device *dev) ++{ ++ return dev->chip_id == BCM5325_DEVICE_ID; ++} ++ ++static inline int is5365(struct b53_device *dev) ++{ ++#ifdef CONFIG_BCM47XX ++ return dev->chip_id == BCM5365_DEVICE_ID; ++#else ++ return 0; ++#endif ++} ++ ++static inline int is5397_98(struct b53_device *dev) ++{ ++ return dev->chip_id == BCM5397_DEVICE_ID || ++ dev->chip_id == BCM5398_DEVICE_ID; ++} ++ ++static inline int is539x(struct b53_device *dev) ++{ ++ return dev->chip_id == BCM5395_DEVICE_ID || ++ dev->chip_id == BCM5397_DEVICE_ID || ++ dev->chip_id == BCM5398_DEVICE_ID; ++} ++ ++static inline int is531x5(struct b53_device *dev) ++{ ++ return dev->chip_id == BCM53115_DEVICE_ID || ++ dev->chip_id == BCM53125_DEVICE_ID || ++ dev->chip_id == BCM53128_DEVICE_ID; ++} ++ ++static inline int is63xx(struct b53_device *dev) ++{ ++#ifdef CONFIG_BCM63XX ++ return dev->chip_id == BCM63XX_DEVICE_ID; ++#else ++ return 0; ++#endif ++} ++ ++static inline int is5301x(struct b53_device *dev) ++{ ++ return dev->chip_id == BCM53010_DEVICE_ID || ++ dev->chip_id == BCM53011_DEVICE_ID || ++ dev->chip_id == BCM53012_DEVICE_ID || ++ dev->chip_id == BCM53018_DEVICE_ID || ++ dev->chip_id == BCM53019_DEVICE_ID; ++} ++ ++#define B53_CPU_PORT_25 5 ++#define B53_CPU_PORT 8 ++ ++static inline int is_cpu_port(struct b53_device *dev, int port) ++{ ++ return dev->sw_dev.cpu_port == port; ++} ++ ++static inline struct b53_device *sw_to_b53(struct switch_dev *sw) ++{ ++ return container_of(sw, struct b53_device, sw_dev); ++} ++ ++struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, ++ void *priv); ++ ++int b53_switch_detect(struct b53_device *dev); ++ ++int b53_switch_register(struct b53_device *dev); ++ ++static inline void b53_switch_remove(struct b53_device *dev) ++{ ++ unregister_switch(&dev->sw_dev); ++} ++ ++static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->read8(dev, page, reg, val); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->read16(dev, page, reg, val); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->read32(dev, page, reg, val); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->read48(dev, page, reg, val); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->read64(dev, page, reg, val); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->write8(dev, page, reg, value); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, ++ u16 value) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->write16(dev, page, reg, value); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, ++ u32 value) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->write32(dev, page, reg, value); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->write48(dev, page, reg, value); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ int ret; ++ ++ mutex_lock(&dev->reg_mutex); ++ ret = dev->ops->write64(dev, page, reg, value); ++ mutex_unlock(&dev->reg_mutex); ++ ++ return ret; ++} ++ ++#ifdef CONFIG_BCM47XX ++ ++#include <bcm47xx_nvram.h> ++#include <bcm47xx_board.h> ++static inline int b53_switch_get_reset_gpio(struct b53_device *dev) ++{ ++ enum bcm47xx_board board = bcm47xx_board_get(); ++ ++ switch (board) { ++ case BCM47XX_BOARD_LINKSYS_WRT300NV11: ++ case BCM47XX_BOARD_LINKSYS_WRT310NV1: ++ return 8; ++ default: ++ return bcm47xx_nvram_gpio_pin("robo_reset"); ++ } ++} ++#else ++static inline int b53_switch_get_reset_gpio(struct b53_device *dev) ++{ ++ return -ENOENT; ++} ++#endif ++#endif +diff --git a/drivers/net/phy/b53/b53_regs.h b/drivers/net/phy/b53/b53_regs.h +new file mode 100644 +index 0000000..ba50915 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_regs.h +@@ -0,0 +1,313 @@ ++/* ++ * B53 register definitions ++ * ++ * Copyright (C) 2004 Broadcom Corporation ++ * Copyright (C) 2011-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#ifndef __B53_REGS_H ++#define __B53_REGS_H ++ ++/* Management Port (SMP) Page offsets */ ++#define B53_CTRL_PAGE 0x00 /* Control */ ++#define B53_STAT_PAGE 0x01 /* Status */ ++#define B53_MGMT_PAGE 0x02 /* Management Mode */ ++#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ ++#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ ++#define B53_ARLIO_PAGE 0x05 /* ARL Access */ ++#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ ++#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ ++ ++/* PHY Registers */ ++#define B53_PORT_MII_PAGE(i) (0x10 + i) /* Port i MII Registers */ ++#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ ++#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ ++ ++/* MIB registers */ ++#define B53_MIB_PAGE(i) (0x20 + i) ++ ++/* Quality of Service (QoS) Registers */ ++#define B53_QOS_PAGE 0x30 ++ ++/* Port VLAN Page */ ++#define B53_PVLAN_PAGE 0x31 ++ ++/* VLAN Registers */ ++#define B53_VLAN_PAGE 0x34 ++ ++/* Jumbo Frame Registers */ ++#define B53_JUMBO_PAGE 0x40 ++ ++/************************************************************************* ++ * Control Page registers ++ *************************************************************************/ ++ ++/* Port Control Register (8 bit) */ ++#define B53_PORT_CTRL(i) (0x00 + i) ++#define PORT_CTRL_RX_DISABLE BIT(0) ++#define PORT_CTRL_TX_DISABLE BIT(1) ++#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ ++#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ ++#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ ++#define PORT_CTRL_STP_STATE_S 5 ++#define PORT_CTRL_STP_STATE_MASK (0x3 << PORT_CTRL_STP_STATE_S) ++ ++/* SMP Control Register (8 bit) */ ++#define B53_SMP_CTRL 0x0a ++ ++/* Switch Mode Control Register (8 bit) */ ++#define B53_SWITCH_MODE 0x0b ++#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ ++#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ ++ ++/* IMP Port state override register (8 bit) */ ++#define B53_PORT_OVERRIDE_CTRL 0x0e ++#define PORT_OVERRIDE_LINK BIT(0) ++#define PORT_OVERRIDE_HALF_DUPLEX BIT(1) /* 0 = Full Duplex */ ++#define PORT_OVERRIDE_SPEED_S 2 ++#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) ++#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) ++#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) ++#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ ++#define PORT_OVERRIDE_RX_FLOW BIT(4) ++#define PORT_OVERRIDE_TX_FLOW BIT(5) ++#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ ++ ++/* Power-down mode control */ ++#define B53_PD_MODE_CTRL_25 0x0f ++ ++/* IP Multicast control (8 bit) */ ++#define B53_IP_MULTICAST_CTRL 0x21 ++#define B53_IPMC_FWD_EN BIT(1) ++#define B53_UC_FWD_EN BIT(6) ++#define B53_MC_FWD_EN BIT(7) ++ ++/* (16 bit) */ ++#define B53_UC_FLOOD_MASK 0x32 ++#define B53_MC_FLOOD_MASK 0x34 ++#define B53_IPMC_FLOOD_MASK 0x36 ++ ++/* Software reset register (8 bit) */ ++#define B53_SOFTRESET 0x79 ++ ++/* Fast Aging Control register (8 bit) */ ++#define B53_FAST_AGE_CTRL 0x88 ++#define FAST_AGE_STATIC BIT(0) ++#define FAST_AGE_DYNAMIC BIT(1) ++#define FAST_AGE_PORT BIT(2) ++#define FAST_AGE_VLAN BIT(3) ++#define FAST_AGE_STP BIT(4) ++#define FAST_AGE_MC BIT(5) ++#define FAST_AGE_DONE BIT(7) ++ ++/************************************************************************* ++ * Status Page registers ++ *************************************************************************/ ++ ++/* Link Status Summary Register (16bit) */ ++#define B53_LINK_STAT 0x00 ++ ++/* Link Status Change Register (16 bit) */ ++#define B53_LINK_STAT_CHANGE 0x02 ++ ++/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ ++#define B53_SPEED_STAT 0x04 ++#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) ++#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) ++#define SPEED_STAT_10M 0 ++#define SPEED_STAT_100M 1 ++#define SPEED_STAT_1000M 2 ++ ++/* Duplex Status Summary (16 bit) */ ++#define B53_DUPLEX_STAT_FE 0x06 ++#define B53_DUPLEX_STAT_GE 0x08 ++#define B53_DUPLEX_STAT_63XX 0x0c ++ ++/* Revision ID register for BCM5325 */ ++#define B53_REV_ID_25 0x50 ++ ++/* Strap Value (48 bit) */ ++#define B53_STRAP_VALUE 0x70 ++#define SV_GMII_CTRL_115 BIT(27) ++ ++/************************************************************************* ++ * Management Mode Page Registers ++ *************************************************************************/ ++ ++/* Global Management Config Register (8 bit) */ ++#define B53_GLOBAL_CONFIG 0x00 ++#define GC_RESET_MIB 0x01 ++#define GC_RX_BPDU_EN 0x02 ++#define GC_MIB_AC_HDR_EN 0x10 ++#define GC_MIB_AC_EN 0x20 ++#define GC_FRM_MGMT_PORT_M 0xC0 ++#define GC_FRM_MGMT_PORT_04 0x00 ++#define GC_FRM_MGMT_PORT_MII 0x80 ++ ++/* Device ID register (8 or 32 bit) */ ++#define B53_DEVICE_ID 0x30 ++ ++/* Revision ID register (8 bit) */ ++#define B53_REV_ID 0x40 ++ ++/************************************************************************* ++ * ARL Access Page Registers ++ *************************************************************************/ ++ ++/* VLAN Table Access Register (8 bit) */ ++#define B53_VT_ACCESS 0x80 ++#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ ++#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ ++#define VTA_CMD_WRITE 0 ++#define VTA_CMD_READ 1 ++#define VTA_CMD_CLEAR 2 ++#define VTA_START_CMD BIT(7) ++ ++/* VLAN Table Index Register (16 bit) */ ++#define B53_VT_INDEX 0x81 ++#define B53_VT_INDEX_9798 0x61 ++#define B53_VT_INDEX_63XX 0x62 ++ ++/* VLAN Table Entry Register (32 bit) */ ++#define B53_VT_ENTRY 0x83 ++#define B53_VT_ENTRY_9798 0x63 ++#define B53_VT_ENTRY_63XX 0x64 ++#define VTE_MEMBERS 0x1ff ++#define VTE_UNTAG_S 9 ++#define VTE_UNTAG (0x1ff << 9) ++ ++/************************************************************************* ++ * Port VLAN Registers ++ *************************************************************************/ ++ ++/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ ++#define B53_PVLAN_PORT_MASK(i) ((i) * 2) ++ ++/************************************************************************* ++ * 802.1Q Page Registers ++ *************************************************************************/ ++ ++/* Global QoS Control (8 bit) */ ++#define B53_QOS_GLOBAL_CTL 0x00 ++ ++/* Enable 802.1Q for individual Ports (16 bit) */ ++#define B53_802_1P_EN 0x04 ++ ++/************************************************************************* ++ * VLAN Page Registers ++ *************************************************************************/ ++ ++/* VLAN Control 0 (8 bit) */ ++#define B53_VLAN_CTRL0 0x00 ++#define VC0_8021PF_CTRL_MASK 0x3 ++#define VC0_8021PF_CTRL_NONE 0x0 ++#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 ++#define VC0_8021PF_CTRL_CHANGE_VID 0x2 ++#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 ++#define VC0_8021QF_CTRL_MASK 0xc ++#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 ++#define VC0_8021QF_CTRL_CHANGE_VID 0x2 ++#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 ++#define VC0_RESERVED_1 BIT(1) ++#define VC0_DROP_VID_MISS BIT(4) ++#define VC0_VID_HASH_VID BIT(5) ++#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ ++#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ ++ ++/* VLAN Control 1 (8 bit) */ ++#define B53_VLAN_CTRL1 0x01 ++#define VC1_RX_MCST_TAG_EN BIT(1) ++#define VC1_RX_MCST_FWD_EN BIT(2) ++#define VC1_RX_MCST_UNTAG_EN BIT(3) ++ ++/* VLAN Control 2 (8 bit) */ ++#define B53_VLAN_CTRL2 0x02 ++ ++/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ ++#define B53_VLAN_CTRL3 0x03 ++#define B53_VLAN_CTRL3_63XX 0x04 ++#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ ++#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ ++ ++/* VLAN Control 4 (8 bit) */ ++#define B53_VLAN_CTRL4 0x05 ++#define B53_VLAN_CTRL4_25 0x04 ++#define B53_VLAN_CTRL4_63XX 0x06 ++#define VC4_ING_VID_CHECK_S 6 ++#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) ++#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ ++#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ ++#define VC4_NO_ING_VID_CHK 2 /* do not check */ ++#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ ++ ++/* VLAN Control 5 (8 bit) */ ++#define B53_VLAN_CTRL5 0x06 ++#define B53_VLAN_CTRL5_25 0x05 ++#define B53_VLAN_CTRL5_63XX 0x07 ++#define VC5_VID_FFF_EN BIT(2) ++#define VC5_DROP_VTABLE_MISS BIT(3) ++ ++/* VLAN Control 6 (8 bit) */ ++#define B53_VLAN_CTRL6 0x07 ++#define B53_VLAN_CTRL6_63XX 0x08 ++ ++/* VLAN Table Access Register (16 bit) */ ++#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ ++#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ ++#define VTA_VID_LOW_MASK_25 0xf ++#define VTA_VID_LOW_MASK_65 0xff ++#define VTA_VID_HIGH_S_25 4 ++#define VTA_VID_HIGH_S_65 8 ++#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) ++#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) ++#define VTA_RW_STATE BIT(12) ++#define VTA_RW_STATE_RD 0 ++#define VTA_RW_STATE_WR BIT(12) ++#define VTA_RW_OP_EN BIT(13) ++ ++/* VLAN Read/Write Registers for (16/32 bit) */ ++#define B53_VLAN_WRITE_25 0x08 ++#define B53_VLAN_WRITE_65 0x0a ++#define B53_VLAN_READ 0x0c ++#define VA_MEMBER_MASK 0x3f ++#define VA_UNTAG_S_25 6 ++#define VA_UNTAG_MASK_25 0x3f ++#define VA_UNTAG_S_65 7 ++#define VA_UNTAG_MASK_65 0x1f ++#define VA_VID_HIGH_S 12 ++#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) ++#define VA_VALID_25 BIT(20) ++#define VA_VALID_25_R4 BIT(24) ++#define VA_VALID_65 BIT(14) ++ ++/* VLAN Port Default Tag (16 bit) */ ++#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) ++ ++/************************************************************************* ++ * Jumbo Frame Page Registers ++ *************************************************************************/ ++ ++/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ ++#define B53_JUMBO_PORT_MASK 0x01 ++#define B53_JUMBO_PORT_MASK_63XX 0x04 ++#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ ++ ++/* Good Frame Max Size without 802.1Q TAG (16 bit) */ ++#define B53_JUMBO_MAX_SIZE 0x05 ++#define B53_JUMBO_MAX_SIZE_63XX 0x08 ++#define JMS_MIN_SIZE 1518 ++#define JMS_MAX_SIZE 9724 ++ ++#endif /* !__B53_REGS_H */ +diff --git a/drivers/net/phy/b53/b53_spi.c b/drivers/net/phy/b53/b53_spi.c +new file mode 100644 +index 0000000..8c6b171 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_spi.c +@@ -0,0 +1,327 @@ ++/* ++ * B53 register access through SPI ++ * ++ * Copyright (C) 2011-2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include <asm/unaligned.h> ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/spi/spi.h> ++#include <linux/platform_data/b53.h> ++ ++#include "b53_priv.h" ++ ++#define B53_SPI_DATA 0xf0 ++ ++#define B53_SPI_STATUS 0xfe ++#define B53_SPI_CMD_SPIF BIT(7) ++#define B53_SPI_CMD_RACK BIT(5) ++ ++#define B53_SPI_CMD_READ 0x00 ++#define B53_SPI_CMD_WRITE 0x01 ++#define B53_SPI_CMD_NORMAL 0x60 ++#define B53_SPI_CMD_FAST 0x10 ++ ++#define B53_SPI_PAGE_SELECT 0xff ++ ++static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, ++ unsigned len) ++{ ++ u8 txbuf[2]; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; ++ txbuf[1] = reg; ++ ++ return spi_write_then_read(spi, txbuf, 2, val, len); ++} ++ ++static inline int b53_spi_clear_status(struct spi_device *spi) ++{ ++ unsigned int i; ++ u8 rxbuf; ++ int ret; ++ ++ for (i = 0; i < 10; i++) { ++ ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); ++ if (ret) ++ return ret; ++ ++ if (!(rxbuf & B53_SPI_CMD_SPIF)) ++ break; ++ ++ mdelay(1); ++ } ++ ++ if (i == 10) ++ return -EIO; ++ ++ return 0; ++} ++ ++static inline int b53_spi_set_page(struct spi_device *spi, u8 page) ++{ ++ u8 txbuf[3]; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = B53_SPI_PAGE_SELECT; ++ txbuf[2] = page; ++ ++ return spi_write(spi, txbuf, sizeof(txbuf)); ++} ++ ++static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) ++{ ++ int ret = b53_spi_clear_status(spi); ++ if (ret) ++ return ret; ++ ++ return b53_spi_set_page(spi, page); ++} ++ ++static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) ++{ ++ u8 rxbuf; ++ int retry_count; ++ int ret; ++ ++ ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); ++ if (ret) ++ return ret; ++ ++ for (retry_count = 0; retry_count < 10; retry_count++) { ++ ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); ++ if (ret) ++ return ret; ++ ++ if (rxbuf & B53_SPI_CMD_RACK) ++ break; ++ ++ mdelay(1); ++ } ++ ++ if (retry_count == 10) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, ++ unsigned len) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ ret = b53_spi_prepare_reg_read(spi, reg); ++ if (ret) ++ return ret; ++ ++ return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); ++} ++ ++static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) ++{ ++ return b53_spi_read(dev, page, reg, val, 1); ++} ++ ++static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) ++{ ++ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); ++ if (!ret) ++ *val = le16_to_cpu(*val); ++ ++ return ret; ++} ++ ++static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) ++{ ++ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); ++ if (!ret) ++ *val = le32_to_cpu(*val); ++ ++ return ret; ++} ++ ++static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ int ret; ++ ++ *val = 0; ++ ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); ++ if (!ret) ++ *val = le64_to_cpu(*val); ++ ++ return ret; ++} ++ ++static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); ++ if (!ret) ++ *val = le64_to_cpu(*val); ++ ++ return ret; ++} ++ ++static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ u8 txbuf[3]; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = reg; ++ txbuf[2] = value; ++ ++ return spi_write(spi, txbuf, sizeof(txbuf)); ++} ++ ++static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ u8 txbuf[4]; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = reg; ++ put_unaligned_le16(value, &txbuf[2]); ++ ++ return spi_write(spi, txbuf, sizeof(txbuf)); ++} ++ ++static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ u8 txbuf[6]; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = reg; ++ put_unaligned_le32(value, &txbuf[2]); ++ ++ return spi_write(spi, txbuf, sizeof(txbuf)); ++} ++ ++static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ u8 txbuf[10]; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = reg; ++ put_unaligned_le64(value, &txbuf[2]); ++ ++ return spi_write(spi, txbuf, sizeof(txbuf) - 2); ++} ++ ++static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) ++{ ++ struct spi_device *spi = dev->priv; ++ int ret; ++ u8 txbuf[10]; ++ ++ ret = b53_prepare_reg_access(spi, page); ++ if (ret) ++ return ret; ++ ++ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; ++ txbuf[1] = reg; ++ put_unaligned_le64(value, &txbuf[2]); ++ ++ return spi_write(spi, txbuf, sizeof(txbuf)); ++} ++ ++static struct b53_io_ops b53_spi_ops = { ++ .read8 = b53_spi_read8, ++ .read16 = b53_spi_read16, ++ .read32 = b53_spi_read32, ++ .read48 = b53_spi_read48, ++ .read64 = b53_spi_read64, ++ .write8 = b53_spi_write8, ++ .write16 = b53_spi_write16, ++ .write32 = b53_spi_write32, ++ .write48 = b53_spi_write48, ++ .write64 = b53_spi_write64, ++}; ++ ++static int b53_spi_probe(struct spi_device *spi) ++{ ++ struct b53_device *dev; ++ int ret; ++ ++ dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); ++ if (!dev) ++ return -ENOMEM; ++ ++ if (spi->dev.platform_data) ++ dev->pdata = spi->dev.platform_data; ++ ++ ret = b53_switch_register(dev); ++ if (ret) ++ return ret; ++ ++ spi_set_drvdata(spi, dev); ++ ++ return 0; ++} ++ ++static int b53_spi_remove(struct spi_device *spi) ++{ ++ struct b53_device *dev = spi_get_drvdata(spi); ++ ++ if (dev) { ++ b53_switch_remove(dev); ++ } ++ ++ return 0; ++} ++ ++static struct spi_driver b53_spi_driver = { ++ .driver = { ++ .name = "b53-switch", ++ .bus = &spi_bus_type, ++ .owner = THIS_MODULE, ++ }, ++ .probe = b53_spi_probe, ++ .remove = b53_spi_remove, ++}; ++ ++module_spi_driver(b53_spi_driver); ++ ++MODULE_AUTHOR("Jonas Gorski jogo@openwrt.org"); ++MODULE_DESCRIPTION("B53 SPI access driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/phy/b53/b53_srab.c b/drivers/net/phy/b53/b53_srab.c +new file mode 100644 +index 0000000..a68e275 +--- /dev/null ++++ b/drivers/net/phy/b53/b53_srab.c +@@ -0,0 +1,379 @@ ++/* ++ * B53 register access through Switch Register Access Bridge Registers ++ * ++ * Copyright (C) 2013 Hauke Mehrtens hauke@hauke-m.de ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/platform_data/b53.h> ++ ++#include "b53_priv.h" ++ ++/* command and status register of the SRAB */ ++#define B53_SRAB_CMDSTAT 0x2c ++#define B53_SRAB_CMDSTAT_RST BIT(2) ++#define B53_SRAB_CMDSTAT_WRITE BIT(1) ++#define B53_SRAB_CMDSTAT_GORDYN BIT(0) ++#define B53_SRAB_CMDSTAT_PAGE 24 ++#define B53_SRAB_CMDSTAT_REG 16 ++ ++/* high order word of write data to switch registe */ ++#define B53_SRAB_WD_H 0x30 ++ ++/* low order word of write data to switch registe */ ++#define B53_SRAB_WD_L 0x34 ++ ++/* high order word of read data from switch register */ ++#define B53_SRAB_RD_H 0x38 ++ ++/* low order word of read data from switch register */ ++#define B53_SRAB_RD_L 0x3c ++ ++/* command and status register of the SRAB */ ++#define B53_SRAB_CTRLS 0x40 ++#define B53_SRAB_CTRLS_RCAREQ BIT(3) ++#define B53_SRAB_CTRLS_RCAGNT BIT(4) ++#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) ++ ++/* the register captures interrupt pulses from the switch */ ++#define B53_SRAB_INTR 0x44 ++ ++static int b53_srab_request_grant(struct b53_device *dev) ++{ ++ u8 __iomem *regs = dev->priv; ++ u32 ctrls; ++ int i; ++ ++ ctrls = readl(regs + B53_SRAB_CTRLS); ++ ctrls |= B53_SRAB_CTRLS_RCAREQ; ++ writel(ctrls, regs + B53_SRAB_CTRLS); ++ ++ for (i = 0; i < 20; i++) { ++ ctrls = readl(regs + B53_SRAB_CTRLS); ++ if (ctrls & B53_SRAB_CTRLS_RCAGNT) ++ break; ++ usleep_range(10, 100); ++ } ++ if (WARN_ON(i == 5)) ++ return -EIO; ++ ++ return 0; ++} ++ ++static void b53_srab_release_grant(struct b53_device *dev) ++{ ++ u8 __iomem *regs = dev->priv; ++ u32 ctrls; ++ ++ ctrls = readl(regs + B53_SRAB_CTRLS); ++ ctrls &= ~B53_SRAB_CTRLS_RCAREQ; ++ writel(ctrls, regs + B53_SRAB_CTRLS); ++} ++ ++static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) ++{ ++ int i; ++ u32 cmdstat; ++ u8 __iomem *regs = dev->priv; ++ ++ /* set register address */ ++ cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | ++ (reg << B53_SRAB_CMDSTAT_REG) | ++ B53_SRAB_CMDSTAT_GORDYN | ++ op; ++ writel(cmdstat, regs + B53_SRAB_CMDSTAT); ++ ++ /* check if operation completed */ ++ for (i = 0; i < 5; ++i) { ++ cmdstat = readl(regs + B53_SRAB_CMDSTAT); ++ if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) ++ break; ++ usleep_range(10, 100); ++ } ++ ++ if (WARN_ON(i == 5)) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ ret = b53_srab_op(dev, page, reg, 0); ++ if (ret) ++ goto err; ++ ++ *val = readl(regs + B53_SRAB_RD_L) & 0xff; ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ ret = b53_srab_op(dev, page, reg, 0); ++ if (ret) ++ goto err; ++ ++ *val = readl(regs + B53_SRAB_RD_L) & 0xffff; ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ ret = b53_srab_op(dev, page, reg, 0); ++ if (ret) ++ goto err; ++ ++ *val = readl(regs + B53_SRAB_RD_L); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ ret = b53_srab_op(dev, page, reg, 0); ++ if (ret) ++ goto err; ++ ++ *val = readl(regs + B53_SRAB_RD_L); ++ *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ ret = b53_srab_op(dev, page, reg, 0); ++ if (ret) ++ goto err; ++ ++ *val = readl(regs + B53_SRAB_RD_L); ++ *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ writel(value, regs + B53_SRAB_WD_L); ++ ++ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, ++ u16 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ writel(value, regs + B53_SRAB_WD_L); ++ ++ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, ++ u32 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ writel(value, regs + B53_SRAB_WD_L); ++ ++ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++ ++} ++ ++static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ writel((u32)value, regs + B53_SRAB_WD_L); ++ writel((u16)(value >> 32), regs + B53_SRAB_WD_H); ++ ++ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++ ++} ++ ++static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, ++ u64 value) ++{ ++ u8 __iomem *regs = dev->priv; ++ int ret = 0; ++ ++ ret = b53_srab_request_grant(dev); ++ if (ret) ++ goto err; ++ ++ writel((u32)value, regs + B53_SRAB_WD_L); ++ writel((u32)(value >> 32), regs + B53_SRAB_WD_H); ++ ++ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); ++ ++err: ++ b53_srab_release_grant(dev); ++ ++ return ret; ++} ++ ++static struct b53_io_ops b53_srab_ops = { ++ .read8 = b53_srab_read8, ++ .read16 = b53_srab_read16, ++ .read32 = b53_srab_read32, ++ .read48 = b53_srab_read48, ++ .read64 = b53_srab_read64, ++ .write8 = b53_srab_write8, ++ .write16 = b53_srab_write16, ++ .write32 = b53_srab_write32, ++ .write48 = b53_srab_write48, ++ .write64 = b53_srab_write64, ++}; ++ ++static int b53_srab_probe(struct platform_device *pdev) ++{ ++ struct b53_platform_data *pdata = pdev->dev.platform_data; ++ struct b53_device *dev; ++ ++ if (!pdata) ++ return -EINVAL; ++ ++ dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs); ++ if (!dev) ++ return -ENOMEM; ++ ++ if (pdata) ++ dev->pdata = pdata; ++ ++ platform_set_drvdata(pdev, dev); ++ ++ return b53_switch_register(dev); ++} ++ ++static int b53_srab_remove(struct platform_device *pdev) ++{ ++ struct b53_device *dev = platform_get_drvdata(pdev); ++ ++ if (dev) { ++ b53_switch_remove(dev); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver b53_srab_driver = { ++ .probe = b53_srab_probe, ++ .remove = b53_srab_remove, ++ .driver = { ++ .name = "b53-srab-switch", ++ }, ++}; ++ ++module_platform_driver(b53_srab_driver); ++MODULE_AUTHOR("Hauke Mehrtens hauke@hauke-m.de"); ++MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/phy/swconfig.c b/drivers/net/phy/swconfig.c +new file mode 100644 +index 0000000..4f2df4c +--- /dev/null ++++ b/drivers/net/phy/swconfig.c +@@ -0,0 +1,1148 @@ ++/* ++ * 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_tail(&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 err; ++#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) ++ int i; ++#endif ++ ++ 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; ++ } ++ return 0; ++ ++unregister: ++ genl_unregister_family(&switch_fam); ++ return err; ++#else ++ err = genl_register_family_with_ops(&switch_fam, swconfig_ops); ++ if (err) ++ return err; ++ return 0; ++#endif ++} ++ ++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/platform_data/b53.h b/include/linux/platform_data/b53.h +new file mode 100644 +index 0000000..7842741 +--- /dev/null ++++ b/include/linux/platform_data/b53.h +@@ -0,0 +1,36 @@ ++/* ++ * B53 platform data ++ * ++ * Copyright (C) 2013 Jonas Gorski jogo@openwrt.org ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#ifndef __B53_H ++#define __B53_H ++ ++#include <linux/kernel.h> ++ ++struct b53_platform_data { ++ u32 chip_id; ++ u16 enabled_ports; ++ ++ /* allow to specify an ethX alias */ ++ const char *alias; ++ ++ /* only used by MMAP'd driver */ ++ unsigned big_endian:1; ++ void __iomem *regs; ++}; ++ ++#endif +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 */
hooks/post-receive -- IPFire 2.x development tree