mirror of
https://github.com/Fishwaldo/build.git
synced 2025-03-21 22:31:51 +00:00
5664 lines
138 KiB
Diff
5664 lines
138 KiB
Diff
b53 driver
|
|
---
|
|
drivers/gpio/devres.c | 29 +
|
|
drivers/net/phy/Kconfig | 8 +
|
|
drivers/net/phy/Makefile | 2 +
|
|
drivers/net/phy/b53/Kconfig | 37 +
|
|
drivers/net/phy/b53/Makefile | 10 +
|
|
drivers/net/phy/b53/b53_common.c | 1428 +++++++++++++++++++++++++++++++++++
|
|
drivers/net/phy/b53/b53_mdio.c | 425 +++++++++++
|
|
drivers/net/phy/b53/b53_mmap.c | 240 ++++++
|
|
drivers/net/phy/b53/b53_phy_fixup.c | 55 ++
|
|
drivers/net/phy/b53/b53_priv.h | 324 ++++++++
|
|
drivers/net/phy/b53/b53_regs.h | 313 ++++++++
|
|
drivers/net/phy/b53/b53_spi.c | 327 ++++++++
|
|
drivers/net/phy/b53/b53_srab.c | 379 ++++++++++
|
|
drivers/net/phy/swconfig.c | 1149 ++++++++++++++++++++++++++++
|
|
drivers/net/phy/swconfig_leds.c | 354 +++++++++
|
|
include/Kbuild | 1 +
|
|
include/asm-generic/gpio.h | 1 +
|
|
include/linux/Kbuild | 1 +
|
|
include/linux/gpio.h | 3 +
|
|
include/linux/platform_data/b53.h | 36 +
|
|
include/linux/switch.h | 178 ++++-
|
|
include/uapi/Kbuild | 1 +
|
|
include/uapi/linux/Kbuild | 1 +
|
|
include/uapi/linux/switch.h | 103 +++
|
|
24 files changed, 5373 insertions(+), 32 deletions(-)
|
|
create mode 100644 drivers/net/phy/b53/Kconfig
|
|
create mode 100644 drivers/net/phy/b53/Makefile
|
|
create mode 100644 drivers/net/phy/b53/b53_common.c
|
|
create mode 100644 drivers/net/phy/b53/b53_mdio.c
|
|
create mode 100644 drivers/net/phy/b53/b53_mmap.c
|
|
create mode 100644 drivers/net/phy/b53/b53_phy_fixup.c
|
|
create mode 100644 drivers/net/phy/b53/b53_priv.h
|
|
create mode 100644 drivers/net/phy/b53/b53_regs.h
|
|
create mode 100644 drivers/net/phy/b53/b53_spi.c
|
|
create mode 100644 drivers/net/phy/b53/b53_srab.c
|
|
create mode 100644 drivers/net/phy/swconfig.c
|
|
create mode 100644 drivers/net/phy/swconfig_leds.c
|
|
create mode 100644 include/linux/platform_data/b53.h
|
|
create mode 100644 include/uapi/Kbuild
|
|
create mode 100644 include/uapi/linux/Kbuild
|
|
create mode 100644 include/uapi/linux/switch.h
|
|
|
|
diff --git a/drivers/gpio/devres.c b/drivers/gpio/devres.c
|
|
index 3dd2939..dd9dffc 100644
|
|
--- a/drivers/gpio/devres.c
|
|
+++ b/drivers/gpio/devres.c
|
|
@@ -71,6 +71,35 @@ int devm_gpio_request(struct device *dev, unsigned gpio, const char *label)
|
|
EXPORT_SYMBOL(devm_gpio_request);
|
|
|
|
/**
|
|
+* devm_gpio_request_one - request a single GPIO with initial setup
|
|
+* @dev: device to request for
|
|
+* @gpio: the GPIO number
|
|
+* @flags: GPIO configuration as specified by GPIOF_*
|
|
+* @label: a literal description string of this GPIO
|
|
+*/
|
|
+int devm_gpio_request_one(struct device *dev, unsigned gpio, unsigned long flags, const char *label)
|
|
+{
|
|
+ unsigned *dr;
|
|
+ int rc;
|
|
+
|
|
+ dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL);
|
|
+ if (!dr)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ rc = gpio_request_one(gpio, flags, label);
|
|
+ if (rc) {
|
|
+ devres_free(dr);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ *dr = gpio;
|
|
+ devres_add(dev, dr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(devm_gpio_request_one);
|
|
+
|
|
+/**
|
|
* devm_gpio_free - free an interrupt
|
|
* @dev: device to free gpio for
|
|
* @gpio: gpio to free
|
|
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
|
|
index 0e01f4e..50768fb 100644
|
|
--- a/drivers/net/phy/Kconfig
|
|
+++ b/drivers/net/phy/Kconfig
|
|
@@ -13,6 +13,12 @@ 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.
|
|
+
|
|
comment "MII PHY device drivers"
|
|
|
|
config AMD_PHY
|
|
@@ -135,6 +141,8 @@ config MDIO_OCTEON
|
|
|
|
If in doubt, say Y.
|
|
|
|
+source "drivers/net/phy/b53/Kconfig"
|
|
+
|
|
endif # PHYLIB
|
|
|
|
config MICREL_KS8995MA
|
|
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
|
|
index b7438b1..6d1adb6 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
|
|
@@ -10,6 +11,7 @@ obj-$(CONFIG_LXT_PHY) += lxt.o
|
|
obj-$(CONFIG_QSEMI_PHY) += qsemi.o
|
|
obj-$(CONFIG_SMSC_PHY) += smsc.o
|
|
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
|
|
+obj-$(CONFIG_B53) += b53/
|
|
obj-$(CONFIG_BROADCOM_PHY) += broadcom.o
|
|
obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o
|
|
obj-$(CONFIG_ICPLUS_PHY) += icplus.o
|
|
diff --git a/drivers/net/phy/b53/Kconfig b/drivers/net/phy/b53/Kconfig
|
|
new file mode 100644
|
|
index 0000000..566f76a
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/Kconfig
|
|
@@ -0,0 +1,37 @@
|
|
+menuconfig B53
|
|
+ tristate "Broadcom bcm53xx managed switch support"
|
|
+ depends on SWCONFIG
|
|
+ help
|
|
+ This driver adds support for Broadcom managed switch chips. It supports
|
|
+ BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX
|
|
+ integrated switches.
|
|
+
|
|
+config B53_SPI_DRIVER
|
|
+ tristate "B53 SPI connected switch driver"
|
|
+ depends on B53 && SPI
|
|
+ help
|
|
+ Select to enable support for registering switches configured through SPI.
|
|
+
|
|
+config B53_PHY_DRIVER
|
|
+ tristate "B53 MDIO connected switch driver"
|
|
+ depends on B53
|
|
+ select B53_PHY_FIXUP
|
|
+ help
|
|
+ Select to enable support for registering switches configured through MDIO.
|
|
+
|
|
+#config B53_MMAP_DRIVER
|
|
+# tristate "B53 MMAP connected switch driver"
|
|
+# depends on B53
|
|
+# help
|
|
+# Select to enable support for memory-mapped switches like the BCM63XX
|
|
+# integrated switches.
|
|
+
|
|
+config B53_SRAB_DRIVER
|
|
+ tristate "B53 SRAB connected switch driver"
|
|
+ depends on B53
|
|
+ help
|
|
+ Select to enable support for memory-mapped Switch Register Access
|
|
+ Bridge Registers (SRAB) like it is found on the BCM53010
|
|
+
|
|
+config B53_PHY_FIXUP
|
|
+ bool
|
|
diff --git a/drivers/net/phy/b53/Makefile b/drivers/net/phy/b53/Makefile
|
|
new file mode 100644
|
|
index 0000000..7cc39c7
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/Makefile
|
|
@@ -0,0 +1,10 @@
|
|
+obj-$(CONFIG_B53) += b53_common.o
|
|
+
|
|
+obj-$(CONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o
|
|
+
|
|
+obj-$(CONFIG_B53_MMAP_DRIVER) += b53_mmap.o
|
|
+obj-$(CONFIG_B53_SRAB_DRIVER) += b53_srab.o
|
|
+obj-$(CONFIG_B53_PHY_DRIVER) += b53_mdio.o
|
|
+obj-$(CONFIG_B53_SPI_DRIVER) += b53_spi.o
|
|
+
|
|
+ccflags-y += -Werror
|
|
diff --git a/drivers/net/phy/b53/b53_common.c b/drivers/net/phy/b53/b53_common.c
|
|
new file mode 100644
|
|
index 0000000..b82bc93
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_common.c
|
|
@@ -0,0 +1,1428 @@
|
|
+/*
|
|
+ * B53 switch driver main logic
|
|
+ *
|
|
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
+
|
|
+#include <linux/delay.h>
|
|
+#include <linux/export.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/switch.h>
|
|
+#include <linux/platform_data/b53.h>
|
|
+
|
|
+#include "b53_regs.h"
|
|
+#include "b53_priv.h"
|
|
+
|
|
+/* buffer size needed for displaying all MIBs with max'd values */
|
|
+#define B53_BUF_SIZE 1188
|
|
+
|
|
+struct b53_mib_desc {
|
|
+ u8 size;
|
|
+ u8 offset;
|
|
+ const char *name;
|
|
+};
|
|
+
|
|
+
|
|
+/* BCM5365 MIB counters */
|
|
+static const struct b53_mib_desc b53_mibs_65[] = {
|
|
+ { 8, 0x00, "TxOctets" },
|
|
+ { 4, 0x08, "TxDropPkts" },
|
|
+ { 4, 0x10, "TxBroadcastPkts" },
|
|
+ { 4, 0x14, "TxMulticastPkts" },
|
|
+ { 4, 0x18, "TxUnicastPkts" },
|
|
+ { 4, 0x1c, "TxCollisions" },
|
|
+ { 4, 0x20, "TxSingleCollision" },
|
|
+ { 4, 0x24, "TxMultipleCollision" },
|
|
+ { 4, 0x28, "TxDeferredTransmit" },
|
|
+ { 4, 0x2c, "TxLateCollision" },
|
|
+ { 4, 0x30, "TxExcessiveCollision" },
|
|
+ { 4, 0x38, "TxPausePkts" },
|
|
+ { 8, 0x44, "RxOctets" },
|
|
+ { 4, 0x4c, "RxUndersizePkts" },
|
|
+ { 4, 0x50, "RxPausePkts" },
|
|
+ { 4, 0x54, "Pkts64Octets" },
|
|
+ { 4, 0x58, "Pkts65to127Octets" },
|
|
+ { 4, 0x5c, "Pkts128to255Octets" },
|
|
+ { 4, 0x60, "Pkts256to511Octets" },
|
|
+ { 4, 0x64, "Pkts512to1023Octets" },
|
|
+ { 4, 0x68, "Pkts1024to1522Octets" },
|
|
+ { 4, 0x6c, "RxOversizePkts" },
|
|
+ { 4, 0x70, "RxJabbers" },
|
|
+ { 4, 0x74, "RxAlignmentErrors" },
|
|
+ { 4, 0x78, "RxFCSErrors" },
|
|
+ { 8, 0x7c, "RxGoodOctets" },
|
|
+ { 4, 0x84, "RxDropPkts" },
|
|
+ { 4, 0x88, "RxUnicastPkts" },
|
|
+ { 4, 0x8c, "RxMulticastPkts" },
|
|
+ { 4, 0x90, "RxBroadcastPkts" },
|
|
+ { 4, 0x94, "RxSAChanges" },
|
|
+ { 4, 0x98, "RxFragments" },
|
|
+ { },
|
|
+};
|
|
+
|
|
+/* BCM63xx MIB counters */
|
|
+static const struct b53_mib_desc b53_mibs_63xx[] = {
|
|
+ { 8, 0x00, "TxOctets" },
|
|
+ { 4, 0x08, "TxDropPkts" },
|
|
+ { 4, 0x0c, "TxQoSPkts" },
|
|
+ { 4, 0x10, "TxBroadcastPkts" },
|
|
+ { 4, 0x14, "TxMulticastPkts" },
|
|
+ { 4, 0x18, "TxUnicastPkts" },
|
|
+ { 4, 0x1c, "TxCollisions" },
|
|
+ { 4, 0x20, "TxSingleCollision" },
|
|
+ { 4, 0x24, "TxMultipleCollision" },
|
|
+ { 4, 0x28, "TxDeferredTransmit" },
|
|
+ { 4, 0x2c, "TxLateCollision" },
|
|
+ { 4, 0x30, "TxExcessiveCollision" },
|
|
+ { 4, 0x38, "TxPausePkts" },
|
|
+ { 8, 0x3c, "TxQoSOctets" },
|
|
+ { 8, 0x44, "RxOctets" },
|
|
+ { 4, 0x4c, "RxUndersizePkts" },
|
|
+ { 4, 0x50, "RxPausePkts" },
|
|
+ { 4, 0x54, "Pkts64Octets" },
|
|
+ { 4, 0x58, "Pkts65to127Octets" },
|
|
+ { 4, 0x5c, "Pkts128to255Octets" },
|
|
+ { 4, 0x60, "Pkts256to511Octets" },
|
|
+ { 4, 0x64, "Pkts512to1023Octets" },
|
|
+ { 4, 0x68, "Pkts1024to1522Octets" },
|
|
+ { 4, 0x6c, "RxOversizePkts" },
|
|
+ { 4, 0x70, "RxJabbers" },
|
|
+ { 4, 0x74, "RxAlignmentErrors" },
|
|
+ { 4, 0x78, "RxFCSErrors" },
|
|
+ { 8, 0x7c, "RxGoodOctets" },
|
|
+ { 4, 0x84, "RxDropPkts" },
|
|
+ { 4, 0x88, "RxUnicastPkts" },
|
|
+ { 4, 0x8c, "RxMulticastPkts" },
|
|
+ { 4, 0x90, "RxBroadcastPkts" },
|
|
+ { 4, 0x94, "RxSAChanges" },
|
|
+ { 4, 0x98, "RxFragments" },
|
|
+ { 4, 0xa0, "RxSymbolErrors" },
|
|
+ { 4, 0xa4, "RxQoSPkts" },
|
|
+ { 8, 0xa8, "RxQoSOctets" },
|
|
+ { 4, 0xb0, "Pkts1523to2047Octets" },
|
|
+ { 4, 0xb4, "Pkts2048to4095Octets" },
|
|
+ { 4, 0xb8, "Pkts4096to8191Octets" },
|
|
+ { 4, 0xbc, "Pkts8192to9728Octets" },
|
|
+ { 4, 0xc0, "RxDiscarded" },
|
|
+ { }
|
|
+};
|
|
+
|
|
+/* MIB counters */
|
|
+static const struct b53_mib_desc b53_mibs[] = {
|
|
+ { 8, 0x00, "TxOctets" },
|
|
+ { 4, 0x08, "TxDropPkts" },
|
|
+ { 4, 0x10, "TxBroadcastPkts" },
|
|
+ { 4, 0x14, "TxMulticastPkts" },
|
|
+ { 4, 0x18, "TxUnicastPkts" },
|
|
+ { 4, 0x1c, "TxCollisions" },
|
|
+ { 4, 0x20, "TxSingleCollision" },
|
|
+ { 4, 0x24, "TxMultipleCollision" },
|
|
+ { 4, 0x28, "TxDeferredTransmit" },
|
|
+ { 4, 0x2c, "TxLateCollision" },
|
|
+ { 4, 0x30, "TxExcessiveCollision" },
|
|
+ { 4, 0x38, "TxPausePkts" },
|
|
+ { 8, 0x50, "RxOctets" },
|
|
+ { 4, 0x58, "RxUndersizePkts" },
|
|
+ { 4, 0x5c, "RxPausePkts" },
|
|
+ { 4, 0x60, "Pkts64Octets" },
|
|
+ { 4, 0x64, "Pkts65to127Octets" },
|
|
+ { 4, 0x68, "Pkts128to255Octets" },
|
|
+ { 4, 0x6c, "Pkts256to511Octets" },
|
|
+ { 4, 0x70, "Pkts512to1023Octets" },
|
|
+ { 4, 0x74, "Pkts1024to1522Octets" },
|
|
+ { 4, 0x78, "RxOversizePkts" },
|
|
+ { 4, 0x7c, "RxJabbers" },
|
|
+ { 4, 0x80, "RxAlignmentErrors" },
|
|
+ { 4, 0x84, "RxFCSErrors" },
|
|
+ { 8, 0x88, "RxGoodOctets" },
|
|
+ { 4, 0x90, "RxDropPkts" },
|
|
+ { 4, 0x94, "RxUnicastPkts" },
|
|
+ { 4, 0x98, "RxMulticastPkts" },
|
|
+ { 4, 0x9c, "RxBroadcastPkts" },
|
|
+ { 4, 0xa0, "RxSAChanges" },
|
|
+ { 4, 0xa4, "RxFragments" },
|
|
+ { 4, 0xa8, "RxJumboPkts" },
|
|
+ { 4, 0xac, "RxSymbolErrors" },
|
|
+ { 4, 0xc0, "RxDiscarded" },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static int b53_do_vlan_op(struct b53_device *dev, u8 op)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op);
|
|
+
|
|
+ for (i = 0; i < 10; i++) {
|
|
+ u8 vta;
|
|
+
|
|
+ b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta);
|
|
+ if (!(vta & VTA_START_CMD))
|
|
+ return 0;
|
|
+
|
|
+ usleep_range(100, 200);
|
|
+ }
|
|
+
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members,
|
|
+ u16 untag)
|
|
+{
|
|
+ if (is5325(dev)) {
|
|
+ u32 entry = 0;
|
|
+
|
|
+ if (members) {
|
|
+ entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) |
|
|
+ members;
|
|
+ if (dev->core_rev >= 3)
|
|
+ entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S;
|
|
+ else
|
|
+ entry |= VA_VALID_25;
|
|
+ }
|
|
+
|
|
+ b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry);
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid |
|
|
+ VTA_RW_STATE_WR | VTA_RW_OP_EN);
|
|
+ } else if (is5365(dev)) {
|
|
+ u16 entry = 0;
|
|
+
|
|
+ if (members)
|
|
+ entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) |
|
|
+ members | VA_VALID_65;
|
|
+
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry);
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid |
|
|
+ VTA_RW_STATE_WR | VTA_RW_OP_EN);
|
|
+ } else {
|
|
+ b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid);
|
|
+ b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2],
|
|
+ (untag << VTE_UNTAG_S) | members);
|
|
+
|
|
+ b53_do_vlan_op(dev, VTA_CMD_WRITE);
|
|
+ }
|
|
+}
|
|
+
|
|
+void b53_set_forwarding(struct b53_device *dev, int enable)
|
|
+{
|
|
+ u8 mgmt;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
|
|
+
|
|
+ if (enable)
|
|
+ mgmt |= SM_SW_FWD_EN;
|
|
+ else
|
|
+ mgmt &= ~SM_SW_FWD_EN;
|
|
+
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
|
|
+}
|
|
+
|
|
+static void b53_enable_vlan(struct b53_device *dev, int enable)
|
|
+{
|
|
+ u8 mgmt, vc0, vc1, vc4 = 0, vc5;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0);
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1);
|
|
+
|
|
+ if (is5325(dev) || is5365(dev)) {
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4);
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5);
|
|
+ } else if (is63xx(dev)) {
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4);
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5);
|
|
+ } else {
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4);
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5);
|
|
+ }
|
|
+
|
|
+ mgmt &= ~SM_SW_FWD_MODE;
|
|
+
|
|
+ if (enable) {
|
|
+ vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID;
|
|
+ vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN;
|
|
+ vc4 &= ~VC4_ING_VID_CHECK_MASK;
|
|
+ vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S;
|
|
+ vc5 |= VC5_DROP_VTABLE_MISS;
|
|
+
|
|
+ if (is5325(dev))
|
|
+ vc0 &= ~VC0_RESERVED_1;
|
|
+
|
|
+ if (is5325(dev) || is5365(dev))
|
|
+ vc1 |= VC1_RX_MCST_TAG_EN;
|
|
+
|
|
+ if (!is5325(dev) && !is5365(dev)) {
|
|
+ if (dev->allow_vid_4095)
|
|
+ vc5 |= VC5_VID_FFF_EN;
|
|
+ else
|
|
+ vc5 &= ~VC5_VID_FFF_EN;
|
|
+ }
|
|
+ } else {
|
|
+ vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID);
|
|
+ vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN);
|
|
+ vc4 &= ~VC4_ING_VID_CHECK_MASK;
|
|
+ vc5 &= ~VC5_DROP_VTABLE_MISS;
|
|
+
|
|
+ if (is5325(dev) || is5365(dev))
|
|
+ vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S;
|
|
+ else
|
|
+ vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S;
|
|
+
|
|
+ if (is5325(dev) || is5365(dev))
|
|
+ vc1 &= ~VC1_RX_MCST_TAG_EN;
|
|
+
|
|
+ if (!is5325(dev) && !is5365(dev))
|
|
+ vc5 &= ~VC5_VID_FFF_EN;
|
|
+ }
|
|
+
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1);
|
|
+
|
|
+ if (is5325(dev) || is5365(dev)) {
|
|
+ /* enable the high 8 bit vid check on 5325 */
|
|
+ if (is5325(dev) && enable)
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3,
|
|
+ VC3_HIGH_8BIT_EN);
|
|
+ else
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0);
|
|
+
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5);
|
|
+ } else if (is63xx(dev)) {
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5);
|
|
+ } else {
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4);
|
|
+ b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5);
|
|
+ }
|
|
+
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
|
|
+}
|
|
+
|
|
+static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100)
|
|
+{
|
|
+ u32 port_mask = 0;
|
|
+ u16 max_size = JMS_MIN_SIZE;
|
|
+
|
|
+ if (is5325(dev) || is5365(dev))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (enable) {
|
|
+ port_mask = dev->enabled_ports;
|
|
+ max_size = JMS_MAX_SIZE;
|
|
+ if (allow_10_100)
|
|
+ port_mask |= JPM_10_100_JUMBO_EN;
|
|
+ }
|
|
+
|
|
+ b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask);
|
|
+ return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size);
|
|
+}
|
|
+
|
|
+static int b53_flush_arl(struct b53_device *dev)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL,
|
|
+ FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC);
|
|
+
|
|
+ for (i = 0; i < 10; i++) {
|
|
+ u8 fast_age_ctrl;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL,
|
|
+ &fast_age_ctrl);
|
|
+
|
|
+ if (!(fast_age_ctrl & FAST_AGE_DONE))
|
|
+ return 0;
|
|
+
|
|
+ mdelay(1);
|
|
+ }
|
|
+
|
|
+ pr_warn("time out while flushing ARL\n");
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static void b53_enable_ports(struct b53_device *dev)
|
|
+{
|
|
+ unsigned i;
|
|
+
|
|
+ b53_for_each_port(dev, i) {
|
|
+ u8 port_ctrl;
|
|
+ u16 pvlan_mask;
|
|
+
|
|
+ /*
|
|
+ * prevent leaking packets between wan and lan in unmanaged
|
|
+ * mode through port vlans.
|
|
+ */
|
|
+ if (dev->enable_vlan || is_cpu_port(dev, i))
|
|
+ pvlan_mask = 0x1ff;
|
|
+ else if (is531x5(dev) || is5301x(dev))
|
|
+ /* BCM53115 may use a different port as cpu port */
|
|
+ pvlan_mask = BIT(dev->sw_dev.cpu_port);
|
|
+ else
|
|
+ pvlan_mask = BIT(B53_CPU_PORT);
|
|
+
|
|
+ /* BCM5325 CPU port is at 8 */
|
|
+ if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25)
|
|
+ i = B53_CPU_PORT;
|
|
+
|
|
+ if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7))
|
|
+ /* disable unused ports 6 & 7 */
|
|
+ port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE;
|
|
+ else if (i == B53_CPU_PORT)
|
|
+ port_ctrl = PORT_CTRL_RX_BCST_EN |
|
|
+ PORT_CTRL_RX_MCST_EN |
|
|
+ PORT_CTRL_RX_UCST_EN;
|
|
+ else
|
|
+ port_ctrl = 0;
|
|
+
|
|
+ b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i),
|
|
+ pvlan_mask);
|
|
+
|
|
+ /* port state is handled by bcm63xx_enet driver */
|
|
+ if (!is63xx(dev) && !(is5301x(dev) && i == 6))
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i),
|
|
+ port_ctrl);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void b53_enable_mib(struct b53_device *dev)
|
|
+{
|
|
+ u8 gc;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_GLOBAL_CONFIG, &gc);
|
|
+
|
|
+ gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN);
|
|
+
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_GLOBAL_CONFIG, gc);
|
|
+}
|
|
+
|
|
+static int b53_apply(struct b53_device *dev)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ /* clear all vlan entries */
|
|
+ if (is5325(dev) || is5365(dev)) {
|
|
+ for (i = 1; i < dev->sw_dev.vlans; i++)
|
|
+ b53_set_vlan_entry(dev, i, 0, 0);
|
|
+ } else {
|
|
+ b53_do_vlan_op(dev, VTA_CMD_CLEAR);
|
|
+ }
|
|
+
|
|
+ b53_enable_vlan(dev, dev->enable_vlan);
|
|
+
|
|
+ /* fill VLAN table */
|
|
+ if (dev->enable_vlan) {
|
|
+ for (i = 0; i < dev->sw_dev.vlans; i++) {
|
|
+ struct b53_vlan *vlan = &dev->vlans[i];
|
|
+
|
|
+ if (!vlan->members)
|
|
+ continue;
|
|
+
|
|
+ b53_set_vlan_entry(dev, i, vlan->members, vlan->untag);
|
|
+ }
|
|
+
|
|
+ b53_for_each_port(dev, i)
|
|
+ b53_write16(dev, B53_VLAN_PAGE,
|
|
+ B53_VLAN_PORT_DEF_TAG(i),
|
|
+ dev->ports[i].pvid);
|
|
+ } else {
|
|
+ b53_for_each_port(dev, i)
|
|
+ b53_write16(dev, B53_VLAN_PAGE,
|
|
+ B53_VLAN_PORT_DEF_TAG(i), 1);
|
|
+
|
|
+ }
|
|
+
|
|
+ b53_enable_ports(dev);
|
|
+
|
|
+ if (!is5325(dev) && !is5365(dev))
|
|
+ b53_set_jumbo(dev, dev->enable_jumbo, 1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void b53_switch_reset_gpio(struct b53_device *dev)
|
|
+{
|
|
+ int gpio = dev->reset_gpio;
|
|
+
|
|
+ if (gpio < 0)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * Reset sequence: RESET low(50ms)->high(20ms)
|
|
+ */
|
|
+ gpio_set_value(gpio, 0);
|
|
+ mdelay(50);
|
|
+
|
|
+ gpio_set_value(gpio, 1);
|
|
+ mdelay(20);
|
|
+
|
|
+ dev->current_page = 0xff;
|
|
+}
|
|
+
|
|
+static int b53_switch_reset(struct b53_device *dev)
|
|
+{
|
|
+ u8 mgmt;
|
|
+
|
|
+ b53_switch_reset_gpio(dev);
|
|
+
|
|
+ if (is539x(dev)) {
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83);
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00);
|
|
+ }
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
|
|
+
|
|
+ if (!(mgmt & SM_SW_FWD_EN)) {
|
|
+ mgmt &= ~SM_SW_FWD_MODE;
|
|
+ mgmt |= SM_SW_FWD_EN;
|
|
+
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
|
|
+
|
|
+ if (!(mgmt & SM_SW_FWD_EN)) {
|
|
+ pr_err("Failed to enable switch!\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* enable all ports */
|
|
+ b53_enable_ports(dev);
|
|
+
|
|
+ /* configure MII port if necessary */
|
|
+ if (is5325(dev)) {
|
|
+ u8 mii_port_override;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
|
|
+ &mii_port_override);
|
|
+ /* reverse mii needs to be enabled */
|
|
+ if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) {
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
|
|
+ mii_port_override | PORT_OVERRIDE_RV_MII_25);
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
|
|
+ &mii_port_override);
|
|
+
|
|
+ if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) {
|
|
+ pr_err("Failed to enable reverse MII mode\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ } else if ((is531x5(dev) || is5301x(dev)) && dev->sw_dev.cpu_port == B53_CPU_PORT) {
|
|
+ u8 mii_port_override;
|
|
+
|
|
+ b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
|
|
+ &mii_port_override);
|
|
+ b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
|
|
+ mii_port_override | PORT_OVERRIDE_EN |
|
|
+ PORT_OVERRIDE_LINK);
|
|
+ }
|
|
+
|
|
+ b53_enable_mib(dev);
|
|
+
|
|
+ return b53_flush_arl(dev);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Swconfig glue functions
|
|
+ */
|
|
+
|
|
+static int b53_global_get_vlan_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ val->value.i = priv->enable_vlan;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_set_vlan_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ priv->enable_vlan = val->value.i;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_get_jumbo_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ val->value.i = priv->enable_jumbo;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_set_jumbo_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ priv->enable_jumbo = val->value.i;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_get_4095_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ val->value.i = priv->allow_vid_4095;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_set_4095_enable(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ priv->allow_vid_4095 = val->value.i;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_global_get_ports(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x",
|
|
+ priv->enabled_ports);
|
|
+ val->value.s = priv->buf;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ *val = priv->ports[port].pvid;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_port_set_pvid(struct switch_dev *dev, int port, int val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ if (val > 15 && is5325(priv))
|
|
+ return -EINVAL;
|
|
+ if (val == 4095 && !priv->allow_vid_4095)
|
|
+ return -EINVAL;
|
|
+
|
|
+ priv->ports[port].pvid = val;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+ struct switch_port *port = &val->value.ports[0];
|
|
+ struct b53_vlan *vlan = &priv->vlans[val->port_vlan];
|
|
+ int i;
|
|
+
|
|
+ val->len = 0;
|
|
+
|
|
+ if (!vlan->members)
|
|
+ return 0;
|
|
+
|
|
+ for (i = 0; i < dev->ports; i++) {
|
|
+ if (!(vlan->members & BIT(i)))
|
|
+ continue;
|
|
+
|
|
+
|
|
+ if (!(vlan->untag & BIT(i)))
|
|
+ port->flags = BIT(SWITCH_PORT_FLAG_TAGGED);
|
|
+ else
|
|
+ port->flags = 0;
|
|
+
|
|
+ port->id = i;
|
|
+ val->len++;
|
|
+ port++;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+ struct switch_port *port;
|
|
+ struct b53_vlan *vlan = &priv->vlans[val->port_vlan];
|
|
+ int i;
|
|
+
|
|
+ /* only BCM5325 and BCM5365 supports VID 0 */
|
|
+ if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* VLAN 4095 needs special handling */
|
|
+ if (val->port_vlan == 4095 && !priv->allow_vid_4095)
|
|
+ return -EINVAL;
|
|
+
|
|
+ port = &val->value.ports[0];
|
|
+ vlan->members = 0;
|
|
+ vlan->untag = 0;
|
|
+ for (i = 0; i < val->len; i++, port++) {
|
|
+ vlan->members |= BIT(port->id);
|
|
+
|
|
+ if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) {
|
|
+ vlan->untag |= BIT(port->id);
|
|
+ priv->ports[port->id].pvid = val->port_vlan;
|
|
+ };
|
|
+ }
|
|
+
|
|
+ /* ignore disabled ports */
|
|
+ vlan->members &= priv->enabled_ports;
|
|
+ vlan->untag &= priv->enabled_ports;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_port_get_link(struct switch_dev *dev, int port,
|
|
+ struct switch_port_link *link)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ if (is_cpu_port(priv, port)) {
|
|
+ link->link = 1;
|
|
+ link->duplex = 1;
|
|
+ link->speed = is5325(priv) || is5365(priv) ?
|
|
+ SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000;
|
|
+ link->aneg = 0;
|
|
+ } else if (priv->enabled_ports & BIT(port)) {
|
|
+ u32 speed;
|
|
+ u16 lnk, duplex;
|
|
+
|
|
+ b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk);
|
|
+ b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex);
|
|
+
|
|
+ lnk = (lnk >> port) & 1;
|
|
+ duplex = (duplex >> port) & 1;
|
|
+
|
|
+ if (is5325(priv) || is5365(priv)) {
|
|
+ u16 tmp;
|
|
+
|
|
+ b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp);
|
|
+ speed = SPEED_PORT_FE(tmp, port);
|
|
+ } else {
|
|
+ b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed);
|
|
+ speed = SPEED_PORT_GE(speed, port);
|
|
+ }
|
|
+
|
|
+ link->link = lnk;
|
|
+ if (lnk) {
|
|
+ link->duplex = duplex;
|
|
+ switch (speed) {
|
|
+ case SPEED_STAT_10M:
|
|
+ link->speed = SWITCH_PORT_SPEED_10;
|
|
+ break;
|
|
+ case SPEED_STAT_100M:
|
|
+ link->speed = SWITCH_PORT_SPEED_100;
|
|
+ break;
|
|
+ case SPEED_STAT_1000M:
|
|
+ link->speed = SWITCH_PORT_SPEED_1000;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ link->aneg = 1;
|
|
+ } else {
|
|
+ link->link = 0;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int b53_global_reset_switch(struct switch_dev *dev)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ /* reset vlans */
|
|
+ priv->enable_vlan = 0;
|
|
+ priv->enable_jumbo = 0;
|
|
+ priv->allow_vid_4095 = 0;
|
|
+
|
|
+ memset(priv->vlans, 0, sizeof(priv->vlans) * dev->vlans);
|
|
+ memset(priv->ports, 0, sizeof(priv->ports) * dev->ports);
|
|
+
|
|
+ return b53_switch_reset(priv);
|
|
+}
|
|
+
|
|
+static int b53_global_apply_config(struct switch_dev *dev)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+
|
|
+ /* disable switching */
|
|
+ b53_set_forwarding(priv, 0);
|
|
+
|
|
+ b53_apply(priv);
|
|
+
|
|
+ /* enable switching */
|
|
+ b53_set_forwarding(priv, 1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int b53_global_reset_mib(struct switch_dev *dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *priv = sw_to_b53(dev);
|
|
+ u8 gc;
|
|
+
|
|
+ b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc);
|
|
+
|
|
+ b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB);
|
|
+ mdelay(1);
|
|
+ b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB);
|
|
+ mdelay(1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_port_get_mib(struct switch_dev *sw_dev,
|
|
+ const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct b53_device *dev = sw_to_b53(sw_dev);
|
|
+ const struct b53_mib_desc *mibs;
|
|
+ int port = val->port_vlan;
|
|
+ int len = 0;
|
|
+
|
|
+ if (!(BIT(port) & dev->enabled_ports))
|
|
+ return -1;
|
|
+
|
|
+ if (is5365(dev)) {
|
|
+ if (port == 5)
|
|
+ port = 8;
|
|
+
|
|
+ mibs = b53_mibs_65;
|
|
+ } else if (is63xx(dev)) {
|
|
+ mibs = b53_mibs_63xx;
|
|
+ } else {
|
|
+ mibs = b53_mibs;
|
|
+ }
|
|
+
|
|
+ dev->buf[0] = 0;
|
|
+
|
|
+ for (; mibs->size > 0; mibs++) {
|
|
+ u64 val;
|
|
+
|
|
+ if (mibs->size == 8) {
|
|
+ b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val);
|
|
+ } else {
|
|
+ u32 val32;
|
|
+
|
|
+ b53_read32(dev, B53_MIB_PAGE(port), mibs->offset,
|
|
+ &val32);
|
|
+ val = val32;
|
|
+ }
|
|
+
|
|
+ len += snprintf(dev->buf + len, B53_BUF_SIZE - len,
|
|
+ "%-20s: %llu\n", mibs->name, val);
|
|
+ }
|
|
+
|
|
+ val->len = len;
|
|
+ val->value.s = dev->buf;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct switch_attr b53_global_ops_25[] = {
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "enable_vlan",
|
|
+ .description = "Enable VLAN mode",
|
|
+ .set = b53_global_set_vlan_enable,
|
|
+ .get = b53_global_get_vlan_enable,
|
|
+ .max = 1,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_STRING,
|
|
+ .name = "ports",
|
|
+ .description = "Available ports (as bitmask)",
|
|
+ .get = b53_global_get_ports,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct switch_attr b53_global_ops_65[] = {
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "enable_vlan",
|
|
+ .description = "Enable VLAN mode",
|
|
+ .set = b53_global_set_vlan_enable,
|
|
+ .get = b53_global_get_vlan_enable,
|
|
+ .max = 1,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_STRING,
|
|
+ .name = "ports",
|
|
+ .description = "Available ports (as bitmask)",
|
|
+ .get = b53_global_get_ports,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "reset_mib",
|
|
+ .description = "Reset MIB counters",
|
|
+ .set = b53_global_reset_mib,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct switch_attr b53_global_ops[] = {
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "enable_vlan",
|
|
+ .description = "Enable VLAN mode",
|
|
+ .set = b53_global_set_vlan_enable,
|
|
+ .get = b53_global_get_vlan_enable,
|
|
+ .max = 1,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_STRING,
|
|
+ .name = "ports",
|
|
+ .description = "Available Ports (as bitmask)",
|
|
+ .get = b53_global_get_ports,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "reset_mib",
|
|
+ .description = "Reset MIB counters",
|
|
+ .set = b53_global_reset_mib,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "enable_jumbo",
|
|
+ .description = "Enable Jumbo Frames",
|
|
+ .set = b53_global_set_jumbo_enable,
|
|
+ .get = b53_global_get_jumbo_enable,
|
|
+ .max = 1,
|
|
+ },
|
|
+ {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "allow_vid_4095",
|
|
+ .description = "Allow VID 4095",
|
|
+ .set = b53_global_set_4095_enable,
|
|
+ .get = b53_global_get_4095_enable,
|
|
+ .max = 1,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct switch_attr b53_port_ops[] = {
|
|
+ {
|
|
+ .type = SWITCH_TYPE_STRING,
|
|
+ .name = "mib",
|
|
+ .description = "Get port's MIB counters",
|
|
+ .get = b53_port_get_mib,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct switch_attr b53_no_ops[] = {
|
|
+};
|
|
+
|
|
+static const struct switch_dev_ops b53_switch_ops_25 = {
|
|
+ .attr_global = {
|
|
+ .attr = b53_global_ops_25,
|
|
+ .n_attr = ARRAY_SIZE(b53_global_ops_25),
|
|
+ },
|
|
+ .attr_port = {
|
|
+ .attr = b53_no_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_no_ops),
|
|
+ },
|
|
+ .attr_vlan = {
|
|
+ .attr = b53_no_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_no_ops),
|
|
+ },
|
|
+
|
|
+ .get_vlan_ports = b53_vlan_get_ports,
|
|
+ .set_vlan_ports = b53_vlan_set_ports,
|
|
+ .get_port_pvid = b53_port_get_pvid,
|
|
+ .set_port_pvid = b53_port_set_pvid,
|
|
+ .apply_config = b53_global_apply_config,
|
|
+ .reset_switch = b53_global_reset_switch,
|
|
+ .get_port_link = b53_port_get_link,
|
|
+};
|
|
+
|
|
+static const struct switch_dev_ops b53_switch_ops_65 = {
|
|
+ .attr_global = {
|
|
+ .attr = b53_global_ops_65,
|
|
+ .n_attr = ARRAY_SIZE(b53_global_ops_65),
|
|
+ },
|
|
+ .attr_port = {
|
|
+ .attr = b53_port_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_port_ops),
|
|
+ },
|
|
+ .attr_vlan = {
|
|
+ .attr = b53_no_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_no_ops),
|
|
+ },
|
|
+
|
|
+ .get_vlan_ports = b53_vlan_get_ports,
|
|
+ .set_vlan_ports = b53_vlan_set_ports,
|
|
+ .get_port_pvid = b53_port_get_pvid,
|
|
+ .set_port_pvid = b53_port_set_pvid,
|
|
+ .apply_config = b53_global_apply_config,
|
|
+ .reset_switch = b53_global_reset_switch,
|
|
+ .get_port_link = b53_port_get_link,
|
|
+};
|
|
+
|
|
+static const struct switch_dev_ops b53_switch_ops = {
|
|
+ .attr_global = {
|
|
+ .attr = b53_global_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_global_ops),
|
|
+ },
|
|
+ .attr_port = {
|
|
+ .attr = b53_port_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_port_ops),
|
|
+ },
|
|
+ .attr_vlan = {
|
|
+ .attr = b53_no_ops,
|
|
+ .n_attr = ARRAY_SIZE(b53_no_ops),
|
|
+ },
|
|
+
|
|
+ .get_vlan_ports = b53_vlan_get_ports,
|
|
+ .set_vlan_ports = b53_vlan_set_ports,
|
|
+ .get_port_pvid = b53_port_get_pvid,
|
|
+ .set_port_pvid = b53_port_set_pvid,
|
|
+ .apply_config = b53_global_apply_config,
|
|
+ .reset_switch = b53_global_reset_switch,
|
|
+ .get_port_link = b53_port_get_link,
|
|
+};
|
|
+
|
|
+struct b53_chip_data {
|
|
+ u32 chip_id;
|
|
+ const char *dev_name;
|
|
+ const char *alias;
|
|
+ u16 vlans;
|
|
+ u16 enabled_ports;
|
|
+ u8 cpu_port;
|
|
+ u8 vta_regs[3];
|
|
+ u8 duplex_reg;
|
|
+ u8 jumbo_pm_reg;
|
|
+ u8 jumbo_size_reg;
|
|
+ const struct switch_dev_ops *sw_ops;
|
|
+};
|
|
+
|
|
+#define B53_VTA_REGS \
|
|
+ { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY }
|
|
+#define B53_VTA_REGS_9798 \
|
|
+ { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 }
|
|
+#define B53_VTA_REGS_63XX \
|
|
+ { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX }
|
|
+
|
|
+static const struct b53_chip_data b53_switch_chips[] = {
|
|
+ {
|
|
+ .chip_id = BCM5325_DEVICE_ID,
|
|
+ .dev_name = "BCM5325",
|
|
+ .alias = "bcm5325",
|
|
+ .vlans = 16,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_FE,
|
|
+ .sw_ops = &b53_switch_ops_25,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM5365_DEVICE_ID,
|
|
+ .dev_name = "BCM5365",
|
|
+ .alias = "bcm5365",
|
|
+ .vlans = 256,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_FE,
|
|
+ .sw_ops = &b53_switch_ops_65,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM5395_DEVICE_ID,
|
|
+ .dev_name = "BCM5395",
|
|
+ .alias = "bcm5395",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM5397_DEVICE_ID,
|
|
+ .dev_name = "BCM5397",
|
|
+ .alias = "bcm5397",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS_9798,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM5398_DEVICE_ID,
|
|
+ .dev_name = "BCM5398",
|
|
+ .alias = "bcm5398",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x7f,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS_9798,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53115_DEVICE_ID,
|
|
+ .dev_name = "BCM53115",
|
|
+ .alias = "bcm53115",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53125_DEVICE_ID,
|
|
+ .dev_name = "BCM53125",
|
|
+ .alias = "bcm53125",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53128_DEVICE_ID,
|
|
+ .dev_name = "BCM53128",
|
|
+ .alias = "bcm53128",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1ff,
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM63XX_DEVICE_ID,
|
|
+ .dev_name = "BCM63xx",
|
|
+ .alias = "bcm63xx",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0, /* pdata must provide them */
|
|
+ .cpu_port = B53_CPU_PORT,
|
|
+ .vta_regs = B53_VTA_REGS_63XX,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_63XX,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53010_DEVICE_ID,
|
|
+ .dev_name = "BCM53010",
|
|
+ .alias = "bcm53011",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53011_DEVICE_ID,
|
|
+ .dev_name = "BCM53011",
|
|
+ .alias = "bcm53011",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53012_DEVICE_ID,
|
|
+ .dev_name = "BCM53012",
|
|
+ .alias = "bcm53011",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53018_DEVICE_ID,
|
|
+ .dev_name = "BCM53018",
|
|
+ .alias = "bcm53018",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+ {
|
|
+ .chip_id = BCM53019_DEVICE_ID,
|
|
+ .dev_name = "BCM53019",
|
|
+ .alias = "bcm53019",
|
|
+ .vlans = 4096,
|
|
+ .enabled_ports = 0x1f,
|
|
+ .cpu_port = B53_CPU_PORT_25, // TODO: auto detect
|
|
+ .vta_regs = B53_VTA_REGS,
|
|
+ .duplex_reg = B53_DUPLEX_STAT_GE,
|
|
+ .jumbo_pm_reg = B53_JUMBO_PORT_MASK,
|
|
+ .jumbo_size_reg = B53_JUMBO_MAX_SIZE,
|
|
+ .sw_ops = &b53_switch_ops,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int b53_switch_init(struct b53_device *dev)
|
|
+{
|
|
+ struct switch_dev *sw_dev = &dev->sw_dev;
|
|
+ unsigned i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) {
|
|
+ const struct b53_chip_data *chip = &b53_switch_chips[i];
|
|
+
|
|
+ if (chip->chip_id == dev->chip_id) {
|
|
+ sw_dev->name = chip->dev_name;
|
|
+ if (!sw_dev->alias)
|
|
+ sw_dev->alias = chip->alias;
|
|
+ if (!dev->enabled_ports)
|
|
+ dev->enabled_ports = chip->enabled_ports;
|
|
+ dev->duplex_reg = chip->duplex_reg;
|
|
+ dev->vta_regs[0] = chip->vta_regs[0];
|
|
+ dev->vta_regs[1] = chip->vta_regs[1];
|
|
+ dev->vta_regs[2] = chip->vta_regs[2];
|
|
+ dev->jumbo_pm_reg = chip->jumbo_pm_reg;
|
|
+ sw_dev->ops = chip->sw_ops;
|
|
+ sw_dev->cpu_port = chip->cpu_port;
|
|
+ sw_dev->vlans = chip->vlans;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!sw_dev->name)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* check which BCM5325x version we have */
|
|
+ if (is5325(dev)) {
|
|
+ u8 vc4;
|
|
+
|
|
+ b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4);
|
|
+
|
|
+ /* check reserved bits */
|
|
+ switch (vc4 & 3) {
|
|
+ case 1:
|
|
+ /* BCM5325E */
|
|
+ break;
|
|
+ case 3:
|
|
+ /* BCM5325F - do not use port 4 */
|
|
+ dev->enabled_ports &= ~BIT(4);
|
|
+ break;
|
|
+ default:
|
|
+/* On the BCM47XX SoCs this is the supported internal switch.*/
|
|
+#ifndef CONFIG_BCM47XX
|
|
+ /* BCM5325M */
|
|
+ return -EINVAL;
|
|
+#else
|
|
+ break;
|
|
+#endif
|
|
+ }
|
|
+ } else if (dev->chip_id == BCM53115_DEVICE_ID) {
|
|
+ u64 strap_value;
|
|
+
|
|
+ b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value);
|
|
+ /* use second IMP port if GMII is enabled */
|
|
+ if (strap_value & SV_GMII_CTRL_115)
|
|
+ sw_dev->cpu_port = 5;
|
|
+ }
|
|
+
|
|
+ /* cpu port is always last */
|
|
+ sw_dev->ports = sw_dev->cpu_port + 1;
|
|
+ dev->enabled_ports |= BIT(sw_dev->cpu_port);
|
|
+
|
|
+ dev->ports = devm_kzalloc(dev->dev,
|
|
+ sizeof(struct b53_port) * sw_dev->ports,
|
|
+ GFP_KERNEL);
|
|
+ if (!dev->ports)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev->vlans = devm_kzalloc(dev->dev,
|
|
+ sizeof(struct b53_vlan) * sw_dev->vlans,
|
|
+ GFP_KERNEL);
|
|
+ if (!dev->vlans)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL);
|
|
+ if (!dev->buf)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev->reset_gpio = b53_switch_get_reset_gpio(dev);
|
|
+ if (dev->reset_gpio >= 0) {
|
|
+ ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, GPIOF_OUT_INIT_HIGH, "robo_reset");
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return b53_switch_reset(dev);
|
|
+}
|
|
+
|
|
+struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops,
|
|
+ void *priv)
|
|
+{
|
|
+ struct b53_device *dev;
|
|
+
|
|
+ dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL);
|
|
+ if (!dev)
|
|
+ return NULL;
|
|
+
|
|
+ dev->dev = base;
|
|
+ dev->ops = ops;
|
|
+ dev->priv = priv;
|
|
+ mutex_init(&dev->reg_mutex);
|
|
+
|
|
+ return dev;
|
|
+}
|
|
+EXPORT_SYMBOL(b53_switch_alloc);
|
|
+
|
|
+int b53_switch_detect(struct b53_device *dev)
|
|
+{
|
|
+ u32 id32;
|
|
+ u16 tmp;
|
|
+ u8 id8;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ switch (id8) {
|
|
+ case 0:
|
|
+ /*
|
|
+ * BCM5325 and BCM5365 do not have this register so reads
|
|
+ * return 0. But the read operation did succeed, so assume
|
|
+ * this is one of them.
|
|
+ *
|
|
+ * Next check if we can write to the 5325's VTA register; for
|
|
+ * 5365 it is read only.
|
|
+ */
|
|
+
|
|
+ b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf);
|
|
+ b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp);
|
|
+
|
|
+ if (tmp == 0xf)
|
|
+ dev->chip_id = BCM5325_DEVICE_ID;
|
|
+ else
|
|
+ dev->chip_id = BCM5365_DEVICE_ID;
|
|
+ break;
|
|
+ case BCM5395_DEVICE_ID:
|
|
+ case BCM5397_DEVICE_ID:
|
|
+ case BCM5398_DEVICE_ID:
|
|
+ dev->chip_id = id8;
|
|
+ break;
|
|
+ default:
|
|
+ ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ switch (id32) {
|
|
+ case BCM53115_DEVICE_ID:
|
|
+ case BCM53125_DEVICE_ID:
|
|
+ case BCM53128_DEVICE_ID:
|
|
+ case BCM53010_DEVICE_ID:
|
|
+ case BCM53011_DEVICE_ID:
|
|
+ case BCM53012_DEVICE_ID:
|
|
+ case BCM53018_DEVICE_ID:
|
|
+ case BCM53019_DEVICE_ID:
|
|
+ dev->chip_id = id32;
|
|
+ break;
|
|
+ default:
|
|
+ pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n",
|
|
+ id8, id32);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dev->chip_id == BCM5325_DEVICE_ID)
|
|
+ return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25,
|
|
+ &dev->core_rev);
|
|
+ else
|
|
+ return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID,
|
|
+ &dev->core_rev);
|
|
+}
|
|
+EXPORT_SYMBOL(b53_switch_detect);
|
|
+
|
|
+int b53_switch_register(struct b53_device *dev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (dev->pdata) {
|
|
+ dev->chip_id = dev->pdata->chip_id;
|
|
+ dev->enabled_ports = dev->pdata->enabled_ports;
|
|
+ dev->sw_dev.alias = dev->pdata->alias;
|
|
+ }
|
|
+
|
|
+ if (!dev->chip_id && b53_switch_detect(dev))
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = b53_switch_init(dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev);
|
|
+
|
|
+ return register_switch(&dev->sw_dev, NULL);
|
|
+}
|
|
+EXPORT_SYMBOL(b53_switch_register);
|
|
+
|
|
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
|
|
+MODULE_DESCRIPTION("B53 switch library");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/net/phy/b53/b53_mdio.c b/drivers/net/phy/b53/b53_mdio.c
|
|
new file mode 100644
|
|
index 0000000..3c25f0e
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_mdio.c
|
|
@@ -0,0 +1,425 @@
|
|
+/*
|
|
+ * B53 register access through MII registers
|
|
+ *
|
|
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/module.h>
|
|
+
|
|
+#include "b53_priv.h"
|
|
+
|
|
+#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
|
|
+
|
|
+/* MII registers */
|
|
+#define REG_MII_PAGE 0x10 /* MII Page register */
|
|
+#define REG_MII_ADDR 0x11 /* MII Address register */
|
|
+#define REG_MII_DATA0 0x18 /* MII Data register 0 */
|
|
+#define REG_MII_DATA1 0x19 /* MII Data register 1 */
|
|
+#define REG_MII_DATA2 0x1a /* MII Data register 2 */
|
|
+#define REG_MII_DATA3 0x1b /* MII Data register 3 */
|
|
+
|
|
+#define REG_MII_PAGE_ENABLE BIT(0)
|
|
+#define REG_MII_ADDR_WRITE BIT(0)
|
|
+#define REG_MII_ADDR_READ BIT(1)
|
|
+
|
|
+static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op)
|
|
+{
|
|
+ int i;
|
|
+ u16 v;
|
|
+ int ret;
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+
|
|
+ if (dev->current_page != page) {
|
|
+ /* set page number */
|
|
+ v = (page << 8) | REG_MII_PAGE_ENABLE;
|
|
+ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ dev->current_page = page;
|
|
+ }
|
|
+
|
|
+ /* set register address */
|
|
+ v = (reg << 8) | op;
|
|
+ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* check if operation completed */
|
|
+ for (i = 0; i < 5; ++i) {
|
|
+ v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR);
|
|
+ if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ)))
|
|
+ break;
|
|
+ usleep_range(10, 100);
|
|
+ }
|
|
+
|
|
+ if (WARN_ON(i == 5))
|
|
+ return -EIO;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
|
|
+ *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ u64 temp = 0;
|
|
+ int i;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ for (i = 2; i >= 0; i--) {
|
|
+ temp <<= 16;
|
|
+ temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
|
|
+ }
|
|
+
|
|
+ *val = temp;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ u64 temp = 0;
|
|
+ int i;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ for (i = 3; i >= 0; i--) {
|
|
+ temp <<= 16;
|
|
+ temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
|
|
+ }
|
|
+
|
|
+ *val = temp;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
|
+}
|
|
+
|
|
+static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u16 value)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
|
+}
|
|
+
|
|
+static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u32 value)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ unsigned int i;
|
|
+ u32 temp = value;
|
|
+
|
|
+ for (i = 0; i < 2; i++) {
|
|
+ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
|
+ temp & 0xffff);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ temp >>= 16;
|
|
+ }
|
|
+
|
|
+ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
|
+
|
|
+}
|
|
+
|
|
+static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ unsigned i;
|
|
+ u64 temp = value;
|
|
+
|
|
+ for (i = 0; i < 3; i++) {
|
|
+ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
|
+ temp & 0xffff);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ temp >>= 16;
|
|
+ }
|
|
+
|
|
+ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
|
+
|
|
+}
|
|
+
|
|
+static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ struct mii_bus *bus = dev->priv;
|
|
+ unsigned i;
|
|
+ u64 temp = value;
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
|
+ temp & 0xffff);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ temp >>= 16;
|
|
+ }
|
|
+
|
|
+ return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
|
+}
|
|
+
|
|
+static struct b53_io_ops b53_mdio_ops = {
|
|
+ .read8 = b53_mdio_read8,
|
|
+ .read16 = b53_mdio_read16,
|
|
+ .read32 = b53_mdio_read32,
|
|
+ .read48 = b53_mdio_read48,
|
|
+ .read64 = b53_mdio_read64,
|
|
+ .write8 = b53_mdio_write8,
|
|
+ .write16 = b53_mdio_write16,
|
|
+ .write32 = b53_mdio_write32,
|
|
+ .write48 = b53_mdio_write48,
|
|
+ .write64 = b53_mdio_write64,
|
|
+};
|
|
+
|
|
+static int b53_phy_probe(struct phy_device *phydev)
|
|
+{
|
|
+ struct b53_device dev;
|
|
+ int ret;
|
|
+
|
|
+ /* allow the generic phy driver to take over */
|
|
+ if (phydev->addr != B53_PSEUDO_PHY && phydev->addr != 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ dev.current_page = 0xff;
|
|
+ dev.priv = phydev->bus;
|
|
+ dev.ops = &b53_mdio_ops;
|
|
+ dev.pdata = NULL;
|
|
+ mutex_init(&dev.reg_mutex);
|
|
+
|
|
+ ret = b53_switch_detect(&dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (is5325(&dev) || is5365(&dev))
|
|
+ phydev->supported = SUPPORTED_100baseT_Full;
|
|
+ else
|
|
+ phydev->supported = SUPPORTED_1000baseT_Full;
|
|
+
|
|
+ phydev->advertising = phydev->supported;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_phy_config_init(struct phy_device *phydev)
|
|
+{
|
|
+ struct b53_device *dev;
|
|
+ int ret;
|
|
+
|
|
+ dev = b53_switch_alloc(&phydev->dev, &b53_mdio_ops, phydev->bus);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* we don't use page 0xff, so force a page set */
|
|
+ dev->current_page = 0xff;
|
|
+ /* force the ethX as alias */
|
|
+ dev->sw_dev.alias = phydev->attached_dev->name;
|
|
+
|
|
+ ret = b53_switch_register(dev);
|
|
+ if (ret) {
|
|
+ dev_err(dev->dev, "failed to register switch: %i\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ phydev->priv = dev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void b53_phy_remove(struct phy_device *phydev)
|
|
+{
|
|
+ struct b53_device *priv = phydev->priv;
|
|
+
|
|
+ if (!priv)
|
|
+ return;
|
|
+
|
|
+ b53_switch_remove(priv);
|
|
+
|
|
+ phydev->priv = NULL;
|
|
+}
|
|
+
|
|
+static int b53_phy_config_aneg(struct phy_device *phydev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_phy_read_status(struct phy_device *phydev)
|
|
+{
|
|
+ struct b53_device *priv = phydev->priv;
|
|
+
|
|
+ if (is5325(priv) || is5365(priv))
|
|
+ phydev->speed = 100;
|
|
+ else
|
|
+ phydev->speed = 1000;
|
|
+
|
|
+ phydev->duplex = DUPLEX_FULL;
|
|
+ phydev->link = 1;
|
|
+ phydev->state = PHY_RUNNING;
|
|
+
|
|
+ netif_carrier_on(phydev->attached_dev);
|
|
+ phydev->adjust_link(phydev->attached_dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* BCM5325, BCM539x */
|
|
+static struct phy_driver b53_phy_driver_id1 = {
|
|
+ .phy_id = 0x0143bc00,
|
|
+ .name = "Broadcom B53 (1)",
|
|
+ .phy_id_mask = 0x1ffffc00,
|
|
+ .features = 0,
|
|
+ .probe = b53_phy_probe,
|
|
+ .remove = b53_phy_remove,
|
|
+ .config_aneg = b53_phy_config_aneg,
|
|
+ .config_init = b53_phy_config_init,
|
|
+ .read_status = b53_phy_read_status,
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+};
|
|
+
|
|
+/* BCM53125, BCM53128 */
|
|
+static struct phy_driver b53_phy_driver_id2 = {
|
|
+ .phy_id = 0x03625c00,
|
|
+ .name = "Broadcom B53 (2)",
|
|
+ .phy_id_mask = 0x1ffffc00,
|
|
+ .features = 0,
|
|
+ .probe = b53_phy_probe,
|
|
+ .remove = b53_phy_remove,
|
|
+ .config_aneg = b53_phy_config_aneg,
|
|
+ .config_init = b53_phy_config_init,
|
|
+ .read_status = b53_phy_read_status,
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+};
|
|
+
|
|
+/* BCM5365 */
|
|
+static struct phy_driver b53_phy_driver_id3 = {
|
|
+ .phy_id = 0x00406000,
|
|
+ .name = "Broadcom B53 (3)",
|
|
+ .phy_id_mask = 0x1ffffc00,
|
|
+ .features = 0,
|
|
+ .probe = b53_phy_probe,
|
|
+ .remove = b53_phy_remove,
|
|
+ .config_aneg = b53_phy_config_aneg,
|
|
+ .config_init = b53_phy_config_init,
|
|
+ .read_status = b53_phy_read_status,
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+};
|
|
+
|
|
+int __init b53_phy_driver_register(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_driver_register(&b53_phy_driver_id1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = phy_driver_register(&b53_phy_driver_id2);
|
|
+ if (ret)
|
|
+ goto err1;
|
|
+
|
|
+ ret = phy_driver_register(&b53_phy_driver_id3);
|
|
+ if (!ret)
|
|
+ return 0;
|
|
+
|
|
+ phy_driver_unregister(&b53_phy_driver_id2);
|
|
+err1:
|
|
+ phy_driver_unregister(&b53_phy_driver_id1);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void __exit b53_phy_driver_unregister(void)
|
|
+{
|
|
+ phy_driver_unregister(&b53_phy_driver_id3);
|
|
+ phy_driver_unregister(&b53_phy_driver_id2);
|
|
+ phy_driver_unregister(&b53_phy_driver_id1);
|
|
+}
|
|
+
|
|
+module_init(b53_phy_driver_register);
|
|
+module_exit(b53_phy_driver_unregister);
|
|
+
|
|
+MODULE_DESCRIPTION("B53 MDIO access driver");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/net/phy/b53/b53_mmap.c b/drivers/net/phy/b53/b53_mmap.c
|
|
new file mode 100644
|
|
index 0000000..272360f
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_mmap.c
|
|
@@ -0,0 +1,240 @@
|
|
+/*
|
|
+ * B53 register access through memory mapped registers
|
|
+ *
|
|
+ * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/platform_data/b53.h>
|
|
+
|
|
+#include "b53_priv.h"
|
|
+
|
|
+static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ *val = readb(regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 2))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian)
|
|
+ *val = readw_be(regs + (page << 8) + reg);
|
|
+ else
|
|
+ *val = readw(regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian)
|
|
+ *val = readl_be(regs + (page << 8) + reg);
|
|
+ else
|
|
+ *val = readl(regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian) {
|
|
+ *val = readl_be(regs + (page << 8) + reg);
|
|
+ *val <<= 16;
|
|
+ *val |= readw_be(regs + (page << 8) + reg + 4);
|
|
+ } else {
|
|
+ *val |= readw(regs + (page << 8) + reg + 4);
|
|
+ *val <<= 32;
|
|
+ *val = readl(regs + (page << 8) + reg);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ u32 hi, lo;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian) {
|
|
+ lo = readl_be(regs + (page << 8) + reg);
|
|
+ hi = readl_be(regs + (page << 8) + reg + 4);
|
|
+ } else {
|
|
+ lo = readl(regs + (page << 8) + reg);
|
|
+ hi = readl(regs + (page << 8) + reg + 4);
|
|
+ }
|
|
+
|
|
+ *val = ((u64)hi << 32) | lo;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ writeb(value, regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u16 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 2))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian)
|
|
+ writew_be(value, regs + (page << 8) + reg);
|
|
+ else
|
|
+ writew(value, regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u32 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian)
|
|
+ writel_be(value, regs + (page << 8) + reg);
|
|
+ else
|
|
+ writel(value, regs + (page << 8) + reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian) {
|
|
+ writel_be((u32)(value >> 16), regs + (page << 8) + reg);
|
|
+ writew_be((u16)value, regs + (page << 8) + reg + 4);
|
|
+ } else {
|
|
+ writel((u32)value, regs + (page << 8) + reg);
|
|
+ writew((u16)(value >> 32), regs + (page << 8) + reg + 4);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ if (WARN_ON(reg % 4))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (dev->pdata && dev->pdata->big_endian) {
|
|
+ writel_be((u32)(value >> 32), regs + (page << 8) + reg);
|
|
+ writel_be((u32)value, regs + (page << 8) + reg + 4);
|
|
+ } else {
|
|
+ writel((u32)value, regs + (page << 8) + reg);
|
|
+ writel((u32)(value >> 32), regs + (page << 8) + reg + 4);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct b53_io_ops b53_mmap_ops = {
|
|
+ .read8 = b53_mmap_read8,
|
|
+ .read16 = b53_mmap_read16,
|
|
+ .read32 = b53_mmap_read32,
|
|
+ .read48 = b53_mmap_read48,
|
|
+ .read64 = b53_mmap_read64,
|
|
+ .write8 = b53_mmap_write8,
|
|
+ .write16 = b53_mmap_write16,
|
|
+ .write32 = b53_mmap_write32,
|
|
+ .write48 = b53_mmap_write48,
|
|
+ .write64 = b53_mmap_write64,
|
|
+};
|
|
+
|
|
+static int b53_mmap_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct b53_platform_data *pdata = pdev->dev.platform_data;
|
|
+ struct b53_device *dev;
|
|
+
|
|
+ if (!pdata)
|
|
+ return -EINVAL;
|
|
+
|
|
+ dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (pdata)
|
|
+ dev->pdata = pdata;
|
|
+
|
|
+ platform_set_drvdata(pdev, dev);
|
|
+
|
|
+ return b53_switch_register(dev);
|
|
+}
|
|
+
|
|
+static int b53_mmap_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct b53_device *dev = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (dev) {
|
|
+ b53_switch_remove(dev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver b53_mmap_driver = {
|
|
+ .probe = b53_mmap_probe,
|
|
+ .remove = b53_mmap_remove,
|
|
+ .driver = {
|
|
+ .name = "b53-switch",
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(b53_mmap_driver);
|
|
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
|
|
+MODULE_DESCRIPTION("B53 MMAP access driver");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/net/phy/b53/b53_phy_fixup.c b/drivers/net/phy/b53/b53_phy_fixup.c
|
|
new file mode 100644
|
|
index 0000000..72d1373
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_phy_fixup.c
|
|
@@ -0,0 +1,55 @@
|
|
+/*
|
|
+ * B53 PHY Fixup call
|
|
+ *
|
|
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/phy.h>
|
|
+
|
|
+#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
|
|
+
|
|
+#define B53_BRCM_OUI_1 0x0143bc00
|
|
+#define B53_BRCM_OUI_2 0x03625c00
|
|
+#define B53_BRCM_OUI_3 0x00406000
|
|
+
|
|
+static int b53_phy_fixup(struct phy_device *dev)
|
|
+{
|
|
+ u32 phy_id;
|
|
+ struct mii_bus *bus = dev->bus;
|
|
+
|
|
+ if (dev->addr != B53_PSEUDO_PHY)
|
|
+ return 0;
|
|
+
|
|
+ /* read the first port's id */
|
|
+ phy_id = mdiobus_read(bus, 0, 2) << 16;
|
|
+ phy_id |= mdiobus_read(bus, 0, 3);
|
|
+
|
|
+ if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 ||
|
|
+ (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 ||
|
|
+ (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) {
|
|
+ dev->phy_id = phy_id;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int __init b53_phy_fixup_register(void)
|
|
+{
|
|
+ return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup);
|
|
+}
|
|
+
|
|
+subsys_initcall(b53_phy_fixup_register);
|
|
diff --git a/drivers/net/phy/b53/b53_priv.h b/drivers/net/phy/b53/b53_priv.h
|
|
new file mode 100644
|
|
index 0000000..bc9b533
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_priv.h
|
|
@@ -0,0 +1,324 @@
|
|
+/*
|
|
+ * B53 common definitions
|
|
+ *
|
|
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#ifndef __B53_PRIV_H
|
|
+#define __B53_PRIV_H
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/switch.h>
|
|
+
|
|
+struct b53_device;
|
|
+
|
|
+struct b53_io_ops {
|
|
+ int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value);
|
|
+ int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value);
|
|
+ int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value);
|
|
+ int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
|
|
+ int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
|
|
+ int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value);
|
|
+ int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value);
|
|
+ int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value);
|
|
+ int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value);
|
|
+ int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value);
|
|
+};
|
|
+
|
|
+enum {
|
|
+ BCM5325_DEVICE_ID = 0x25,
|
|
+ BCM5365_DEVICE_ID = 0x65,
|
|
+ BCM5395_DEVICE_ID = 0x95,
|
|
+ BCM5397_DEVICE_ID = 0x97,
|
|
+ BCM5398_DEVICE_ID = 0x98,
|
|
+ BCM53115_DEVICE_ID = 0x53115,
|
|
+ BCM53125_DEVICE_ID = 0x53125,
|
|
+ BCM53128_DEVICE_ID = 0x53128,
|
|
+ BCM63XX_DEVICE_ID = 0x6300,
|
|
+ BCM53010_DEVICE_ID = 0x53010,
|
|
+ BCM53011_DEVICE_ID = 0x53011,
|
|
+ BCM53012_DEVICE_ID = 0x53012,
|
|
+ BCM53018_DEVICE_ID = 0x53018,
|
|
+ BCM53019_DEVICE_ID = 0x53019,
|
|
+};
|
|
+
|
|
+#define B53_N_PORTS 9
|
|
+#define B53_N_PORTS_25 6
|
|
+
|
|
+struct b53_vlan {
|
|
+ unsigned int members:B53_N_PORTS;
|
|
+ unsigned int untag:B53_N_PORTS;
|
|
+};
|
|
+
|
|
+struct b53_port {
|
|
+ unsigned int pvid:12;
|
|
+};
|
|
+
|
|
+struct b53_device {
|
|
+ struct switch_dev sw_dev;
|
|
+ struct b53_platform_data *pdata;
|
|
+
|
|
+ struct mutex reg_mutex;
|
|
+ const struct b53_io_ops *ops;
|
|
+
|
|
+ /* chip specific data */
|
|
+ u32 chip_id;
|
|
+ u8 core_rev;
|
|
+ u8 vta_regs[3];
|
|
+ u8 duplex_reg;
|
|
+ u8 jumbo_pm_reg;
|
|
+ u8 jumbo_size_reg;
|
|
+ int reset_gpio;
|
|
+
|
|
+ /* used ports mask */
|
|
+ u16 enabled_ports;
|
|
+
|
|
+ /* connect specific data */
|
|
+ u8 current_page;
|
|
+ struct device *dev;
|
|
+ void *priv;
|
|
+
|
|
+ /* run time configuration */
|
|
+ unsigned enable_vlan:1;
|
|
+ unsigned enable_jumbo:1;
|
|
+ unsigned allow_vid_4095:1;
|
|
+
|
|
+ struct b53_port *ports;
|
|
+ struct b53_vlan *vlans;
|
|
+
|
|
+ char *buf;
|
|
+};
|
|
+
|
|
+#define b53_for_each_port(dev, i) \
|
|
+ for (i = 0; i < B53_N_PORTS; i++) \
|
|
+ if (dev->enabled_ports & BIT(i))
|
|
+
|
|
+
|
|
+
|
|
+static inline int is5325(struct b53_device *dev)
|
|
+{
|
|
+ return dev->chip_id == BCM5325_DEVICE_ID;
|
|
+}
|
|
+
|
|
+static inline int is5365(struct b53_device *dev)
|
|
+{
|
|
+#ifdef CONFIG_BCM47XX
|
|
+ return dev->chip_id == BCM5365_DEVICE_ID;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int is5397_98(struct b53_device *dev)
|
|
+{
|
|
+ return dev->chip_id == BCM5397_DEVICE_ID ||
|
|
+ dev->chip_id == BCM5398_DEVICE_ID;
|
|
+}
|
|
+
|
|
+static inline int is539x(struct b53_device *dev)
|
|
+{
|
|
+ return dev->chip_id == BCM5395_DEVICE_ID ||
|
|
+ dev->chip_id == BCM5397_DEVICE_ID ||
|
|
+ dev->chip_id == BCM5398_DEVICE_ID;
|
|
+}
|
|
+
|
|
+static inline int is531x5(struct b53_device *dev)
|
|
+{
|
|
+ return dev->chip_id == BCM53115_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53125_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53128_DEVICE_ID;
|
|
+}
|
|
+
|
|
+static inline int is63xx(struct b53_device *dev)
|
|
+{
|
|
+#ifdef CONFIG_BCM63XX
|
|
+ return dev->chip_id == BCM63XX_DEVICE_ID;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int is5301x(struct b53_device *dev)
|
|
+{
|
|
+ return dev->chip_id == BCM53010_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53011_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53012_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53018_DEVICE_ID ||
|
|
+ dev->chip_id == BCM53019_DEVICE_ID;
|
|
+}
|
|
+
|
|
+#define B53_CPU_PORT_25 5
|
|
+#define B53_CPU_PORT 8
|
|
+
|
|
+static inline int is_cpu_port(struct b53_device *dev, int port)
|
|
+{
|
|
+ return dev->sw_dev.cpu_port == port;
|
|
+}
|
|
+
|
|
+static inline struct b53_device *sw_to_b53(struct switch_dev *sw)
|
|
+{
|
|
+ return container_of(sw, struct b53_device, sw_dev);
|
|
+}
|
|
+
|
|
+struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops,
|
|
+ void *priv);
|
|
+
|
|
+int b53_switch_detect(struct b53_device *dev);
|
|
+
|
|
+int b53_switch_register(struct b53_device *dev);
|
|
+
|
|
+static inline void b53_switch_remove(struct b53_device *dev)
|
|
+{
|
|
+ unregister_switch(&dev->sw_dev);
|
|
+}
|
|
+
|
|
+static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->read8(dev, page, reg, val);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->read16(dev, page, reg, val);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->read32(dev, page, reg, val);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->read48(dev, page, reg, val);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->read64(dev, page, reg, val);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->write8(dev, page, reg, value);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u16 value)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->write16(dev, page, reg, value);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u32 value)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->write32(dev, page, reg, value);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->write48(dev, page, reg, value);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&dev->reg_mutex);
|
|
+ ret = dev->ops->write64(dev, page, reg, value);
|
|
+ mutex_unlock(&dev->reg_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_BCM47XX
|
|
+
|
|
+#include <bcm47xx_nvram.h>
|
|
+#include <bcm47xx_board.h>
|
|
+static inline int b53_switch_get_reset_gpio(struct b53_device *dev)
|
|
+{
|
|
+ enum bcm47xx_board board = bcm47xx_board_get();
|
|
+
|
|
+ switch (board) {
|
|
+ case BCM47XX_BOARD_LINKSYS_WRT300NV11:
|
|
+ case BCM47XX_BOARD_LINKSYS_WRT310NV1:
|
|
+ return 8;
|
|
+ default:
|
|
+ return bcm47xx_nvram_gpio_pin("robo_reset");
|
|
+ }
|
|
+}
|
|
+#else
|
|
+static inline int b53_switch_get_reset_gpio(struct b53_device *dev)
|
|
+{
|
|
+ return -ENOENT;
|
|
+}
|
|
+#endif
|
|
+#endif
|
|
diff --git a/drivers/net/phy/b53/b53_regs.h b/drivers/net/phy/b53/b53_regs.h
|
|
new file mode 100644
|
|
index 0000000..ba50915
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_regs.h
|
|
@@ -0,0 +1,313 @@
|
|
+/*
|
|
+ * B53 register definitions
|
|
+ *
|
|
+ * Copyright (C) 2004 Broadcom Corporation
|
|
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#ifndef __B53_REGS_H
|
|
+#define __B53_REGS_H
|
|
+
|
|
+/* Management Port (SMP) Page offsets */
|
|
+#define B53_CTRL_PAGE 0x00 /* Control */
|
|
+#define B53_STAT_PAGE 0x01 /* Status */
|
|
+#define B53_MGMT_PAGE 0x02 /* Management Mode */
|
|
+#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */
|
|
+#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */
|
|
+#define B53_ARLIO_PAGE 0x05 /* ARL Access */
|
|
+#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */
|
|
+#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */
|
|
+
|
|
+/* PHY Registers */
|
|
+#define B53_PORT_MII_PAGE(i) (0x10 + i) /* Port i MII Registers */
|
|
+#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */
|
|
+#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */
|
|
+
|
|
+/* MIB registers */
|
|
+#define B53_MIB_PAGE(i) (0x20 + i)
|
|
+
|
|
+/* Quality of Service (QoS) Registers */
|
|
+#define B53_QOS_PAGE 0x30
|
|
+
|
|
+/* Port VLAN Page */
|
|
+#define B53_PVLAN_PAGE 0x31
|
|
+
|
|
+/* VLAN Registers */
|
|
+#define B53_VLAN_PAGE 0x34
|
|
+
|
|
+/* Jumbo Frame Registers */
|
|
+#define B53_JUMBO_PAGE 0x40
|
|
+
|
|
+/*************************************************************************
|
|
+ * Control Page registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Port Control Register (8 bit) */
|
|
+#define B53_PORT_CTRL(i) (0x00 + i)
|
|
+#define PORT_CTRL_RX_DISABLE BIT(0)
|
|
+#define PORT_CTRL_TX_DISABLE BIT(1)
|
|
+#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */
|
|
+#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */
|
|
+#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */
|
|
+#define PORT_CTRL_STP_STATE_S 5
|
|
+#define PORT_CTRL_STP_STATE_MASK (0x3 << PORT_CTRL_STP_STATE_S)
|
|
+
|
|
+/* SMP Control Register (8 bit) */
|
|
+#define B53_SMP_CTRL 0x0a
|
|
+
|
|
+/* Switch Mode Control Register (8 bit) */
|
|
+#define B53_SWITCH_MODE 0x0b
|
|
+#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */
|
|
+#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */
|
|
+
|
|
+/* IMP Port state override register (8 bit) */
|
|
+#define B53_PORT_OVERRIDE_CTRL 0x0e
|
|
+#define PORT_OVERRIDE_LINK BIT(0)
|
|
+#define PORT_OVERRIDE_HALF_DUPLEX BIT(1) /* 0 = Full Duplex */
|
|
+#define PORT_OVERRIDE_SPEED_S 2
|
|
+#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S)
|
|
+#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S)
|
|
+#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S)
|
|
+#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */
|
|
+#define PORT_OVERRIDE_RX_FLOW BIT(4)
|
|
+#define PORT_OVERRIDE_TX_FLOW BIT(5)
|
|
+#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */
|
|
+
|
|
+/* Power-down mode control */
|
|
+#define B53_PD_MODE_CTRL_25 0x0f
|
|
+
|
|
+/* IP Multicast control (8 bit) */
|
|
+#define B53_IP_MULTICAST_CTRL 0x21
|
|
+#define B53_IPMC_FWD_EN BIT(1)
|
|
+#define B53_UC_FWD_EN BIT(6)
|
|
+#define B53_MC_FWD_EN BIT(7)
|
|
+
|
|
+/* (16 bit) */
|
|
+#define B53_UC_FLOOD_MASK 0x32
|
|
+#define B53_MC_FLOOD_MASK 0x34
|
|
+#define B53_IPMC_FLOOD_MASK 0x36
|
|
+
|
|
+/* Software reset register (8 bit) */
|
|
+#define B53_SOFTRESET 0x79
|
|
+
|
|
+/* Fast Aging Control register (8 bit) */
|
|
+#define B53_FAST_AGE_CTRL 0x88
|
|
+#define FAST_AGE_STATIC BIT(0)
|
|
+#define FAST_AGE_DYNAMIC BIT(1)
|
|
+#define FAST_AGE_PORT BIT(2)
|
|
+#define FAST_AGE_VLAN BIT(3)
|
|
+#define FAST_AGE_STP BIT(4)
|
|
+#define FAST_AGE_MC BIT(5)
|
|
+#define FAST_AGE_DONE BIT(7)
|
|
+
|
|
+/*************************************************************************
|
|
+ * Status Page registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Link Status Summary Register (16bit) */
|
|
+#define B53_LINK_STAT 0x00
|
|
+
|
|
+/* Link Status Change Register (16 bit) */
|
|
+#define B53_LINK_STAT_CHANGE 0x02
|
|
+
|
|
+/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */
|
|
+#define B53_SPEED_STAT 0x04
|
|
+#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1)
|
|
+#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3)
|
|
+#define SPEED_STAT_10M 0
|
|
+#define SPEED_STAT_100M 1
|
|
+#define SPEED_STAT_1000M 2
|
|
+
|
|
+/* Duplex Status Summary (16 bit) */
|
|
+#define B53_DUPLEX_STAT_FE 0x06
|
|
+#define B53_DUPLEX_STAT_GE 0x08
|
|
+#define B53_DUPLEX_STAT_63XX 0x0c
|
|
+
|
|
+/* Revision ID register for BCM5325 */
|
|
+#define B53_REV_ID_25 0x50
|
|
+
|
|
+/* Strap Value (48 bit) */
|
|
+#define B53_STRAP_VALUE 0x70
|
|
+#define SV_GMII_CTRL_115 BIT(27)
|
|
+
|
|
+/*************************************************************************
|
|
+ * Management Mode Page Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Global Management Config Register (8 bit) */
|
|
+#define B53_GLOBAL_CONFIG 0x00
|
|
+#define GC_RESET_MIB 0x01
|
|
+#define GC_RX_BPDU_EN 0x02
|
|
+#define GC_MIB_AC_HDR_EN 0x10
|
|
+#define GC_MIB_AC_EN 0x20
|
|
+#define GC_FRM_MGMT_PORT_M 0xC0
|
|
+#define GC_FRM_MGMT_PORT_04 0x00
|
|
+#define GC_FRM_MGMT_PORT_MII 0x80
|
|
+
|
|
+/* Device ID register (8 or 32 bit) */
|
|
+#define B53_DEVICE_ID 0x30
|
|
+
|
|
+/* Revision ID register (8 bit) */
|
|
+#define B53_REV_ID 0x40
|
|
+
|
|
+/*************************************************************************
|
|
+ * ARL Access Page Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* VLAN Table Access Register (8 bit) */
|
|
+#define B53_VT_ACCESS 0x80
|
|
+#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */
|
|
+#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */
|
|
+#define VTA_CMD_WRITE 0
|
|
+#define VTA_CMD_READ 1
|
|
+#define VTA_CMD_CLEAR 2
|
|
+#define VTA_START_CMD BIT(7)
|
|
+
|
|
+/* VLAN Table Index Register (16 bit) */
|
|
+#define B53_VT_INDEX 0x81
|
|
+#define B53_VT_INDEX_9798 0x61
|
|
+#define B53_VT_INDEX_63XX 0x62
|
|
+
|
|
+/* VLAN Table Entry Register (32 bit) */
|
|
+#define B53_VT_ENTRY 0x83
|
|
+#define B53_VT_ENTRY_9798 0x63
|
|
+#define B53_VT_ENTRY_63XX 0x64
|
|
+#define VTE_MEMBERS 0x1ff
|
|
+#define VTE_UNTAG_S 9
|
|
+#define VTE_UNTAG (0x1ff << 9)
|
|
+
|
|
+/*************************************************************************
|
|
+ * Port VLAN Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */
|
|
+#define B53_PVLAN_PORT_MASK(i) ((i) * 2)
|
|
+
|
|
+/*************************************************************************
|
|
+ * 802.1Q Page Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Global QoS Control (8 bit) */
|
|
+#define B53_QOS_GLOBAL_CTL 0x00
|
|
+
|
|
+/* Enable 802.1Q for individual Ports (16 bit) */
|
|
+#define B53_802_1P_EN 0x04
|
|
+
|
|
+/*************************************************************************
|
|
+ * VLAN Page Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* VLAN Control 0 (8 bit) */
|
|
+#define B53_VLAN_CTRL0 0x00
|
|
+#define VC0_8021PF_CTRL_MASK 0x3
|
|
+#define VC0_8021PF_CTRL_NONE 0x0
|
|
+#define VC0_8021PF_CTRL_CHANGE_PRI 0x1
|
|
+#define VC0_8021PF_CTRL_CHANGE_VID 0x2
|
|
+#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3
|
|
+#define VC0_8021QF_CTRL_MASK 0xc
|
|
+#define VC0_8021QF_CTRL_CHANGE_PRI 0x1
|
|
+#define VC0_8021QF_CTRL_CHANGE_VID 0x2
|
|
+#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3
|
|
+#define VC0_RESERVED_1 BIT(1)
|
|
+#define VC0_DROP_VID_MISS BIT(4)
|
|
+#define VC0_VID_HASH_VID BIT(5)
|
|
+#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */
|
|
+#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */
|
|
+
|
|
+/* VLAN Control 1 (8 bit) */
|
|
+#define B53_VLAN_CTRL1 0x01
|
|
+#define VC1_RX_MCST_TAG_EN BIT(1)
|
|
+#define VC1_RX_MCST_FWD_EN BIT(2)
|
|
+#define VC1_RX_MCST_UNTAG_EN BIT(3)
|
|
+
|
|
+/* VLAN Control 2 (8 bit) */
|
|
+#define B53_VLAN_CTRL2 0x02
|
|
+
|
|
+/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */
|
|
+#define B53_VLAN_CTRL3 0x03
|
|
+#define B53_VLAN_CTRL3_63XX 0x04
|
|
+#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */
|
|
+#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */
|
|
+
|
|
+/* VLAN Control 4 (8 bit) */
|
|
+#define B53_VLAN_CTRL4 0x05
|
|
+#define B53_VLAN_CTRL4_25 0x04
|
|
+#define B53_VLAN_CTRL4_63XX 0x06
|
|
+#define VC4_ING_VID_CHECK_S 6
|
|
+#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S)
|
|
+#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */
|
|
+#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */
|
|
+#define VC4_NO_ING_VID_CHK 2 /* do not check */
|
|
+#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */
|
|
+
|
|
+/* VLAN Control 5 (8 bit) */
|
|
+#define B53_VLAN_CTRL5 0x06
|
|
+#define B53_VLAN_CTRL5_25 0x05
|
|
+#define B53_VLAN_CTRL5_63XX 0x07
|
|
+#define VC5_VID_FFF_EN BIT(2)
|
|
+#define VC5_DROP_VTABLE_MISS BIT(3)
|
|
+
|
|
+/* VLAN Control 6 (8 bit) */
|
|
+#define B53_VLAN_CTRL6 0x07
|
|
+#define B53_VLAN_CTRL6_63XX 0x08
|
|
+
|
|
+/* VLAN Table Access Register (16 bit) */
|
|
+#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */
|
|
+#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */
|
|
+#define VTA_VID_LOW_MASK_25 0xf
|
|
+#define VTA_VID_LOW_MASK_65 0xff
|
|
+#define VTA_VID_HIGH_S_25 4
|
|
+#define VTA_VID_HIGH_S_65 8
|
|
+#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E)
|
|
+#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65)
|
|
+#define VTA_RW_STATE BIT(12)
|
|
+#define VTA_RW_STATE_RD 0
|
|
+#define VTA_RW_STATE_WR BIT(12)
|
|
+#define VTA_RW_OP_EN BIT(13)
|
|
+
|
|
+/* VLAN Read/Write Registers for (16/32 bit) */
|
|
+#define B53_VLAN_WRITE_25 0x08
|
|
+#define B53_VLAN_WRITE_65 0x0a
|
|
+#define B53_VLAN_READ 0x0c
|
|
+#define VA_MEMBER_MASK 0x3f
|
|
+#define VA_UNTAG_S_25 6
|
|
+#define VA_UNTAG_MASK_25 0x3f
|
|
+#define VA_UNTAG_S_65 7
|
|
+#define VA_UNTAG_MASK_65 0x1f
|
|
+#define VA_VID_HIGH_S 12
|
|
+#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S)
|
|
+#define VA_VALID_25 BIT(20)
|
|
+#define VA_VALID_25_R4 BIT(24)
|
|
+#define VA_VALID_65 BIT(14)
|
|
+
|
|
+/* VLAN Port Default Tag (16 bit) */
|
|
+#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i))
|
|
+
|
|
+/*************************************************************************
|
|
+ * Jumbo Frame Page Registers
|
|
+ *************************************************************************/
|
|
+
|
|
+/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */
|
|
+#define B53_JUMBO_PORT_MASK 0x01
|
|
+#define B53_JUMBO_PORT_MASK_63XX 0x04
|
|
+#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */
|
|
+
|
|
+/* Good Frame Max Size without 802.1Q TAG (16 bit) */
|
|
+#define B53_JUMBO_MAX_SIZE 0x05
|
|
+#define B53_JUMBO_MAX_SIZE_63XX 0x08
|
|
+#define JMS_MIN_SIZE 1518
|
|
+#define JMS_MAX_SIZE 9724
|
|
+
|
|
+#endif /* !__B53_REGS_H */
|
|
diff --git a/drivers/net/phy/b53/b53_spi.c b/drivers/net/phy/b53/b53_spi.c
|
|
new file mode 100644
|
|
index 0000000..8c6b171
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_spi.c
|
|
@@ -0,0 +1,327 @@
|
|
+/*
|
|
+ * B53 register access through SPI
|
|
+ *
|
|
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/platform_data/b53.h>
|
|
+
|
|
+#include "b53_priv.h"
|
|
+
|
|
+#define B53_SPI_DATA 0xf0
|
|
+
|
|
+#define B53_SPI_STATUS 0xfe
|
|
+#define B53_SPI_CMD_SPIF BIT(7)
|
|
+#define B53_SPI_CMD_RACK BIT(5)
|
|
+
|
|
+#define B53_SPI_CMD_READ 0x00
|
|
+#define B53_SPI_CMD_WRITE 0x01
|
|
+#define B53_SPI_CMD_NORMAL 0x60
|
|
+#define B53_SPI_CMD_FAST 0x10
|
|
+
|
|
+#define B53_SPI_PAGE_SELECT 0xff
|
|
+
|
|
+static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val,
|
|
+ unsigned len)
|
|
+{
|
|
+ u8 txbuf[2];
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ;
|
|
+ txbuf[1] = reg;
|
|
+
|
|
+ return spi_write_then_read(spi, txbuf, 2, val, len);
|
|
+}
|
|
+
|
|
+static inline int b53_spi_clear_status(struct spi_device *spi)
|
|
+{
|
|
+ unsigned int i;
|
|
+ u8 rxbuf;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < 10; i++) {
|
|
+ ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (!(rxbuf & B53_SPI_CMD_SPIF))
|
|
+ break;
|
|
+
|
|
+ mdelay(1);
|
|
+ }
|
|
+
|
|
+ if (i == 10)
|
|
+ return -EIO;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int b53_spi_set_page(struct spi_device *spi, u8 page)
|
|
+{
|
|
+ u8 txbuf[3];
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = B53_SPI_PAGE_SELECT;
|
|
+ txbuf[2] = page;
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf));
|
|
+}
|
|
+
|
|
+static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page)
|
|
+{
|
|
+ int ret = b53_spi_clear_status(spi);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return b53_spi_set_page(spi, page);
|
|
+}
|
|
+
|
|
+static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg)
|
|
+{
|
|
+ u8 rxbuf;
|
|
+ int retry_count;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_spi_read_reg(spi, reg, &rxbuf, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ for (retry_count = 0; retry_count < 10; retry_count++) {
|
|
+ ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (rxbuf & B53_SPI_CMD_RACK)
|
|
+ break;
|
|
+
|
|
+ mdelay(1);
|
|
+ }
|
|
+
|
|
+ if (retry_count == 10)
|
|
+ return -EIO;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data,
|
|
+ unsigned len)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = b53_spi_prepare_reg_read(spi, reg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return b53_spi_read_reg(spi, B53_SPI_DATA, data, len);
|
|
+}
|
|
+
|
|
+static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
|
+{
|
|
+ return b53_spi_read(dev, page, reg, val, 1);
|
|
+}
|
|
+
|
|
+static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
|
+{
|
|
+ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2);
|
|
+ if (!ret)
|
|
+ *val = le16_to_cpu(*val);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
|
+{
|
|
+ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4);
|
|
+ if (!ret)
|
|
+ *val = le32_to_cpu(*val);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ *val = 0;
|
|
+ ret = b53_spi_read(dev, page, reg, (u8 *)val, 6);
|
|
+ if (!ret)
|
|
+ *val = le64_to_cpu(*val);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8);
|
|
+ if (!ret)
|
|
+ *val = le64_to_cpu(*val);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+ u8 txbuf[3];
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = reg;
|
|
+ txbuf[2] = value;
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf));
|
|
+}
|
|
+
|
|
+static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+ u8 txbuf[4];
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = reg;
|
|
+ put_unaligned_le16(value, &txbuf[2]);
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf));
|
|
+}
|
|
+
|
|
+static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+ u8 txbuf[6];
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = reg;
|
|
+ put_unaligned_le32(value, &txbuf[2]);
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf));
|
|
+}
|
|
+
|
|
+static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+ u8 txbuf[10];
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = reg;
|
|
+ put_unaligned_le64(value, &txbuf[2]);
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf) - 2);
|
|
+}
|
|
+
|
|
+static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value)
|
|
+{
|
|
+ struct spi_device *spi = dev->priv;
|
|
+ int ret;
|
|
+ u8 txbuf[10];
|
|
+
|
|
+ ret = b53_prepare_reg_access(spi, page);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
|
+ txbuf[1] = reg;
|
|
+ put_unaligned_le64(value, &txbuf[2]);
|
|
+
|
|
+ return spi_write(spi, txbuf, sizeof(txbuf));
|
|
+}
|
|
+
|
|
+static struct b53_io_ops b53_spi_ops = {
|
|
+ .read8 = b53_spi_read8,
|
|
+ .read16 = b53_spi_read16,
|
|
+ .read32 = b53_spi_read32,
|
|
+ .read48 = b53_spi_read48,
|
|
+ .read64 = b53_spi_read64,
|
|
+ .write8 = b53_spi_write8,
|
|
+ .write16 = b53_spi_write16,
|
|
+ .write32 = b53_spi_write32,
|
|
+ .write48 = b53_spi_write48,
|
|
+ .write64 = b53_spi_write64,
|
|
+};
|
|
+
|
|
+static int b53_spi_probe(struct spi_device *spi)
|
|
+{
|
|
+ struct b53_device *dev;
|
|
+ int ret;
|
|
+
|
|
+ dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (spi->dev.platform_data)
|
|
+ dev->pdata = spi->dev.platform_data;
|
|
+
|
|
+ ret = b53_switch_register(dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ spi_set_drvdata(spi, dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_spi_remove(struct spi_device *spi)
|
|
+{
|
|
+ struct b53_device *dev = spi_get_drvdata(spi);
|
|
+
|
|
+ if (dev) {
|
|
+ b53_switch_remove(dev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct spi_driver b53_spi_driver = {
|
|
+ .driver = {
|
|
+ .name = "b53-switch",
|
|
+ .bus = &spi_bus_type,
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .probe = b53_spi_probe,
|
|
+ .remove = b53_spi_remove,
|
|
+};
|
|
+
|
|
+module_spi_driver(b53_spi_driver);
|
|
+
|
|
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
|
|
+MODULE_DESCRIPTION("B53 SPI access driver");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/net/phy/b53/b53_srab.c b/drivers/net/phy/b53/b53_srab.c
|
|
new file mode 100644
|
|
index 0000000..a68e275
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/b53/b53_srab.c
|
|
@@ -0,0 +1,379 @@
|
|
+/*
|
|
+ * B53 register access through Switch Register Access Bridge Registers
|
|
+ *
|
|
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/platform_data/b53.h>
|
|
+
|
|
+#include "b53_priv.h"
|
|
+
|
|
+/* command and status register of the SRAB */
|
|
+#define B53_SRAB_CMDSTAT 0x2c
|
|
+#define B53_SRAB_CMDSTAT_RST BIT(2)
|
|
+#define B53_SRAB_CMDSTAT_WRITE BIT(1)
|
|
+#define B53_SRAB_CMDSTAT_GORDYN BIT(0)
|
|
+#define B53_SRAB_CMDSTAT_PAGE 24
|
|
+#define B53_SRAB_CMDSTAT_REG 16
|
|
+
|
|
+/* high order word of write data to switch registe */
|
|
+#define B53_SRAB_WD_H 0x30
|
|
+
|
|
+/* low order word of write data to switch registe */
|
|
+#define B53_SRAB_WD_L 0x34
|
|
+
|
|
+/* high order word of read data from switch register */
|
|
+#define B53_SRAB_RD_H 0x38
|
|
+
|
|
+/* low order word of read data from switch register */
|
|
+#define B53_SRAB_RD_L 0x3c
|
|
+
|
|
+/* command and status register of the SRAB */
|
|
+#define B53_SRAB_CTRLS 0x40
|
|
+#define B53_SRAB_CTRLS_RCAREQ BIT(3)
|
|
+#define B53_SRAB_CTRLS_RCAGNT BIT(4)
|
|
+#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6)
|
|
+
|
|
+/* the register captures interrupt pulses from the switch */
|
|
+#define B53_SRAB_INTR 0x44
|
|
+
|
|
+static int b53_srab_request_grant(struct b53_device *dev)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ u32 ctrls;
|
|
+ int i;
|
|
+
|
|
+ ctrls = readl(regs + B53_SRAB_CTRLS);
|
|
+ ctrls |= B53_SRAB_CTRLS_RCAREQ;
|
|
+ writel(ctrls, regs + B53_SRAB_CTRLS);
|
|
+
|
|
+ for (i = 0; i < 20; i++) {
|
|
+ ctrls = readl(regs + B53_SRAB_CTRLS);
|
|
+ if (ctrls & B53_SRAB_CTRLS_RCAGNT)
|
|
+ break;
|
|
+ usleep_range(10, 100);
|
|
+ }
|
|
+ if (WARN_ON(i == 5))
|
|
+ return -EIO;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void b53_srab_release_grant(struct b53_device *dev)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ u32 ctrls;
|
|
+
|
|
+ ctrls = readl(regs + B53_SRAB_CTRLS);
|
|
+ ctrls &= ~B53_SRAB_CTRLS_RCAREQ;
|
|
+ writel(ctrls, regs + B53_SRAB_CTRLS);
|
|
+}
|
|
+
|
|
+static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op)
|
|
+{
|
|
+ int i;
|
|
+ u32 cmdstat;
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+
|
|
+ /* set register address */
|
|
+ cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) |
|
|
+ (reg << B53_SRAB_CMDSTAT_REG) |
|
|
+ B53_SRAB_CMDSTAT_GORDYN |
|
|
+ op;
|
|
+ writel(cmdstat, regs + B53_SRAB_CMDSTAT);
|
|
+
|
|
+ /* check if operation completed */
|
|
+ for (i = 0; i < 5; ++i) {
|
|
+ cmdstat = readl(regs + B53_SRAB_CMDSTAT);
|
|
+ if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN))
|
|
+ break;
|
|
+ usleep_range(10, 100);
|
|
+ }
|
|
+
|
|
+ if (WARN_ON(i == 5))
|
|
+ return -EIO;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, 0);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ *val = readl(regs + B53_SRAB_RD_L) & 0xff;
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, 0);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ *val = readl(regs + B53_SRAB_RD_L) & 0xffff;
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, 0);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ *val = readl(regs + B53_SRAB_RD_L);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, 0);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ *val = readl(regs + B53_SRAB_RD_L);
|
|
+ *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32;
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, 0);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ *val = readl(regs + B53_SRAB_RD_L);
|
|
+ *val += (u64)readl(regs + B53_SRAB_RD_H) << 32;
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ writel(value, regs + B53_SRAB_WD_L);
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u16 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ writel(value, regs + B53_SRAB_WD_L);
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u32 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ writel(value, regs + B53_SRAB_WD_L);
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ writel((u32)value, regs + B53_SRAB_WD_L);
|
|
+ writel((u16)(value >> 32), regs + B53_SRAB_WD_H);
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg,
|
|
+ u64 value)
|
|
+{
|
|
+ u8 __iomem *regs = dev->priv;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = b53_srab_request_grant(dev);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+
|
|
+ writel((u32)value, regs + B53_SRAB_WD_L);
|
|
+ writel((u32)(value >> 32), regs + B53_SRAB_WD_H);
|
|
+
|
|
+ ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
|
+
|
|
+err:
|
|
+ b53_srab_release_grant(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct b53_io_ops b53_srab_ops = {
|
|
+ .read8 = b53_srab_read8,
|
|
+ .read16 = b53_srab_read16,
|
|
+ .read32 = b53_srab_read32,
|
|
+ .read48 = b53_srab_read48,
|
|
+ .read64 = b53_srab_read64,
|
|
+ .write8 = b53_srab_write8,
|
|
+ .write16 = b53_srab_write16,
|
|
+ .write32 = b53_srab_write32,
|
|
+ .write48 = b53_srab_write48,
|
|
+ .write64 = b53_srab_write64,
|
|
+};
|
|
+
|
|
+static int b53_srab_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct b53_platform_data *pdata = pdev->dev.platform_data;
|
|
+ struct b53_device *dev;
|
|
+
|
|
+ if (!pdata)
|
|
+ return -EINVAL;
|
|
+
|
|
+ dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (pdata)
|
|
+ dev->pdata = pdata;
|
|
+
|
|
+ platform_set_drvdata(pdev, dev);
|
|
+
|
|
+ return b53_switch_register(dev);
|
|
+}
|
|
+
|
|
+static int b53_srab_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct b53_device *dev = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (dev) {
|
|
+ b53_switch_remove(dev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver b53_srab_driver = {
|
|
+ .probe = b53_srab_probe,
|
|
+ .remove = b53_srab_remove,
|
|
+ .driver = {
|
|
+ .name = "b53-srab-switch",
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(b53_srab_driver);
|
|
+MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
|
|
+MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/net/phy/swconfig.c b/drivers/net/phy/swconfig.c
|
|
new file mode 100644
|
|
index 0000000..71500a9
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/swconfig.c
|
|
@@ -0,0 +1,1149 @@
|
|
+/*
|
|
+ * swconfig.c: Switch configuration API
|
|
+ *
|
|
+ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License
|
|
+ * as published by the Free Software Foundation; either version 2
|
|
+ * of the License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ */
|
|
+
|
|
+#include <linux/types.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/if.h>
|
|
+#include <linux/if_ether.h>
|
|
+#include <linux/capability.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/switch.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/version.h>
|
|
+
|
|
+#define SWCONFIG_DEVNAME "switch%d"
|
|
+
|
|
+#include "swconfig_leds.c"
|
|
+
|
|
+MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+static int swdev_id;
|
|
+static struct list_head swdevs;
|
|
+static DEFINE_SPINLOCK(swdevs_lock);
|
|
+struct swconfig_callback;
|
|
+
|
|
+struct swconfig_callback {
|
|
+ struct sk_buff *msg;
|
|
+ struct genlmsghdr *hdr;
|
|
+ struct genl_info *info;
|
|
+ int cmd;
|
|
+
|
|
+ /* callback for filling in the message data */
|
|
+ int (*fill)(struct swconfig_callback *cb, void *arg);
|
|
+
|
|
+ /* callback for closing the message before sending it */
|
|
+ int (*close)(struct swconfig_callback *cb, void *arg);
|
|
+
|
|
+ struct nlattr *nest[4];
|
|
+ int args[4];
|
|
+};
|
|
+
|
|
+/* defaults */
|
|
+
|
|
+static int
|
|
+swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ int ret;
|
|
+ if (val->port_vlan >= dev->vlans)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!dev->ops->get_vlan_ports)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ ret = dev->ops->get_vlan_ports(dev, val);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct switch_port *ports = val->value.ports;
|
|
+ const struct switch_dev_ops *ops = dev->ops;
|
|
+ int i;
|
|
+
|
|
+ if (val->port_vlan >= dev->vlans)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* validate ports */
|
|
+ if (val->len > dev->ports)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!ops->set_vlan_ports)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ for (i = 0; i < val->len; i++) {
|
|
+ if (ports[i].id >= dev->ports)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (ops->set_port_pvid &&
|
|
+ !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED)))
|
|
+ ops->set_port_pvid(dev, ports[i].id, val->port_vlan);
|
|
+ }
|
|
+
|
|
+ return ops->set_vlan_ports(dev, val);
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ if (val->port_vlan >= dev->ports)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!dev->ops->set_port_pvid)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i);
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ if (val->port_vlan >= dev->ports)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!dev->ops->get_port_pvid)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i);
|
|
+}
|
|
+
|
|
+static const char *
|
|
+swconfig_speed_str(enum switch_port_speed speed)
|
|
+{
|
|
+ switch (speed) {
|
|
+ case SWITCH_PORT_SPEED_10:
|
|
+ return "10baseT";
|
|
+ case SWITCH_PORT_SPEED_100:
|
|
+ return "100baseT";
|
|
+ case SWITCH_PORT_SPEED_1000:
|
|
+ return "1000baseT";
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return "unknown";
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ struct switch_port_link link;
|
|
+ int len;
|
|
+ int ret;
|
|
+
|
|
+ if (val->port_vlan >= dev->ports)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!dev->ops->get_port_link)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ memset(&link, 0, sizeof(link));
|
|
+ ret = dev->ops->get_port_link(dev, val->port_vlan, &link);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ memset(dev->buf, 0, sizeof(dev->buf));
|
|
+
|
|
+ if (link.link)
|
|
+ len = snprintf(dev->buf, sizeof(dev->buf),
|
|
+ "port:%d link:up speed:%s %s-duplex %s%s%s",
|
|
+ val->port_vlan,
|
|
+ swconfig_speed_str(link.speed),
|
|
+ link.duplex ? "full" : "half",
|
|
+ link.tx_flow ? "txflow " : "",
|
|
+ link.rx_flow ? "rxflow " : "",
|
|
+ link.aneg ? "auto" : "");
|
|
+ else
|
|
+ len = snprintf(dev->buf, sizeof(dev->buf), "port:%d link:down",
|
|
+ val->port_vlan);
|
|
+
|
|
+ val->value.s = dev->buf;
|
|
+ val->len = len;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ /* don't complain if not supported by the switch driver */
|
|
+ if (!dev->ops->apply_config)
|
|
+ return 0;
|
|
+
|
|
+ return dev->ops->apply_config(dev);
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr,
|
|
+ struct switch_val *val)
|
|
+{
|
|
+ /* don't complain if not supported by the switch driver */
|
|
+ if (!dev->ops->reset_switch)
|
|
+ return 0;
|
|
+
|
|
+ return dev->ops->reset_switch(dev);
|
|
+}
|
|
+
|
|
+enum global_defaults {
|
|
+ GLOBAL_APPLY,
|
|
+ GLOBAL_RESET,
|
|
+};
|
|
+
|
|
+enum vlan_defaults {
|
|
+ VLAN_PORTS,
|
|
+};
|
|
+
|
|
+enum port_defaults {
|
|
+ PORT_PVID,
|
|
+ PORT_LINK,
|
|
+};
|
|
+
|
|
+static struct switch_attr default_global[] = {
|
|
+ [GLOBAL_APPLY] = {
|
|
+ .type = SWITCH_TYPE_NOVAL,
|
|
+ .name = "apply",
|
|
+ .description = "Activate changes in the hardware",
|
|
+ .set = swconfig_apply_config,
|
|
+ },
|
|
+ [GLOBAL_RESET] = {
|
|
+ .type = SWITCH_TYPE_NOVAL,
|
|
+ .name = "reset",
|
|
+ .description = "Reset the switch",
|
|
+ .set = swconfig_reset_switch,
|
|
+ }
|
|
+};
|
|
+
|
|
+static struct switch_attr default_port[] = {
|
|
+ [PORT_PVID] = {
|
|
+ .type = SWITCH_TYPE_INT,
|
|
+ .name = "pvid",
|
|
+ .description = "Primary VLAN ID",
|
|
+ .set = swconfig_set_pvid,
|
|
+ .get = swconfig_get_pvid,
|
|
+ },
|
|
+ [PORT_LINK] = {
|
|
+ .type = SWITCH_TYPE_STRING,
|
|
+ .name = "link",
|
|
+ .description = "Get port link information",
|
|
+ .set = NULL,
|
|
+ .get = swconfig_get_link,
|
|
+ }
|
|
+};
|
|
+
|
|
+static struct switch_attr default_vlan[] = {
|
|
+ [VLAN_PORTS] = {
|
|
+ .type = SWITCH_TYPE_PORTS,
|
|
+ .name = "ports",
|
|
+ .description = "VLAN port mapping",
|
|
+ .set = swconfig_set_vlan_ports,
|
|
+ .get = swconfig_get_vlan_ports,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct switch_attr *
|
|
+swconfig_find_attr_by_name(const struct switch_attrlist *alist,
|
|
+ const char *name)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < alist->n_attr; i++)
|
|
+ if (strcmp(name, alist->attr[i].name) == 0)
|
|
+ return &alist->attr[i];
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void swconfig_defaults_init(struct switch_dev *dev)
|
|
+{
|
|
+ const struct switch_dev_ops *ops = dev->ops;
|
|
+
|
|
+ dev->def_global = 0;
|
|
+ dev->def_vlan = 0;
|
|
+ dev->def_port = 0;
|
|
+
|
|
+ if (ops->get_vlan_ports || ops->set_vlan_ports)
|
|
+ set_bit(VLAN_PORTS, &dev->def_vlan);
|
|
+
|
|
+ if (ops->get_port_pvid || ops->set_port_pvid)
|
|
+ set_bit(PORT_PVID, &dev->def_port);
|
|
+
|
|
+ if (ops->get_port_link &&
|
|
+ !swconfig_find_attr_by_name(&ops->attr_port, "link"))
|
|
+ set_bit(PORT_LINK, &dev->def_port);
|
|
+
|
|
+ /* always present, can be no-op */
|
|
+ set_bit(GLOBAL_APPLY, &dev->def_global);
|
|
+ set_bit(GLOBAL_RESET, &dev->def_global);
|
|
+}
|
|
+
|
|
+
|
|
+static struct genl_family switch_fam = {
|
|
+ .id = GENL_ID_GENERATE,
|
|
+ .name = "switch",
|
|
+ .hdrsize = 0,
|
|
+ .version = 1,
|
|
+ .maxattr = SWITCH_ATTR_MAX,
|
|
+};
|
|
+
|
|
+static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = {
|
|
+ [SWITCH_ATTR_ID] = { .type = NLA_U32 },
|
|
+ [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 },
|
|
+ [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 },
|
|
+ [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 },
|
|
+ [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 },
|
|
+ [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING },
|
|
+ [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED },
|
|
+ [SWITCH_ATTR_TYPE] = { .type = NLA_U32 },
|
|
+};
|
|
+
|
|
+static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = {
|
|
+ [SWITCH_PORT_ID] = { .type = NLA_U32 },
|
|
+ [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG },
|
|
+};
|
|
+
|
|
+static inline void
|
|
+swconfig_lock(void)
|
|
+{
|
|
+ spin_lock(&swdevs_lock);
|
|
+}
|
|
+
|
|
+static inline void
|
|
+swconfig_unlock(void)
|
|
+{
|
|
+ spin_unlock(&swdevs_lock);
|
|
+}
|
|
+
|
|
+static struct switch_dev *
|
|
+swconfig_get_dev(struct genl_info *info)
|
|
+{
|
|
+ struct switch_dev *dev = NULL;
|
|
+ struct switch_dev *p;
|
|
+ int id;
|
|
+
|
|
+ if (!info->attrs[SWITCH_ATTR_ID])
|
|
+ goto done;
|
|
+
|
|
+ id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]);
|
|
+ swconfig_lock();
|
|
+ list_for_each_entry(p, &swdevs, dev_list) {
|
|
+ if (id != p->id)
|
|
+ continue;
|
|
+
|
|
+ dev = p;
|
|
+ break;
|
|
+ }
|
|
+ if (dev)
|
|
+ mutex_lock(&dev->sw_mutex);
|
|
+ else
|
|
+ pr_debug("device %d not found\n", id);
|
|
+ swconfig_unlock();
|
|
+done:
|
|
+ return dev;
|
|
+}
|
|
+
|
|
+static inline void
|
|
+swconfig_put_dev(struct switch_dev *dev)
|
|
+{
|
|
+ mutex_unlock(&dev->sw_mutex);
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_dump_attr(struct swconfig_callback *cb, void *arg)
|
|
+{
|
|
+ struct switch_attr *op = arg;
|
|
+ struct genl_info *info = cb->info;
|
|
+ struct sk_buff *msg = cb->msg;
|
|
+ int id = cb->args[0];
|
|
+ void *hdr;
|
|
+
|
|
+ hdr = genlmsg_put(msg, info->snd_pid, 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_pid, 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).pid,
|
|
+ cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
|
+ dev) < 0)
|
|
+ break;
|
|
+ }
|
|
+ swconfig_unlock();
|
|
+ cb->args[0] = idx;
|
|
+
|
|
+ return skb->len;
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_done(struct netlink_callback *cb)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct genl_ops swconfig_ops[] = {
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_LIST_GLOBAL,
|
|
+ .doit = swconfig_list_attrs,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_LIST_VLAN,
|
|
+ .doit = swconfig_list_attrs,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_LIST_PORT,
|
|
+ .doit = swconfig_list_attrs,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_GET_GLOBAL,
|
|
+ .doit = swconfig_get_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_GET_VLAN,
|
|
+ .doit = swconfig_get_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_GET_PORT,
|
|
+ .doit = swconfig_get_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_SET_GLOBAL,
|
|
+ .doit = swconfig_set_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_SET_VLAN,
|
|
+ .doit = swconfig_set_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_SET_PORT,
|
|
+ .doit = swconfig_set_attr,
|
|
+ .policy = switch_policy,
|
|
+ },
|
|
+ {
|
|
+ .cmd = SWITCH_CMD_GET_SWITCH,
|
|
+ .dumpit = swconfig_dump_switches,
|
|
+ .policy = switch_policy,
|
|
+ .done = swconfig_done,
|
|
+ }
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+void
|
|
+of_switch_load_portmap(struct switch_dev *dev)
|
|
+{
|
|
+ struct device_node *port;
|
|
+
|
|
+ if (!dev->of_node)
|
|
+ return;
|
|
+
|
|
+ for_each_child_of_node(dev->of_node, port) {
|
|
+ const __be32 *prop;
|
|
+ const char *segment;
|
|
+ int size, phys;
|
|
+
|
|
+ if (!of_device_is_compatible(port, "swconfig,port"))
|
|
+ continue;
|
|
+
|
|
+ if (of_property_read_string(port, "swconfig,segment", &segment))
|
|
+ continue;
|
|
+
|
|
+ prop = of_get_property(port, "swconfig,portmap", &size);
|
|
+ if (!prop)
|
|
+ continue;
|
|
+
|
|
+ if (size != (2 * sizeof(*prop))) {
|
|
+ pr_err("%s: failed to parse port mapping\n",
|
|
+ port->name);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ phys = be32_to_cpup(prop++);
|
|
+ if ((phys < 0) | (phys >= dev->ports)) {
|
|
+ pr_err("%s: physical port index out of range\n",
|
|
+ port->name);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL);
|
|
+ dev->portmap[phys].virt = be32_to_cpup(prop);
|
|
+ pr_debug("Found port: %s, physical: %d, virtual: %d\n",
|
|
+ segment, phys, dev->portmap[phys].virt);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+int
|
|
+register_switch(struct switch_dev *dev, struct net_device *netdev)
|
|
+{
|
|
+ struct switch_dev *sdev;
|
|
+ const int max_switches = 8 * sizeof(unsigned long);
|
|
+ unsigned long in_use = 0;
|
|
+ int err;
|
|
+ int i;
|
|
+
|
|
+ INIT_LIST_HEAD(&dev->dev_list);
|
|
+ if (netdev) {
|
|
+ dev->netdev = netdev;
|
|
+ if (!dev->alias)
|
|
+ dev->alias = netdev->name;
|
|
+ }
|
|
+ BUG_ON(!dev->alias);
|
|
+
|
|
+ if (dev->ports > 0) {
|
|
+ dev->portbuf = kzalloc(sizeof(struct switch_port) *
|
|
+ dev->ports, GFP_KERNEL);
|
|
+ if (!dev->portbuf)
|
|
+ return -ENOMEM;
|
|
+ dev->portmap = kzalloc(sizeof(struct switch_portmap) *
|
|
+ dev->ports, GFP_KERNEL);
|
|
+ if (!dev->portmap) {
|
|
+ kfree(dev->portbuf);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ }
|
|
+ swconfig_defaults_init(dev);
|
|
+ mutex_init(&dev->sw_mutex);
|
|
+ swconfig_lock();
|
|
+ dev->id = ++swdev_id;
|
|
+
|
|
+ list_for_each_entry(sdev, &swdevs, dev_list) {
|
|
+ if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i))
|
|
+ continue;
|
|
+ if (i < 0 || i > max_switches)
|
|
+ continue;
|
|
+
|
|
+ set_bit(i, &in_use);
|
|
+ }
|
|
+ i = find_first_zero_bit(&in_use, max_switches);
|
|
+
|
|
+ if (i == max_switches) {
|
|
+ swconfig_unlock();
|
|
+ return -ENFILE;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+ if (dev->ports)
|
|
+ of_switch_load_portmap(dev);
|
|
+#endif
|
|
+
|
|
+ /* fill device name */
|
|
+ snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i);
|
|
+
|
|
+ list_add_tail(&dev->dev_list, &swdevs);
|
|
+ swconfig_unlock();
|
|
+
|
|
+ err = swconfig_create_led_trigger(dev);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(register_switch);
|
|
+
|
|
+void
|
|
+unregister_switch(struct switch_dev *dev)
|
|
+{
|
|
+ swconfig_destroy_led_trigger(dev);
|
|
+ kfree(dev->portbuf);
|
|
+ mutex_lock(&dev->sw_mutex);
|
|
+ swconfig_lock();
|
|
+ list_del(&dev->dev_list);
|
|
+ swconfig_unlock();
|
|
+ mutex_unlock(&dev->sw_mutex);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(unregister_switch);
|
|
+
|
|
+
|
|
+static int __init
|
|
+swconfig_init(void)
|
|
+{
|
|
+ int err;
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
|
|
+ int i;
|
|
+#endif
|
|
+
|
|
+ INIT_LIST_HEAD(&swdevs);
|
|
+
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
|
|
+ err = genl_register_family(&switch_fam);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) {
|
|
+ err = genl_register_ops(&switch_fam, &swconfig_ops[i]);
|
|
+ if (err)
|
|
+ goto unregister;
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+unregister:
|
|
+ genl_unregister_family(&switch_fam);
|
|
+ return err;
|
|
+#else
|
|
+ err = genl_register_family_with_ops(&switch_fam, swconfig_ops);
|
|
+ if (err)
|
|
+ return err;
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void __exit
|
|
+swconfig_exit(void)
|
|
+{
|
|
+ genl_unregister_family(&switch_fam);
|
|
+}
|
|
+
|
|
+module_init(swconfig_init);
|
|
+module_exit(swconfig_exit);
|
|
+
|
|
diff --git a/drivers/net/phy/swconfig_leds.c b/drivers/net/phy/swconfig_leds.c
|
|
new file mode 100644
|
|
index 0000000..abd7bed
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/swconfig_leds.c
|
|
@@ -0,0 +1,354 @@
|
|
+/*
|
|
+ * swconfig_led.c: LED trigger support for the switch configuration API
|
|
+ *
|
|
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License
|
|
+ * as published by the Free Software Foundation; either version 2
|
|
+ * of the License, or (at your option) any later version.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifdef CONFIG_SWCONFIG_LEDS
|
|
+
|
|
+#include <linux/leds.h>
|
|
+#include <linux/ctype.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/workqueue.h>
|
|
+
|
|
+#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10)
|
|
+#define SWCONFIG_LED_NUM_PORTS 32
|
|
+
|
|
+struct switch_led_trigger {
|
|
+ struct led_trigger trig;
|
|
+ struct switch_dev *swdev;
|
|
+
|
|
+ struct delayed_work sw_led_work;
|
|
+ u32 port_mask;
|
|
+ u32 port_link;
|
|
+ unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS];
|
|
+};
|
|
+
|
|
+struct swconfig_trig_data {
|
|
+ struct led_classdev *led_cdev;
|
|
+ struct switch_dev *swdev;
|
|
+
|
|
+ rwlock_t lock;
|
|
+ u32 port_mask;
|
|
+
|
|
+ bool prev_link;
|
|
+ unsigned long prev_traffic;
|
|
+ enum led_brightness prev_brightness;
|
|
+};
|
|
+
|
|
+static void
|
|
+swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data,
|
|
+ enum led_brightness brightness)
|
|
+{
|
|
+ led_set_brightness(trig_data->led_cdev, brightness);
|
|
+ trig_data->prev_brightness = brightness;
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_trig_update_port_mask(struct led_trigger *trigger)
|
|
+{
|
|
+ struct list_head *entry;
|
|
+ struct switch_led_trigger *sw_trig;
|
|
+ u32 port_mask;
|
|
+
|
|
+ if (!trigger)
|
|
+ return;
|
|
+
|
|
+ sw_trig = (void *) trigger;
|
|
+
|
|
+ port_mask = 0;
|
|
+ read_lock(&trigger->leddev_list_lock);
|
|
+ list_for_each(entry, &trigger->led_cdevs) {
|
|
+ struct led_classdev *led_cdev;
|
|
+ struct swconfig_trig_data *trig_data;
|
|
+
|
|
+ led_cdev = list_entry(entry, struct led_classdev, trig_list);
|
|
+ trig_data = led_cdev->trigger_data;
|
|
+ if (trig_data) {
|
|
+ read_lock(&trig_data->lock);
|
|
+ port_mask |= trig_data->port_mask;
|
|
+ read_unlock(&trig_data->lock);
|
|
+ }
|
|
+ }
|
|
+ read_unlock(&trigger->leddev_list_lock);
|
|
+
|
|
+ sw_trig->port_mask = port_mask;
|
|
+
|
|
+ if (port_mask)
|
|
+ schedule_delayed_work(&sw_trig->sw_led_work,
|
|
+ SWCONFIG_LED_TIMER_INTERVAL);
|
|
+ else
|
|
+ cancel_delayed_work_sync(&sw_trig->sw_led_work);
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t size)
|
|
+{
|
|
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
+ struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
|
+ unsigned long port_mask;
|
|
+ ssize_t ret = -EINVAL;
|
|
+ char *after;
|
|
+ size_t count;
|
|
+
|
|
+ port_mask = simple_strtoul(buf, &after, 16);
|
|
+ count = after - buf;
|
|
+
|
|
+ if (*after && isspace(*after))
|
|
+ count++;
|
|
+
|
|
+ if (count == size) {
|
|
+ bool changed;
|
|
+
|
|
+ write_lock(&trig_data->lock);
|
|
+
|
|
+ changed = (trig_data->port_mask != port_mask);
|
|
+ if (changed) {
|
|
+ trig_data->port_mask = port_mask;
|
|
+ if (port_mask == 0)
|
|
+ swconfig_trig_set_brightness(trig_data, LED_OFF);
|
|
+ }
|
|
+
|
|
+ write_unlock(&trig_data->lock);
|
|
+
|
|
+ if (changed)
|
|
+ swconfig_trig_update_port_mask(led_cdev->trigger);
|
|
+
|
|
+ ret = count;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
+ struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
|
+
|
|
+ read_lock(&trig_data->lock);
|
|
+ sprintf(buf, "%#x\n", trig_data->port_mask);
|
|
+ read_unlock(&trig_data->lock);
|
|
+
|
|
+ return strlen(buf) + 1;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show,
|
|
+ swconfig_trig_port_mask_store);
|
|
+
|
|
+static void
|
|
+swconfig_trig_activate(struct led_classdev *led_cdev)
|
|
+{
|
|
+ struct switch_led_trigger *sw_trig;
|
|
+ struct swconfig_trig_data *trig_data;
|
|
+ int err;
|
|
+
|
|
+ if (led_cdev->trigger->activate != swconfig_trig_activate)
|
|
+ return;
|
|
+
|
|
+ trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL);
|
|
+ if (!trig_data)
|
|
+ return;
|
|
+
|
|
+ sw_trig = (void *) led_cdev->trigger;
|
|
+
|
|
+ rwlock_init(&trig_data->lock);
|
|
+ trig_data->led_cdev = led_cdev;
|
|
+ trig_data->swdev = sw_trig->swdev;
|
|
+ led_cdev->trigger_data = trig_data;
|
|
+
|
|
+ err = device_create_file(led_cdev->dev, &dev_attr_port_mask);
|
|
+ if (err)
|
|
+ goto err_free;
|
|
+
|
|
+ return;
|
|
+
|
|
+err_free:
|
|
+ led_cdev->trigger_data = NULL;
|
|
+ kfree(trig_data);
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_trig_deactivate(struct led_classdev *led_cdev)
|
|
+{
|
|
+ struct swconfig_trig_data *trig_data;
|
|
+
|
|
+ swconfig_trig_update_port_mask(led_cdev->trigger);
|
|
+
|
|
+ trig_data = (void *) led_cdev->trigger_data;
|
|
+ if (trig_data) {
|
|
+ device_remove_file(led_cdev->dev, &dev_attr_port_mask);
|
|
+ kfree(trig_data);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_trig_led_event(struct switch_led_trigger *sw_trig,
|
|
+ struct led_classdev *led_cdev)
|
|
+{
|
|
+ struct swconfig_trig_data *trig_data;
|
|
+ u32 port_mask;
|
|
+ bool link;
|
|
+
|
|
+ trig_data = led_cdev->trigger_data;
|
|
+ if (!trig_data)
|
|
+ return;
|
|
+
|
|
+ read_lock(&trig_data->lock);
|
|
+ port_mask = trig_data->port_mask;
|
|
+ read_unlock(&trig_data->lock);
|
|
+
|
|
+ link = !!(sw_trig->port_link & port_mask);
|
|
+ if (!link) {
|
|
+ if (link != trig_data->prev_link)
|
|
+ swconfig_trig_set_brightness(trig_data, LED_OFF);
|
|
+ } else {
|
|
+ unsigned long traffic;
|
|
+ int i;
|
|
+
|
|
+ traffic = 0;
|
|
+ for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
|
|
+ if (port_mask & (1 << i))
|
|
+ traffic += sw_trig->port_traffic[i];
|
|
+ }
|
|
+
|
|
+ if (trig_data->prev_brightness != LED_FULL)
|
|
+ swconfig_trig_set_brightness(trig_data, LED_FULL);
|
|
+ else if (traffic != trig_data->prev_traffic)
|
|
+ swconfig_trig_set_brightness(trig_data, LED_OFF);
|
|
+
|
|
+ trig_data->prev_traffic = traffic;
|
|
+ }
|
|
+
|
|
+ trig_data->prev_link = link;
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_trig_update_leds(struct switch_led_trigger *sw_trig)
|
|
+{
|
|
+ struct list_head *entry;
|
|
+ struct led_trigger *trigger;
|
|
+
|
|
+ trigger = &sw_trig->trig;
|
|
+ read_lock(&trigger->leddev_list_lock);
|
|
+ list_for_each(entry, &trigger->led_cdevs) {
|
|
+ struct led_classdev *led_cdev;
|
|
+
|
|
+ led_cdev = list_entry(entry, struct led_classdev, trig_list);
|
|
+ swconfig_trig_led_event(sw_trig, led_cdev);
|
|
+ }
|
|
+ read_unlock(&trigger->leddev_list_lock);
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_led_work_func(struct work_struct *work)
|
|
+{
|
|
+ struct switch_led_trigger *sw_trig;
|
|
+ struct switch_dev *swdev;
|
|
+ u32 port_mask;
|
|
+ u32 link;
|
|
+ int i;
|
|
+
|
|
+ sw_trig = container_of(work, struct switch_led_trigger,
|
|
+ sw_led_work.work);
|
|
+
|
|
+ port_mask = sw_trig->port_mask;
|
|
+ swdev = sw_trig->swdev;
|
|
+
|
|
+ link = 0;
|
|
+ for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
|
|
+ u32 port_bit;
|
|
+
|
|
+ port_bit = BIT(i);
|
|
+ if ((port_mask & port_bit) == 0)
|
|
+ continue;
|
|
+
|
|
+ if (swdev->ops->get_port_link) {
|
|
+ struct switch_port_link port_link;
|
|
+
|
|
+ memset(&port_link, '\0', sizeof(port_link));
|
|
+ swdev->ops->get_port_link(swdev, i, &port_link);
|
|
+
|
|
+ if (port_link.link)
|
|
+ link |= port_bit;
|
|
+ }
|
|
+
|
|
+ if (swdev->ops->get_port_stats) {
|
|
+ struct switch_port_stats port_stats;
|
|
+
|
|
+ memset(&port_stats, '\0', sizeof(port_stats));
|
|
+ swdev->ops->get_port_stats(swdev, i, &port_stats);
|
|
+ sw_trig->port_traffic[i] = port_stats.tx_bytes +
|
|
+ port_stats.rx_bytes;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ sw_trig->port_link = link;
|
|
+
|
|
+ swconfig_trig_update_leds(sw_trig);
|
|
+
|
|
+ schedule_delayed_work(&sw_trig->sw_led_work,
|
|
+ SWCONFIG_LED_TIMER_INTERVAL);
|
|
+}
|
|
+
|
|
+static int
|
|
+swconfig_create_led_trigger(struct switch_dev *swdev)
|
|
+{
|
|
+ struct switch_led_trigger *sw_trig;
|
|
+ int err;
|
|
+
|
|
+ if (!swdev->ops->get_port_link)
|
|
+ return 0;
|
|
+
|
|
+ sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL);
|
|
+ if (!sw_trig)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ sw_trig->swdev = swdev;
|
|
+ sw_trig->trig.name = swdev->devname;
|
|
+ sw_trig->trig.activate = swconfig_trig_activate;
|
|
+ sw_trig->trig.deactivate = swconfig_trig_deactivate;
|
|
+
|
|
+ INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func);
|
|
+
|
|
+ err = led_trigger_register(&sw_trig->trig);
|
|
+ if (err)
|
|
+ goto err_free;
|
|
+
|
|
+ swdev->led_trigger = sw_trig;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_free:
|
|
+ kfree(sw_trig);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void
|
|
+swconfig_destroy_led_trigger(struct switch_dev *swdev)
|
|
+{
|
|
+ struct switch_led_trigger *sw_trig;
|
|
+
|
|
+ sw_trig = swdev->led_trigger;
|
|
+ if (sw_trig) {
|
|
+ cancel_delayed_work_sync(&sw_trig->sw_led_work);
|
|
+ led_trigger_unregister(&sw_trig->trig);
|
|
+ kfree(sw_trig);
|
|
+ }
|
|
+}
|
|
+
|
|
+#else /* SWCONFIG_LEDS */
|
|
+static inline int
|
|
+swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; }
|
|
+
|
|
+static inline void
|
|
+swconfig_destroy_led_trigger(struct switch_dev *swdev) { }
|
|
+#endif /* CONFIG_SWCONFIG_LEDS */
|
|
diff --git a/include/Kbuild b/include/Kbuild
|
|
index 8d226bf..5b531a9 100644
|
|
--- a/include/Kbuild
|
|
+++ b/include/Kbuild
|
|
@@ -10,3 +10,4 @@ header-y += video/
|
|
header-y += drm/
|
|
header-y += xen/
|
|
header-y += scsi/
|
|
+header-y += uapi/
|
|
\ No newline at end of file
|
|
diff --git a/include/asm-generic/gpio.h b/include/asm-generic/gpio.h
|
|
index 5f52690..e141045 100644
|
|
--- a/include/asm-generic/gpio.h
|
|
+++ b/include/asm-generic/gpio.h
|
|
@@ -180,6 +180,7 @@ extern void gpio_free_array(const struct gpio *array, size_t num);
|
|
/* bindings for managed devices that want to request gpios */
|
|
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
|
|
void devm_gpio_free(struct device *dev, unsigned int gpio);
|
|
+int devm_gpio_request_one(struct device *dev, unsigned gpio, unsigned long flags, const char *label);
|
|
|
|
#ifdef CONFIG_GPIO_SYSFS
|
|
|
|
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
|
|
index 4bf4100..e4632d7 100644
|
|
--- a/include/linux/Kbuild
|
|
+++ b/include/linux/Kbuild
|
|
@@ -359,6 +359,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/linux/gpio.h b/include/linux/gpio.h
|
|
index 6155ecf..f8eb2f3 100644
|
|
--- a/include/linux/gpio.h
|
|
+++ b/include/linux/gpio.h
|
|
@@ -42,6 +42,9 @@ struct gpio {
|
|
#include <linux/errno.h>
|
|
#include <linux/bug.h>
|
|
|
|
+
|
|
+int devm_gpio_request_one(struct device *dev, unsigned gpio, unsigned long flags, const char *label);
|
|
+
|
|
struct device;
|
|
struct gpio_chip;
|
|
|
|
diff --git a/include/linux/platform_data/b53.h b/include/linux/platform_data/b53.h
|
|
new file mode 100644
|
|
index 0000000..7842741
|
|
--- /dev/null
|
|
+++ b/include/linux/platform_data/b53.h
|
|
@@ -0,0 +1,36 @@
|
|
+/*
|
|
+ * B53 platform data
|
|
+ *
|
|
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
|
|
+ *
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
+ *
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
+ */
|
|
+
|
|
+#ifndef __B53_H
|
|
+#define __B53_H
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+
|
|
+struct b53_platform_data {
|
|
+ u32 chip_id;
|
|
+ u16 enabled_ports;
|
|
+
|
|
+ /* allow to specify an ethX alias */
|
|
+ const char *alias;
|
|
+
|
|
+ /* only used by MMAP'd driver */
|
|
+ unsigned big_endian:1;
|
|
+ void __iomem *regs;
|
|
+};
|
|
+
|
|
+#endif
|
|
diff --git a/include/linux/switch.h b/include/linux/switch.h
|
|
index 3e4c748..b53431e 100644
|
|
--- a/include/linux/switch.h
|
|
+++ b/include/linux/switch.h
|
|
@@ -1,53 +1,167 @@
|
|
/*
|
|
- * Switch class driver
|
|
+ * switch.h: Switch configuration API
|
|
*
|
|
- * Copyright (C) 2008 Google, Inc.
|
|
- * Author: Mike Lockwood <lockwood@android.com>
|
|
+ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
*
|
|
- * This software is licensed under the terms of the GNU General Public
|
|
- * License version 2, as published by the Free Software Foundation, and
|
|
- * may be copied, distributed, and modified under those terms.
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License
|
|
+ * as published by the Free Software Foundation; either version 2
|
|
+ * of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
+ */
|
|
+#ifndef _LINUX_SWITCH_H
|
|
+#define _LINUX_SWITCH_H
|
|
+
|
|
+#include <net/genetlink.h>
|
|
+#include <uapi/linux/switch.h>
|
|
+
|
|
+struct switch_dev;
|
|
+struct switch_op;
|
|
+struct switch_val;
|
|
+struct switch_attr;
|
|
+struct switch_attrlist;
|
|
+struct switch_led_trigger;
|
|
+
|
|
+int register_switch(struct switch_dev *dev, struct net_device *netdev);
|
|
+void unregister_switch(struct switch_dev *dev);
|
|
+
|
|
+/**
|
|
+ * struct switch_attrlist - attribute list
|
|
*
|
|
-*/
|
|
+ * @n_attr: number of attributes
|
|
+ * @attr: pointer to the attributes array
|
|
+ */
|
|
+struct switch_attrlist {
|
|
+ int n_attr;
|
|
+ const struct switch_attr *attr;
|
|
+};
|
|
|
|
-#ifndef __LINUX_SWITCH_H__
|
|
-#define __LINUX_SWITCH_H__
|
|
+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_dev {
|
|
- const char *name;
|
|
- struct device *dev;
|
|
- int index;
|
|
- int state;
|
|
+struct switch_port_link {
|
|
+ bool link;
|
|
+ bool duplex;
|
|
+ bool aneg;
|
|
+ bool tx_flow;
|
|
+ bool rx_flow;
|
|
+ enum switch_port_speed speed;
|
|
+};
|
|
|
|
- ssize_t (*print_name)(struct switch_dev *sdev, char *buf);
|
|
- ssize_t (*print_state)(struct switch_dev *sdev, char *buf);
|
|
+struct switch_port_stats {
|
|
+ unsigned long tx_bytes;
|
|
+ unsigned long rx_bytes;
|
|
};
|
|
|
|
-struct gpio_switch_platform_data {
|
|
+/**
|
|
+ * 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;
|
|
- unsigned gpio;
|
|
+ /* 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];
|
|
|
|
- /* if NULL, switch_dev.name will be printed */
|
|
- const char *name_on;
|
|
- const char *name_off;
|
|
- /* if NULL, "0" or "1" will be printed */
|
|
- const char *state_on;
|
|
- const char *state_off;
|
|
+#ifdef CONFIG_SWCONFIG_LEDS
|
|
+ struct switch_led_trigger *led_trigger;
|
|
+#endif
|
|
};
|
|
|
|
-extern int switch_dev_register(struct switch_dev *sdev);
|
|
-extern void switch_dev_unregister(struct switch_dev *sdev);
|
|
+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;
|
|
|
|
-static inline int switch_get_state(struct switch_dev *sdev)
|
|
-{
|
|
- return sdev->state;
|
|
-}
|
|
+ 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);
|
|
|
|
-extern void switch_set_state(struct switch_dev *sdev, int state);
|
|
+ /* for driver internal use */
|
|
+ int id;
|
|
+ int ofs;
|
|
+ int max;
|
|
+};
|
|
|
|
-#endif /* __LINUX_SWITCH_H__ */
|
|
+#endif /* _LINUX_SWITCH_H */
|
|
diff --git a/include/uapi/Kbuild b/include/uapi/Kbuild
|
|
new file mode 100644
|
|
index 0000000..82c1251
|
|
--- /dev/null
|
|
+++ b/include/uapi/Kbuild
|
|
@@ -0,0 +1 @@
|
|
+header-y += linux/
|
|
\ No newline at end of file
|
|
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
|
|
new file mode 100644
|
|
index 0000000..2466878
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/Kbuild
|
|
@@ -0,0 +1 @@
|
|
+header-y += switch.h
|
|
\ No newline at end of file
|
|
diff --git a/include/uapi/linux/switch.h b/include/uapi/linux/switch.h
|
|
new file mode 100644
|
|
index 0000000..a59b239
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/switch.h
|
|
@@ -0,0 +1,103 @@
|
|
+/*
|
|
+ * switch.h: Switch configuration API
|
|
+ *
|
|
+ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License
|
|
+ * as published by the Free Software Foundation; either version 2
|
|
+ * of the License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ */
|
|
+
|
|
+#ifndef _UAPI_LINUX_SWITCH_H
|
|
+#define _UAPI_LINUX_SWITCH_H
|
|
+
|
|
+#include <linux/types.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/netlink.h>
|
|
+#include <linux/genetlink.h>
|
|
+#ifndef __KERNEL__
|
|
+#include <netlink/netlink.h>
|
|
+#include <netlink/genl/genl.h>
|
|
+#include <netlink/genl/ctrl.h>
|
|
+#endif
|
|
+
|
|
+/* main attributes */
|
|
+enum {
|
|
+ SWITCH_ATTR_UNSPEC,
|
|
+ /* global */
|
|
+ SWITCH_ATTR_TYPE,
|
|
+ /* device */
|
|
+ SWITCH_ATTR_ID,
|
|
+ SWITCH_ATTR_DEV_NAME,
|
|
+ SWITCH_ATTR_ALIAS,
|
|
+ SWITCH_ATTR_NAME,
|
|
+ SWITCH_ATTR_VLANS,
|
|
+ SWITCH_ATTR_PORTS,
|
|
+ SWITCH_ATTR_PORTMAP,
|
|
+ SWITCH_ATTR_CPU_PORT,
|
|
+ /* attributes */
|
|
+ SWITCH_ATTR_OP_ID,
|
|
+ SWITCH_ATTR_OP_TYPE,
|
|
+ SWITCH_ATTR_OP_NAME,
|
|
+ SWITCH_ATTR_OP_PORT,
|
|
+ SWITCH_ATTR_OP_VLAN,
|
|
+ SWITCH_ATTR_OP_VALUE_INT,
|
|
+ SWITCH_ATTR_OP_VALUE_STR,
|
|
+ SWITCH_ATTR_OP_VALUE_PORTS,
|
|
+ SWITCH_ATTR_OP_DESCRIPTION,
|
|
+ /* port lists */
|
|
+ SWITCH_ATTR_PORT,
|
|
+ SWITCH_ATTR_MAX
|
|
+};
|
|
+
|
|
+enum {
|
|
+ /* port map */
|
|
+ SWITCH_PORTMAP_PORTS,
|
|
+ SWITCH_PORTMAP_SEGMENT,
|
|
+ SWITCH_PORTMAP_VIRT,
|
|
+ SWITCH_PORTMAP_MAX
|
|
+};
|
|
+
|
|
+/* commands */
|
|
+enum {
|
|
+ SWITCH_CMD_UNSPEC,
|
|
+ SWITCH_CMD_GET_SWITCH,
|
|
+ SWITCH_CMD_NEW_ATTR,
|
|
+ SWITCH_CMD_LIST_GLOBAL,
|
|
+ SWITCH_CMD_GET_GLOBAL,
|
|
+ SWITCH_CMD_SET_GLOBAL,
|
|
+ SWITCH_CMD_LIST_PORT,
|
|
+ SWITCH_CMD_GET_PORT,
|
|
+ SWITCH_CMD_SET_PORT,
|
|
+ SWITCH_CMD_LIST_VLAN,
|
|
+ SWITCH_CMD_GET_VLAN,
|
|
+ SWITCH_CMD_SET_VLAN
|
|
+};
|
|
+
|
|
+/* data types */
|
|
+enum switch_val_type {
|
|
+ SWITCH_TYPE_UNSPEC,
|
|
+ SWITCH_TYPE_INT,
|
|
+ SWITCH_TYPE_STRING,
|
|
+ SWITCH_TYPE_PORTS,
|
|
+ SWITCH_TYPE_NOVAL,
|
|
+};
|
|
+
|
|
+/* port nested attributes */
|
|
+enum {
|
|
+ SWITCH_PORT_UNSPEC,
|
|
+ SWITCH_PORT_ID,
|
|
+ SWITCH_PORT_FLAG_TAGGED,
|
|
+ SWITCH_PORT_ATTR_MAX
|
|
+};
|
|
+
|
|
+#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000
|
|
+
|
|
+
|
|
+#endif /* _UAPI_LINUX_SWITCH_H */
|
|
--
|
|
1.9.1
|