Created
December 2, 2020 13:05
-
-
Save stintel/497f86066cf654aca444ccb77b4ba3cc to your computer and use it in GitHub Desktop.
target/linux/mvebu64/patches-4.4.52/0004-dsa-support-for-ar8236.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- a/drivers/net/dsa/Kconfig | |
+++ b/drivers/net/dsa/Kconfig | |
@@ -17,6 +17,13 @@ config NET_DSA_MV88E6XXX | |
This enables support for most of the Marvell 88E6xxx models of | |
Ethernet switch chips, except 88E6060. | |
+config NET_DSA_AR8236 | |
+ tristate "Atheros AR8236 Ethernet switch chip support" | |
+ depends on NET_DSA | |
+ select NET_DSA_TAG_QCA | |
+ ---help--- | |
+ This enables support for Atheros AR8236. | |
+ | |
config NET_DSA_BCM_SF2 | |
tristate "Broadcom Starfighter 2 Ethernet switch support" | |
depends on HAS_IOMEM && NET_DSA | |
--- a/drivers/net/dsa/Makefile | |
+++ b/drivers/net/dsa/Makefile | |
@@ -1,3 +1,4 @@ | |
obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o | |
obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o | |
+obj-$(CONFIG_NET_DSA_AR8236) += ar8236.o | |
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm_sf2.o | |
--- /dev/null | |
+++ b/drivers/net/dsa/ar8236.c | |
@@ -0,0 +1,750 @@ | |
+#include <linux/version.h> | |
+#include <linux/delay.h> | |
+#include <linux/etherdevice.h> | |
+#include <linux/ethtool.h> | |
+#include <linux/if_bridge.h> | |
+#include <linux/jiffies.h> | |
+#include <linux/list.h> | |
+#include <linux/mdio.h> | |
+#include <linux/module.h> | |
+#include <linux/of_mdio.h> | |
+#include <linux/netdevice.h> | |
+#include <linux/gpio/consumer.h> | |
+#include <linux/phy.h> | |
+#include <linux/proc_fs.h> | |
+#include <linux/seq_file.h> | |
+#include <net/dsa.h> | |
+#include <net/switchdev.h> | |
+#include "ar8236.h" | |
+ | |
+#define AR8236_MIB_DESC(_s , _o, _n) \ | |
+ { \ | |
+ .size = (_s), \ | |
+ .offset = (_o), \ | |
+ .name = (_n), \ | |
+ } | |
+ | |
+static struct ar8236_priv *ar8236_priv_date = NULL; | |
+static struct proc_dir_entry *ar8236_proc_dir = NULL; | |
+ | |
+const struct ar8236_mib_desc ar8236_mibs[39] = { | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), | |
+ AR8236_MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), | |
+ AR8236_MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), | |
+ AR8236_MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), | |
+ AR8236_MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), | |
+}; | |
+ | |
+static int ar8236_reg_read(struct ar8236_priv *ps, uint32_t reg_addr) | |
+{ | |
+ int reg_word_addr; | |
+ int phy_addr, reg_val; | |
+ unsigned short phy_val; | |
+ unsigned short tmp_val; | |
+ unsigned char phy_reg; | |
+ | |
+ /* change reg_addr to 16-bit word address, 32-bit aligned */ | |
+ reg_word_addr = (reg_addr & 0xfffffffc) >> 1; | |
+ | |
+ /* configure register high address */ | |
+ phy_addr = 0x18; | |
+ phy_reg = 0x0; | |
+ phy_val = (unsigned short) ((reg_word_addr >> 8) & 0x1ff); /* bit16-8 of reg address */ | |
+ mdiobus_write_nested(ps->bus, phy_addr, phy_reg, phy_val); | |
+ | |
+ /* | |
+ * For some registers such as MIBs, since it is read/clear, we should | |
+ * read the lower 16-bit register then the higher one | |
+ */ | |
+ | |
+ /* read register in lower address */ | |
+ phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ | |
+ phy_reg = (unsigned char) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ | |
+ phy_val = mdiobus_read_nested(ps->bus, phy_addr, phy_reg); | |
+ | |
+ /* read register in higher address */ | |
+ reg_word_addr++; | |
+ phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ | |
+ phy_reg = (unsigned char) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ | |
+ tmp_val = mdiobus_read_nested(ps->bus, phy_addr, phy_reg); | |
+ reg_val = (tmp_val << 16 | phy_val); | |
+ | |
+ return reg_val; | |
+} | |
+ | |
+static int ar8236_reg_write(struct ar8236_priv *ps, uint32_t reg_addr, uint32_t reg_val) | |
+{ | |
+ int reg_word_addr; | |
+ int phy_addr; | |
+ unsigned short phy_val; | |
+ unsigned char phy_reg; | |
+ | |
+ /* change reg_addr to 16-bit word address, 32-bit aligned */ | |
+ reg_word_addr = (reg_addr & 0xfffffffc) >> 1; | |
+ | |
+ /* configure register high address */ | |
+ phy_addr = 0x18; | |
+ phy_reg = 0x0; | |
+ phy_val = (unsigned short) ((reg_word_addr >> 8) & 0x1ff); /* bit16-8 of reg address */ | |
+ mdiobus_write_nested(ps->bus, phy_addr, phy_reg, phy_val); | |
+ | |
+ /* | |
+ * For some registers such as ARL and VLAN, since they include BUSY bit | |
+ * in lower address, we should write the higher 16-bit register then the | |
+ * lower one | |
+ */ | |
+ | |
+ /* read register in higher address */ | |
+ reg_word_addr++; | |
+ phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ | |
+ phy_reg = (unsigned char) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ | |
+ phy_val = (unsigned short) ((reg_val >> 16) & 0xffff); | |
+ mdiobus_write_nested(ps->bus, phy_addr, phy_reg, phy_val); | |
+ | |
+ /* write register in lower address */ | |
+ reg_word_addr--; | |
+ phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ | |
+ phy_reg = (unsigned char) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ | |
+ phy_val = (unsigned short) (reg_val & 0xffff); | |
+ mdiobus_write_nested(ps->bus, phy_addr, phy_reg, phy_val); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_phy_read(struct dsa_switch *ds, int port, int reg) | |
+{ | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ | |
+ return mdiobus_read_nested(priv->bus, priv->sw_addr + port, reg); | |
+} | |
+ | |
+static int ar8236_phy_write(struct dsa_switch *ds, int port, int reg, u16 val) | |
+{ | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ | |
+ return mdiobus_write_nested(priv->bus, priv->sw_addr + port, reg, val); | |
+} | |
+ | |
+static int ar8236_port_enable(struct dsa_switch *ds, int port, | |
+ struct phy_device *phy) | |
+{ | |
+ int val = 0; | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ | |
+ /* Port 0-5 is valid */ | |
+ if ((port < 0) || (port > 5)) | |
+ return -1; | |
+ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_CTRL(port)); | |
+ val |= (4 << AR8236_PORT_CTRL_STATE_S); | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_CTRL(port), val); | |
+ | |
+ /* not cpu port, also enable phy of this port */ | |
+ if (port != AR8236_CPU_PORT) | |
+ { | |
+ int phy_id = port -1; /* cpu-port0, phy0-port1, phy1-port2, etc ... */ | |
+ int phy_val; | |
+ | |
+ phy_val = ar8236_phy_read(ds, phy_id, MII_PHYSID1); | |
+ phy_val &= ~BMCR_PDOWN; /* 0 is power up */ | |
+ ar8236_phy_write(ds, phy_id, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+static void ar8236_port_disable(struct dsa_switch *ds, int port, | |
+ struct phy_device *phy) | |
+{ | |
+ int val = 0; | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ | |
+ /* Port 0-5 is valid */ | |
+ if ((port < 0) || (port > 5)) | |
+ return; | |
+ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_CTRL(port)); | |
+ val &= ~AR8236_PORT_CTRL_STATE; | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_CTRL(port), val); | |
+ | |
+ /* not cpu port, also disable phy of this port */ | |
+ if (port != AR8236_CPU_PORT) | |
+ { | |
+ int phy_id = port -1; /* cpu-port0, phy0-port1, phy1-port2, etc ... */ | |
+ int phy_val; | |
+ | |
+ phy_val = ar8236_phy_read(ds, phy_id, MII_PHYSID1); | |
+ phy_val |= BMCR_PDOWN; /* 1 is power down */ | |
+ ar8236_phy_write(ds, phy_id, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); | |
+ } | |
+ | |
+ return; | |
+} | |
+ | |
+static const char *ar8236_get_name(struct mii_bus *bus, int sw_addr) | |
+{ | |
+ int phy_id; | |
+ int phy_reg; | |
+ | |
+ phy_reg = mdiobus_read_nested(bus, sw_addr, MII_PHYSID1); | |
+ if (phy_reg < 0) | |
+ return NULL; | |
+ | |
+ phy_id = (phy_reg & 0xffff) << 16; | |
+ | |
+ phy_reg = mdiobus_read_nested(bus, sw_addr, MII_PHYSID2); | |
+ if (phy_reg < 0) | |
+ return NULL; | |
+ | |
+ phy_id |= (phy_reg & 0xffff); | |
+ | |
+ dev_info(&bus->dev, "Attach switch ID 0x%08x\n", phy_id); | |
+ if (phy_id == AR8236_PHY_ID) | |
+ return "Atheros AR8236"; | |
+ | |
+ /* not our switch */ | |
+ return NULL; | |
+} | |
+ | |
+static int ar8236_set_addr(struct dsa_switch *ds, u8 *addr) | |
+{ | |
+ return 0; | |
+} | |
+ | |
+/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ | |
+static int ar8236_phy_poll_reset(struct dsa_switch *ds, int phy) | |
+{ | |
+ /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ | |
+ unsigned int retries = 12; | |
+ int ret; | |
+ | |
+ do { | |
+ msleep(50); | |
+ ret = ar8236_phy_read(ds, phy, MII_BMCR); | |
+ if (ret < 0) | |
+ return ret; | |
+ } while (ret & BMCR_RESET && --retries); | |
+ if (ret & BMCR_RESET) | |
+ { | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ dev_err(&priv->bus->dev, "phy poll reset error for phy%d\n", phy); | |
+ return -ETIMEDOUT; | |
+ } | |
+ | |
+ msleep(1); | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_setup(struct dsa_switch *ds) | |
+{ | |
+ int val = 0; | |
+ int i = 0; | |
+ struct ar8236_priv *priv = ds_to_priv(ds); | |
+ | |
+ priv->ds = ds; | |
+ | |
+ dev_info(&priv->bus->dev, "Setup driver for AR8236\n"); | |
+ | |
+ /* Reset the switch before initialization */ | |
+ ar8236_reg_write(priv, AR8236_REG_CTRL, AR8236_CTRL_RESET); | |
+ do { | |
+ udelay(10); | |
+ val = ar8236_reg_read(priv, AR8236_REG_CTRL); | |
+ } while (val & AR8236_CTRL_RESET); | |
+ | |
+ /* phy initialize */ | |
+ for (i = 0; i < 5; i++) { | |
+ ar8236_phy_write(ds, i, MII_ADVERTISE, | |
+ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); | |
+ | |
+ ar8236_phy_write(ds, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); | |
+ ar8236_phy_poll_reset(ds, i); | |
+ } | |
+ | |
+ /* config MII PHY mode */ | |
+ ar8236_reg_write(priv, 0x2c, 0x7e3f003f); | |
+ ar8236_reg_write(priv, 0x4, 0x500); | |
+ | |
+ /* Enable MIB counters */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_MIB_FUNC); | |
+ val |= AR8236_MIB_EN; | |
+ ar8236_reg_write(priv, AR8236_REG_MIB_FUNC, val); | |
+ | |
+ /* Enable CPU Port */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_CPU_PORT); | |
+ val |= AR8236_CPU_PORT_EN; | |
+ ar8236_reg_write(priv, AR8236_REG_CPU_PORT, val); | |
+ | |
+ /* Forward all unknown frames to CPU port for Linux processing */ | |
+ ar8236_reg_write(priv, AR8236_REG_FLOOD_MASK, | |
+ BIT(0) << AR8236_REG_FLOOD_MASK_IGMP_DP_S | | |
+ BIT(0) << AR8236_REG_FLOOD_MASK_BC_DP_S | | |
+ BIT(0) << AR8236_REG_FLOOD_MASK_MC_DP_S | | |
+ BIT(0) << AR8236_REG_FLOOD_MASK_UC_DP_S); | |
+ | |
+ for (i = 0; i < 6; i++) { | |
+ /* Enable port learning and tx */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_CTRL(i)); | |
+ val |= AR8236_PORT_CTRL_LEARN | (4 << AR8236_PORT_CTRL_STATE_S); | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_CTRL(i), val); | |
+ | |
+ if (i == AR8236_CPU_PORT) | |
+ { | |
+ /* Enable QCA header mode on the cpu port */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_CTRL(i)); | |
+ val |= AR8236_PORT_CTRL_HEADER; | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_CTRL(i), val); | |
+ | |
+ /* CPU port gets connected to all user ports of the switch */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_VLAN2(i)); | |
+ val |= (ds->enabled_port_mask << AR8236_PORT_VLAN2_MEMBER_S); | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_VLAN2(i), val); | |
+ | |
+ /* CPU port is forced link-up, full-duplex and 100MB speed */ | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_STATUS(i), | |
+ AR8236_PORT_STATUS_LINK_UP | | |
+ AR8236_PORT_SPEED_100M | | |
+ AR8236_PORT_STATUS_TXMAC | | |
+ AR8236_PORT_STATUS_RXMAC | | |
+ AR8236_PORT_STATUS_DUPLEX); | |
+ } | |
+ else | |
+ { | |
+ /* Invividual user ports get connected to CPU port only */ | |
+ val = ar8236_reg_read(priv, AR8236_REG_PORT_VLAN2(i)); | |
+ val |= (BIT(AR8236_CPU_PORT) << AR8236_PORT_VLAN2_MEMBER_S); | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_VLAN2(i), val); | |
+ | |
+ ar8236_reg_write(priv, AR8236_REG_PORT_STATUS(i), | |
+ AR8236_PORT_STATUS_LINK_AUTO | AR8236_PORT_STATUS_LINK_FLOW_EN | AR8236_PORT_STATUS_TX_HALF_FLOW); | |
+ } | |
+ } | |
+ | |
+ ar8236_priv_date = priv; | |
+ | |
+ return 0; | |
+} | |
+ | |
+static const char *ar8236_drv_probe(struct device *dsa_dev, | |
+ struct device *host_dev, int sw_addr, | |
+ void **_priv) | |
+{ | |
+ struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev); | |
+ struct ar8236_priv *priv; | |
+ const char *name; | |
+ | |
+ name = ar8236_get_name(bus, sw_addr); | |
+ if (name) { | |
+ dev_info(&bus->dev, "%s sw_addr is %d\n", name, sw_addr); | |
+ priv = devm_kzalloc(dsa_dev, sizeof(*priv), GFP_KERNEL); | |
+ if (!priv) | |
+ return NULL; | |
+ *_priv = priv; | |
+ priv->bus = bus; | |
+ priv->sw_addr = sw_addr; | |
+ | |
+ priv->mib_stats = kzalloc(AR8236_PORTS * ARRAY_SIZE(ar8236_mibs) * sizeof(*priv->mib_stats), GFP_KERNEL); | |
+ if (!priv->mib_stats) | |
+ return NULL; | |
+ } | |
+ | |
+ return name; | |
+} | |
+ | |
+static struct dsa_switch_driver ar8236_switch_ops = { | |
+ .tag_protocol = DSA_TAG_PROTO_QCA, | |
+ .probe = ar8236_drv_probe, | |
+ .setup = ar8236_setup, | |
+ .set_addr = ar8236_set_addr, | |
+ .phy_read = ar8236_phy_read, | |
+ .phy_write = ar8236_phy_write, | |
+ .port_enable = ar8236_port_enable, | |
+ .port_disable = ar8236_port_disable, | |
+}; | |
+ | |
+static int ar8236_proc_stats_show(struct seq_file *m, void *v, int clear) | |
+{ | |
+ unsigned int base; | |
+ static struct ar8236_priv *priv; | |
+ u64 *mib_stats; | |
+ int port, index; | |
+ | |
+ priv = ar8236_priv_date; | |
+ | |
+ for (port = 0; port < AR8236_PORTS; port ++) { | |
+ base = 0x20000 + 0x100 * port; /* mib base * mib length per port */ | |
+ mib_stats = &priv->mib_stats[port * ARRAY_SIZE(ar8236_mibs)]; | |
+ | |
+ for (index = 0; index < ARRAY_SIZE(ar8236_mibs); index++) { | |
+ const struct ar8236_mib_desc *mib; | |
+ u64 t; | |
+ | |
+ mib = &ar8236_mibs[index]; | |
+ t = (u32)ar8236_reg_read(priv, base + mib->offset); | |
+ if (mib->size == 2) { | |
+ u64 hi; | |
+ | |
+ hi = (u32)ar8236_reg_read(priv, base + mib->offset + 4); | |
+ t |= hi << 32; | |
+ } | |
+ | |
+ if (clear) | |
+ mib_stats[index] = 0; | |
+ else | |
+ mib_stats[index] += t; | |
+ } | |
+ } | |
+ | |
+ /* show it */ | |
+ /* header */ | |
+ seq_printf(m, "%16s ", "port"); | |
+ for (port = 0; port < AR8236_PORTS; port ++) { | |
+ seq_printf(m, "%20llu", (u64)port); | |
+ } | |
+ seq_printf(m, "\n\n"); | |
+ | |
+ /* context */ | |
+ for (index = 0; index < ARRAY_SIZE(ar8236_mibs); index++) { | |
+ const struct ar8236_mib_desc *mib; | |
+ | |
+ mib = &ar8236_mibs[index]; | |
+ seq_printf(m, "%16s ", mib->name); | |
+ for (port = 0; port < AR8236_PORTS; port ++) { | |
+ mib_stats = &priv->mib_stats[port * ARRAY_SIZE(ar8236_mibs)]; | |
+ seq_printf(m, "%20llu", mib_stats[index]); | |
+ } | |
+ seq_printf(m, "\n"); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_proc_stats_show_clear(struct seq_file *m, void *v) | |
+{ | |
+ return ar8236_proc_stats_show(m, v, 1); | |
+} | |
+ | |
+static int ar8236_proc_stats_show_noclear(struct seq_file *m, void *v) | |
+{ | |
+ return ar8236_proc_stats_show(m, v, 0); | |
+} | |
+static int ar8236_proc_stats_clear_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, ar8236_proc_stats_show_clear, NULL); | |
+} | |
+ | |
+static int ar8236_proc_stats_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, ar8236_proc_stats_show_noclear, NULL); | |
+} | |
+ | |
+static const struct file_operations ar8236_proc_stats_clear = { | |
+ .open = ar8236_proc_stats_clear_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+ .owner = THIS_MODULE, | |
+}; | |
+ | |
+static const struct file_operations ar8236_proc_stats = { | |
+ .open = ar8236_proc_stats_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+ .owner = THIS_MODULE, | |
+}; | |
+ | |
+static int ar8236_proc_fdb_show(struct seq_file *m, void *v) | |
+{ | |
+ static struct ar8236_priv *priv; | |
+ struct ar8236_arl_entry *a; | |
+ u32 val0, val1, val2; | |
+ int timeout = 20; | |
+ int i, j; | |
+ | |
+ priv = ar8236_priv_date; | |
+ | |
+ ar8236_reg_write(priv, AR8236_REG_ATU_FUNC1, 0x0); | |
+ ar8236_reg_write(priv, AR8236_REG_ATU_FUNC2, 0x0); | |
+ ar8236_reg_write(priv, AR8236_REG_ATU_FUNC0, AR8236_ATU_BUSY|AR8236_ATU_OP_GET_NEXT); | |
+ | |
+ for(i = 0; i < AR8236_ARL_ENTRY; ++i) { | |
+ timeout = 20; | |
+ while ((ar8236_reg_read(priv, AR8236_REG_ATU_FUNC0) & AR8236_ATU_BUSY) && --timeout) | |
+ udelay(10); | |
+ | |
+ if (timeout <= 0) | |
+ { | |
+ dev_info(&priv->bus->dev, "atu get entry timeout\n"); | |
+ return -1; | |
+ } | |
+ | |
+ a = &priv->arl_table[i]; | |
+ | |
+ val0 = ar8236_reg_read(priv, AR8236_REG_ATU_FUNC0); | |
+ val1 = ar8236_reg_read(priv, AR8236_REG_ATU_FUNC1); | |
+ val2 = ar8236_reg_read(priv, AR8236_REG_ATU_FUNC2); | |
+ | |
+ if ((val1 == 0) && (val2 == 0)) | |
+ { | |
+ /* mean search finish */ | |
+ break; | |
+ } | |
+ | |
+ a->port = (val2 & AR8236_ATU_PORTS) >> AR8236_ATU_PORTS_S; | |
+ a->status = (val2 & AR8236_ATU_STATUS) >> AR8236_ATU_STATUS_S; | |
+ a->mac[0] = (val0 & AR8236_ATU_ADDR5) >> AR8236_ATU_ADDR5_S; | |
+ a->mac[1] = (val0 & AR8236_ATU_ADDR4) >> AR8236_ATU_ADDR4_S; | |
+ a->mac[2] = (val1 & AR8236_ATU_ADDR3) >> AR8236_ATU_ADDR3_S; | |
+ a->mac[3] = (val1 & AR8236_ATU_ADDR2) >> AR8236_ATU_ADDR2_S; | |
+ a->mac[4] = (val1 & AR8236_ATU_ADDR1) >> AR8236_ATU_ADDR1_S; | |
+ a->mac[5] = (val1 & AR8236_ATU_ADDR0) >> AR8236_ATU_ADDR0_S; | |
+ | |
+ /* avoid duplicates | |
+ * ARL table can include multiple valid entries | |
+ * per MAC, just with differing status codes | |
+ | |
+ for (j = 0; j < i; ++j) { | |
+ a1 = &priv->arl_table[j]; | |
+ if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) | |
+ goto duplicate; | |
+ }*/ | |
+ | |
+ /* get next one */ | |
+ val0 = ar8236_reg_read(priv, AR8236_REG_ATU_FUNC0); | |
+ val0 |= AR8236_ATU_BUSY|AR8236_ATU_OP_GET_NEXT; | |
+ ar8236_reg_write(priv, AR8236_REG_ATU_FUNC0, val0); | |
+ } | |
+ | |
+ seq_printf(m, "%6s %17s %12s %7s\n", "index", "mac_address", "dest_ports", "status"); | |
+ for (j = 0; j < i; ++j) { | |
+ a = &priv->arl_table[j]; | |
+ | |
+ seq_printf(m, "%6d %02x:%02x:%02x:%02x:%02x:%02x %12d %7d\n", j, | |
+ a->mac[5], a->mac[4], a->mac[3], a->mac[2], a->mac[1], a->mac[0], | |
+ a->port, a->status); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_proc_fdb_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, ar8236_proc_fdb_show, NULL); | |
+} | |
+ | |
+static const struct file_operations ar8236_proc_fdb = { | |
+ .open = ar8236_proc_fdb_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+ .owner = THIS_MODULE, | |
+}; | |
+ | |
+static int ar8236_proc_reg_read_show(struct seq_file *m, void *v) | |
+{ | |
+ seq_printf(m, "use 'echo reg_addr > reg_read' to read register\n"); | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_proc_reg_read_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, ar8236_proc_reg_read_show, NULL); | |
+} | |
+static ssize_t ar8236_proc_reg_read_write(struct file *f, const char __user *buf, size_t count, loff_t *ppos) | |
+{ | |
+ uint32_t reg_addr; | |
+ char addr_buf[64] = {0}; | |
+ static struct ar8236_priv *priv; | |
+ | |
+ priv = ar8236_priv_date; | |
+ | |
+ if (copy_from_user(addr_buf, buf, (count > 64)?64:count)) | |
+ return -EFAULT; | |
+ | |
+ reg_addr = simple_strtoul(addr_buf, NULL, 16); | |
+ dev_info(&priv->bus->dev, "register 0x%X value = 0x%08X\n", reg_addr, ar8236_reg_read(priv, reg_addr)); | |
+ | |
+ return count; | |
+} | |
+ | |
+static const struct file_operations ar8236_proc_reg_read = { | |
+ .open = ar8236_proc_reg_read_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+ .write = ar8236_proc_reg_read_write, | |
+ .owner = THIS_MODULE, | |
+}; | |
+ | |
+static int ar8236_proc_reg_write_show(struct seq_file *m, void *v) | |
+{ | |
+ seq_printf(m, "use 'echo reg_addr reg_value > reg_write' to write register\n"); | |
+ return 0; | |
+} | |
+ | |
+static int ar8236_proc_reg_write_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, ar8236_proc_reg_write_show, NULL); | |
+} | |
+ | |
+static ssize_t ar8236_proc_reg_write_write(struct file *f, const char __user *buf, size_t count, loff_t *ppos) | |
+{ | |
+ uint32_t reg_addr, reg_val; | |
+ char addr_buf[64] = {0}; | |
+ char *p = NULL; | |
+ static struct ar8236_priv *priv; | |
+ | |
+ priv = ar8236_priv_date; | |
+ | |
+ if (copy_from_user(addr_buf, buf, (count > 64)?64:count)) | |
+ return -EFAULT; | |
+ | |
+ p = addr_buf; | |
+ while (*p != '\0') | |
+ { | |
+ if (*p == ' ') | |
+ { | |
+ *p = '\0'; | |
+ p++; | |
+ break; | |
+ } | |
+ | |
+ p++; | |
+ } | |
+ | |
+ if (*p == '\0') | |
+ return -EFAULT; | |
+ | |
+ reg_addr = simple_strtoul(addr_buf, NULL, 16); | |
+ reg_val = simple_strtoul(p, NULL, 16); | |
+ | |
+ ar8236_reg_write(priv, reg_addr, reg_val); | |
+ | |
+ dev_info(&priv->bus->dev, "register 0x%X value = 0x%08X\n", reg_addr, ar8236_reg_read(priv, reg_addr)); | |
+ | |
+ return count; | |
+} | |
+ | |
+static const struct file_operations ar8236_proc_reg_write = { | |
+ .open = ar8236_proc_reg_write_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+ .write = ar8236_proc_reg_write_write, | |
+ .owner = THIS_MODULE, | |
+}; | |
+ | |
+#ifdef CONFIG_PROC_FS | |
+static int ar8236_proc_init(void) | |
+{ | |
+ struct proc_dir_entry *entry; | |
+ | |
+ ar8236_proc_dir = proc_mkdir("ar8236", NULL); | |
+ if (!ar8236_proc_dir) | |
+ goto err_dir; | |
+ | |
+ entry = proc_create("stats", 0, ar8236_proc_dir, &ar8236_proc_stats); | |
+ if (!entry) | |
+ goto err_stats; | |
+ | |
+ entry = proc_create("stats_clear", 0, ar8236_proc_dir, &ar8236_proc_stats_clear); | |
+ if (!entry) | |
+ goto err_stats_clear; | |
+ | |
+ entry = proc_create("fdb", 0, ar8236_proc_dir, &ar8236_proc_fdb); | |
+ if (!entry) | |
+ goto err_fdb; | |
+ | |
+ entry = proc_create("reg_read", 0644, ar8236_proc_dir, &ar8236_proc_reg_read); | |
+ if (!entry) | |
+ goto err_reg_read; | |
+ | |
+ entry = proc_create("reg_write", 0644, ar8236_proc_dir, &ar8236_proc_reg_write); | |
+ if (!entry) | |
+ goto err_reg_write; | |
+ | |
+ return 0; | |
+ | |
+err_reg_write: | |
+ remove_proc_entry("reg_read", ar8236_proc_dir); | |
+err_reg_read: | |
+ remove_proc_entry("fdb", ar8236_proc_dir); | |
+err_fdb: | |
+ remove_proc_entry("stats_clear", ar8236_proc_dir); | |
+err_stats_clear: | |
+ remove_proc_entry("stats", ar8236_proc_dir); | |
+err_stats: | |
+ remove_proc_entry("ar8236", NULL); | |
+err_dir: | |
+ return -ENOMEM; | |
+} | |
+static void ar8236_proc_cleanup(void) | |
+{ | |
+ remove_proc_entry("reg_write", ar8236_proc_dir); | |
+ remove_proc_entry("reg_read", ar8236_proc_dir); | |
+ remove_proc_entry("stats_rd_noclear", ar8236_proc_dir); | |
+ remove_proc_entry("stats_rd_clear", ar8236_proc_dir); | |
+ remove_proc_entry("fdb", ar8236_proc_dir); | |
+ remove_proc_entry("ar8236", NULL); | |
+} | |
+#endif | |
+ | |
+static int __init ar8236_init(void) | |
+{ | |
+ register_switch_driver(&ar8236_switch_ops); | |
+ | |
+#ifdef CONFIG_PROC_FS | |
+ ar8236_proc_init(); | |
+#endif | |
+ return 0; | |
+} | |
+module_init(ar8236_init); | |
+ | |
+static void __exit ar8236_cleanup(void) | |
+{ | |
+ unregister_switch_driver(&ar8236_switch_ops); | |
+ | |
+#ifdef CONFIG_PROC_FS | |
+ ar8236_proc_cleanup(); | |
+#endif | |
+} | |
+module_exit(ar8236_cleanup); | |
+ | |
+MODULE_AUTHOR("Weigang <[email protected]>"); | |
+MODULE_DESCRIPTION("Driver for Atheros AR8236 ethernet switch chips"); | |
+MODULE_LICENSE("GPL"); | |
+ | |
--- /dev/null | |
+++ b/drivers/net/dsa/ar8236.h | |
@@ -0,0 +1,210 @@ | |
+#ifndef __AR8236_H | |
+#define __AR8236_H | |
+ | |
+#include <linux/if_vlan.h> | |
+#include <linux/gpio/consumer.h> | |
+ | |
+ | |
+#define AR8236_PORTS 6 | |
+#define AR8236_ARL_ENTRY 1024 | |
+#define AR8236_PHY_ID 0x004dd043 | |
+#define AR8236_CPU_PORT 0 | |
+ | |
+#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) | |
+ | |
+#define AR8236_PORT_OFFSET(_i) (0x0100 * (_i + 1)) | |
+ | |
+#define AR8236_REG_CTRL 0x0000 | |
+#define AR8236_CTRL_REVISION BITS(0, 8) | |
+#define AR8236_CTRL_REVISION_S 0 | |
+#define AR8236_CTRL_VERSION BITS(8, 8) | |
+#define AR8236_CTRL_VERSION_S 8 | |
+#define AR8236_CTRL_RESET BIT(31) | |
+ | |
+#define AR8236_REG_PORT_STATUS(_i) (AR8236_PORT_OFFSET(_i) + 0x0000) | |
+#define AR8236_PORT_STATUS_SPEED BITS(0,2) | |
+#define AR8236_PORT_STATUS_SPEED_S 0 | |
+#define AR8236_PORT_STATUS_TXMAC BIT(2) | |
+#define AR8236_PORT_STATUS_RXMAC BIT(3) | |
+#define AR8236_PORT_STATUS_TXFLOW BIT(4) | |
+#define AR8236_PORT_STATUS_RXFLOW BIT(5) | |
+#define AR8236_PORT_STATUS_DUPLEX BIT(6) | |
+#define AR8236_PORT_STATUS_TX_HALF_FLOW BIT(7) | |
+#define AR8236_PORT_STATUS_LINK_UP BIT(8) | |
+#define AR8236_PORT_STATUS_LINK_AUTO BIT(9) | |
+#define AR8236_PORT_STATUS_LINK_PAUSE BIT(10) | |
+#define AR8236_PORT_STATUS_LINK_FLOW_EN BIT(12) | |
+ | |
+#define AR8236_REG_PORT_CTRL(_i) (AR8236_PORT_OFFSET(_i) + 0x0004) | |
+/* port forwarding state */ | |
+#define AR8236_PORT_CTRL_STATE BITS(0, 3) | |
+#define AR8236_PORT_CTRL_STATE_S 0 | |
+#define AR8236_PORT_CTRL_LEARN_LOCK BIT(7) | |
+/* egress 802.1q mode */ | |
+#define AR8236_PORT_CTRL_VLAN_MODE BITS(8, 2) | |
+#define AR8236_PORT_CTRL_VLAN_MODE_S 8 | |
+#define AR8236_PORT_CTRL_IGMP_SNOOP BIT(10) | |
+#define AR8236_PORT_CTRL_HEADER BIT(11) | |
+#define AR8236_PORT_CTRL_MAC_LOOP BIT(12) | |
+#define AR8236_PORT_CTRL_SINGLE_VLAN BIT(13) | |
+#define AR8236_PORT_CTRL_LEARN BIT(14) | |
+#define AR8236_PORT_CTRL_MIRROR_TX BIT(16) | |
+#define AR8236_PORT_CTRL_MIRROR_RX BIT(17) | |
+ | |
+#define AR8236_REG_CPU_PORT 0x0078 | |
+#define AR8236_CPU_PORT_EN BIT(8) | |
+ | |
+#define AR8236_REG_ATU_FUNC0 0x0050 | |
+#define AR8236_ATU_OP BITS(0, 3) | |
+#define AR8236_ATU_OP_NOOP 0x0 | |
+#define AR8236_ATU_OP_FLUSH 0x1 | |
+#define AR8236_ATU_OP_LOAD 0x2 | |
+#define AR8236_ATU_OP_PURGE 0x3 | |
+#define AR8236_ATU_OP_FLUSH_UNLOCKED 0x4 | |
+#define AR8236_ATU_OP_FLUSH_PORT 0x5 | |
+#define AR8236_ATU_OP_GET_NEXT 0x6 | |
+#define AR8236_ATU_BUSY BIT(3) | |
+#define AR8236_ATU_PORT_NUM BITS(8, 4) | |
+#define AR8236_ATU_PORT_NUM_S 8 | |
+#define AR8236_ATU_FULL_VIO BIT(12) | |
+#define AR8236_ATU_ADDR5 BITS(16, 8) | |
+#define AR8236_ATU_ADDR5_S 16 | |
+#define AR8236_ATU_ADDR4 BITS(24, 8) | |
+#define AR8236_ATU_ADDR4_S 24 | |
+ | |
+#define AR8236_REG_ATU_FUNC1 0x0054 | |
+#define AR8236_ATU_ADDR3 BITS(0, 8) | |
+#define AR8236_ATU_ADDR3_S 0 | |
+#define AR8236_ATU_ADDR2 BITS(8, 8) | |
+#define AR8236_ATU_ADDR2_S 8 | |
+#define AR8236_ATU_ADDR1 BITS(16, 8) | |
+#define AR8236_ATU_ADDR1_S 16 | |
+#define AR8236_ATU_ADDR0 BITS(24, 8) | |
+#define AR8236_ATU_ADDR0_S 24 | |
+ | |
+#define AR8236_REG_ATU_FUNC2 0x0058 | |
+#define AR8236_ATU_PORTS BITS(0, 6) | |
+#define AR8236_ATU_PORTS_S BIT(0) | |
+#define AR8236_ATU_PORT0 BIT(0) | |
+#define AR8236_ATU_PORT1 BIT(1) | |
+#define AR8236_ATU_PORT2 BIT(2) | |
+#define AR8236_ATU_PORT3 BIT(3) | |
+#define AR8236_ATU_PORT4 BIT(4) | |
+#define AR8236_ATU_PORT5 BIT(5) | |
+#define AR8236_ATU_STATUS BITS(16, 4) | |
+#define AR8236_ATU_STATUS_S 16 | |
+ | |
+#define AR8236_REG_ATU_CTRL 0x005C | |
+#define AR8236_ATU_CTRL_AGE_EN BIT(17) | |
+#define AR8236_ATU_CTRL_AGE_TIME BITS(0, 16) | |
+#define AR8236_ATU_CTRL_AGE_TIME_S 0 | |
+#define AR8236_ATU_CTRL_RES BIT(20) | |
+ | |
+#define AR8236_REG_MIB_FUNC 0x0080 | |
+#define AR8236_MIB_TIMER BITS(0, 16) | |
+#define AR8236_MIB_AT_HALF_EN BIT(16) | |
+#define AR8236_MIB_BUSY BIT(17) | |
+#define AR8236_MIB_FUNC BITS(24, 3) | |
+#define AR8236_MIB_FUNC_S 24 | |
+#define AR8236_MIB_FUNC_NO_OP 0x0 | |
+#define AR8236_MIB_FUNC_FLUSH 0x1 | |
+#define AR8236_MIB_FUNC_CAPTURE 0x3 | |
+#define AR8236_MIB_EN BIT(30) | |
+ | |
+#define AR8236_REG_PORT_VLAN(_i) (AR8236_PORT_OFFSET((_i)) + 0x0008) | |
+#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) | |
+#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 | |
+#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) | |
+#define AR8236_PORT_VLAN_PRIORITY_S 28 | |
+ | |
+#define AR8236_REG_PORT_VLAN2(_i) (AR8236_PORT_OFFSET((_i)) + 0x000c) | |
+#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) | |
+#define AR8236_PORT_VLAN2_MEMBER_S 16 | |
+#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) | |
+#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) | |
+#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 | |
+ | |
+#define AR8236_REG_FLOOD_MASK 0x002c | |
+#define AR8236_REG_FLOOD_MASK_IGMP_DP_S 8 | |
+#define AR8236_REG_FLOOD_MASK_BC_DP_S 25 | |
+#define AR8236_REG_FLOOD_MASK_MC_DP_S 16 | |
+#define AR8236_REG_FLOOD_MASK_UC_DP_S 0 | |
+ | |
+#define AR8236_STATS_RXBROAD 0x00 | |
+#define AR8236_STATS_RXPAUSE 0x04 | |
+#define AR8236_STATS_RXMULTI 0x08 | |
+#define AR8236_STATS_RXFCSERR 0x0c | |
+#define AR8236_STATS_RXALIGNERR 0x10 | |
+#define AR8236_STATS_RXRUNT 0x14 | |
+#define AR8236_STATS_RXFRAGMENT 0x18 | |
+#define AR8236_STATS_RX64BYTE 0x1c | |
+#define AR8236_STATS_RX128BYTE 0x20 | |
+#define AR8236_STATS_RX256BYTE 0x24 | |
+#define AR8236_STATS_RX512BYTE 0x28 | |
+#define AR8236_STATS_RX1024BYTE 0x2c | |
+#define AR8236_STATS_RX1518BYTE 0x30 | |
+#define AR8236_STATS_RXMAXBYTE 0x34 | |
+#define AR8236_STATS_RXTOOLONG 0x38 | |
+#define AR8236_STATS_RXGOODBYTE 0x3c | |
+#define AR8236_STATS_RXBADBYTE 0x44 | |
+#define AR8236_STATS_RXOVERFLOW 0x4c | |
+#define AR8236_STATS_FILTERED 0x50 | |
+#define AR8236_STATS_TXBROAD 0x54 | |
+#define AR8236_STATS_TXPAUSE 0x58 | |
+#define AR8236_STATS_TXMULTI 0x5c | |
+#define AR8236_STATS_TXUNDERRUN 0x60 | |
+#define AR8236_STATS_TX64BYTE 0x64 | |
+#define AR8236_STATS_TX128BYTE 0x68 | |
+#define AR8236_STATS_TX256BYTE 0x6c | |
+#define AR8236_STATS_TX512BYTE 0x70 | |
+#define AR8236_STATS_TX1024BYTE 0x74 | |
+#define AR8236_STATS_TX1518BYTE 0x78 | |
+#define AR8236_STATS_TXMAXBYTE 0x7c | |
+#define AR8236_STATS_TXOVERSIZE 0x80 | |
+#define AR8236_STATS_TXBYTE 0x84 | |
+#define AR8236_STATS_TXCOLLISION 0x8c | |
+#define AR8236_STATS_TXABORTCOL 0x90 | |
+#define AR8236_STATS_TXMULTICOL 0x94 | |
+#define AR8236_STATS_TXSINGLECOL 0x98 | |
+#define AR8236_STATS_TXEXCDEFER 0x9c | |
+#define AR8236_STATS_TXDEFER 0xa0 | |
+#define AR8236_STATS_TXLATECOL 0xa4 | |
+ | |
+struct ar8236_mib_desc { | |
+ unsigned int size; | |
+ unsigned int offset; | |
+ const char *name; | |
+}; | |
+ | |
+struct ar8236_arl_entry { | |
+ u8 port; | |
+ u8 status; | |
+ u8 mac[6]; | |
+}; | |
+ | |
+/* port speed */ | |
+enum { | |
+ AR8236_PORT_SPEED_10M = 0, | |
+ AR8236_PORT_SPEED_100M = 1, | |
+ AR8236_PORT_SPEED_1000M = 2, | |
+ AR8236_PORT_SPEED_ERR = 3, | |
+}; | |
+ | |
+struct ar8236_priv { | |
+ /* The dsa_switch this private structure is related to */ | |
+ struct dsa_switch *ds; | |
+ | |
+ /* MDIO bus and address on bus to use. When in single chip | |
+ * mode, address is 0, and the switch uses multiple addresses | |
+ * on the bus. When in multi-chip mode, the switch uses a | |
+ * single address which contains two registers used for | |
+ * indirect access to more registers. | |
+ */ | |
+ struct mii_bus *bus; | |
+ int sw_addr; | |
+ | |
+ u64 *mib_stats; | |
+ struct ar8236_arl_entry arl_table[AR8236_ARL_ENTRY]; | |
+}; | |
+ | |
+#endif | |
--- a/drivers/net/ethernet/marvell/mvneta.c | |
+++ b/drivers/net/ethernet/marvell/mvneta.c | |
@@ -4608,6 +4608,9 @@ static int mvneta_port_power_up(struct m | |
case PHY_INTERFACE_MODE_RGMII_ID: | |
ctrl |= MVNETA_GMAC2_PORT_RGMII; | |
break; | |
+ case PHY_INTERFACE_MODE_MII: | |
+ ctrl &= ~MVNETA_GMAC2_PORT_RGMII; | |
+ break; | |
default: | |
return -EINVAL; | |
} | |
@@ -4620,6 +4623,14 @@ static int mvneta_port_power_up(struct m | |
MVNETA_GMAC2_PORT_RESET) != 0) | |
continue; | |
+ if (PHY_INTERFACE_MODE_MII == phy_mode) | |
+ { | |
+ u32 val; | |
+ val = mvreg_read(pp, 0x243c); | |
+ val |= BIT(28)|BIT(29)|BIT(30); | |
+ mvreg_write(pp, 0x243c, val); | |
+ } | |
+ | |
return 0; | |
} | |
--- a/include/net/dsa.h | |
+++ b/include/net/dsa.h | |
@@ -26,6 +26,7 @@ enum dsa_tag_protocol { | |
DSA_TAG_PROTO_TRAILER, | |
DSA_TAG_PROTO_EDSA, | |
DSA_TAG_PROTO_BRCM, | |
+ DSA_TAG_PROTO_QCA, | |
DSA_TAG_LAST, /* MUST BE LAST */ | |
}; | |
--- a/net/dsa/Kconfig | |
+++ b/net/dsa/Kconfig | |
@@ -38,4 +38,7 @@ config NET_DSA_TAG_EDSA | |
config NET_DSA_TAG_TRAILER | |
bool | |
+config NET_DSA_TAG_QCA | |
+ bool | |
+ | |
endif | |
--- a/net/dsa/Makefile | |
+++ b/net/dsa/Makefile | |
@@ -7,3 +7,4 @@ dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += t | |
dsa_core-$(CONFIG_NET_DSA_TAG_DSA) += tag_dsa.o | |
dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o | |
dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o | |
+dsa_core-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o | |
--- a/net/dsa/dsa.c | |
+++ b/net/dsa/dsa.c | |
@@ -54,6 +54,9 @@ const struct dsa_device_ops *dsa_device_ | |
#ifdef CONFIG_NET_DSA_TAG_BRCM | |
[DSA_TAG_PROTO_BRCM] = &brcm_netdev_ops, | |
#endif | |
+#ifdef CONFIG_NET_DSA_TAG_QCA | |
+ [DSA_TAG_PROTO_QCA] = &qca_netdev_ops, | |
+#endif | |
[DSA_TAG_PROTO_NONE] = &none_ops, | |
}; | |
@@ -345,8 +348,13 @@ static int dsa_switch_setup_one(struct d | |
/* Make the built-in MII bus mask match the number of ports, | |
* switch drivers can override this later | |
+ * | |
+ * because we use port0 as cpu port, so port1 is phy0,port2 is phy1 | |
+ * the mii_mask should be adjust for this reason | |
*/ | |
- ds->phys_mii_mask = ds->enabled_port_mask; | |
+ ds->phys_mii_mask = (ds->enabled_port_mask) >> 1; | |
+ netdev_info(dst->master_netdev, "ds->enabled_port_mask : 0x%x\n", ds->enabled_port_mask); | |
+ netdev_info(dst->master_netdev, "ds->phys_mii_mask : 0x%x\n", ds->phys_mii_mask); | |
/* | |
* If the CPU connects to this switch, set the switch tree | |
@@ -404,6 +412,8 @@ static int dsa_switch_setup_one(struct d | |
index, i, cd->port_names[i], ret); | |
ret = 0; | |
} | |
+ netdev_info(dst->master_netdev, "[%d]: create dsa slave device for port %d(%s)\n", | |
+ index, i, cd->port_names[i]); | |
} | |
/* Perform configuration of the CPU and DSA ports */ | |
--- a/net/dsa/dsa_priv.h | |
+++ b/net/dsa/dsa_priv.h | |
@@ -81,5 +81,7 @@ extern const struct dsa_device_ops trail | |
/* tag_brcm.c */ | |
extern const struct dsa_device_ops brcm_netdev_ops; | |
+/* tag_qca.c */ | |
+extern const struct dsa_device_ops qca_netdev_ops; | |
#endif | |
--- /dev/null | |
+++ b/net/dsa/tag_qca.c | |
@@ -0,0 +1,132 @@ | |
+/* | |
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved. | |
+ * | |
+ * This program is free software; you can redistribute it and/or modify | |
+ * it under the terms of the GNU General Public License version 2 and | |
+ * only version 2 as published by the Free Software Foundation. | |
+ * | |
+ * 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/etherdevice.h> | |
+ | |
+#include "dsa_priv.h" | |
+ | |
+#define QCA_HDR_LEN 2 | |
+#define QCA_HDR_VERSION 0x2 | |
+ | |
+#define QCA_HDR_RECV_VERSION_MASK GENMASK(15, 14) | |
+#define QCA_HDR_RECV_VERSION_S 14 | |
+#define QCA_HDR_RECV_PRIORITY_MASK GENMASK(13, 11) | |
+#define QCA_HDR_RECV_PRIORITY_S 11 | |
+#define QCA_HDR_RECV_TYPE_MASK GENMASK(10, 6) | |
+#define QCA_HDR_RECV_TYPE_S 6 | |
+#define QCA_HDR_RECV_FRAME_IS_TAGGED BIT(3) | |
+#define QCA_HDR_RECV_SOURCE_PORT_MASK GENMASK(2, 0) | |
+ | |
+#define QCA_HDR_XMIT_VERSION_MASK GENMASK(15, 14) | |
+#define QCA_HDR_XMIT_VERSION_S 14 | |
+#define QCA_HDR_XMIT_PRIORITY_MASK GENMASK(13, 11) | |
+#define QCA_HDR_XMIT_PRIORITY_S 11 | |
+#define QCA_HDR_XMIT_CONTROL_MASK GENMASK(10, 8) | |
+#define QCA_HDR_XMIT_CONTROL_S 8 | |
+#define QCA_HDR_XMIT_FROM_CPU BIT(7) | |
+#define QCA_HDR_XMIT_DP_BIT_MASK GENMASK(6, 0) | |
+ | |
+static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev) | |
+{ | |
+ struct dsa_slave_priv *p = netdev_priv(dev); | |
+ u16 *phdr, hdr; | |
+ | |
+ dev->stats.tx_packets++; | |
+ dev->stats.tx_bytes += skb->len; | |
+ | |
+ if (skb_cow_head(skb, QCA_HDR_LEN) < 0) | |
+ goto out_free; | |
+ | |
+ skb_push(skb, QCA_HDR_LEN); | |
+ | |
+ memmove(skb->data, skb->data + QCA_HDR_LEN, 2 * ETH_ALEN); | |
+ phdr = (u16 *)(skb->data + 2 * ETH_ALEN); | |
+ | |
+ /* Set the version field, and set destination port information */ | |
+ hdr = QCA_HDR_VERSION << QCA_HDR_XMIT_VERSION_S | | |
+ QCA_HDR_XMIT_FROM_CPU | | |
+ BIT(p->port); | |
+ | |
+ *phdr = htons(hdr); | |
+ | |
+ return skb; | |
+ | |
+out_free: | |
+ kfree_skb(skb); | |
+ return NULL; | |
+} | |
+ | |
+static int qca_tag_rcv(struct sk_buff *skb, struct net_device *dev, | |
+ struct packet_type *pt, struct net_device *orig_dev) | |
+{ | |
+ struct dsa_switch_tree *dst = dev->dsa_ptr; | |
+ struct dsa_switch *ds; | |
+ u8 ver; | |
+ int port; | |
+ __be16 *phdr, hdr; | |
+ | |
+ if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN))) | |
+ goto out_drop; | |
+ | |
+ skb = skb_unshare(skb, GFP_ATOMIC); | |
+ if (skb == NULL) | |
+ goto out; | |
+ | |
+ ds = dst->ds[0]; | |
+ | |
+ /* The QCA header is added by the switch between src addr and Ethertype | |
+ * At this point, skb->data points to ethertype so header should be | |
+ * right before | |
+ */ | |
+ phdr = (__be16 *)(skb->data - 2); | |
+ hdr = ntohs(*phdr); | |
+ | |
+ /* Make sure the version is correct */ | |
+ ver = (hdr & QCA_HDR_RECV_VERSION_MASK) >> QCA_HDR_RECV_VERSION_S; | |
+ if (unlikely(ver != QCA_HDR_VERSION)) | |
+ goto out_drop; | |
+ | |
+ /* Remove QCA tag and recalculate checksum */ | |
+ skb_pull_rcsum(skb, QCA_HDR_LEN); | |
+ memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - QCA_HDR_LEN, | |
+ ETH_HLEN - QCA_HDR_LEN); | |
+ | |
+ /* Get source port information */ | |
+ port = (hdr & QCA_HDR_RECV_SOURCE_PORT_MASK); | |
+ if (!ds->ports[port].netdev) | |
+ goto out_drop; | |
+ | |
+ /* Update skb & forward the frame accordingly */ | |
+ skb->dev = ds->ports[port].netdev; | |
+ | |
+ skb_push(skb, ETH_HLEN); | |
+ skb->pkt_type = PACKET_HOST; | |
+ skb->protocol = eth_type_trans(skb, skb->dev); | |
+ | |
+ skb->dev->stats.rx_packets++; | |
+ skb->dev->stats.rx_bytes += skb->len; | |
+ | |
+ netif_receive_skb(skb); | |
+ | |
+ return 0; | |
+ | |
+out_drop: | |
+ kfree_skb(skb); | |
+out: | |
+ return 0; | |
+} | |
+ | |
+const struct dsa_device_ops qca_netdev_ops = { | |
+ .xmit = qca_tag_xmit, | |
+ .rcv = qca_tag_rcv, | |
+}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment