From 7726e3a66bf397742a346512922ef67184d8f483 Mon Sep 17 00:00:00 2001 From: Arne Fitzenreiter Date: Tue, 27 Jan 2015 19:11:57 +0100 Subject: [PATCH] kernel: add support for lamobo-r1. --- .../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 diff --git a/config/kernel/kernel.config.armv5tel-ipfire-multi b/config/kernel/kernel.config.armv5tel-ipfire-multi index caf4c4422a..6c387b305e 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 aad84bdf09..0086bfe8bc 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 0000000000..4e604dfa2f --- /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 ++ * Arne Fitzenreiter ++ * ++ * 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 ++ ++/ { ++ 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 = ; ++ 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 ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++ * ++ * 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 ++#include ++#include ++ ++#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 ++ * ++ * 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 ++#include ++#include ++#include ++ ++#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 "); ++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 ++ * ++ * 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 ++#include ++#include ++ ++#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 ++ * ++ * 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 ++#include ++#include ++ ++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 ++#include ++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 ++ * ++ * 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 ++ * ++ * 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 ++ ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++ * ++ * 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 ++#include ++#include ++#include ++ ++#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 "); ++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 ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SWCONFIG_DEVNAME "switch%d" ++ ++#include "swconfig_leds.c" ++ ++MODULE_AUTHOR("Felix Fietkau "); ++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 ++ * ++ * 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 ++#include ++#include ++#include ++ ++#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 ++ * ++ * 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 ++ ++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 ++ * ++ * 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 ++#include ++ ++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 ++ * ++ * 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 ++#include ++#include ++#include ++#ifndef __KERNEL__ ++#include ++#include ++#include ++#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 */ -- 2.39.2