* latest netdev net-next kernel panic
From: Paweł Staszewski @ 2017-09-07 21:30 UTC (permalink / raw)
To: Linux Kernel Network Developers
I want to mention also here cause last trace for RIP was dst_dev_put
https://bugzilla.kernel.org/attachment.cgi?id=258259
^ permalink raw reply
* Re: [PATCH RFC 2/6] Create new file ksz9477.c from KSZ9477 code in ksz_common.c
From: Andrew Lunn @ 2017-09-07 21:32 UTC (permalink / raw)
To: Tristram.Ha
Cc: muvarov, pavel, nathan.leigh.conrad, vivien.didelot, f.fainelli,
netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD41121A06@CHN-SV-EXMX02.mchp-main.com>
On Thu, Sep 07, 2017 at 09:09:04PM +0000, Tristram.Ha@microchip.com wrote:
> From: Tristram Ha <Tristram.Ha@microchip.com>
>
> Create new ksz9477.c file from original ksz_common.c.
>
> Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
> ---
> diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c
> new file mode 100644
> index 0000000..bc722b4
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/ksz9477.c
> @@ -0,0 +1,1317 @@
> +/*
> + * Microchip switch driver main logic
> + *
> + * Copyright (C) 2017
> + *
> + * Permission to use, copy, modify, and/or distribute this software for
> +any
> + * purpose with or without fee is hereby granted, provided that the
> +above
Tristram
It looks like something hand mangled this comment. "any" and "above"
appear to be on a line on there own.
> + * 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/delay.h>
> +#include <linux/export.h>
> +#include <linux/gpio.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_data/microchip-ksz.h>
> +#include <linux/phy.h>
> +#include <linux/etherdevice.h>
> +#include <linux/if_bridge.h>
> +#include <net/dsa.h>
> +#include <net/switchdev.h>
> +
> +#include "ksz_priv.h"
> +#include "ksz_9477_reg.h"
> +
> +static const struct {
> + int index;
> + char string[ETH_GSTRING_LEN];
> +} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
> + { 0x00, "rx_hi" },
> + { 0x01, "rx_undersize" },
> + { 0x02, "rx_fragments" },
> + { 0x03, "rx_oversize" },
> + { 0x04, "rx_jabbers" },
> + { 0x05, "rx_symbol_err" },
> + { 0x06, "rx_crc_err" },
> + { 0x07, "rx_align_err" },
> + { 0x08, "rx_mac_ctrl" },
> + { 0x09, "rx_pause" },
> + { 0x0A, "rx_bcast" },
> + { 0x0B, "rx_mcast" },
> + { 0x0C, "rx_ucast" },
> + { 0x0D, "rx_64_or_less" },
> + { 0x0E, "rx_65_127" },
> + { 0x0F, "rx_128_255" },
> + { 0x10, "rx_256_511" },
> + { 0x11, "rx_512_1023" },
> + { 0x12, "rx_1024_1522" },
> + { 0x13, "rx_1523_2000" },
> + { 0x14, "rx_2001" },
> + { 0x15, "tx_hi" },
> + { 0x16, "tx_late_col" },
> + { 0x17, "tx_pause" },
> + { 0x18, "tx_bcast" },
> + { 0x19, "tx_mcast" },
> + { 0x1A, "tx_ucast" },
> + { 0x1B, "tx_deferred" },
> + { 0x1C, "tx_total_col" },
> + { 0x1D, "tx_exc_col" },
> + { 0x1E, "tx_single_col" },
> + { 0x1F, "tx_mult_col" },
> + { 0x80, "rx_total" },
> + { 0x81, "tx_total" },
> + { 0x82, "rx_discards" },
> + { 0x83, "tx_discards" },
> +};
> +
> +static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool
> +set) {
More mangling? Should set be on the end of the previous line?
> +static void read_table(struct dsa_switch *ds, u32 *table) {
> + struct ksz_device *dev = ds->priv;
> +
> + ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]);
> + ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]);
> + ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]);
> + ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]); }
> +
> +static void write_table(struct dsa_switch *ds, u32 *table) {
> + struct ksz_device *dev = ds->priv;
> +
> + ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]);
> + ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]);
> + ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]);
> + ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]); }
More mangling? } at the end of a line?
I will stop reading now and wait for a v2 this is not corrupt.
Andrew
^ permalink raw reply
* RE: [PATCH net-next] mdio_bus: Remove unneeded gpiod NULL check
From: Woojung.Huh @ 2017-09-07 21:30 UTC (permalink / raw)
To: linus.walleij, andrew
Cc: festevam, sergei.shtylyov, davem, f.fainelli, netdev,
fabio.estevam
In-Reply-To: <CACRpkdYw5GWv3HRCqnWjDfctLEfzcoFdJ7ib_NVKRgw0+Y9dEg@mail.gmail.com>
> If someone is using GPIO descriptors with GPIO disabled, i.e. calling
> gpiod_get() and friends, this is very likely to be a bug, and what
> the driver wants to do is:
>
> depends on GPIOLIB
>
> or
>
> select GPIOLIB
>
> in Kconfig. The whole optional thing is mainly a leftover from when it
> was possible to have a local implementation of the GPIOLIB API in
> some custom header file, noone sane should be doing that anymore,
> and if they do, they can very well face the warnings.
>
> If someone is facing a lot of WARN_ON() messages to this, it is a clear
> indication that they need to fix their Kconfig and in that case it is proper.
Linus & Andrew,
I knew that it is already in David's pulling request.
Configuring GPIOLIB is the right solution even if platform doesn't use it?
Did I miss any new patch?
Thanks.
Woojung
^ permalink raw reply
* Re: [PATCH RFC 1/6] The file ksz_common.c will be used by other KSZ switch drivers.
From: Andrew Lunn @ 2017-09-07 21:24 UTC (permalink / raw)
To: Tristram.Ha
Cc: muvarov, pavel, nathan.leigh.conrad, vivien.didelot, f.fainelli,
netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411219FF@CHN-SV-EXMX02.mchp-main.com>
On Thu, Sep 07, 2017 at 09:08:58PM +0000, Tristram.Ha@microchip.com wrote:
> From: Tristram Ha <Tristram.Ha@microchip.com>
>
> Break ksz_common.c into 2 files so that the common code can be used by other KSZ switch drivers.
>
> Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
> ---
> diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
> index ed335e2..0961c30 100644
> --- a/drivers/net/dsa/microchip/Makefile
> +++ b/drivers/net/dsa/microchip/Makefile
> @@ -1,2 +1,2 @@
> -obj-$(CONFIG_MICROCHIP_KSZ) += ksz_common.o
> +obj-$(CONFIG_MICROCHIP_KSZ) += ksz9477.o ksz_common.o
> obj-$(CONFIG_MICROCHIP_KSZ_SPI_DRIVER) += ksz_spi.o
Hi Tristram
I would of thought this would break the build. You don't add ksz9477.c
until the next patch.
Each patch needs to compile, otherwise you break git bisect.
Andrew
^ permalink raw reply
* Re: Can you help to analyze this panic issue when using VPN?
From: Cong Wang @ 2017-09-07 21:20 UTC (permalink / raw)
To: Songchuan
Cc: netdev@vger.kernel.org, weiwan@google.com, davem@davemloft.net,
Wanghao (R), sunjilong, Liwei (Sirius), sangguanlin,
Xuhui (hunter, Device company ), Eric Dumazet
In-Reply-To: <3B91CCFCFC433E4596591DB9A191998574453125@nkgeml514-mbx.china.huawei.com>
On Thu, Sep 7, 2017 at 3:18 AM, Songchuan <songchuan@huawei.com> wrote:
> <4>[ 1730.085113s][pid:1928,cpu3,HeapTaskDaemon]CPU: 3 PID: 1928 Comm: HeapTaskDaemon Tainted: G B W 4.1.18-kasan-g6e99722-dirty #1
Sorry, 4.1 is too old for upstream. As you know 4.13 is just out.
Please try to reproduce it on the latest or reasonably recent kernel.
^ permalink raw reply
* [PATCH RFC 5/5] Add KSZ8795 SPI driver
From: Tristram.Ha @ 2017-09-07 21:17 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add KSZ8795 switch support with SPI access.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz8795_spi.c b/drivers/net/dsa/microchip/ksz8795_spi.c
new file mode 100644
index 0000000..0f9c731
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795_spi.c
@@ -0,0 +1,229 @@
+/*
+ * Microchip KSZ8795 series register access through SPI
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.com>
+ *
+ * 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/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "ksz_priv.h"
+
+int ksz8795_switch_register(struct ksz_device *dev);
+
+/* SPI frame opcodes */
+#define KS_SPIOP_RD 3
+#define KS_SPIOP_WR 2
+
+#define SPI_ADDR_S 12
+#define SPI_ADDR_M (BIT(SPI_ADDR_S) - 1)
+#define SPI_TURNAROUND_S 1
+
+/* Enough to read all switch registers. */
+#define SPI_TX_BUF_LEN 0x100
+
+static int ksz_spi_read_reg(struct spi_device *spi, u32 reg, u8 *val,
+ unsigned int len)
+{
+ u16 txbuf;
+ int ret;
+
+ txbuf = reg & SPI_ADDR_M;
+ txbuf |= KS_SPIOP_RD << SPI_ADDR_S;
+ txbuf <<= SPI_TURNAROUND_S;
+ txbuf = cpu_to_be16(txbuf);
+
+ ret = spi_write_then_read(spi, &txbuf, 2, val, len);
+ return ret;
+}
+
+static int ksz_spi_read(struct ksz_device *dev, u32 reg, u8 *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ return ksz_spi_read_reg(spi, reg, data, len); }
+
+static int ksz_spi_read8(struct ksz_device *dev, u32 reg, u8 *val) {
+ return ksz_spi_read(dev, reg, val, 1); }
+
+static int ksz_spi_read16(struct ksz_device *dev, u32 reg, u16 *val) {
+ int ret = ksz_spi_read(dev, reg, (u8 *)val, 2);
+
+ if (!ret)
+ *val = be16_to_cpu(*val);
+
+ return ret;
+}
+
+static int ksz_spi_read32(struct ksz_device *dev, u32 reg, u32 *val) {
+ int ret = ksz_spi_read(dev, reg, (u8 *)val, 4);
+
+ if (!ret)
+ *val = be32_to_cpu(*val);
+
+ return ret;
+}
+
+static int ksz_spi_write_reg(struct spi_device *spi, u32 reg, u8 *val,
+ unsigned int len)
+{
+ u16 *txbuf = (u16 *)val;
+
+ *txbuf = reg & SPI_ADDR_M;
+ *txbuf |= (KS_SPIOP_WR << SPI_ADDR_S);
+ *txbuf <<= SPI_TURNAROUND_S;
+ *txbuf = cpu_to_be16(*txbuf);
+
+ return spi_write(spi, txbuf, 2 + len); }
+
+static int ksz_spi_write(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len)
+{
+ struct spi_device *spi = dev->priv;
+
+ if (len > SPI_TX_BUF_LEN)
+ len = SPI_TX_BUF_LEN;
+ memcpy(&dev->txbuf[2], data, len);
+ return ksz_spi_write_reg(spi, reg, dev->txbuf, len); }
+
+static unsigned int ksz_spi_get(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len)
+{
+ int err;
+
+ err = ksz_spi_read(dev, reg, data, len);
+ if (err)
+ return 0;
+ return len;
+}
+
+static unsigned int ksz_spi_set(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len)
+{
+ int err;
+
+ err = ksz_spi_write(dev, reg, data, len);
+ if (err)
+ return 0;
+ return len;
+}
+
+static int ksz_spi_write8(struct ksz_device *dev, u32 reg, u8 value) {
+ return ksz_spi_write(dev, reg, &value, 1); }
+
+static int ksz_spi_write16(struct ksz_device *dev, u32 reg, u16 value)
+{
+ value = cpu_to_be16(value);
+ return ksz_spi_write(dev, reg, &value, 2); }
+
+static int ksz_spi_write32(struct ksz_device *dev, u32 reg, u32 value)
+{
+ value = cpu_to_be32(value);
+ return ksz_spi_write(dev, reg, &value, 4); }
+
+static const struct ksz_io_ops ksz_spi_ops = {
+ .read8 = ksz_spi_read8,
+ .read16 = ksz_spi_read16,
+ .read32 = ksz_spi_read32,
+ .write8 = ksz_spi_write8,
+ .write16 = ksz_spi_write16,
+ .write32 = ksz_spi_write32,
+ .get = ksz_spi_get,
+ .set = ksz_spi_set,
+};
+
+static int ksz_spi_probe(struct spi_device *spi) {
+ struct ksz_device *dev;
+ int ret;
+
+ dev = ksz_switch_alloc(&spi->dev, &ksz_spi_ops, spi);
+ if (!dev)
+ return -ENOMEM;
+
+ if (spi->dev.platform_data)
+ dev->pdata = spi->dev.platform_data;
+
+ dev->txbuf = devm_kzalloc(dev->dev, 2 + SPI_TX_BUF_LEN, GFP_KERNEL);
+
+ ret = ksz8795_switch_register(dev);
+
+ /* Main DSA driver may not be started yet. */
+ if (ret)
+ return ret;
+
+ spi_set_drvdata(spi, dev);
+
+ return 0;
+}
+
+static int ksz_spi_remove(struct spi_device *spi) {
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev)
+ ksz_switch_remove(dev);
+
+ return 0;
+}
+
+static void ksz_spi_shutdown(struct spi_device *spi) {
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev)
+ dev->dev_ops->reset(dev);
+}
+
+static const struct of_device_id ksz_dt_ids[] = {
+ { .compatible = "microchip,ksz8795" },
+ { .compatible = "microchip,ksz8765" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ksz_dt_ids);
+
+static struct spi_driver ksz_spi_driver = {
+ .driver = {
+ .name = "ksz8795-switch",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ksz_dt_ids),
+ },
+ .probe = ksz_spi_probe,
+ .remove = ksz_spi_remove,
+ .shutdown = ksz_spi_shutdown,
+};
+
+module_spi_driver(ksz_spi_driver);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8795 Series Switch SPI Driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
* [PATCH RFC 4/5] Add KSZ8795 register definitions
From: Tristram.Ha @ 2017-09-07 21:17 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add KSZ8795 switch support with register definitions.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz8795.h b/drivers/net/dsa/microchip/ksz8795.h
new file mode 100644
index 0000000..005eda5
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795.h
@@ -0,0 +1,1015 @@
+/**
+ * Microchip KSZ8795 definition file
+ *
+ * Copyright (c) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.com>
+ *
+ * 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 __KSZ8795_H
+#define __KSZ8795_H
+
+#define KS_PORT_M 0x1F
+
+#define KS_PRIO_M 0x3
+#define KS_PRIO_S 2
+
+#define REG_CHIP_ID0 0x00
+
+#define FAMILY_ID 0x87
+
+#define REG_CHIP_ID1 0x01
+
+#define SW_CHIP_ID_M 0xF0
+#define SW_CHIP_ID_S 4
+#define SW_REVISION_M 0x0E
+#define SW_REVISION_S 1
+#define SW_START 0x01
+
+#define CHIP_ID_94 0x60
+#define CHIP_ID_95 0x90
+
+#define REG_SW_CTRL_0 0x02
+
+#define SW_NEW_BACKOFF BIT(7)
+#define SW_GLOBAL_RESET BIT(6)
+#define SW_FLUSH_DYN_MAC_TABLE BIT(5)
+#define SW_FLUSH_STA_MAC_TABLE BIT(4)
+#define SW_LINK_AUTO_AGING BIT(0)
+
+#define REG_SW_CTRL_1 0x03
+
+#define SW_HUGE_PACKET BIT(6)
+#define SW_TX_FLOW_CTRL_DISABLE BIT(5)
+#define SW_RX_FLOW_CTRL_DISABLE BIT(4)
+#define SW_CHECK_LENGTH BIT(3)
+#define SW_AGING_ENABLE BIT(2)
+#define SW_FAST_AGING BIT(1)
+#define SW_AGGR_BACKOFF BIT(0)
+
+#define REG_SW_CTRL_2 0x04
+
+#define UNICAST_VLAN_BOUNDARY BIT(7)
+#define MULTICAST_STORM_DISABLE BIT(6)
+#define SW_BACK_PRESSURE BIT(5)
+#define FAIR_FLOW_CTRL BIT(4)
+#define NO_EXC_COLLISION_DROP BIT(3)
+#define SW_LEGAL_PACKET_DISABLE BIT(1)
+
+#define REG_SW_CTRL_3 0x05
+ #define WEIGHTED_FAIR_QUEUE_ENABLE BIT(3)
+
+#define SW_VLAN_ENABLE BIT(7)
+#define SW_IGMP_SNOOP BIT(6)
+#define SW_MIRROR_RX_TX BIT(0)
+
+#define REG_SW_CTRL_4 0x06
+
+#define SW_HALF_DUPLEX_FLOW_CTRL BIT(7)
+#define SW_HALF_DUPLEX BIT(6)
+#define SW_FLOW_CTRL BIT(5)
+#define SW_10_MBIT BIT(4)
+#define SW_REPLACE_VID BIT(3)
+#define BROADCAST_STORM_RATE_HI 0x07
+
+#define REG_SW_CTRL_5 0x07
+
+#define BROADCAST_STORM_RATE_LO 0xFF
+#define BROADCAST_STORM_RATE 0x07FF
+
+#define REG_SW_CTRL_6 0x08
+
+#define SW_MIB_COUNTER_FLUSH BIT(7)
+#define SW_MIB_COUNTER_FREEZE BIT(6)
+#define SW_MIB_COUNTER_CTRL_ENABLE KS_PORT_M
+
+#define REG_SW_CTRL_9 0x0B
+
+#define SPI_CLK_125_MHZ 0x80
+#define SPI_CLK_62_5_MHZ 0x40
+#define SPI_CLK_31_25_MHZ 0x00
+
+#define SW_LED_MODE_M 0x3
+#define SW_LED_MODE_S 4
+#define SW_LED_LINK_ACT_SPEED 0
+#define SW_LED_LINK_ACT 1
+#define SW_LED_LINK_ACT_DUPLEX 2
+#define SW_LED_LINK_DUPLEX 3
+
+#define REG_SW_CTRL_10 0x0C
+
+#define SW_TAIL_TAG_ENABLE BIT(1)
+#define SW_PASS_PAUSE BIT(0)
+
+#define REG_SW_CTRL_11 0x0D
+
+#define REG_POWER_MANAGEMENT_1 0x0E
+
+#define SW_PLL_POWER_DOWN BIT(5)
+#define SW_POWER_MANAGEMENT_MODE_M 0x3
+#define SW_POWER_MANAGEMENT_MODE_S 3
+#define SW_POWER_NORMAL 0
+#define SW_ENERGY_DETECTION 1
+#define SW_SOFTWARE_POWER_DOWN 2
+
+#define REG_POWER_MANAGEMENT_2 0x0F
+
+#define REG_PORT_1_CTRL_0 0x10
+#define REG_PORT_2_CTRL_0 0x20
+#define REG_PORT_3_CTRL_0 0x30
+#define REG_PORT_4_CTRL_0 0x40
+#define REG_PORT_5_CTRL_0 0x50
+
+#define PORT_BROADCAST_STORM BIT(7)
+#define PORT_DIFFSERV_ENABLE BIT(6)
+#define PORT_802_1P_ENABLE BIT(5)
+#define PORT_BASED_PRIO_S 3
+#define PORT_BASED_PRIO_M KS_PRIO_M
+#define PORT_BASED_PRIO_0 0
+#define PORT_BASED_PRIO_1 1
+#define PORT_BASED_PRIO_2 2
+#define PORT_BASED_PRIO_3 3
+#define PORT_INSERT_TAG BIT(2)
+#define PORT_REMOVE_TAG BIT(1)
+#define PORT_QUEUE_SPLIT_L BIT(0)
+
+#define REG_PORT_1_CTRL_1 0x11
+#define REG_PORT_2_CTRL_1 0x21
+#define REG_PORT_3_CTRL_1 0x31
+#define REG_PORT_4_CTRL_1 0x41
+#define REG_PORT_5_CTRL_1 0x51
+
+#define PORT_MIRROR_SNIFFER BIT(7)
+#define PORT_MIRROR_RX BIT(6)
+#define PORT_MIRROR_TX BIT(5)
+#define PORT_VLAN_MEMBERSHIP KS_PORT_M
+
+#define REG_PORT_1_CTRL_2 0x12
+#define REG_PORT_2_CTRL_2 0x22
+#define REG_PORT_3_CTRL_2 0x32
+#define REG_PORT_4_CTRL_2 0x42
+#define REG_PORT_5_CTRL_2 0x52
+
+#define PORT_802_1P_REMAPPING BIT(7)
+#define PORT_INGRESS_FILTER BIT(6)
+#define PORT_DISCARD_NON_VID BIT(5)
+#define PORT_FORCE_FLOW_CTRL BIT(4)
+#define PORT_BACK_PRESSURE BIT(3)
+#define PORT_TX_ENABLE BIT(2)
+#define PORT_RX_ENABLE BIT(1)
+#define PORT_LEARN_DISABLE BIT(0)
+
+#define REG_PORT_1_CTRL_3 0x13
+#define REG_PORT_2_CTRL_3 0x23
+#define REG_PORT_3_CTRL_3 0x33
+#define REG_PORT_4_CTRL_3 0x43
+#define REG_PORT_5_CTRL_3 0x53
+#define REG_PORT_1_CTRL_4 0x14
+#define REG_PORT_2_CTRL_4 0x24
+#define REG_PORT_3_CTRL_4 0x34
+#define REG_PORT_4_CTRL_4 0x44
+#define REG_PORT_5_CTRL_4 0x54
+
+#define PORT_DEFAULT_VID 0x0001
+
+#define REG_PORT_1_CTRL_5 0x15
+#define REG_PORT_2_CTRL_5 0x25
+#define REG_PORT_3_CTRL_5 0x35
+#define REG_PORT_4_CTRL_5 0x45
+#define REG_PORT_5_CTRL_5 0x55
+
+#define PORT_ACL_ENABLE BIT(2)
+#define PORT_AUTHEN_MODE 0x3
+#define PORT_AUTHEN_PASS 0
+#define PORT_AUTHEN_BLOCK 1
+#define PORT_AUTHEN_TRAP 2
+
+#define REG_PORT_5_CTRL_6 0x56
+
+#define PORT_MII_INTERNAL_CLOCK BIT(7)
+#define PORT_GMII_1GPS_MODE BIT(6)
+#define PORT_RGMII_ID_IN_ENABLE BIT(4)
+#define PORT_RGMII_ID_OUT_ENABLE BIT(3)
+#define PORT_GMII_MAC_MODE BIT(2)
+#define PORT_INTERFACE_TYPE 0x3
+#define PORT_INTERFACE_MII 0
+#define PORT_INTERFACE_RMII 1
+#define PORT_INTERFACE_GMII 2
+#define PORT_INTERFACE_RGMII 3
+
+#define REG_PORT_1_CTRL_7 0x17
+#define REG_PORT_2_CTRL_7 0x27
+#define REG_PORT_3_CTRL_7 0x37
+#define REG_PORT_4_CTRL_7 0x47
+
+#define PORT_AUTO_NEG_ASYM_PAUSE BIT(5)
+#define PORT_AUTO_NEG_SYM_PAUSE BIT(4)
+#define PORT_AUTO_NEG_100BTX_FD BIT(3)
+#define PORT_AUTO_NEG_100BTX BIT(2)
+#define PORT_AUTO_NEG_10BT_FD BIT(1)
+#define PORT_AUTO_NEG_10BT BIT(0)
+
+#define REG_PORT_1_STATUS_0 0x18
+#define REG_PORT_2_STATUS_0 0x28
+#define REG_PORT_3_STATUS_0 0x38
+#define REG_PORT_4_STATUS_0 0x48
+
+/* For KSZ8765. */
+#define PORT_FIBER_MODE BIT(7)
+
+#define PORT_REMOTE_ASYM_PAUSE BIT(5)
+#define PORT_REMOTE_SYM_PAUSE BIT(4)
+#define PORT_REMOTE_100BTX_FD BIT(3)
+#define PORT_REMOTE_100BTX BIT(2)
+#define PORT_REMOTE_10BT_FD BIT(1)
+#define PORT_REMOTE_10BT BIT(0)
+
+#define REG_PORT_1_STATUS_1 0x19
+#define REG_PORT_2_STATUS_1 0x29
+#define REG_PORT_3_STATUS_1 0x39
+#define REG_PORT_4_STATUS_1 0x49
+
+#define PORT_HP_MDIX BIT(7)
+#define PORT_REVERSED_POLARITY BIT(5)
+#define PORT_TX_FLOW_CTRL BIT(4)
+#define PORT_RX_FLOW_CTRL BIT(3)
+#define PORT_STAT_SPEED_100MBIT BIT(2)
+#define PORT_STAT_FULL_DUPLEX BIT(1)
+
+#define PORT_REMOTE_FAULT BIT(0)
+
+#define REG_PORT_1_LINK_MD_CTRL 0x1A
+#define REG_PORT_2_LINK_MD_CTRL 0x2A
+#define REG_PORT_3_LINK_MD_CTRL 0x3A
+#define REG_PORT_4_LINK_MD_CTRL 0x4A
+
+#define PORT_CABLE_10M_SHORT BIT(7)
+#define PORT_CABLE_DIAG_RESULT_M 0x3
+#define PORT_CABLE_DIAG_RESULT_S 5
+#define PORT_CABLE_STAT_NORMAL 0
+#define PORT_CABLE_STAT_OPEN 1
+#define PORT_CABLE_STAT_SHORT 2
+#define PORT_CABLE_STAT_FAILED 3
+#define PORT_START_CABLE_DIAG BIT(4)
+#define PORT_FORCE_LINK BIT(3)
+#define PORT_POWER_SAVING BIT(2)
+#define PORT_PHY_REMOTE_LOOPBACK BIT(1)
+#define PORT_CABLE_FAULT_COUNTER_H 0x01
+
+#define REG_PORT_1_LINK_MD_RESULT 0x1B
+#define REG_PORT_2_LINK_MD_RESULT 0x2B
+#define REG_PORT_3_LINK_MD_RESULT 0x3B
+#define REG_PORT_4_LINK_MD_RESULT 0x4B
+
+#define PORT_CABLE_FAULT_COUNTER_L 0xFF
+#define PORT_CABLE_FAULT_COUNTER 0x1FF
+
+#define REG_PORT_1_CTRL_9 0x1C
+#define REG_PORT_2_CTRL_9 0x2C
+#define REG_PORT_3_CTRL_9 0x3C
+#define REG_PORT_4_CTRL_9 0x4C
+
+#define PORT_AUTO_NEG_DISABLE BIT(7)
+#define PORT_FORCE_100_MBIT BIT(6)
+#define PORT_FORCE_FULL_DUPLEX BIT(5)
+
+#define REG_PORT_1_CTRL_10 0x1D
+#define REG_PORT_2_CTRL_10 0x2D
+#define REG_PORT_3_CTRL_10 0x3D
+#define REG_PORT_4_CTRL_10 0x4D
+
+#define PORT_LED_OFF BIT(7)
+#define PORT_TX_DISABLE BIT(6)
+#define PORT_AUTO_NEG_RESTART BIT(5)
+#define PORT_POWER_DOWN BIT(3)
+#define PORT_AUTO_MDIX_DISABLE BIT(2)
+#define PORT_FORCE_MDIX BIT(1)
+#define PORT_MAC_LOOPBACK BIT(0)
+
+#define REG_PORT_1_STATUS_2 0x1E
+#define REG_PORT_2_STATUS_2 0x2E
+#define REG_PORT_3_STATUS_2 0x3E
+#define REG_PORT_4_STATUS_2 0x4E
+
+#define PORT_MDIX_STATUS BIT(7)
+#define PORT_AUTO_NEG_COMPLETE BIT(6)
+#define PORT_STAT_LINK_GOOD BIT(5)
+
+#define REG_PORT_1_STATUS_3 0x1F
+#define REG_PORT_2_STATUS_3 0x2F
+#define REG_PORT_3_STATUS_3 0x3F
+#define REG_PORT_4_STATUS_3 0x4F
+
+#define PORT_PHY_LOOPBACK BIT(7)
+#define PORT_PHY_ISOLATE BIT(5)
+#define PORT_PHY_SOFT_RESET BIT(4)
+#define PORT_PHY_FORCE_LINK BIT(3)
+#define PORT_PHY_MODE_M 0x7
+#define PHY_MODE_IN_AUTO_NEG 1
+#define PHY_MODE_10BT_HALF 2
+#define PHY_MODE_100BT_HALF 3
+#define PHY_MODE_10BT_FULL 5
+#define PHY_MODE_100BT_FULL 6
+#define PHY_MODE_ISOLDATE 7
+
+#define REG_PORT_CTRL_0 0x00
+#define REG_PORT_CTRL_1 0x01
+#define REG_PORT_CTRL_2 0x02
+#define REG_PORT_CTRL_VID 0x03
+
+#define REG_PORT_CTRL_5 0x05
+
+#define REG_PORT_CTRL_7 0x07
+#define REG_PORT_STATUS_0 0x08
+#define REG_PORT_STATUS_1 0x09
+#define REG_PORT_LINK_MD_CTRL 0x0A
+#define REG_PORT_LINK_MD_RESULT 0x0B
+#define REG_PORT_CTRL_9 0x0C
+#define REG_PORT_CTRL_10 0x0D
+#define REG_PORT_STATUS_2 0x0E
+#define REG_PORT_STATUS_3 0x0F
+
+#define REG_PORT_CTRL_12 0xA0
+#define REG_PORT_CTRL_13 0xA1
+#define REG_PORT_RATE_CTRL_3 0xA2
+#define REG_PORT_RATE_CTRL_2 0xA3
+#define REG_PORT_RATE_CTRL_1 0xA4
+#define REG_PORT_RATE_CTRL_0 0xA5
+#define REG_PORT_RATE_LIMIT 0xA6
+#define REG_PORT_IN_RATE_0 0xA7
+#define REG_PORT_IN_RATE_1 0xA8
+#define REG_PORT_IN_RATE_2 0xA9
+#define REG_PORT_IN_RATE_3 0xAA
+#define REG_PORT_OUT_RATE_0 0xAB
+#define REG_PORT_OUT_RATE_1 0xAC
+#define REG_PORT_OUT_RATE_2 0xAD
+#define REG_PORT_OUT_RATE_3 0xAE
+
+#define PORT_CTRL_ADDR(port, addr) \
+ ((addr) + REG_PORT_1_CTRL_0 + (port) * \
+ (REG_PORT_2_CTRL_0 - REG_PORT_1_CTRL_0))
+
+#define REG_SW_MAC_ADDR_0 0x68
+#define REG_SW_MAC_ADDR_1 0x69
+#define REG_SW_MAC_ADDR_2 0x6A
+#define REG_SW_MAC_ADDR_3 0x6B
+#define REG_SW_MAC_ADDR_4 0x6C
+#define REG_SW_MAC_ADDR_5 0x6D
+
+#define REG_IND_CTRL_0 0x6E
+
+#define TABLE_EXT_SELECT_S 5
+#define TABLE_EEE_V 1
+#define TABLE_ACL_V 2
+#define TABLE_PME_V 4
+#define TABLE_LINK_MD_V 5
+#define TABLE_EEE (TABLE_EEE_V << TABLE_EXT_SELECT_S)
+#define TABLE_ACL (TABLE_ACL_V << TABLE_EXT_SELECT_S)
+#define TABLE_PME (TABLE_PME_V << TABLE_EXT_SELECT_S)
+#define TABLE_LINK_MD (TABLE_LINK_MD << TABLE_EXT_SELECT_S)
+#define TABLE_READ BIT(4)
+#define TABLE_SELECT_S 2
+#define TABLE_STATIC_MAC_V 0
+#define TABLE_VLAN_V 1
+#define TABLE_DYNAMIC_MAC_V 2
+#define TABLE_MIB_V 3
+#define TABLE_STATIC_MAC (TABLE_STATIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_VLAN (TABLE_VLAN_V << TABLE_SELECT_S)
+#define TABLE_DYNAMIC_MAC (TABLE_DYNAMIC_MAC_V << TABLE_SELECT_S)
+#define TABLE_MIB (TABLE_MIB_V << TABLE_SELECT_S)
+
+#define REG_IND_CTRL_1 0x6F
+
+#define TABLE_ENTRY_MASK 0x03FF
+#define TABLE_EXT_ENTRY_MASK 0x0FFF
+
+#define REG_IND_DATA_8 0x70
+#define REG_IND_DATA_7 0x71
+#define REG_IND_DATA_6 0x72
+#define REG_IND_DATA_5 0x73
+#define REG_IND_DATA_4 0x74
+#define REG_IND_DATA_3 0x75
+#define REG_IND_DATA_2 0x76
+#define REG_IND_DATA_1 0x77
+#define REG_IND_DATA_0 0x78
+
+#define REG_IND_DATA_PME_EEE_ACL 0xA0
+
+#define REG_IND_DATA_CHECK REG_IND_DATA_6
+#define REG_IND_MIB_CHECK REG_IND_DATA_4
+#define REG_IND_DATA_HI REG_IND_DATA_7
+#define REG_IND_DATA_LO REG_IND_DATA_3
+
+#define REG_INT_STATUS 0x7C
+#define REG_INT_ENABLE 0x7D
+
+#define INT_PME BIT(4)
+
+#define REG_ACL_INT_STATUS 0x7E
+#define REG_ACL_INT_ENABLE 0x7F
+
+#define INT_PORT_5 BIT(4)
+#define INT_PORT_4 BIT(3)
+#define INT_PORT_3 BIT(2)
+#define INT_PORT_2 BIT(1)
+#define INT_PORT_1 BIT(0)
+
+#define INT_PORT_ALL \
+ (INT_PORT_5 | INT_PORT_4 | INT_PORT_3 | INT_PORT_2 | INT_PORT_1)
+
+#define REG_SW_CTRL_12 0x80
+#define REG_SW_CTRL_13 0x81
+
+#define SWITCH_802_1P_MASK 3
+#define SWITCH_802_1P_BASE 3
+#define SWITCH_802_1P_SHIFT 2
+
+#define SW_802_1P_MAP_M KS_PRIO_M
+#define SW_802_1P_MAP_S KS_PRIO_S
+
+#define REG_SWITCH_CTRL_14 0x82
+
+#define SW_PRIO_MAPPING_M KS_PRIO_M
+#define SW_PRIO_MAPPING_S 6
+#define SW_PRIO_MAP_3_HI 0
+#define SW_PRIO_MAP_2_HI 2
+#define SW_PRIO_MAP_0_LO 3
+
+#define REG_SW_CTRL_15 0x83
+#define REG_SW_CTRL_16 0x84
+#define REG_SW_CTRL_17 0x85
+#define REG_SW_CTRL_18 0x86
+
+#define SW_SELF_ADDR_FILTER_ENABLE BIT(6)
+
+#define REG_SW_UNK_UCAST_CTRL 0x83
+#define REG_SW_UNK_MCAST_CTRL 0x84
+#define REG_SW_UNK_VID_CTRL 0x85
+#define REG_SW_UNK_IP_MCAST_CTRL 0x86
+
+#define SW_UNK_FWD_ENABLE BIT(5)
+#define SW_UNK_FWD_MAP KS_PORT_M
+
+#define REG_SW_CTRL_19 0x87
+
+#define SW_IN_RATE_LIMIT_PERIOD_M 0x3
+#define SW_IN_RATE_LIMIT_PERIOD_S 4
+#define SW_IN_RATE_LIMIT_16_MS 0
+#define SW_IN_RATE_LIMIT_64_MS 1
+#define SW_IN_RATE_LIMIT_256_MS 2
+#define SW_OUT_RATE_LIMIT_QUEUE_BASED BIT(3)
+#define SW_INS_TAG_ENABLE BIT(2)
+
+#define REG_TOS_PRIO_CTRL_0 0x90
+#define REG_TOS_PRIO_CTRL_1 0x91
+#define REG_TOS_PRIO_CTRL_2 0x92
+#define REG_TOS_PRIO_CTRL_3 0x93
+#define REG_TOS_PRIO_CTRL_4 0x94
+#define REG_TOS_PRIO_CTRL_5 0x95
+#define REG_TOS_PRIO_CTRL_6 0x96
+#define REG_TOS_PRIO_CTRL_7 0x97
+#define REG_TOS_PRIO_CTRL_8 0x98
+#define REG_TOS_PRIO_CTRL_9 0x99
+#define REG_TOS_PRIO_CTRL_10 0x9A
+#define REG_TOS_PRIO_CTRL_11 0x9B
+#define REG_TOS_PRIO_CTRL_12 0x9C
+#define REG_TOS_PRIO_CTRL_13 0x9D
+#define REG_TOS_PRIO_CTRL_14 0x9E
+#define REG_TOS_PRIO_CTRL_15 0x9F
+
+#define TOS_PRIO_M KS_PRIO_M
+#define TOS_PRIO_S KS_PRIO_S
+
+#define REG_SW_CTRL_20 0xA3
+
+#define SW_GMII_DRIVE_STRENGTH_S 4
+#define SW_DRIVE_STRENGTH_M 0x7
+#define SW_DRIVE_STRENGTH_2MA 0
+#define SW_DRIVE_STRENGTH_4MA 1
+#define SW_DRIVE_STRENGTH_8MA 2
+#define SW_DRIVE_STRENGTH_12MA 3
+#define SW_DRIVE_STRENGTH_16MA 4
+#define SW_DRIVE_STRENGTH_20MA 5
+#define SW_DRIVE_STRENGTH_24MA 6
+#define SW_DRIVE_STRENGTH_28MA 7
+#define SW_MII_DRIVE_STRENGTH_S 0
+
+#define REG_SW_CTRL_21 0xA4
+
+#define SW_IPV6_MLD_OPTION BIT(3)
+#define SW_IPV6_MLD_SNOOP BIT(2)
+
+#define REG_PORT_1_CTRL_12 0xB0
+#define REG_PORT_2_CTRL_12 0xC0
+#define REG_PORT_3_CTRL_12 0xD0
+#define REG_PORT_4_CTRL_12 0xE0
+#define REG_PORT_5_CTRL_12 0xF0
+
+#define PORT_PASS_ALL BIT(6)
+#define PORT_INS_TAG_FOR_PORT_5_S 3
+#define PORT_INS_TAG_FOR_PORT_5 BIT(3)
+#define PORT_INS_TAG_FOR_PORT_4 BIT(2)
+#define PORT_INS_TAG_FOR_PORT_3 BIT(1)
+#define PORT_INS_TAG_FOR_PORT_2 BIT(0)
+
+#define REG_PORT_1_CTRL_13 0xB1
+#define REG_PORT_2_CTRL_13 0xC1
+#define REG_PORT_3_CTRL_13 0xD1
+#define REG_PORT_4_CTRL_13 0xE1
+#define REG_PORT_5_CTRL_13 0xF1
+
+#define PORT_QUEUE_SPLIT_H BIT(1)
+#define PORT_QUEUE_SPLIT_1 0
+#define PORT_QUEUE_SPLIT_2 1
+#define PORT_QUEUE_SPLIT_4 2
+#define PORT_DROP_TAG BIT(0)
+
+#define REG_PORT_1_CTRL_14 0xB2
+#define REG_PORT_2_CTRL_14 0xC2
+#define REG_PORT_3_CTRL_14 0xD2
+#define REG_PORT_4_CTRL_14 0xE2
+#define REG_PORT_5_CTRL_14 0xF2
+#define REG_PORT_1_CTRL_15 0xB3
+#define REG_PORT_2_CTRL_15 0xC3
+#define REG_PORT_3_CTRL_15 0xD3
+#define REG_PORT_4_CTRL_15 0xE3
+#define REG_PORT_5_CTRL_15 0xF3
+#define REG_PORT_1_CTRL_16 0xB4
+#define REG_PORT_2_CTRL_16 0xC4
+#define REG_PORT_3_CTRL_16 0xD4
+#define REG_PORT_4_CTRL_16 0xE4
+#define REG_PORT_5_CTRL_16 0xF4
+#define REG_PORT_1_CTRL_17 0xB5
+#define REG_PORT_2_CTRL_17 0xC5
+#define REG_PORT_3_CTRL_17 0xD5
+#define REG_PORT_4_CTRL_17 0xE5
+#define REG_PORT_5_CTRL_17 0xF5
+
+#define REG_PORT_1_RATE_CTRL_3 0xB2
+#define REG_PORT_1_RATE_CTRL_2 0xB3
+#define REG_PORT_1_RATE_CTRL_1 0xB4
+#define REG_PORT_1_RATE_CTRL_0 0xB5
+#define REG_PORT_2_RATE_CTRL_3 0xC2
+#define REG_PORT_2_RATE_CTRL_2 0xC3
+#define REG_PORT_2_RATE_CTRL_1 0xC4
+#define REG_PORT_2_RATE_CTRL_0 0xC5
+#define REG_PORT_3_RATE_CTRL_3 0xD2
+#define REG_PORT_3_RATE_CTRL_2 0xD3
+#define REG_PORT_3_RATE_CTRL_1 0xD4
+#define REG_PORT_3_RATE_CTRL_0 0xD5
+#define REG_PORT_4_RATE_CTRL_3 0xE2
+#define REG_PORT_4_RATE_CTRL_2 0xE3
+#define REG_PORT_4_RATE_CTRL_1 0xE4
+#define REG_PORT_4_RATE_CTRL_0 0xE5
+#define REG_PORT_5_RATE_CTRL_3 0xF2
+#define REG_PORT_5_RATE_CTRL_2 0xF3
+#define REG_PORT_5_RATE_CTRL_1 0xF4
+#define REG_PORT_5_RATE_CTRL_0 0xF5
+
+#define RATE_CTRL_ENABLE BIT(7)
+#define RATE_RATIO_M (BIT(7) - 1)
+
+#define PORT_OUT_RATE_ENABLE BIT(7)
+
+#define REG_PORT_1_RATE_LIMIT 0xB6
+#define REG_PORT_2_RATE_LIMIT 0xC6
+#define REG_PORT_3_RATE_LIMIT 0xD6
+#define REG_PORT_4_RATE_LIMIT 0xE6
+#define REG_PORT_5_RATE_LIMIT 0xF6
+
+#define PORT_IN_PORT_BASED_S 6
+#define PORT_RATE_PACKET_BASED_S 5
+#define PORT_IN_FLOW_CTRL_S 4
+#define PORT_IN_LIMIT_MODE_M 0x3
+#define PORT_IN_LIMIT_MODE_S 2
+#define PORT_COUNT_IFG_S 1
+#define PORT_COUNT_PREAMBLE_S 0
+#define PORT_IN_PORT_BASED BIT(PORT_IN_PORT_BASED_S)
+#define PORT_RATE_PACKET_BASED BIT(PORT_RATE_PACKET_BASED_S)
+#define PORT_IN_FLOW_CTRL BIT(PORT_IN_FLOW_CTRL_S)
+#define PORT_IN_ALL 0
+#define PORT_IN_UNICAST 1
+#define PORT_IN_MULTICAST 2
+#define PORT_IN_BROADCAST 3
+#define PORT_COUNT_IFG BIT(PORT_COUNT_IFG_S)
+#define PORT_COUNT_PREAMBLE BIT(PORT_COUNT_PREAMBLE_S)
+
+#define REG_PORT_1_IN_RATE_0 0xB7
+#define REG_PORT_2_IN_RATE_0 0xC7
+#define REG_PORT_3_IN_RATE_0 0xD7
+#define REG_PORT_4_IN_RATE_0 0xE7
+#define REG_PORT_5_IN_RATE_0 0xF7
+#define REG_PORT_1_IN_RATE_1 0xB8
+#define REG_PORT_2_IN_RATE_1 0xC8
+#define REG_PORT_3_IN_RATE_1 0xD8
+#define REG_PORT_4_IN_RATE_1 0xE8
+#define REG_PORT_5_IN_RATE_1 0xF8
+#define REG_PORT_1_IN_RATE_2 0xB9
+#define REG_PORT_2_IN_RATE_2 0xC9
+#define REG_PORT_3_IN_RATE_2 0xD9
+#define REG_PORT_4_IN_RATE_2 0xE9
+#define REG_PORT_5_IN_RATE_2 0xF9
+#define REG_PORT_1_IN_RATE_3 0xBA
+#define REG_PORT_2_IN_RATE_3 0xCA
+#define REG_PORT_3_IN_RATE_3 0xDA
+#define REG_PORT_4_IN_RATE_3 0xEA
+#define REG_PORT_5_IN_RATE_3 0xFA
+
+#define PORT_IN_RATE_ENABLE BIT(7)
+#define PORT_RATE_LIMIT_M (BIT(7) - 1)
+
+#define REG_PORT_1_OUT_RATE_0 0xBB
+#define REG_PORT_2_OUT_RATE_0 0xCB
+#define REG_PORT_3_OUT_RATE_0 0xDB
+#define REG_PORT_4_OUT_RATE_0 0xEB
+#define REG_PORT_5_OUT_RATE_0 0xFB
+#define REG_PORT_1_OUT_RATE_1 0xBC
+#define REG_PORT_2_OUT_RATE_1 0xCC
+#define REG_PORT_3_OUT_RATE_1 0xDC
+#define REG_PORT_4_OUT_RATE_1 0xEC
+#define REG_PORT_5_OUT_RATE_1 0xFC
+#define REG_PORT_1_OUT_RATE_2 0xBD
+#define REG_PORT_2_OUT_RATE_2 0xCD
+#define REG_PORT_3_OUT_RATE_2 0xDD
+#define REG_PORT_4_OUT_RATE_2 0xED
+#define REG_PORT_5_OUT_RATE_2 0xFD
+#define REG_PORT_1_OUT_RATE_3 0xBE
+#define REG_PORT_2_OUT_RATE_3 0xCE
+#define REG_PORT_3_OUT_RATE_3 0xDE
+#define REG_PORT_4_OUT_RATE_3 0xEE
+#define REG_PORT_5_OUT_RATE_3 0xFE
+
+/* PME */
+
+#define SW_PME_OUTPUT_ENABLE BIT(1)
+#define SW_PME_ACTIVE_HIGH BIT(0)
+
+#define PORT_MAGIC_PACKET_DETECT BIT(2)
+#define PORT_LINK_UP_DETECT BIT(1)
+#define PORT_ENERGY_DETECT BIT(0)
+
+/* ACL */
+
+#define ACL_FIRST_RULE_M 0xF
+
+#define ACL_MODE_M 0x3
+#define ACL_MODE_S 4
+#define ACL_MODE_DISABLE 0
+#define ACL_MODE_LAYER_2 1
+#define ACL_MODE_LAYER_3 2
+#define ACL_MODE_LAYER_4 3
+#define ACL_ENABLE_M 0x3
+#define ACL_ENABLE_S 2
+#define ACL_ENABLE_2_COUNT 0
+#define ACL_ENABLE_2_TYPE 1
+#define ACL_ENABLE_2_MAC 2
+#define ACL_ENABLE_2_BOTH 3
+#define ACL_ENABLE_3_IP 1
+#define ACL_ENABLE_3_SRC_DST_COMP 2
+#define ACL_ENABLE_4_PROTOCOL 0
+#define ACL_ENABLE_4_TCP_PORT_COMP 1
+#define ACL_ENABLE_4_UDP_PORT_COMP 2
+#define ACL_ENABLE_4_TCP_SEQN_COMP 3
+#define ACL_SRC BIT(1)
+#define ACL_EQUAL BIT(0)
+
+#define ACL_MAX_PORT 0xFFFF
+
+#define ACL_MIN_PORT 0xFFFF
+#define ACL_IP_ADDR 0xFFFFFFFF
+#define ACL_TCP_SEQNUM 0xFFFFFFFF
+
+#define ACL_RESERVED 0xF8
+#define ACL_PORT_MODE_M 0x3
+#define ACL_PORT_MODE_S 1
+#define ACL_PORT_MODE_DISABLE 0
+#define ACL_PORT_MODE_EITHER 1
+#define ACL_PORT_MODE_IN_RANGE 2
+#define ACL_PORT_MODE_OUT_OF_RANGE 3
+
+#define ACL_TCP_FLAG_ENABLE BIT(0)
+
+#define ACL_TCP_FLAG_M 0xFF
+
+#define ACL_TCP_FLAG 0xFF
+#define ACL_ETH_TYPE 0xFFFF
+#define ACL_IP_M 0xFFFFFFFF
+
+#define ACL_PRIO_MODE_M 0x3
+#define ACL_PRIO_MODE_S 6
+#define ACL_PRIO_MODE_DISABLE 0
+#define ACL_PRIO_MODE_HIGHER 1
+#define ACL_PRIO_MODE_LOWER 2
+#define ACL_PRIO_MODE_REPLACE 3
+#define ACL_PRIO_M 0x7
+#define ACL_PRIO_S 3
+#define ACL_VLAN_PRIO_REPLACE BIT(2)
+#define ACL_VLAN_PRIO_M 0x7
+#define ACL_VLAN_PRIO_HI_M 0x3
+
+#define ACL_VLAN_PRIO_LO_M 0x8
+#define ACL_VLAN_PRIO_S 7
+#define ACL_MAP_MODE_M 0x3
+#define ACL_MAP_MODE_S 5
+#define ACL_MAP_MODE_DISABLE 0
+#define ACL_MAP_MODE_OR 1
+#define ACL_MAP_MODE_AND 2
+#define ACL_MAP_MODE_REPLACE 3
+#define ACL_MAP_PORT_M 0x1F
+
+#define ACL_CNT_M (BIT(11) - 1)
+#define ACL_CNT_S 5
+#define ACL_MSEC_UNIT BIT(4)
+#define ACL_INTR_MODE BIT(3)
+
+#define REG_PORT_ACL_BYTE_EN_MSB 0x10
+
+#define ACL_BYTE_EN_MSB_M 0x3F
+
+#define REG_PORT_ACL_BYTE_EN_LSB 0x11
+
+#define ACL_ACTION_START 0xA
+#define ACL_ACTION_LEN 2
+#define ACL_INTR_CNT_START 0xB
+#define ACL_RULESET_START 0xC
+#define ACL_RULESET_LEN 2
+#define ACL_TABLE_LEN 14
+
+#define ACL_ACTION_ENABLE 0x000C
+#define ACL_MATCH_ENABLE 0x1FF0
+#define ACL_RULESET_ENABLE 0x2003
+#define ACL_BYTE_ENABLE ((ACL_BYTE_EN_MSB_M << 8) | 0xFF)
+#define ACL_MODE_ENABLE (0x10 << 8)
+
+#define REG_PORT_ACL_CTRL_0 0x12
+
+#define PORT_ACL_WRITE_DONE BIT(6)
+#define PORT_ACL_READ_DONE BIT(5)
+#define PORT_ACL_WRITE BIT(4)
+#define PORT_ACL_INDEX_M 0xF
+
+#define REG_PORT_ACL_CTRL_1 0x13
+
+#define PORT_ACL_FORCE_DLR_MISS BIT(0)
+
+#ifndef PHY_REG_CTRL
+#define PHY_REG_CTRL 0
+
+#define PHY_RESET BIT(15)
+#define PHY_LOOPBACK BIT(14)
+#define PHY_SPEED_100MBIT BIT(13)
+#define PHY_AUTO_NEG_ENABLE BIT(12)
+#define PHY_POWER_DOWN BIT(11)
+#define PHY_MII_DISABLE BIT(10)
+#define PHY_AUTO_NEG_RESTART BIT(9)
+#define PHY_FULL_DUPLEX BIT(8)
+#define PHY_COLLISION_TEST_NOT BIT(7)
+#define PHY_HP_MDIX BIT(5)
+#define PHY_FORCE_MDIX BIT(4)
+#define PHY_AUTO_MDIX_DISABLE BIT(3)
+#define PHY_REMOTE_FAULT_DISABLE BIT(2)
+#define PHY_TRANSMIT_DISABLE BIT(1)
+#define PHY_LED_DISABLE BIT(0)
+
+#define PHY_REG_STATUS 1
+
+#define PHY_100BT4_CAPABLE BIT(15)
+#define PHY_100BTX_FD_CAPABLE BIT(14)
+#define PHY_100BTX_CAPABLE BIT(13)
+#define PHY_10BT_FD_CAPABLE BIT(12)
+#define PHY_10BT_CAPABLE BIT(11)
+#define PHY_MII_SUPPRESS_CAPABLE_NOT BIT(6)
+#define PHY_AUTO_NEG_ACKNOWLEDGE BIT(5)
+#define PHY_REMOTE_FAULT BIT(4)
+#define PHY_AUTO_NEG_CAPABLE BIT(3)
+#define PHY_LINK_STATUS BIT(2)
+#define PHY_JABBER_DETECT_NOT BIT(1)
+#define PHY_EXTENDED_CAPABILITY BIT(0)
+
+#define PHY_REG_ID_1 2
+#define PHY_REG_ID_2 3
+
+#define PHY_REG_AUTO_NEGOTIATION 4
+
+#define PHY_AUTO_NEG_NEXT_PAGE_NOT BIT(15)
+#define PHY_AUTO_NEG_REMOTE_FAULT_NOT BIT(13)
+#define PHY_AUTO_NEG_SYM_PAUSE BIT(10)
+#define PHY_AUTO_NEG_100BT4 BIT(9)
+#define PHY_AUTO_NEG_100BTX_FD BIT(8)
+#define PHY_AUTO_NEG_100BTX BIT(7)
+#define PHY_AUTO_NEG_10BT_FD BIT(6)
+#define PHY_AUTO_NEG_10BT BIT(5)
+#define PHY_AUTO_NEG_SELECTOR 0x001F
+#define PHY_AUTO_NEG_802_3 0x0001
+
+#define PHY_REG_REMOTE_CAPABILITY 5
+
+#define PHY_REMOTE_NEXT_PAGE_NOT BIT(15)
+#define PHY_REMOTE_ACKNOWLEDGE_NOT BIT(14)
+#define PHY_REMOTE_REMOTE_FAULT_NOT BIT(13)
+#define PHY_REMOTE_SYM_PAUSE BIT(10)
+#define PHY_REMOTE_100BTX_FD BIT(8)
+#define PHY_REMOTE_100BTX BIT(7)
+#define PHY_REMOTE_10BT_FD BIT(6)
+#define PHY_REMOTE_10BT BIT(5)
+#endif
+
+#define KSZ8795_ID_HI 0x0022
+#define KSZ8795_ID_LO 0x1550
+
+#define KSZ8795_SW_ID 0x8795
+
+#define PHY_REG_LINK_MD 0x1D
+
+#define PHY_START_CABLE_DIAG BIT(15)
+#define PHY_CABLE_DIAG_RESULT 0x6000
+#define PHY_CABLE_STAT_NORMAL 0x0000
+#define PHY_CABLE_STAT_OPEN 0x2000
+#define PHY_CABLE_STAT_SHORT 0x4000
+#define PHY_CABLE_STAT_FAILED 0x6000
+#define PHY_CABLE_10M_SHORT BIT(12)
+#define PHY_CABLE_FAULT_COUNTER 0x01FF
+
+#define PHY_REG_PHY_CTRL 0x1F
+
+#define PHY_MODE_M 0x7
+#define PHY_MODE_S 8
+#define PHY_STAT_REVERSED_POLARITY BIT(5)
+#define PHY_STAT_MDIX BIT(4)
+#define PHY_FORCE_LINK BIT(3)
+#define PHY_POWER_SAVING_ENABLE BIT(2)
+#define PHY_REMOTE_LOOPBACK BIT(1)
+
+/* Chip resource */
+
+#define PRIO_QUEUES 4
+
+#define KS_PRIO_IN_REG 4
+
+#define TOTAL_PORT_NUM 5
+
+/* Host port can only be last of them. */
+#define SWITCH_PORT_NUM (TOTAL_PORT_NUM - 1)
+
+#define KSZ8795_COUNTER_NUM 0x20
+#define TOTAL_KSZ8795_COUNTER_NUM (KSZ8795_COUNTER_NUM + 4)
+
+#define SWITCH_COUNTER_NUM KSZ8795_COUNTER_NUM
+#define TOTAL_SWITCH_COUNTER_NUM TOTAL_KSZ8795_COUNTER_NUM
+
+/* Common names used by other drivers */
+
+#define P_BCAST_STORM_CTRL REG_PORT_CTRL_0
+#define P_PRIO_CTRL REG_PORT_CTRL_0
+#define P_TAG_CTRL REG_PORT_CTRL_0
+#define P_MIRROR_CTRL REG_PORT_CTRL_1
+#define P_802_1P_CTRL REG_PORT_CTRL_2
+#define P_STP_CTRL REG_PORT_CTRL_2
+#define P_LOCAL_CTRL REG_PORT_CTRL_7
+#define P_REMOTE_STATUS REG_PORT_STATUS_0
+#define P_FORCE_CTRL REG_PORT_CTRL_9
+#define P_NEG_RESTART_CTRL REG_PORT_CTRL_10
+#define P_SPEED_STATUS REG_PORT_STATUS_1
+#define P_LINK_STATUS REG_PORT_STATUS_2
+#define P_PASS_ALL_CTRL REG_PORT_CTRL_12
+#define P_INS_SRC_PVID_CTRL REG_PORT_CTRL_12
+#define P_DROP_TAG_CTRL REG_PORT_CTRL_13
+#define P_RATE_LIMIT_CTRL REG_PORT_RATE_LIMIT
+
+#define S_UNKNOWN_DA_CTRL REG_SWITCH_CTRL_12
+#define S_FORWARD_INVALID_VID_CTRL REG_FORWARD_INVALID_VID
+
+#define S_FLUSH_TABLE_CTRL REG_SW_CTRL_0
+#define S_LINK_AGING_CTRL REG_SW_CTRL_0
+#define S_HUGE_PACKET_CTRL REG_SW_CTRL_1
+#define S_MIRROR_CTRL REG_SW_CTRL_3
+#define S_REPLACE_VID_CTRL REG_SW_CTRL_4
+#define S_PASS_PAUSE_CTRL REG_SW_CTRL_10
+#define S_TAIL_TAG_CTRL REG_SW_CTRL_10
+#define S_802_1P_PRIO_CTRL REG_SW_CTRL_12
+#define S_TOS_PRIO_CTRL REG_TOS_PRIO_CTRL_0
+#define S_IPV6_MLD_CTRL REG_SW_CTRL_21
+
+#define IND_ACC_TABLE(table) ((table) << 8)
+
+/* Driver set switch broadcast storm protection at 10% rate. */
+#define BROADCAST_STORM_PROT_RATE 10
+
+/* 148,800 frames * 67 ms / 100 */
+#define BROADCAST_STORM_VALUE 9969
+
+/**
+ * STATIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * STATIC_MAC_TABLE_FWD_PORTS 00-001F0000-00000000
+ * STATIC_MAC_TABLE_VALID 00-00200000-00000000
+ * STATIC_MAC_TABLE_OVERRIDE 00-00400000-00000000
+ * STATIC_MAC_TABLE_USE_FID 00-00800000-00000000
+ * STATIC_MAC_TABLE_FID 00-7F000000-00000000
+ */
+
+#define STATIC_MAC_TABLE_ADDR 0x0000FFFF
+#define STATIC_MAC_TABLE_FWD_PORTS 0x001F0000
+#define STATIC_MAC_TABLE_VALID 0x00200000
+#define STATIC_MAC_TABLE_OVERRIDE 0x00400000
+#define STATIC_MAC_TABLE_USE_FID 0x00800000
+#define STATIC_MAC_TABLE_FID 0x7F000000
+
+#define STATIC_MAC_FWD_PORTS_S 16
+#define STATIC_MAC_FID_S 24
+
+/**
+ * VLAN_TABLE_FID 00-007F007F-007F007F
+ * VLAN_TABLE_MEMBERSHIP 00-0F800F80-0F800F80
+ * VLAN_TABLE_VALID 00-10001000-10001000
+ */
+
+#define VLAN_TABLE_FID 0x007F
+#define VLAN_TABLE_MEMBERSHIP 0x0F80
+#define VLAN_TABLE_VALID 0x1000
+
+#define VLAN_TABLE_MEMBERSHIP_S 7
+#define VLAN_TABLE_S 16
+
+/**
+ * DYNAMIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF
+ * DYNAMIC_MAC_TABLE_FID 00-007F0000-00000000
+ * DYNAMIC_MAC_TABLE_NOT_READY 00-00800000-00000000
+ * DYNAMIC_MAC_TABLE_SRC_PORT 00-07000000-00000000
+ * DYNAMIC_MAC_TABLE_TIMESTAMP 00-18000000-00000000
+ * DYNAMIC_MAC_TABLE_ENTRIES 7F-E0000000-00000000
+ * DYNAMIC_MAC_TABLE_MAC_EMPTY 80-00000000-00000000
+ */
+
+#define DYNAMIC_MAC_TABLE_ADDR 0x0000FFFF
+#define DYNAMIC_MAC_TABLE_FID 0x007F0000
+#define DYNAMIC_MAC_TABLE_SRC_PORT 0x07000000
+#define DYNAMIC_MAC_TABLE_TIMESTAMP 0x18000000
+#define DYNAMIC_MAC_TABLE_ENTRIES 0xE0000000
+
+#define DYNAMIC_MAC_TABLE_NOT_READY 0x80
+
+#define DYNAMIC_MAC_TABLE_ENTRIES_H 0x7F
+#define DYNAMIC_MAC_TABLE_MAC_EMPTY 0x80
+
+#define DYNAMIC_MAC_FID_S 16
+#define DYNAMIC_MAC_SRC_PORT_S 24
+#define DYNAMIC_MAC_TIMESTAMP_S 27
+#define DYNAMIC_MAC_ENTRIES_S 29
+#define DYNAMIC_MAC_ENTRIES_H_S 3
+
+/**
+ * MIB_COUNTER_VALUE 00-00000000-3FFFFFFF
+ * MIB_TOTAL_BYTES 00-0000000F-FFFFFFFF
+ * MIB_PACKET_DROPPED 00-00000000-0000FFFF
+ * MIB_COUNTER_VALID 00-00000020-00000000
+ * MIB_COUNTER_OVERFLOW 00-00000040-00000000
+ */
+
+#define MIB_COUNTER_OVERFLOW BIT(6)
+#define MIB_COUNTER_VALID BIT(5)
+
+#define MIB_COUNTER_VALUE 0x3FFFFFFF
+
+#define KS_MIB_TOTAL_RX_0 0x100
+#define KS_MIB_TOTAL_TX_0 0x101
+#define KS_MIB_PACKET_DROPPED_RX_0 0x102
+#define KS_MIB_PACKET_DROPPED_TX_0 0x103
+#define KS_MIB_TOTAL_RX_1 0x104
+#define KS_MIB_TOTAL_TX_1 0x105
+#define KS_MIB_PACKET_DROPPED_TX_1 0x106
+#define KS_MIB_PACKET_DROPPED_RX_1 0x107
+#define KS_MIB_TOTAL_RX_2 0x108
+#define KS_MIB_TOTAL_TX_2 0x109
+#define KS_MIB_PACKET_DROPPED_TX_2 0x10A
+#define KS_MIB_PACKET_DROPPED_RX_2 0x10B
+#define KS_MIB_TOTAL_RX_3 0x10C
+#define KS_MIB_TOTAL_TX_3 0x10D
+#define KS_MIB_PACKET_DROPPED_TX_3 0x10E
+#define KS_MIB_PACKET_DROPPED_RX_3 0x10F
+#define KS_MIB_TOTAL_RX_4 0x110
+#define KS_MIB_TOTAL_TX_4 0x111
+#define KS_MIB_PACKET_DROPPED_TX_4 0x112
+#define KS_MIB_PACKET_DROPPED_RX_4 0x113
+
+#define MIB_PACKET_DROPPED 0x0000FFFF
+
+#define MIB_TOTAL_BYTES_H 0x0000000F
+
+#define TAIL_TAG_OVERRIDE BIT(6)
+#define TAIL_TAG_LOOKUP BIT(7)
+
+#define VLAN_TABLE_ENTRIES (4096 / 4)
+#define FID_ENTRIES 128
+
+#endif
^ permalink raw reply related
* [PATCH RFC 3/5] Add KSZ8795 switch driver
From: Tristram.Ha @ 2017-09-07 21:17 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add KSZ8795 switch support with function code.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8795.c
new file mode 100644
index 0000000..e4d4e6a
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz8795.c
@@ -0,0 +1,2066 @@
+/*
+ * Microchip KSZ8795 switch driver
+ *
+ * Copyright (C) 2017 Microchip Technology Inc.
+ * Tristram Ha <Tristram.Ha@microchip.com>
+ *
+ * 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/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_data/microchip-ksz.h>
+#include <linux/phy.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "ksz_priv.h"
+#include "ksz8795.h"
+
+/**
+ * Some counters do not need to be read too often because they are less likely
+ * to increase much.
+ */
+static const struct {
+ int interval;
+ char string[ETH_GSTRING_LEN];
+} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
+ { 1, "rx_hi" },
+ { 4, "rx_undersize" },
+ { 4, "rx_fragments" },
+ { 4, "rx_oversize" },
+ { 4, "rx_jabbers" },
+ { 4, "rx_symbol_err" },
+ { 4, "rx_crc_err" },
+ { 4, "rx_align_err" },
+ { 4, "rx_mac_ctrl" },
+ { 1, "rx_pause" },
+ { 1, "rx_bcast" },
+ { 1, "rx_mcast" },
+ { 1, "rx_ucast" },
+ { 2, "rx_64_or_less" },
+ { 2, "rx_65_127" },
+ { 2, "rx_128_255" },
+ { 2, "rx_256_511" },
+ { 2, "rx_512_1023" },
+ { 2, "rx_1024_1522" },
+ { 2, "rx_1523_2000" },
+ { 2, "rx_2001" },
+ { 1, "tx_hi" },
+ { 4, "tx_late_col" },
+ { 1, "tx_pause" },
+ { 1, "tx_bcast" },
+ { 1, "tx_mcast" },
+ { 1, "tx_ucast" },
+ { 4, "tx_deferred" },
+ { 4, "tx_total_col" },
+ { 4, "tx_exc_col" },
+ { 4, "tx_single_col" },
+ { 4, "tx_mult_col" },
+ { 1, "rx_total" },
+ { 1, "tx_total" },
+ { 2, "rx_discards" },
+ { 2, "tx_discards" },
+};
+
+static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
+{
+ u8 data;
+
+ ksz_read8(dev, addr, &data);
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+ ksz_write8(dev, addr, data);
+}
+
+static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
+ bool set)
+{
+ u32 addr;
+ u8 data;
+
+ addr = PORT_CTRL_ADDR(port, offset);
+ ksz_read8(dev, addr, &data);
+
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+
+ ksz_write8(dev, addr, data);
+}
+
+static int chk_last_port(struct ksz_device *dev, int p)
+{
+ if (dev->last_port && p == dev->last_port)
+ p = dev->cpu_port;
+ return p;
+}
+
+static int ksz_reset_switch(struct ksz_device *dev)
+{
+ /* reset switch */
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1,
+ SW_SOFTWARE_POWER_DOWN << SW_POWER_MANAGEMENT_MODE_S);
+ ksz_write8(dev, REG_POWER_MANAGEMENT_1, 0);
+
+ return 0;
+}
+
+static void port_set_prio_queue(struct ksz_device *dev, int port, int queue)
+{
+ u8 hi;
+ u8 lo;
+
+ switch (queue) {
+ case 4:
+ case 3:
+ queue = PORT_QUEUE_SPLIT_4;
+ break;
+ case 2:
+ queue = PORT_QUEUE_SPLIT_2;
+ break;
+ default:
+ queue = PORT_QUEUE_SPLIT_1;
+ }
+ ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo);
+ ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi);
+ lo &= ~PORT_QUEUE_SPLIT_L;
+ if (queue & 1)
+ lo |= PORT_QUEUE_SPLIT_L;
+ hi &= ~PORT_QUEUE_SPLIT_H;
+ if (queue & 2)
+ hi |= PORT_QUEUE_SPLIT_H;
+ ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo);
+ ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi);
+
+ /* Default is port based for egress rate limit. */
+ if (queue)
+ ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED,
+ true);
+}
+
+static void port_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt)
+{
+ u32 data;
+ u16 ctrl_addr;
+ u8 check;
+ int timeout;
+
+ ctrl_addr = addr + SWITCH_COUNTER_NUM * port;
+
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+ mutex_lock(&dev->stats_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ for (timeout = 1; timeout > 0; timeout--) {
+ ksz_read8(dev, REG_IND_MIB_CHECK, &check);
+
+ if (check & MIB_COUNTER_VALID) {
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ if (check & MIB_COUNTER_OVERFLOW)
+ *cnt += MIB_COUNTER_VALUE + 1;
+ *cnt += data & MIB_COUNTER_VALUE;
+ break;
+ }
+ }
+ mutex_unlock(&dev->stats_mutex);
+}
+
+static void port_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *cnt)
+{
+ u32 data;
+ u16 ctrl_addr;
+ u8 check;
+ int timeout;
+
+ addr -= SWITCH_COUNTER_NUM;
+ ctrl_addr = (KS_MIB_TOTAL_RX_1 - KS_MIB_TOTAL_RX_0) * port;
+ ctrl_addr += addr + KS_MIB_TOTAL_RX_0;
+
+ ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);
+ mutex_lock(&dev->stats_mutex);
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ for (timeout = 1; timeout > 0; timeout--) {
+ ksz_read8(dev, REG_IND_MIB_CHECK, &check);
+
+ if (check & MIB_COUNTER_VALID) {
+ ksz_read32(dev, REG_IND_DATA_LO, &data);
+ if (addr < 2) {
+ u64 total;
+
+ total = check & MIB_TOTAL_BYTES_H;
+ total <<= 32;
+ *cnt += total;
+ *cnt += data;
+ if (check & MIB_COUNTER_OVERFLOW) {
+ total = MIB_TOTAL_BYTES_H + 1;
+ total <<= 32;
+ *cnt += total;
+ }
+ } else {
+ if (check & MIB_COUNTER_OVERFLOW)
+ *cnt += MIB_PACKET_DROPPED + 1;
+ *cnt += data & MIB_PACKET_DROPPED;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&dev->stats_mutex);
+}
+
+static int port_r_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+ struct ksz_port_mib_info *info;
+
+ /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < SWITCH_COUNTER_NUM) {
+ info = &mib->info[mib->cnt_ptr];
+ spin_lock(&dev->mib_read_lock);
+ ++info->read_cnt;
+ if (info->read_cnt >= info->read_max) {
+ info->read_cnt = 0;
+ port_r_mib_cnt(dev, port, mib->cnt_ptr,
+ &info->counter);
+ }
+ spin_unlock(&dev->mib_read_lock);
+ ++mib->cnt_ptr;
+ }
+
+ /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < TOTAL_SWITCH_COUNTER_NUM) {
+ info = &mib->info[mib->cnt_ptr];
+ spin_lock(&dev->mib_read_lock);
+ ++info->read_cnt;
+ if (info->read_cnt >= info->read_max) {
+ info->read_cnt = 0;
+ port_r_mib_pkt(dev, port, mib->cnt_ptr,
+ &info->counter);
+ }
+ spin_unlock(&dev->mib_read_lock);
+ ++mib->cnt_ptr;
+ }
+ mib->cnt_ptr = 0;
+ return 0;
+}
+
+static void port_init_cnt(struct ksz_device *dev, int port)
+{
+ struct ksz_port_mib *mib = &dev->ports[port].mib;
+ struct ksz_port_mib_info *info;
+
+ mib->cnt_ptr = 0;
+
+ /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < SWITCH_COUNTER_NUM) {
+ info = &mib->info[mib->cnt_ptr];
+ info->read_cnt = 0;
+ info->read_max = mib_names[mib->cnt_ptr].interval;
+ port_r_mib_cnt(dev, port, mib->cnt_ptr,
+ &info->counter);
+ info->counter = 0;
+ ++mib->cnt_ptr;
+ }
+
+ /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
+ while (mib->cnt_ptr < TOTAL_SWITCH_COUNTER_NUM) {
+ info->read_cnt = 0;
+ info->read_max = mib_names[mib->cnt_ptr].interval;
+ port_r_mib_pkt(dev, port, mib->cnt_ptr,
+ &info->counter);
+ info->counter = 0;
+ ++mib->cnt_ptr;
+ }
+ mib->cnt_ptr = 0;
+ mib->time = jiffies;
+}
+
+static void ksz_r_table_64(struct ksz_device *dev, int table, u16 addr,
+ u64 *data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr;
+
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+ dev->ops->get(dev, REG_IND_DATA_HI, data, sizeof(u64));
+ *data = be64_to_cpu(*data);
+}
+
+static void ksz_w_table_64(struct ksz_device *dev, int table, u16 addr,
+ u64 data)
+{
+ u16 ctrl_addr;
+
+ ctrl_addr = IND_ACC_TABLE(table) | addr;
+ data = cpu_to_be64(data);
+
+ dev->ops->set(dev, REG_IND_DATA_HI, &data, sizeof(u64));
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+}
+
+static int valid_dyn_entry(struct ksz_device *dev, u8 *data)
+{
+ int timeout = 100;
+
+ do {
+ ksz_read8(dev, REG_IND_DATA_CHECK, data);
+ timeout--;
+ } while ((*data & DYNAMIC_MAC_TABLE_NOT_READY) && timeout);
+
+ /* Entry is not ready for accessing. */
+ if (*data & DYNAMIC_MAC_TABLE_NOT_READY) {
+ return 1;
+ /* Entry is ready for accessing. */
+ } else {
+ ksz_read8(dev, REG_IND_DATA_8, data);
+
+ /* There is no valid entry in the table. */
+ if (*data & DYNAMIC_MAC_TABLE_MAC_EMPTY)
+ return 2;
+ }
+ return 0;
+}
+
+static int ksz_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr,
+ u8 *fid, u8 *src_port, u8 *timestamp,
+ u16 *entries)
+{
+ u32 data_hi;
+ u32 data_lo;
+ u16 ctrl_addr;
+ int rc;
+ u8 data;
+
+ ctrl_addr = IND_ACC_TABLE(TABLE_DYNAMIC_MAC | TABLE_READ) | addr;
+
+ ksz_write16(dev, REG_IND_CTRL_0, ctrl_addr);
+
+ rc = valid_dyn_entry(dev, &data);
+ if (rc == 1) {
+ if (addr == 0)
+ *entries = 0;
+ } else if (rc == 2) {
+ *entries = 0;
+ /* At least one valid entry in the table. */
+ } else {
+ u64 buf;
+
+ dev->ops->get(dev, REG_IND_DATA_HI, &buf, sizeof(buf));
+ buf = be64_to_cpu(buf);
+ data_hi = (u32)(buf >> 32);
+ data_lo = (u32)buf;
+
+ /* Check out how many valid entry in the table. */
+ *entries = (u16)(((((u16)
+ data & DYNAMIC_MAC_TABLE_ENTRIES_H) <<
+ DYNAMIC_MAC_ENTRIES_H_S) |
+ (((data_hi & DYNAMIC_MAC_TABLE_ENTRIES) >>
+ DYNAMIC_MAC_ENTRIES_S))) + 1);
+
+ *fid = (u8)((data_hi & DYNAMIC_MAC_TABLE_FID) >>
+ DYNAMIC_MAC_FID_S);
+ *src_port = (u8)((data_hi & DYNAMIC_MAC_TABLE_SRC_PORT) >>
+ DYNAMIC_MAC_SRC_PORT_S);
+ *timestamp = (u8)((
+ data_hi & DYNAMIC_MAC_TABLE_TIMESTAMP) >>
+ DYNAMIC_MAC_TIMESTAMP_S);
+
+ mac_addr[5] = (u8)data_lo;
+ mac_addr[4] = (u8)(data_lo >> 8);
+ mac_addr[3] = (u8)(data_lo >> 16);
+ mac_addr[2] = (u8)(data_lo >> 24);
+
+ mac_addr[1] = (u8)data_hi;
+ mac_addr[0] = (u8)(data_hi >> 8);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+struct alu_struct {
+ u8 is_static:1;
+ u8 prio_age:3;
+ u8 is_override:1;
+ u8 is_use_fid:1;
+ u8 port_forward:5;
+ u8 fid:7;
+ u8 mac[ETH_ALEN];
+};
+
+static int ksz_r_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ ksz_r_table_64(dev, TABLE_STATIC_MAC, addr, &data);
+ data_hi = data >> 32;
+ data_lo = (u32)data;
+ if (data_hi & (STATIC_MAC_TABLE_VALID | STATIC_MAC_TABLE_OVERRIDE)) {
+ alu->mac[5] = (u8)data_lo;
+ alu->mac[4] = (u8)(data_lo >> 8);
+ alu->mac[3] = (u8)(data_lo >> 16);
+ alu->mac[2] = (u8)(data_lo >> 24);
+ alu->mac[1] = (u8)data_hi;
+ alu->mac[0] = (u8)(data_hi >> 8);
+ alu->port_forward =
+ (u8)((data_hi & STATIC_MAC_TABLE_FWD_PORTS) >>
+ STATIC_MAC_FWD_PORTS_S);
+ alu->is_override =
+ (data_hi & STATIC_MAC_TABLE_OVERRIDE) ? 1 : 0;
+ data_hi >>= 1;
+ alu->is_use_fid = (data_hi & STATIC_MAC_TABLE_USE_FID) ? 1 : 0;
+ alu->fid = (u8)((data_hi & STATIC_MAC_TABLE_FID) >>
+ STATIC_MAC_FID_S);
+ return 0;
+ }
+ return -1;
+}
+
+static void ksz_w_sta_mac_table(struct ksz_device *dev, u16 addr,
+ struct alu_struct *alu)
+{
+ u64 data;
+ u32 data_hi;
+ u32 data_lo;
+
+ data_lo = ((u32)alu->mac[2] << 24) |
+ ((u32)alu->mac[3] << 16) |
+ ((u32)alu->mac[4] << 8) | alu->mac[5];
+ data_hi = ((u32)alu->mac[0] << 8) | alu->mac[1];
+ data_hi |= (u32)alu->port_forward << STATIC_MAC_FWD_PORTS_S;
+
+ if (alu->is_override)
+ data_hi |= STATIC_MAC_TABLE_OVERRIDE;
+ if (alu->is_use_fid) {
+ data_hi |= STATIC_MAC_TABLE_USE_FID;
+ data_hi |= (u32)alu->fid << STATIC_MAC_FID_S;
+ }
+ if (alu->is_static)
+ data_hi |= STATIC_MAC_TABLE_VALID;
+ else
+ data_hi &= ~STATIC_MAC_TABLE_OVERRIDE;
+
+ data = (u64)data_hi << 32 | data_lo;
+ ksz_w_table_64(dev, TABLE_STATIC_MAC, addr, data);
+}
+
+static inline void from_vlan(u16 vlan, u8 *fid, u8 *member, u8 *valid)
+{
+ *fid = (u8)(vlan & VLAN_TABLE_FID);
+ *member = (u8)(vlan & VLAN_TABLE_MEMBERSHIP) >> VLAN_TABLE_MEMBERSHIP_S;
+ *valid = !!(vlan & VLAN_TABLE_VALID);
+}
+
+static inline void to_vlan(u8 fid, u8 member, u8 valid, u16 *vlan)
+{
+ *vlan = fid;
+ *vlan |= (u16)member << VLAN_TABLE_MEMBERSHIP_S;
+ if (valid)
+ *vlan |= VLAN_TABLE_VALID;
+}
+
+static void ksz_r_vlan_entries(struct ksz_device *dev, u16 addr)
+{
+ u64 data;
+ int i;
+
+ ksz_r_table_64(dev, TABLE_VLAN, addr, &data);
+ addr *= 4;
+ for (i = 0; i < 4; i++) {
+ dev->vlan_cache[addr + i].table[0] = (u16)data;
+ data >>= VLAN_TABLE_S;
+ }
+}
+
+static void ksz_r_vlan_table(struct ksz_device *dev, u16 vid,
+ u16 *vlan)
+{
+ u64 buf;
+ u16 *data = (u16 *)&buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz_r_table_64(dev, TABLE_VLAN, addr, &buf);
+ *vlan = data[index];
+}
+
+static void ksz_w_vlan_table(struct ksz_device *dev, u16 vid,
+ u16 vlan)
+{
+ u64 buf;
+ u16 *data = (u16 *)&buf;
+ u16 addr;
+ int index;
+
+ addr = vid / 4;
+ index = vid & 3;
+ ksz_r_table_64(dev, TABLE_VLAN, addr, &buf);
+ data[index] = vlan;
+ dev->vlan_cache[vid].table[0] = vlan;
+ ksz_w_table_64(dev, TABLE_VLAN, addr, buf);
+}
+
+static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds)
+{
+ return DSA_TAG_PROTO_KSZ;
+}
+
+static void ksz_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
+{
+ struct ksz_port *port;
+ u8 ctrl;
+ u8 restart;
+ u8 link;
+ u8 speed;
+ u8 force;
+ u8 p = phy;
+ u16 data = 0;
+ int processed = true;
+
+ port = &dev->ports[p];
+ switch (reg) {
+ case PHY_REG_CTRL:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ ksz_pread8(dev, p, P_FORCE_CTRL, &force);
+ if (restart & PORT_PHY_LOOPBACK)
+ data |= PHY_LOOPBACK;
+ if (force & PORT_FORCE_100_MBIT)
+ data |= PHY_SPEED_100MBIT;
+ if (!(force & PORT_AUTO_NEG_DISABLE))
+ data |= PHY_AUTO_NEG_ENABLE;
+ if (restart & PORT_POWER_DOWN)
+ data |= PHY_POWER_DOWN;
+ if (restart & PORT_AUTO_NEG_RESTART)
+ data |= PHY_AUTO_NEG_RESTART;
+ if (force & PORT_FORCE_FULL_DUPLEX)
+ data |= PHY_FULL_DUPLEX;
+ if (speed & PORT_HP_MDIX)
+ data |= PHY_HP_MDIX;
+ if (restart & PORT_FORCE_MDIX)
+ data |= PHY_FORCE_MDIX;
+ if (restart & PORT_AUTO_MDIX_DISABLE)
+ data |= PHY_AUTO_MDIX_DISABLE;
+ if (restart & PORT_TX_DISABLE)
+ data |= PHY_TRANSMIT_DISABLE;
+ if (restart & PORT_LED_OFF)
+ data |= PHY_LED_DISABLE;
+ break;
+ case PHY_REG_STATUS:
+ ksz_pread8(dev, p, P_LINK_STATUS, &link);
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ data = PHY_100BTX_FD_CAPABLE |
+ PHY_100BTX_CAPABLE |
+ PHY_10BT_FD_CAPABLE |
+ PHY_10BT_CAPABLE |
+ PHY_AUTO_NEG_CAPABLE;
+ if (link & PORT_AUTO_NEG_COMPLETE)
+ data |= PHY_AUTO_NEG_ACKNOWLEDGE;
+ if (link & PORT_STAT_LINK_GOOD) {
+ data |= PHY_LINK_STATUS;
+ if (!port->link_up) {
+ port->link_up = 1;
+ dev->live_ports |= (1 << phy) & dev->on_ports;
+ }
+ } else if (port->link_up) {
+ port->link_down = 1;
+ port->link_up = 0;
+ dev->live_ports &= ~(1 << phy);
+ }
+ break;
+ case PHY_REG_ID_1:
+ data = KSZ8795_ID_HI;
+ break;
+ case PHY_REG_ID_2:
+ data = KSZ8795_ID_LO;
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = PHY_AUTO_NEG_802_3;
+ if (ctrl & PORT_AUTO_NEG_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (ctrl & PORT_AUTO_NEG_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (ctrl & PORT_AUTO_NEG_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (ctrl & PORT_AUTO_NEG_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (ctrl & PORT_AUTO_NEG_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ break;
+ case PHY_REG_REMOTE_CAPABILITY:
+ ksz_pread8(dev, p, P_REMOTE_STATUS, &link);
+ data = PHY_AUTO_NEG_802_3;
+ if (link & PORT_REMOTE_SYM_PAUSE)
+ data |= PHY_AUTO_NEG_SYM_PAUSE;
+ if (link & PORT_REMOTE_100BTX_FD)
+ data |= PHY_AUTO_NEG_100BTX_FD;
+ if (link & PORT_REMOTE_100BTX)
+ data |= PHY_AUTO_NEG_100BTX;
+ if (link & PORT_REMOTE_10BT_FD)
+ data |= PHY_AUTO_NEG_10BT_FD;
+ if (link & PORT_REMOTE_10BT)
+ data |= PHY_AUTO_NEG_10BT;
+ break;
+ default:
+ processed = false;
+ break;
+ }
+ if (processed)
+ *val = data;
+}
+
+static void ksz_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
+{
+ u8 ctrl;
+ u8 restart;
+ u8 speed;
+ u8 data;
+ u8 p = phy;
+
+ switch (reg) {
+ case PHY_REG_CTRL:
+
+ /* Do not support PHY reset function. */
+ if (val & PHY_RESET)
+ break;
+ ksz_pread8(dev, p, P_SPEED_STATUS, &speed);
+ data = speed;
+ if (val & PHY_HP_MDIX)
+ data |= PORT_HP_MDIX;
+ else
+ data &= ~PORT_HP_MDIX;
+ if (data != speed)
+ ksz_pwrite8(dev, p, P_SPEED_STATUS, data);
+ ksz_pread8(dev, p, P_FORCE_CTRL, &ctrl);
+ data = ctrl;
+ if (!(val & PHY_AUTO_NEG_ENABLE))
+ data |= PORT_AUTO_NEG_DISABLE;
+ else
+ data &= ~PORT_AUTO_NEG_DISABLE;
+
+ /* Fiber port does not support auto-negotiation. */
+ if (dev->ports[p].fiber)
+ data |= PORT_AUTO_NEG_DISABLE;
+ if (val & PHY_SPEED_100MBIT)
+ data |= PORT_FORCE_100_MBIT;
+ else
+ data &= ~PORT_FORCE_100_MBIT;
+ if (val & PHY_FULL_DUPLEX)
+ data |= PORT_FORCE_FULL_DUPLEX;
+ else
+ data &= ~PORT_FORCE_FULL_DUPLEX;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_FORCE_CTRL, data);
+ ksz_pread8(dev, p, P_NEG_RESTART_CTRL, &restart);
+ data = restart;
+ if (val & PHY_LED_DISABLE)
+ data |= PORT_LED_OFF;
+ else
+ data &= ~PORT_LED_OFF;
+ if (val & PHY_TRANSMIT_DISABLE)
+ data |= PORT_TX_DISABLE;
+ else
+ data &= ~PORT_TX_DISABLE;
+ if (val & PHY_AUTO_NEG_RESTART)
+ data |= PORT_AUTO_NEG_RESTART;
+ else
+ data &= ~(PORT_AUTO_NEG_RESTART);
+ if (val & PHY_POWER_DOWN)
+ data |= PORT_POWER_DOWN;
+ else
+ data &= ~PORT_POWER_DOWN;
+ if (val & PHY_AUTO_MDIX_DISABLE)
+ data |= PORT_AUTO_MDIX_DISABLE;
+ else
+ data &= ~PORT_AUTO_MDIX_DISABLE;
+ if (val & PHY_FORCE_MDIX)
+ data |= PORT_FORCE_MDIX;
+ else
+ data &= ~PORT_FORCE_MDIX;
+ if (val & PHY_LOOPBACK)
+ data |= PORT_PHY_LOOPBACK;
+ else
+ data &= ~PORT_PHY_LOOPBACK;
+ if (data != restart)
+ ksz_pwrite8(dev, p, P_NEG_RESTART_CTRL, data);
+ break;
+ case PHY_REG_AUTO_NEGOTIATION:
+ ksz_pread8(dev, p, P_LOCAL_CTRL, &ctrl);
+ data = ctrl;
+ data &= ~(PORT_AUTO_NEG_SYM_PAUSE |
+ PORT_AUTO_NEG_100BTX_FD |
+ PORT_AUTO_NEG_100BTX |
+ PORT_AUTO_NEG_10BT_FD |
+ PORT_AUTO_NEG_10BT);
+ if (val & PHY_AUTO_NEG_SYM_PAUSE)
+ data |= PORT_AUTO_NEG_SYM_PAUSE;
+ if (val & PHY_AUTO_NEG_100BTX_FD)
+ data |= PORT_AUTO_NEG_100BTX_FD;
+ if (val & PHY_AUTO_NEG_100BTX)
+ data |= PORT_AUTO_NEG_100BTX;
+ if (val & PHY_AUTO_NEG_10BT_FD)
+ data |= PORT_AUTO_NEG_10BT_FD;
+ if (val & PHY_AUTO_NEG_10BT)
+ data |= PORT_AUTO_NEG_10BT;
+ if (data != ctrl)
+ ksz_pwrite8(dev, p, P_LOCAL_CTRL, data);
+ break;
+ default:
+ break;
+ }
+}
+
+static int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg)
+{
+ struct ksz_device *dev = ds->priv;
+ u16 val = 0xffff;
+
+ ksz_r_phy(dev, addr, reg, &val);
+
+ return val;
+}
+
+static int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val)
+{
+ struct ksz_device *dev = ds->priv;
+
+ ksz_w_phy(dev, addr, reg, val);
+
+ return 0;
+}
+
+static int ksz_sset_count(struct dsa_switch *ds)
+{
+ return TOTAL_SWITCH_COUNTER_NUM;
+}
+
+static void ksz_get_strings(struct dsa_switch *ds, int port, uint8_t *buf)
+{
+ int i;
+
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
+ ETH_GSTRING_LEN);
+ }
+}
+
+static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
+ uint64_t *buf)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port_mib *mib;
+ struct ksz_port_mib_info *info;
+ int i;
+
+ mib = &dev->ports[port].mib;
+
+ spin_lock(&dev->mib_read_lock);
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ info = &mib->info[i];
+ buf[i] = info->counter;
+ info->read_cnt += info->read_max;
+ }
+ spin_unlock(&dev->mib_read_lock);
+
+ mib->ctrl = 1;
+ schedule_work(&dev->mib_read);
+}
+
+static u8 STP_MULTICAST_ADDR[] = {
+ 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00
+};
+
+static void ksz_cfg_port_member(struct ksz_device *dev, int port, u8 member)
+{
+ u8 data;
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+ data &= ~PORT_VLAN_MEMBERSHIP;
+ data |= (member & dev->PORT_MASK);
+ ksz_pwrite8(dev, port, P_MIRROR_CTRL, data);
+ dev->ports[port].member = member;
+}
+
+static void ksz_update_port_member(struct ksz_device *dev, int port)
+{
+ struct ksz_port *p;
+ int i;
+
+ for (i = 0; i < dev->port_cnt; i++) {
+ if (i == port)
+ continue;
+ p = &dev->ports[i];
+ if (!(dev->member & (1 << i)))
+ continue;
+
+ /* Port is a member of the bridge and is forwarding. */
+ if (p->stp_state == BR_STATE_FORWARDING)
+ ksz_cfg_port_member(dev, i, dev->member);
+ }
+}
+
+static int ksz_port_bridge_join(struct dsa_switch *ds, int port,
+ struct net_device *br)
+{
+ struct ksz_device *dev = ds->priv;
+
+ dev->br_member |= (1 << port);
+
+ /**
+ * port_stp_state_set() will be called after to put the port in
+ * appropriate state so there is no need to do anything.
+ */
+
+ return 0;
+}
+
+static void ksz_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct net_device *br)
+{
+ struct ksz_device *dev = ds->priv;
+
+ dev->br_member &= ~(1 << port);
+ dev->member &= ~(1 << port);
+
+ /**
+ * port_stp_state_set() will be called after to put the port in
+ * forwarding state so there is no need to do anything.
+ */
+}
+
+static void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p = &dev->ports[port];
+ u8 data;
+ u16 forward = dev->member;
+ int member = -1;
+
+ ksz_pread8(dev, port, P_STP_CTRL, &data);
+ data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM)
+ member = 0;
+ break;
+ case BR_STATE_LISTENING:
+ data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->HOST_MASK | p->vid_member;
+ break;
+ case BR_STATE_LEARNING:
+ data |= PORT_RX_ENABLE;
+ break;
+ case BR_STATE_FORWARDING:
+ data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
+
+ /* This function is also used internally. */
+ if (port == dev->cpu_port)
+ break;
+
+ /* Port is a member of a bridge. */
+ if (dev->br_member & (1 << port)) {
+ dev->member |= (1 << port);
+ member = dev->member;
+ } else {
+ member = dev->HOST_MASK | p->vid_member;
+ }
+ break;
+ case BR_STATE_BLOCKING:
+ data |= PORT_LEARN_DISABLE;
+ if (port < SWITCH_PORT_NUM &&
+ p->stp_state == BR_STATE_DISABLED)
+ member = dev->HOST_MASK | p->vid_member;
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ ksz_pwrite8(dev, port, P_STP_CTRL, data);
+ p->stp_state = state;
+ if (data & PORT_RX_ENABLE)
+ dev->rx_ports |= (1 << port);
+ else
+ dev->rx_ports &= ~(1 << port);
+ if (data & PORT_TX_ENABLE)
+ dev->tx_ports |= (1 << port);
+ else
+ dev->tx_ports &= ~(1 << port);
+
+ /* Port membership may share register with STP state. */
+ if (member >= 0 && member != p->member)
+ ksz_cfg_port_member(dev, port, (u8)member);
+
+ /* Check if forwarding needs to be updated. */
+ if (state != BR_STATE_FORWARDING) {
+ if (dev->br_member & (1 << port))
+ dev->member &= ~(1 << port);
+ }
+
+ /* Topology is changed. */
+ if (forward != dev->member)
+ ksz_update_port_member(dev, port);
+}
+
+static void ksz_flush_dyn_mac_table(struct ksz_device *dev, int port)
+{
+ int cnt;
+ int first;
+ int index;
+ int last;
+ u8 learn[TOTAL_PORT_NUM];
+
+ if ((uint)port < TOTAL_PORT_NUM) {
+ first = port;
+ cnt = port + 1;
+ } else {
+ /* Flush all ports. */
+ first = 0;
+ cnt = dev->mib_port_cnt;
+ }
+ for (index = first; index < cnt; index++) {
+ last = chk_last_port(dev, index);
+ if (last != index)
+ continue;
+ ksz_pread8(dev, index, P_STP_CTRL, &learn[index]);
+ if (!(learn[index] & PORT_LEARN_DISABLE))
+ ksz_pwrite8(dev, index, P_STP_CTRL,
+ learn[index] | PORT_LEARN_DISABLE);
+ }
+ ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true);
+ for (index = first; index < cnt; index++) {
+ last = chk_last_port(dev, index);
+ if (last != index)
+ continue;
+ ksz_pwrite8(dev, index, P_STP_CTRL, learn[index]);
+ }
+}
+
+static void ksz_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct ksz_device *dev = ds->priv;
+
+ ksz_flush_dyn_mac_table(dev, port);
+}
+
+static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag)
+{
+ struct ksz_device *dev = ds->priv;
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag);
+
+ return 0;
+}
+
+static int ksz_port_vlan_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans)
+{
+ /* nothing needed */
+
+ return 0;
+}
+
+static void ksz_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz_r_vlan_table(dev, vid, &data);
+ from_vlan(data, &fid, &member, &valid);
+
+ /* First time to setup the VLAN entry. */
+ if (!valid) {
+ /* Need to find a way to map VID to FID. */
+ fid = 1;
+ valid = 1;
+ }
+ member |= BIT(port);
+
+ to_vlan(fid, member, valid, &data);
+ ksz_w_vlan_table(dev, vid, data);
+
+ /* change PVID */
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ new_pvid = vid;
+ }
+
+ if (new_pvid) {
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &vid);
+ vid &= 0xfff;
+ vid |= new_pvid;
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, vid);
+ }
+}
+
+static int ksz_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u16 data;
+ u16 vid;
+ u16 pvid;
+ u8 fid;
+ u8 member;
+ u8 valid;
+ u16 new_pvid = 0;
+
+ ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid);
+ pvid = pvid & 0xFFF;
+
+ ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ ksz_r_vlan_table(dev, vid, &data);
+ from_vlan(data, &fid, &member, &valid);
+
+ member &= ~BIT(port);
+
+ /* Invalidate the entry if no more member. */
+ if (!member) {
+ fid = 0;
+ valid = 0;
+ }
+
+ if (pvid == vid)
+ new_pvid = 1;
+
+ to_vlan(fid, member, valid, &data);
+ ksz_w_vlan_table(dev, vid, data);
+ }
+
+ if (new_pvid != pvid)
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, pvid);
+
+ return 0;
+}
+
+static int ksz_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+ u16 i = 0;
+ u16 entries = 0;
+ u8 timestamp = 0;
+ u8 fid;
+ u8 member;
+ struct alu_struct alu;
+
+ mutex_lock(&dev->alu_mutex);
+ do {
+ alu.is_static = false;
+ ret = ksz_r_dyn_mac_table(dev, i, alu.mac, &fid, &member,
+ ×tamp, &entries);
+ if (!ret && (member & BIT(port))) {
+ ret = cb(alu.mac, alu.fid, alu.is_static, data);
+ if (ret)
+ break;
+ }
+ i++;
+ } while (i < entries);
+ mutex_unlock(&dev->alu_mutex);
+ if (i >= entries)
+ ret = 0;
+
+ return ret;
+}
+
+static int ksz_port_mdb_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct switchdev_trans *trans)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static void ksz_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct switchdev_trans *trans)
+{
+ struct ksz_device *dev = ds->priv;
+ struct alu_struct alu;
+ int index;
+ int empty = 0;
+
+ mutex_lock(&dev->alu_mutex);
+
+ for (index = 0; index < dev->num_statics; index++) {
+ if (!ksz_r_sta_mac_table(dev, index, &alu)) {
+ /* Found one already in static MAC table. */
+ if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
+ alu.fid == mdb->vid)
+ break;
+ /* Remember the first empty entry. */
+ } else if (!empty) {
+ empty = index + 1;
+ }
+ }
+
+ /* no available entry */
+ if (index == dev->num_statics && !empty)
+ goto exit;
+
+ /* add entry */
+ if (index == dev->num_statics) {
+ index = empty - 1;
+ memset(&alu, 0, sizeof(alu));
+ memcpy(alu.mac, mdb->addr, ETH_ALEN);
+ alu.is_static = true;
+ }
+ alu.port_forward |= BIT(port);
+ if (mdb->vid) {
+ alu.is_use_fid = true;
+
+ /* Need a way to map VID to FID. */
+ alu.fid = mdb->vid;
+ }
+ ksz_w_sta_mac_table(dev, index, &alu);
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static int ksz_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct ksz_device *dev = ds->priv;
+ struct alu_struct alu;
+ int index;
+ int ret = 0;
+
+ mutex_lock(&dev->alu_mutex);
+
+ for (index = 0; index < dev->num_statics; index++) {
+ if (!ksz_r_sta_mac_table(dev, index, &alu)) {
+ /* Found one already in static MAC table. */
+ if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
+ alu.fid == mdb->vid)
+ break;
+ }
+ }
+
+ /* no available entry */
+ if (index == dev->num_statics) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* clear port */
+ alu.port_forward &= ~BIT(port);
+ if (!alu.port_forward)
+ alu.is_static = false;
+ ksz_w_sta_mac_table(dev, index, &alu);
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
+static int ksz_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
+ dev->mirror_rx |= (1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);
+ dev->mirror_tx |= (1 << port);
+ }
+
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false);
+
+ /* configure mirror port */
+ if (dev->mirror_rx || dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, true);
+
+ return 0;
+}
+
+static void ksz_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror)
+{
+ struct ksz_device *dev = ds->priv;
+ u8 data;
+
+ if (mirror->ingress) {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
+ dev->mirror_rx &= ~(1 << port);
+ } else {
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);
+ dev->mirror_tx &= ~(1 << port);
+ }
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+
+ if (!dev->mirror_rx && !dev->mirror_tx)
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, false);
+}
+
+static u8 ksz_advertised_flow_ctrl(u8 flow_ctrl, u8 ctrl)
+{
+ ctrl &= ~PORT_AUTO_NEG_SYM_PAUSE;
+ switch (flow_ctrl) {
+ case PHY_FLOW_CTRL:
+ ctrl |= PORT_AUTO_NEG_SYM_PAUSE;
+ break;
+
+ /* Not supported. */
+ case PHY_TX_ONLY:
+ case PHY_RX_ONLY:
+ default:
+ break;
+ }
+ return ctrl;
+}
+
+static u8 ksz_determine_flow_ctrl(u8 local, u8 remote, int force)
+{
+ int rx;
+ int tx;
+ u8 flow = 0;
+
+ rx = 0;
+ tx = 0;
+ if (force) {
+ rx = 1;
+ tx = 1;
+ }
+ if (remote & PORT_REMOTE_SYM_PAUSE) {
+ if (local & PORT_AUTO_NEG_SYM_PAUSE) {
+ rx = 1;
+ tx = 1;
+ }
+ }
+ if (rx)
+ flow |= 0x01;
+ if (tx)
+ flow |= 0x02;
+ return flow;
+}
+
+static void port_get_link_speed(struct ksz_device *dev, int port)
+{
+ struct ksz_port *p = &dev->ports[port];
+ u8 data;
+ u8 link;
+ u8 local;
+ u8 remote;
+ u8 status;
+
+ ksz_pread8(dev, port, P_LOCAL_CTRL, &local);
+ ksz_pread8(dev, port, P_REMOTE_STATUS, &remote);
+ ksz_pread8(dev, port, P_SPEED_STATUS, &status);
+ ksz_pread8(dev, port, P_LINK_STATUS, &data);
+
+ /**
+ * The partner capability register is updated but the
+ * auto-negotiation is not completed yet.
+ */
+ link = data & (PORT_AUTO_NEG_COMPLETE | PORT_STAT_LINK_GOOD);
+ link |= status & (PORT_STAT_SPEED_100MBIT | PORT_STAT_FULL_DUPLEX);
+
+ if (local == p->advertised && link == p->link)
+ return;
+
+ if (data & PORT_STAT_LINK_GOOD) {
+ p->speed = SPEED_10;
+ if (status & PORT_STAT_SPEED_100MBIT)
+ p->speed = SPEED_100;
+ p->duplex = 1;
+ if (status & PORT_STAT_FULL_DUPLEX)
+ p->duplex = 2;
+ ksz_port_cfg(dev, port, P_STP_CTRL, PORT_BACK_PRESSURE,
+ p->duplex == 1);
+ p->flow_ctrl = ksz_determine_flow_ctrl(local, remote,
+ p->force);
+ if (status & PORT_RX_FLOW_CTRL)
+ p->flow_ctrl |= 0x10;
+ if (status & PORT_TX_FLOW_CTRL)
+ p->flow_ctrl |= 0x20;
+ p->link_up = 1;
+ dev->live_ports |= (1 << port) & dev->on_ports;
+ } else if (p->link_up) {
+ p->link_up = 0;
+ p->link_down = 1;
+ dev->live_ports &= ~(1 << port);
+ }
+ p->advertised = local;
+ p->partner = remote;
+ p->link = link;
+}
+
+static void port_set_link_speed(struct ksz_device *dev, int port, int speed,
+ int duplex)
+{
+ struct ksz_port *p = &dev->ports[port];
+ u8 adv;
+ u8 cfg;
+ u8 data;
+ u8 local;
+ u8 status;
+
+ if (p->fiber)
+ return;
+
+ ksz_pread8(dev, port, P_LOCAL_CTRL, &local);
+ ksz_pread8(dev, port, P_LINK_STATUS, &status);
+ ksz_pread8(dev, port, P_FORCE_CTRL, &data);
+
+ adv = 0;
+ cfg = 0;
+ if (status & PORT_STAT_LINK_GOOD) {
+ cfg = data;
+ adv = local;
+ }
+
+ data &= ~PORT_AUTO_NEG_DISABLE;
+ local = ksz_advertised_flow_ctrl(PHY_FLOW_CTRL, local);
+
+ local |= PORT_AUTO_NEG_100BTX_FD | PORT_AUTO_NEG_100BTX |
+ PORT_AUTO_NEG_10BT_FD | PORT_AUTO_NEG_10BT;
+
+ if (speed || duplex) {
+ if (speed == SPEED_10)
+ local &= ~(PORT_AUTO_NEG_100BTX_FD |
+ PORT_AUTO_NEG_100BTX);
+ else if (speed == SPEED_100)
+ local &= ~(PORT_AUTO_NEG_10BT_FD |
+ PORT_AUTO_NEG_10BT);
+ if (duplex == 1)
+ local &= ~(PORT_AUTO_NEG_100BTX_FD |
+ PORT_AUTO_NEG_10BT_FD);
+ else if (duplex == 2)
+ local &= ~(PORT_AUTO_NEG_100BTX |
+ PORT_AUTO_NEG_10BT);
+ }
+ if (data != cfg || local != adv) {
+ ksz_pwrite8(dev, port, P_FORCE_CTRL, data);
+ ksz_pwrite8(dev, port, P_LOCAL_CTRL, local);
+ ksz_pread8(dev, port, P_NEG_RESTART_CTRL, &data);
+ data |= PORT_AUTO_NEG_RESTART;
+ ksz_pwrite8(dev, port, P_NEG_RESTART_CTRL, data);
+
+ /* Link is going down. */
+ if (p->link_up)
+ p->link_down = 1;
+ p->link_up = 0;
+ }
+ p->force = 0;
+}
+
+static void port_force_link_speed(struct ksz_device *dev, int port, int speed,
+ int duplex)
+{
+ struct ksz_port *p = &dev->ports[port];
+ u8 data;
+
+ ksz_pread8(dev, port, P_FORCE_CTRL, &data);
+ data |= PORT_AUTO_NEG_DISABLE;
+ if (speed == SPEED_10)
+ data &= ~PORT_FORCE_100_MBIT;
+ else
+ data |= PORT_FORCE_100_MBIT;
+ if (duplex == 1)
+ data &= ~PORT_FORCE_FULL_DUPLEX;
+ else
+ data |= PORT_FORCE_FULL_DUPLEX;
+ ksz_pwrite8(dev, port, P_FORCE_CTRL, data);
+ p->force = 1;
+}
+
+static void ksz_mib_read_work(struct work_struct *work)
+{
+ struct ksz_device *dev =
+ container_of(work, struct ksz_device, mib_read);
+ struct ksz_port *p;
+ struct ksz_port_mib *mib;
+ struct ksz_port_mib_info *info;
+ int i;
+ unsigned long next_jiffies;
+
+ /**
+ * Want to read MIB counters from all ports every second and spread
+ * the register reading around so that this very low priority
+ * operation does not affect critical register access like PTP or
+ * interrupt. In fact the MIB counter reading may stop when an
+ * interrupt is triggered.
+ */
+ next_jiffies = jiffies + dev->MIB_READ_INTERVAL * dev->mib_port_cnt;
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ p = &dev->ports[i];
+ mib = &p->mib;
+ if (mib->cnt_ptr || 1 == mib->ctrl) {
+ if (port_r_cnt(dev, i))
+ break;
+ mib->ctrl = 0;
+
+ if (mib->cnt_ptr == 0)
+ mib->ctrl = 2;
+ } else if (time_is_before_eq_jiffies(mib->time) &&
+ (dev->live_ports & (1 << i))) {
+ mib->ctrl = 1;
+ mib->time = next_jiffies;
+ next_jiffies += dev->MIB_READ_INTERVAL;
+ } else if (p->link_down) {
+ int j;
+
+ spin_lock(&dev->mib_read_lock);
+ for (j = 0; j < TOTAL_SWITCH_COUNTER_NUM; j++) {
+ info = &mib->info[j];
+ info->read_cnt += info->read_max;
+ }
+ spin_unlock(&dev->mib_read_lock);
+ p->link_down = 0;
+
+ /* Read counters one last time after link is lost. */
+ mib->ctrl = 1;
+ }
+ }
+ do {
+ u8 intr_status;
+
+ ksz_read8(dev, REG_INT_STATUS, &intr_status);
+ intr_status &= ~INT_PME;
+ if (!intr_status)
+ break;
+ ksz_write8(dev, REG_INT_STATUS, intr_status);
+ for (i = 0; i < dev->port_cnt; i++)
+ port_get_link_speed(dev, i);
+ } while (0);
+}
+
+static void mib_monitor(unsigned long ptr)
+{
+ struct ksz_device *dev = (struct ksz_device *)ptr;
+
+ mod_timer(&dev->mib_read_timer, jiffies + dev->MIB_READ_INTERVAL);
+ schedule_work(&dev->mib_read);
+}
+
+static void port_setup(struct ksz_device *dev, int port, bool cpu_port)
+{
+ u8 data8;
+ u8 member;
+ struct ksz_port *p = &dev->ports[port];
+
+ /* enable broadcast storm limit */
+ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
+
+ port_set_prio_queue(dev, port, 4);
+
+ /* disable DiffServ priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false);
+
+ /* replace priority */
+ ksz_port_cfg(dev, port, P_802_1P_CTRL, PORT_802_1P_REMAPPING, false);
+
+ /* enable 802.1p priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true);
+
+ if (cpu_port) {
+ /* Configure MII interface for proper network communication. */
+ ksz_read8(dev, REG_PORT_5_CTRL_6, &data8);
+ data8 &= ~PORT_INTERFACE_TYPE;
+ data8 &= ~PORT_GMII_1GPS_MODE;
+ switch (dev->interface) {
+ case PHY_INTERFACE_MODE_MII:
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ data8 |= PORT_INTERFACE_RMII;
+ break;
+ case PHY_INTERFACE_MODE_GMII:
+ data8 |= PORT_GMII_1GPS_MODE;
+ data8 |= PORT_INTERFACE_GMII;
+ break;
+ default:
+ data8 &= ~PORT_RGMII_ID_IN_ENABLE;
+ data8 &= ~PORT_RGMII_ID_OUT_ENABLE;
+ if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ dev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+ data8 |= PORT_RGMII_ID_IN_ENABLE;
+ if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ dev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+ data8 |= PORT_RGMII_ID_OUT_ENABLE;
+ data8 |= PORT_GMII_1GPS_MODE;
+ data8 |= PORT_INTERFACE_RGMII;
+ break;
+ }
+ ksz_write8(dev, REG_PORT_5_CTRL_6, data8);
+
+ member = dev->PORT_MASK;
+ dev->on_ports = dev->HOST_MASK;
+ dev->live_ports = dev->HOST_MASK;
+ } else {
+ member = dev->HOST_MASK | p->vid_member;
+ dev->on_ports |= (1 << port);
+
+ /* Link was detected before port is enabled. */
+ if (p->link_up)
+ dev->live_ports |= (1 << port);
+ }
+ ksz_cfg_port_member(dev, port, member);
+}
+
+static void ksz_config_cpu_port(struct dsa_switch *ds)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_port *p;
+ int i;
+ u8 remote;
+
+ ds->num_ports = dev->port_cnt + 1;
+
+ /* Switch marks the maximum frame with extra byte as oversize. */
+ ksz_cfg(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, true);
+ ksz_cfg(dev, S_TAIL_TAG_CTRL, SW_TAIL_TAG_ENABLE, true);
+
+ p = &dev->ports[dev->cpu_port];
+ p->vid_member = dev->PORT_MASK;
+ p->on = 1;
+
+ port_setup(dev, dev->cpu_port, true);
+ dev->member = dev->HOST_MASK;
+
+ for (i = 0; i < SWITCH_PORT_NUM; i++) {
+ p = &dev->ports[i];
+
+ /**
+ * Initialize to non-zero so that ksz_cfg_port_member() will
+ * be called.
+ */
+ p->vid_member = (1 << i);
+ p->member = dev->PORT_MASK;
+ ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED);
+ if (i == dev->port_cnt)
+ break;
+ p->on = 1;
+ }
+ for (i = 0; i < dev->port_cnt; i++) {
+ p = &dev->ports[i];
+ if (!p->on)
+ continue;
+ ksz_pread8(dev, i, P_REMOTE_STATUS, &remote);
+ if (remote & PORT_FIBER_MODE)
+ p->fiber = 1;
+ port_get_link_speed(dev, i);
+ if (p->fiber) {
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ true);
+ port_force_link_speed(dev, i, 100, 2);
+ } else {
+ u8 data;
+
+ ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
+ false);
+ ksz_pread8(dev, i, P_FORCE_CTRL, &data);
+ data &= ~PORT_FORCE_FULL_DUPLEX;
+ ksz_pwrite8(dev, i, P_FORCE_CTRL, data);
+ port_set_link_speed(dev, i, 0, 0);
+ }
+ }
+}
+
+static int ksz_enable_port(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct ksz_device *dev = ds->priv;
+
+ /* setup slave port */
+ port_setup(dev, port, false);
+
+ /**
+ * port_stp_state_set() will be called after to enable the port so
+ * there is no need to do anything.
+ */
+
+ return 0;
+}
+
+static void ksz_disable_port(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct ksz_device *dev = ds->priv;
+
+ dev->on_ports &= ~(1 << port);
+ dev->live_ports &= ~(1 << port);
+
+ /**
+ * port_stp_state_set() will be called after to disable the port so
+ * there is no need to do anything.
+ */
+}
+
+static int ksz_set_addr(struct dsa_switch *ds, u8 *addr)
+{
+ struct ksz_device *dev = ds->priv;
+
+ mutex_lock(&dev->reg_mutex);
+ dev->ops->set(dev, REG_SW_MAC_ADDR_0, addr, ETH_ALEN);
+ mutex_unlock(&dev->reg_mutex);
+
+ return 0;
+}
+
+static int ksz_setup(struct dsa_switch *ds)
+{
+ u8 data8;
+ u16 data16;
+ u32 value;
+ int i;
+ struct alu_struct alu;
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+
+ ret = ksz_reset_switch(dev);
+ if (ret) {
+ dev_err(ds->dev, "failed to reset switch\n");
+ return ret;
+ }
+
+ spin_lock_init(&dev->mib_read_lock);
+ INIT_WORK(&dev->mib_read, ksz_mib_read_work);
+ setup_timer(&dev->mib_read_timer, mib_monitor, (unsigned long)dev);
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_FLOW_CTRL, true);
+
+ /* Enable automatic fast aging when link changed detected. */
+ ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
+
+ ksz_read8(dev, REG_SW_CTRL_1, &data8);
+
+ /* Enable aggressive back off algorithm in half duplex mode. */
+ data8 |= SW_AGGR_BACKOFF;
+ ksz_write8(dev, REG_SW_CTRL_1, data8);
+
+ ksz_read8(dev, REG_SW_CTRL_2, &data8);
+
+ /* Make sure unicast VLAN boundary is set as default. */
+ data8 |= UNICAST_VLAN_BOUNDARY;
+
+ /* Enable no excessive collision drop. */
+ data8 |= NO_EXC_COLLISION_DROP;
+ ksz_write8(dev, REG_SW_CTRL_2, data8);
+
+ ksz_config_cpu_port(ds);
+
+ ksz_cfg(dev, REG_SW_CTRL_2, MULTICAST_STORM_DISABLE, true);
+
+ ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false);
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
+
+ /* set broadcast storm protection 10% rate */
+ data8 = BROADCAST_STORM_PROT_RATE;
+ value = ((u32)BROADCAST_STORM_VALUE * data8) / 100;
+ if (value > BROADCAST_STORM_RATE)
+ value = BROADCAST_STORM_RATE;
+ ksz_read16(dev, S_REPLACE_VID_CTRL, &data16);
+ data16 &= ~BROADCAST_STORM_RATE;
+ data16 |= value;
+ ksz_write16(dev, S_REPLACE_VID_CTRL, data16);
+
+ for (i = 0; i < VLAN_TABLE_ENTRIES; i++)
+ ksz_r_vlan_entries(dev, i);
+
+ for (i = 0; i < dev->mib_port_cnt; i++)
+ port_init_cnt(dev, i);
+
+ /* Setup STP address for STP operation. */
+ memset(&alu, 0, sizeof(alu));
+ memcpy(alu.mac, STP_MULTICAST_ADDR, ETH_ALEN);
+ alu.is_static = true;
+ alu.is_override = true;
+ alu.port_forward = dev->HOST_MASK;
+
+ mutex_lock(&dev->alu_mutex);
+ ksz_w_sta_mac_table(dev, 0, &alu);
+ mutex_unlock(&dev->alu_mutex);
+
+ /* Start the timer 2 seconds later. */
+ dev->mib_read_timer.expires = jiffies + msecs_to_jiffies(2000);
+ add_timer(&dev->mib_read_timer);
+
+ return 0;
+}
+
+static const struct dsa_switch_ops ksz_switch_ops = {
+ .get_tag_protocol = ksz_get_tag_protocol,
+ .setup = ksz_setup,
+ .set_addr = ksz_set_addr,
+ .phy_read = ksz_phy_read16,
+ .phy_write = ksz_phy_write16,
+ .port_enable = ksz_enable_port,
+ .port_disable = ksz_disable_port,
+ .get_strings = ksz_get_strings,
+ .get_ethtool_stats = ksz_get_ethtool_stats,
+ .get_sset_count = ksz_sset_count,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_fast_age = ksz_port_fast_age,
+ .port_vlan_filtering = ksz_port_vlan_filtering,
+ .port_vlan_prepare = ksz_port_vlan_prepare,
+ .port_vlan_add = ksz_port_vlan_add,
+ .port_vlan_del = ksz_port_vlan_del,
+ .port_fdb_dump = ksz_port_fdb_dump,
+ .port_mdb_prepare = ksz_port_mdb_prepare,
+ .port_mdb_add = ksz_port_mdb_add,
+ .port_mdb_del = ksz_port_mdb_del,
+ .port_mirror_add = ksz_port_mirror_add,
+ .port_mirror_del = ksz_port_mirror_del,
+};
+
+/* Register access function can be disabled if desired. */
+#if 1
+#define KSZ_REGS_SIZE 0x100
+#endif
+
+#ifdef KSZ_REGS_SIZE
+static ssize_t ksz_registers_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ size_t i;
+ u8 reg;
+ struct device *dev;
+ struct ksz_device *swdev;
+
+ if (unlikely(off >= KSZ_REGS_SIZE))
+ return 0;
+
+ if ((off + count) >= KSZ_REGS_SIZE)
+ count = KSZ_REGS_SIZE - off;
+
+ if (unlikely(!count))
+ return count;
+
+ dev = container_of(kobj, struct device, kobj);
+ swdev = dev_get_drvdata(dev);
+
+ reg = off;
+ mutex_lock(&swdev->reg_mutex);
+ i = swdev->ops->get(swdev, reg, buf, count);
+ mutex_unlock(&swdev->reg_mutex);
+ return i;
+}
+
+static ssize_t ksz_registers_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ size_t i;
+ u8 reg;
+ struct device *dev;
+ struct ksz_device *swdev;
+
+ if (unlikely(off >= KSZ_REGS_SIZE))
+ return -EFBIG;
+
+ if ((off + count) >= KSZ_REGS_SIZE)
+ count = KSZ_REGS_SIZE - off;
+
+ if (unlikely(!count))
+ return count;
+
+ dev = container_of(kobj, struct device, kobj);
+ swdev = dev_get_drvdata(dev);
+
+ reg = off;
+ mutex_lock(&swdev->reg_mutex);
+ i = swdev->ops->set(swdev, reg, buf, count);
+ mutex_unlock(&swdev->reg_mutex);
+ return i;
+}
+
+static struct bin_attribute ksz_registers_attr = {
+ .attr = {
+ .name = "registers",
+ .mode = 00600,
+ },
+ .size = KSZ_REGS_SIZE,
+ .read = ksz_registers_read,
+ .write = ksz_registers_write,
+};
+#endif
+
+static u32 ksz_get_port_addr(int port, int offset)
+{
+ return PORT_CTRL_ADDR(port, offset);
+}
+
+/**
+ * This information needs to be communicated to the MAC driver so that it can
+ * receive the frames correctly from the switch as some MAC drivers adhere to
+ * receive exactly 1518 bytes.
+ */
+static int ksz_get_rx_len(struct ksz_device *dev)
+{
+ return 1;
+}
+
+/**
+ * This information tells how many more bytes need to be allocated to
+ * accommodate the tail tag.
+ */
+static int ksz_get_tx_len(struct ksz_device *dev)
+{
+ return 1;
+}
+
+static void ksz_add_tail_tag(struct ksz_device *dev, struct sk_buff *skb,
+ int port)
+{
+ u8 *trailer;
+ int len = 1;
+
+ port = 1 << port;
+
+ /* Need override as the port may be transmit disabled. */
+ if (!memcmp(skb->data, STP_MULTICAST_ADDR, ETH_ALEN))
+ port |= TAIL_TAG_OVERRIDE;
+ trailer = skb_put(skb, len);
+ trailer[0] = (u8)port;
+}
+
+static int ksz_get_tail_tag(struct ksz_device *dev, struct sk_buff *skb,
+ int *port)
+{
+ u8 *trailer;
+ int len = 1;
+
+ trailer = skb_tail_pointer(skb) - len;
+ *port = *trailer;
+ if (*port >= 0 && *port < SWITCH_PORT_NUM) {
+ struct ksz_port *p = &dev->ports[*port];
+
+ /**
+ * Switch is forwarding when port membership includes other
+ * ports.
+ */
+ if (p->member & ~((1 << *port) | dev->HOST_MASK))
+ skb->offload_fwd_mark = 1;
+ }
+ return len;
+}
+
+static int ksz_switch_detect(struct ksz_device *dev)
+{
+ u16 id16;
+ u8 id1;
+ u8 id2;
+ int ret;
+
+ /* read chip id */
+ ret = ksz_read16(dev, REG_CHIP_ID0, &id16);
+ if (ret)
+ return ret;
+
+ id1 = id16 >> 8;
+ id2 = id16 & SW_CHIP_ID_M;
+ if (id1 != FAMILY_ID ||
+ (id2 != CHIP_ID_94 && id2 != CHIP_ID_95))
+ return -ENODEV;
+
+ dev->mib_port_cnt = TOTAL_PORT_NUM;
+ dev->port_cnt = SWITCH_PORT_NUM;
+
+ if (id2 == CHIP_ID_95) {
+ u8 val;
+
+ id2 = 0x95;
+ ksz_read8(dev, REG_PORT_1_STATUS_0, &val);
+ if (val & PORT_FIBER_MODE)
+ id2 = 0x65;
+ } else if (id2 == CHIP_ID_94) {
+ dev->port_cnt--;
+ dev->last_port = dev->port_cnt;
+ dev->features |= USE_FEWER_PORTS;
+ id2 = 0x94;
+ }
+ id16 &= ~0xff;
+ id16 |= id2;
+ dev->chip_id = id16;
+
+ dev->cpu_port = dev->mib_port_cnt - 1;
+ dev->HOST_MASK = (1 << dev->cpu_port);
+
+ dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM;
+
+ /* Try to read MIB counters in all ports every second. */
+ dev->MIB_READ_INTERVAL = msecs_to_jiffies(1000 / dev->mib_port_cnt);
+
+ return 0;
+}
+
+struct ksz_chip_data {
+ u16 chip_id;
+ const char *dev_name;
+ int num_vlans;
+ int num_alus;
+ int num_statics;
+ int cpu_ports;
+ int port_cnt;
+};
+
+static const struct ksz_chip_data ksz_switch_chips[] = {
+ {
+ .chip_id = 0x8795,
+ .dev_name = "KSZ8795",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 4, /* total physical port count */
+ },
+ {
+ .chip_id = 0x8765,
+ .dev_name = "KSZ8765",
+ .num_vlans = 4096,
+ .num_alus = 0,
+ .num_statics = 8,
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 4, /* total physical port count */
+ },
+};
+
+static int ksz_switch_init(struct ksz_device *dev)
+{
+ int i;
+
+ mutex_init(&dev->reg_mutex);
+ mutex_init(&dev->stats_mutex);
+ mutex_init(&dev->alu_mutex);
+ mutex_init(&dev->vlan_mutex);
+
+ dev->ds->ops = &ksz_switch_ops;
+
+ for (i = 0; i < ARRAY_SIZE(ksz_switch_chips); i++) {
+ const struct ksz_chip_data *chip = &ksz_switch_chips[i];
+
+ if (dev->chip_id == chip->chip_id) {
+ dev->name = chip->dev_name;
+ dev->num_vlans = chip->num_vlans;
+ dev->num_alus = chip->num_alus;
+ dev->num_statics = chip->num_statics;
+ dev->port_cnt = chip->port_cnt;
+ dev->cpu_ports = chip->cpu_ports;
+
+ break;
+ }
+ }
+
+ /* no switch found */
+ if (!dev->port_cnt)
+ return -ENODEV;
+
+ dev->PORT_MASK = (1 << dev->port_cnt) - 1;
+ dev->PORT_MASK |= dev->HOST_MASK;
+
+ i = dev->mib_port_cnt;
+ dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i,
+ GFP_KERNEL);
+ if (!dev->ports)
+ return -ENOMEM;
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ dev->ports[i].mib.info =
+ devm_kzalloc(dev->dev,
+ sizeof(struct ksz_port_mib_info) *
+ TOTAL_SWITCH_COUNTER_NUM, GFP_KERNEL);
+ if (!dev->ports[i].mib.info)
+ return -ENOMEM;
+ }
+
+#ifdef KSZ_REGS_SIZE
+ i = sysfs_create_bin_file(&dev->dev->kobj,
+ &ksz_registers_attr);
+#endif
+
+ return 0;
+}
+
+static void ksz_switch_exit(struct ksz_device *dev)
+{
+ if (dev->mib_read_timer.expires)
+ del_timer_sync(&dev->mib_read_timer);
+
+#ifdef KSZ_REGS_SIZE
+ sysfs_remove_bin_file(&dev->dev->kobj, &ksz_registers_attr);
+#endif
+}
+
+static const struct ksz_dev_ops ksz8795_dev_ops = {
+ .get_port_addr = ksz_get_port_addr,
+ .reset = ksz_reset_switch,
+ .get_rx_len = ksz_get_rx_len,
+ .get_tx_len = ksz_get_tx_len,
+ .add_tail_tag = ksz_add_tail_tag,
+ .get_tail_tag = ksz_get_tail_tag,
+ .detect = ksz_switch_detect,
+ .init = ksz_switch_init,
+ .exit = ksz_switch_exit,
+};
+
+int ksz8795_switch_register(struct ksz_device *dev)
+{
+ return ksz_switch_register(dev, &ksz8795_dev_ops);
+}
+EXPORT_SYMBOL(ksz8795_switch_register);
+
+MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ8795 Series Switch DSA Driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
* [PATCH RFC 2/5] Add KSZ8795 switch driver support in Makefile
From: Tristram.Ha @ 2017-09-07 21:17 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add KSZ8795 switch support with SPI access.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
index 0961c30..0d8ba48 100644
--- a/drivers/net/dsa/microchip/Makefile
+++ b/drivers/net/dsa/microchip/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_MICROCHIP_KSZ) += ksz9477.o ksz_common.o
obj-$(CONFIG_MICROCHIP_KSZ_SPI_DRIVER) += ksz_spi.o
+obj-$(CONFIG_MICROCHIP_KSZ8795) += ksz8795.o ksz_common.o
+obj-$(CONFIG_MICROCHIP_KSZ8795_SPI_DRIVER) += ksz8795_spi.o
^ permalink raw reply related
* [PATCH RFC 1/5] Add KSZ8795 switch driver support in Kconfig
From: Tristram.Ha @ 2017-09-07 21:17 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add KSZ8795 switch support with SPI access.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index a8b8f59..0b6225e 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -10,3 +10,21 @@ config MICROCHIP_KSZ_SPI_DRIVER
depends on MICROCHIP_KSZ && SPI
help
Select to enable support for registering switches configured through SPI.
+
+menuconfig MICROCHIP_KSZ8795
+ tristate "Microchip KSZ8795 series switch support"
+ depends on NET_DSA
+ select NET_DSA_TAG_KSZ
+ help
+ This driver adds support for Microchip KSZ8795 switch chips.
+
+config MICROCHIP_KSZ8795_SPI_DRIVER
+ tristate "KSZ8795 series SPI connected switch driver"
+ depends on MICROCHIP_KSZ8795 && SPI
+ default y
+ help
+ This driver accesses KSZ8795 chip through SPI.
+
+ It is required to use the KSZ8795 switch driver as the only access
+ is through SPI.
+
^ permalink raw reply related
* [PATCH RFC 0/5] Add DSA driver support for KSZ8795 switch
From: Tristram.Ha @ 2017-09-07 21:16 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
This series of patches is to add DSA driver support for KSZ8795 switch.
Previous patches for KSZ9477 have to be applied first before these.
This patch set is against net-next.
drivers/net/dsa/microchip/Kconfig | 18 +
drivers/net/dsa/microchip/Makefile | 2 +
drivers/net/dsa/microchip/ksz8795.c | 2066 +++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795.h | 1015 +++++++++++++++
drivers/net/dsa/microchip/ksz8795_spi.c | 229 ++++
5 files changed, 3330 insertions(+)
create mode 100644 drivers/net/dsa/microchip/ksz8795.c
create mode 100644 drivers/net/dsa/microchip/ksz8795.h
create mode 100644 drivers/net/dsa/microchip/ksz8795_spi.c
^ permalink raw reply
* [PATCH RFC] Update documentation for KSZ DSA drivers so that new drivers can be added
From: Tristram.Ha @ 2017-09-07 21:11 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
From: Tristram Ha <Tristram.Ha@microchip.com>
Add other KSZ switches support so that patch check does not complain.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
Documentation/devicetree/bindings/net/dsa/ksz.txt | 117 ++++++++++++----------
1 file changed, 62 insertions(+), 55 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/ksz.txt b/Documentation/devicetree/bindings/net/dsa/ksz.txt
index 0ab8b39..34af0e0 100644
--- a/Documentation/devicetree/bindings/net/dsa/ksz.txt
+++ b/Documentation/devicetree/bindings/net/dsa/ksz.txt
@@ -3,8 +3,15 @@ Microchip KSZ Series Ethernet switches
Required properties:
-- compatible: For external switch chips, compatible string must be exactly one
- of: "microchip,ksz9477"
+- compatible: Should be "microchip,ksz9477" for KSZ9477 chip,
+ "microchip,ksz8795" for KSZ8795 chip,
+ "microchip,ksz8794" for KSZ8794 chip,
+ "microchip,ksz8765" for KSZ8765 chip,
+ "microchip,ksz8895" for KSZ8895 chip,
+ "microchip,ksz8864" for KSZ8864 chip,
+ "microchip,ksz8873" for KSZ8873 chip,
+ "microchip,ksz8863" for KSZ8863 chip,
+ "microchip,ksz8463" for KSZ8463 chip
See Documentation/devicetree/bindings/dsa/dsa.txt for a list of additional required and optional properties.
@@ -13,60 +20,60 @@ Examples:
Ethernet switch connected via SPI to the host, CPU port wired to eth0:
- eth0: ethernet@10001000 {
- fixed-link {
- speed = <1000>;
- full-duplex;
- };
- };
+ eth0: ethernet@10001000 {
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
- spi1: spi@f8008000 {
- pinctrl-0 = <&pinctrl_spi_ksz>;
- cs-gpios = <&pioC 25 0>;
- id = <1>;
- status = "okay";
+ spi1: spi@f8008000 {
+ cs-gpios = <&pioC 25 0>;
+ id = <1>;
+ status = "okay";
- ksz9477: ksz9477@0 {
- compatible = "microchip,ksz9477";
- reg = <0>;
+ ksz9477: ksz9477@0 {
+ compatible = "microchip,ksz9477";
+ reg = <0>;
- spi-max-frequency = <44000000>;
- spi-cpha;
- spi-cpol;
+ spi-max-frequency = <44000000>;
+ spi-cpha;
+ spi-cpol;
+
+ status = "okay";
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ label = "lan1";
+ };
+ port@1 {
+ reg = <1>;
+ label = "lan2";
+ };
+ port@2 {
+ reg = <2>;
+ label = "lan3";
+ };
+ port@3 {
+ reg = <3>;
+ label = "lan4";
+ };
+ port@4 {
+ reg = <4>;
+ label = "lan5";
+ };
+ port@5 {
+ reg = <5>;
+ label = "cpu";
+ ethernet = <ð0>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+ };
+ };
- status = "okay";
- ports {
- #address-cells = <1>;
- #size-cells = <0>;
- port@0 {
- reg = <0>;
- label = "lan1";
- };
- port@1 {
- reg = <1>;
- label = "lan2";
- };
- port@2 {
- reg = <2>;
- label = "lan3";
- };
- port@3 {
- reg = <3>;
- label = "lan4";
- };
- port@4 {
- reg = <4>;
- label = "lan5";
- };
- port@5 {
- reg = <5>;
- label = "cpu";
- ethernet = <ð0>;
- fixed-link {
- speed = <1000>;
- full-duplex;
- };
- };
- };
- };
- };
^ permalink raw reply related
* [PATCH RFC 6/6] Modify tag_ksz.c to support other KSZ switch drivers
From: Tristram.Ha @ 2017-09-07 21:09 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD41121901@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
The KSZ tail tag code can be used by different KSZ switch drivers.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index 010ca0a..d5faf14 100644
--- a/net/dsa/tag_ksz.c
+++ b/net/dsa/tag_ksz.c
@@ -13,35 +13,21 @@
#include <linux/slab.h>
#include <net/dsa.h>
#include "dsa_priv.h"
-
-/* For Ingress (Host -> KSZ), 2 bytes are added before FCS.
- * ---------------------------------------------------------------------------
- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes)
- * ---------------------------------------------------------------------------
- * tag0 : Prioritization (not used now)
- * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5)
- *
- * For Egress (KSZ -> Host), 1 byte is added before FCS.
- * ---------------------------------------------------------------------------
- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes)
- * ---------------------------------------------------------------------------
- * tag0 : zero-based value represents port
- * (eg, 0x00=port1, 0x02=port3, 0x06=port7)
- */
-
-#define KSZ_INGRESS_TAG_LEN 2
-#define KSZ_EGRESS_TAG_LEN 1
+#include "../../drivers/net/dsa/microchip/ksz_priv.h"
static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev) {
struct dsa_slave_priv *p = netdev_priv(dev);
+ struct dsa_switch *ds = p->dp->ds;
+ struct ksz_device *swdev = ds->priv;
struct sk_buff *nskb;
+ int len;
int padlen;
- u8 *tag;
padlen = (skb->len >= ETH_ZLEN) ? 0 : ETH_ZLEN - skb->len;
- if (skb_tailroom(skb) >= padlen + KSZ_INGRESS_TAG_LEN) {
+ len = swdev->dev_ops->get_tx_len(swdev);
+ if (skb_tailroom(skb) >= padlen + len) {
/* Let dsa_slave_xmit() free skb */
if (__skb_put_padto(skb, skb->len + padlen, false))
return NULL;
@@ -49,7 +35,7 @@ static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev)
nskb = skb;
} else {
nskb = alloc_skb(NET_IP_ALIGN + skb->len +
- padlen + KSZ_INGRESS_TAG_LEN, GFP_ATOMIC);
+ padlen + len, GFP_ATOMIC);
if (!nskb)
return NULL;
skb_reserve(nskb, NET_IP_ALIGN);
@@ -70,9 +56,7 @@ static struct sk_buff *ksz_xmit(struct sk_buff *skb, struct net_device *dev)
consume_skb(skb);
}
- tag = skb_put(nskb, KSZ_INGRESS_TAG_LEN);
- tag[0] = 0;
- tag[1] = 1 << p->dp->index; /* destination port */
+ swdev->dev_ops->add_tail_tag(swdev, nskb, p->dp->index);
return nskb;
}
@@ -83,16 +67,16 @@ static struct sk_buff *ksz_rcv(struct sk_buff *skb, struct net_device *dev,
struct dsa_switch_tree *dst = dev->dsa_ptr;
struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
struct dsa_switch *ds = cpu_dp->ds;
- u8 *tag;
+ struct ksz_device *swdev = ds->priv;
+ int len;
int source_port;
- tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN;
+ len = swdev->dev_ops->get_tail_tag(swdev, skb, &source_port);
- source_port = tag[0] & 7;
if (source_port >= ds->num_ports || !ds->ports[source_port].netdev)
return NULL;
- pskb_trim_rcsum(skb, skb->len - KSZ_EGRESS_TAG_LEN);
+ pskb_trim_rcsum(skb, skb->len - len);
skb->dev = ds->ports[source_port].netdev;
^ permalink raw reply
* [PATCH RFC 5/6] Switch SPI driver calls its own driver switch register function
From: Tristram.Ha @ 2017-09-07 21:09 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218F4@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
SPI driver calls own specific switch register function.
Shutdown callback function is added to reset switch to default state.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index c519469..d03eb83 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -25,6 +25,8 @@
#include "ksz_priv.h"
+int ksz9477_switch_register(struct ksz_device *dev);
+
/* SPI frame opcodes */
#define KS_SPIOP_RD 3
#define KS_SPIOP_WR 2
@@ -174,7 +176,7 @@ static int ksz_spi_probe(struct spi_device *spi)
if (spi->dev.platform_data)
dev->pdata = spi->dev.platform_data;
- ret = ksz_switch_register(dev);
+ ret = ksz9477_switch_register(dev);
if (ret)
return ret;
@@ -193,6 +195,14 @@ static int ksz_spi_remove(struct spi_device *spi)
return 0;
}
+static void ksz_spi_shutdown(struct spi_device *spi) {
+ struct ksz_device *dev = spi_get_drvdata(spi);
+
+ if (dev)
+ dev->dev_ops->reset(dev);
+}
+
static const struct of_device_id ksz_dt_ids[] = {
{ .compatible = "microchip,ksz9477" },
{},
@@ -207,6 +217,7 @@ static int ksz_spi_remove(struct spi_device *spi)
},
.probe = ksz_spi_probe,
.remove = ksz_spi_remove,
+ .shutdown = ksz_spi_shutdown,
};
module_spi_driver(ksz_spi_driver);
^ permalink raw reply related
* [PATCH RFC 4/6] The common header ksz_priv.h does not contain chip specific data
From: Tristram.Ha @ 2017-09-07 21:09 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218E7@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
The header ksz_priv.h is used by all KSZ switch drivers so chip specific data are removed and commonly used variables are added.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz_priv.h b/drivers/net/dsa/microchip/ksz_priv.h
index 2a98dbd..343b509 100644
--- a/drivers/net/dsa/microchip/ksz_priv.h
+++ b/drivers/net/dsa/microchip/ksz_priv.h
@@ -25,14 +25,56 @@
#include <linux/etherdevice.h>
#include <net/dsa.h>
-#include "ksz_9477_reg.h"
-
struct ksz_io_ops;
struct vlan_table {
u32 table[3];
};
+enum {
+ PHY_NO_FLOW_CTRL,
+ PHY_FLOW_CTRL,
+ PHY_TX_ONLY,
+ PHY_RX_ONLY
+};
+
+struct ksz_port_mib {
+ u8 cnt_ptr;
+ u8 ctrl;
+ unsigned long time;
+
+ struct ksz_port_mib_info {
+ u64 counter;
+ u8 read_cnt;
+ u8 read_max;
+ } *info;
+};
+
+struct ksz_port {
+ u16 member;
+ u16 vid_member;
+ int stp_state;
+ u16 speed;
+ u8 duplex;
+ u8 advertised;
+ u8 flow_ctrl;
+ u8 link;
+ u8 partner;
+
+ u32 on:1; /* port is not disabled by hardware */
+ u32 fiber:1; /* port is fiber */
+ u32 sgmii:1; /* port is SGMII */
+ u32 force:1;
+ u32 link_down:1; /* link just goes down */
+ u32 link_up:1; /* link is up */
+
+ struct ksz_port_mib mib;
+};
+
+#define USE_FEWER_PORTS BIT(18)
+
+#define PTP_TAG BIT(29)
+
struct ksz_device {
struct dsa_switch *ds;
struct ksz_platform_data *pdata;
@@ -43,6 +85,7 @@ struct ksz_device {
struct mutex alu_mutex; /* ALU access */
struct mutex vlan_mutex; /* vlan access */
const struct ksz_io_ops *ops;
+ const struct ksz_dev_ops *dev_ops;
struct device *dev;
@@ -56,10 +99,32 @@ struct ksz_device {
int cpu_port; /* port connected to CPU */
int cpu_ports; /* port bitmap can be cpu port */
int port_cnt;
+ int mib_cnt;
+ int mib_port_cnt;
+ int last_port; /* ports after that not used */
+ int interface;
struct vlan_table *vlan_cache;
- u64 mib_value[TOTAL_SWITCH_COUNTER_NUM];
+ u8 *txbuf;
+
+ struct ksz_port *ports;
+ struct timer_list mib_read_timer;
+ struct work_struct mib_read;
+ spinlock_t mib_read_lock; /* use to update read_cnt */
+ unsigned long MIB_READ_INTERVAL;
+ u32 features;
+ u32 overrides;
+ u16 br_member;
+ u16 member;
+ u16 live_ports;
+ u16 on_ports; /* ports enabled by DSA */
+ u16 rx_ports;
+ u16 tx_ports;
+ u16 mirror_rx;
+ u16 mirror_tx;
+ u16 HOST_MASK;
+ u16 PORT_MASK;
};
struct ksz_io_ops {
@@ -75,12 +140,30 @@ struct ksz_io_ops {
u16 *value);
int (*phy_write16)(struct ksz_device *dev, int addr, int reg,
u16 value);
+ unsigned int (*get)(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len);
+ unsigned int (*set)(struct ksz_device *dev, u32 reg, void *data,
+ unsigned int len);
+};
+
+struct ksz_dev_ops {
+ u32 (*get_port_addr)(int port, int offset);
+ int (*reset)(struct ksz_device *dev);
+ int (*get_rx_len)(struct ksz_device *dev);
+ int (*get_tx_len)(struct ksz_device *dev);
+ void (*add_tail_tag)(struct ksz_device *dev, struct sk_buff *skb,
+ int port);
+ int (*get_tail_tag)(struct ksz_device *dev, struct sk_buff *skb,
+ int *port);
+ int (*detect)(struct ksz_device *dev);
+ int (*init)(struct ksz_device *dev);
+ void (*exit)(struct ksz_device *dev);
};
struct ksz_device *ksz_switch_alloc(struct device *base,
const struct ksz_io_ops *ops, void *priv); -int ksz_switch_detect(struct ksz_device *dev); -int ksz_switch_register(struct ksz_device *dev);
+int ksz_switch_register(struct ksz_device *dev,
+ const struct ksz_dev_ops *ops);
void ksz_switch_remove(struct ksz_device *dev);
static inline int ksz_read8(struct ksz_device *dev, u32 reg, u8 *val) @@ -174,37 +257,37 @@ static inline int ksz_write32(struct ksz_device *dev, u32 reg, u32 value) static inline void ksz_pread8(struct ksz_device *dev, int port, int offset,
u8 *data)
{
- ksz_read8(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_read8(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
static inline void ksz_pread16(struct ksz_device *dev, int port, int offset,
u16 *data)
{
- ksz_read16(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_read16(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
static inline void ksz_pread32(struct ksz_device *dev, int port, int offset,
u32 *data)
{
- ksz_read32(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_read32(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
static inline void ksz_pwrite8(struct ksz_device *dev, int port, int offset,
u8 data)
{
- ksz_write8(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_write8(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
static inline void ksz_pwrite16(struct ksz_device *dev, int port, int offset,
u16 data)
{
- ksz_write16(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_write16(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
static inline void ksz_pwrite32(struct ksz_device *dev, int port, int offset,
u32 data)
{
- ksz_write32(dev, PORT_CTRL_ADDR(port, offset), data);
+ ksz_write32(dev, dev->dev_ops->get_port_addr(port, offset), data);
}
#endif
^ permalink raw reply related
* [PATCH RFC 3/6] The file ksz_common.c contains common code shared by all KSZ switch drivers
From: Tristram.Ha @ 2017-09-07 21:09 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218DC@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
The file ksz_common.c only holds common code used by all KSZ switch drivers.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index 56cd6d3..bebcc65 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -25,1114 +25,12 @@
#include <linux/phy.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
+#include <linux/of_net.h>
#include <net/dsa.h>
#include <net/switchdev.h>
#include "ksz_priv.h"
-static const struct {
- int index;
- char string[ETH_GSTRING_LEN];
-} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
- { 0x00, "rx_hi" },
- { 0x01, "rx_undersize" },
- { 0x02, "rx_fragments" },
- { 0x03, "rx_oversize" },
- { 0x04, "rx_jabbers" },
- { 0x05, "rx_symbol_err" },
- { 0x06, "rx_crc_err" },
- { 0x07, "rx_align_err" },
- { 0x08, "rx_mac_ctrl" },
- { 0x09, "rx_pause" },
- { 0x0A, "rx_bcast" },
- { 0x0B, "rx_mcast" },
- { 0x0C, "rx_ucast" },
- { 0x0D, "rx_64_or_less" },
- { 0x0E, "rx_65_127" },
- { 0x0F, "rx_128_255" },
- { 0x10, "rx_256_511" },
- { 0x11, "rx_512_1023" },
- { 0x12, "rx_1024_1522" },
- { 0x13, "rx_1523_2000" },
- { 0x14, "rx_2001" },
- { 0x15, "tx_hi" },
- { 0x16, "tx_late_col" },
- { 0x17, "tx_pause" },
- { 0x18, "tx_bcast" },
- { 0x19, "tx_mcast" },
- { 0x1A, "tx_ucast" },
- { 0x1B, "tx_deferred" },
- { 0x1C, "tx_total_col" },
- { 0x1D, "tx_exc_col" },
- { 0x1E, "tx_single_col" },
- { 0x1F, "tx_mult_col" },
- { 0x80, "rx_total" },
- { 0x81, "tx_total" },
- { 0x82, "rx_discards" },
- { 0x83, "tx_discards" },
-};
-
-static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) -{
- u8 data;
-
- ksz_read8(dev, addr, &data);
- if (set)
- data |= bits;
- else
- data &= ~bits;
- ksz_write8(dev, addr, data);
-}
-
-static void ksz_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) -{
- u32 data;
-
- ksz_read32(dev, addr, &data);
- if (set)
- data |= bits;
- else
- data &= ~bits;
- ksz_write32(dev, addr, data);
-}
-
-static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
- bool set)
-{
- u32 addr;
- u8 data;
-
- addr = PORT_CTRL_ADDR(port, offset);
- ksz_read8(dev, addr, &data);
-
- if (set)
- data |= bits;
- else
- data &= ~bits;
-
- ksz_write8(dev, addr, data);
-}
-
-static void ksz_port_cfg32(struct ksz_device *dev, int port, int offset,
- u32 bits, bool set)
-{
- u32 addr;
- u32 data;
-
- addr = PORT_CTRL_ADDR(port, offset);
- ksz_read32(dev, addr, &data);
-
- if (set)
- data |= bits;
- else
- data &= ~bits;
-
- ksz_write32(dev, addr, data);
-}
-
-static int wait_vlan_ctrl_ready(struct ksz_device *dev, u32 waiton, int timeout) -{
- u8 data;
-
- do {
- ksz_read8(dev, REG_SW_VLAN_CTRL, &data);
- if (!(data & waiton))
- break;
- usleep_range(1, 10);
- } while (timeout-- > 0);
-
- if (timeout <= 0)
- return -ETIMEDOUT;
-
- return 0;
-}
-
-static int get_vlan_table(struct dsa_switch *ds, u16 vid, u32 *vlan_table) -{
- struct ksz_device *dev = ds->priv;
- int ret;
-
- mutex_lock(&dev->vlan_mutex);
-
- ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
- ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START);
-
- /* wait to be cleared */
- ret = wait_vlan_ctrl_ready(dev, VLAN_START, 1000);
- if (ret < 0) {
- dev_dbg(dev->dev, "Failed to read vlan table\n");
- goto exit;
- }
-
- ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]);
- ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]);
- ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]);
-
- ksz_write8(dev, REG_SW_VLAN_CTRL, 0);
-
-exit:
- mutex_unlock(&dev->vlan_mutex);
-
- return ret;
-}
-
-static int set_vlan_table(struct dsa_switch *ds, u16 vid, u32 *vlan_table) -{
- struct ksz_device *dev = ds->priv;
- int ret;
-
- mutex_lock(&dev->vlan_mutex);
-
- ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]);
- ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]);
- ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_table[2]);
-
- ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
- ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE);
-
- /* wait to be cleared */
- ret = wait_vlan_ctrl_ready(dev, VLAN_START, 1000);
- if (ret < 0) {
- dev_dbg(dev->dev, "Failed to write vlan table\n");
- goto exit;
- }
-
- ksz_write8(dev, REG_SW_VLAN_CTRL, 0);
-
- /* update vlan cache table */
- dev->vlan_cache[vid].table[0] = vlan_table[0];
- dev->vlan_cache[vid].table[1] = vlan_table[1];
- dev->vlan_cache[vid].table[2] = vlan_table[2];
-
-exit:
- mutex_unlock(&dev->vlan_mutex);
-
- return ret;
-}
-
-static void read_table(struct dsa_switch *ds, u32 *table) -{
- struct ksz_device *dev = ds->priv;
-
- ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]);
- ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]);
- ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]);
- ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]);
-}
-
-static void write_table(struct dsa_switch *ds, u32 *table) -{
- struct ksz_device *dev = ds->priv;
-
- ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]);
- ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]);
- ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]);
- ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]);
-}
-
-static int wait_alu_ready(struct ksz_device *dev, u32 waiton, int timeout) -{
- u32 data;
-
- do {
- ksz_read32(dev, REG_SW_ALU_CTRL__4, &data);
- if (!(data & waiton))
- break;
- usleep_range(1, 10);
- } while (timeout-- > 0);
-
- if (timeout <= 0)
- return -ETIMEDOUT;
-
- return 0;
-}
-
-static int wait_alu_sta_ready(struct ksz_device *dev, u32 waiton, int timeout) -{
- u32 data;
-
- do {
- ksz_read32(dev, REG_SW_ALU_STAT_CTRL__4, &data);
- if (!(data & waiton))
- break;
- usleep_range(1, 10);
- } while (timeout-- > 0);
-
- if (timeout <= 0)
- return -ETIMEDOUT;
-
- return 0;
-}
-
-static int ksz_reset_switch(struct dsa_switch *ds) -{
- struct ksz_device *dev = ds->priv;
- u8 data8;
- u16 data16;
- u32 data32;
-
- /* reset switch */
- ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true);
-
- /* turn off SPI DO Edge select */
- ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8);
- data8 &= ~SPI_AUTO_EDGE_DETECTION;
- ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8);
-
- /* default configuration */
- ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8);
- data8 = SW_AGING_ENABLE | SW_LINK_AUTO_AGING |
- SW_SRC_ADDR_FILTER | SW_FLUSH_STP_TABLE | SW_FLUSH_MSTP_TABLE;
- ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
-
- /* disable interrupts */
- ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK);
- ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F);
- ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32);
-
- /* set broadcast storm protection 10% rate */
- ksz_read16(dev, REG_SW_MAC_CTRL_2, &data16);
- data16 &= ~BROADCAST_STORM_RATE;
- data16 |= (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100;
- ksz_write16(dev, REG_SW_MAC_CTRL_2, data16);
-
- return 0;
-}
-
-static void port_setup(struct ksz_device *dev, int port, bool cpu_port) -{
- u8 data8;
- u16 data16;
-
- /* enable tag tail for host port */
- if (cpu_port)
- ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE,
- true);
-
- ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false);
-
- /* set back pressure */
- ksz_port_cfg(dev, port, REG_PORT_MAC_CTRL_1, PORT_BACK_PRESSURE, true);
-
- /* set flow control */
- ksz_port_cfg(dev, port, REG_PORT_CTRL_0,
- PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, true);
-
- /* enable broadcast storm limit */
- ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
-
- /* disable DiffServ priority */
- ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_PRIO_ENABLE, false);
-
- /* replace priority */
- ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING,
- false);
- ksz_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4,
- MTI_PVID_REPLACE, false);
-
- /* enable 802.1p priority */
- ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_PRIO_ENABLE, true);
-
- /* configure MAC to 1G & RGMII mode */
- ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8);
- data8 |= PORT_RGMII_ID_EG_ENABLE;
- data8 &= ~PORT_MII_NOT_1GBIT;
- data8 &= ~PORT_MII_SEL_M;
- data8 |= PORT_RGMII_SEL;
- ksz_pwrite8(dev, port, REG_PORT_XMII_CTRL_1, data8);
-
- /* clear pending interrupts */
- ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16);
-}
-
-static void ksz_config_cpu_port(struct dsa_switch *ds) -{
- struct ksz_device *dev = ds->priv;
- int i;
-
- ds->num_ports = dev->port_cnt;
-
- for (i = 0; i < ds->num_ports; i++) {
- if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) {
- dev->cpu_port = i;
-
- /* enable cpu port */
- port_setup(dev, i, true);
- }
- }
-}
-
-static int ksz_setup(struct dsa_switch *ds) -{
- struct ksz_device *dev = ds->priv;
- int ret = 0;
-
- dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
- dev->num_vlans, GFP_KERNEL);
- if (!dev->vlan_cache)
- return -ENOMEM;
-
- ret = ksz_reset_switch(ds);
- if (ret) {
- dev_err(ds->dev, "failed to reset switch\n");
- return ret;
- }
-
- /* accept packet up to 2000bytes */
- ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_LEGAL_PACKET_DISABLE, true);
-
- ksz_config_cpu_port(ds);
-
- ksz_cfg(dev, REG_SW_MAC_CTRL_1, MULTICAST_STORM_DISABLE, true);
-
- /* queue based egress rate limit */
- ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true);
-
- /* start switch */
- ksz_cfg(dev, REG_SW_OPERATION, SW_START, true);
-
- return 0;
-}
-
-static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds) -{
- return DSA_TAG_PROTO_KSZ;
-}
-
-static int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg) -{
- struct ksz_device *dev = ds->priv;
- u16 val = 0;
-
- ksz_pread16(dev, addr, 0x100 + (reg << 1), &val);
-
- return val;
-}
-
-static int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val) -{
- struct ksz_device *dev = ds->priv;
-
- ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val);
-
- return 0;
-}
-
-static int ksz_enable_port(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ksz_device *dev = ds->priv;
-
- /* setup slave port */
- port_setup(dev, port, false);
-
- return 0;
-}
-
-static void ksz_disable_port(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ksz_device *dev = ds->priv;
-
- /* there is no port disable */
- ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, true);
-}
-
-static int ksz_sset_count(struct dsa_switch *ds) -{
- return TOTAL_SWITCH_COUNTER_NUM;
-}
-
-static void ksz_get_strings(struct dsa_switch *ds, int port, uint8_t *buf) -{
- int i;
-
- for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
- memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
- ETH_GSTRING_LEN);
- }
-}
-
-static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
- uint64_t *buf)
-{
- struct ksz_device *dev = ds->priv;
- int i;
- u32 data;
- int timeout;
-
- mutex_lock(&dev->stats_mutex);
-
- for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
- data = MIB_COUNTER_READ;
- data |= ((mib_names[i].index & 0xFF) << MIB_COUNTER_INDEX_S);
- ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
-
- timeout = 1000;
- do {
- ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
- &data);
- usleep_range(1, 10);
- if (!(data & MIB_COUNTER_READ))
- break;
- } while (timeout-- > 0);
-
- /* failed to read MIB. get out of loop */
- if (!timeout) {
- dev_dbg(dev->dev, "Failed to get MIB\n");
- break;
- }
-
- /* count resets upon read */
- ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
-
- dev->mib_value[i] += (uint64_t)data;
- buf[i] = dev->mib_value[i];
- }
-
- mutex_unlock(&dev->stats_mutex);
-}
-
-static void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{
- struct ksz_device *dev = ds->priv;
- u8 data;
-
- ksz_pread8(dev, port, P_STP_CTRL, &data);
- data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
-
- switch (state) {
- case BR_STATE_DISABLED:
- data |= PORT_LEARN_DISABLE;
- break;
- case BR_STATE_LISTENING:
- data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE);
- break;
- case BR_STATE_LEARNING:
- data |= PORT_RX_ENABLE;
- break;
- case BR_STATE_FORWARDING:
- data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
- break;
- case BR_STATE_BLOCKING:
- data |= PORT_LEARN_DISABLE;
- break;
- default:
- dev_err(ds->dev, "invalid STP state: %d\n", state);
- return;
- }
-
- ksz_pwrite8(dev, port, P_STP_CTRL, data);
-}
-
-static void ksz_port_fast_age(struct dsa_switch *ds, int port) -{
- struct ksz_device *dev = ds->priv;
- u8 data8;
-
- ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8);
- data8 |= SW_FAST_AGING;
- ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
-
- data8 &= ~SW_FAST_AGING;
- ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
-}
-
-static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag) -{
- struct ksz_device *dev = ds->priv;
-
- if (flag) {
- ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
- PORT_VLAN_LOOKUP_VID_0, true);
- ksz_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, true);
- ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, true);
- } else {
- ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, false);
- ksz_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, false);
- ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
- PORT_VLAN_LOOKUP_VID_0, false);
- }
-
- return 0;
-}
-
-static int ksz_port_vlan_prepare(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan,
- struct switchdev_trans *trans)
-{
- /* nothing needed */
-
- return 0;
-}
-
-static void ksz_port_vlan_add(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan,
- struct switchdev_trans *trans)
-{
- struct ksz_device *dev = ds->priv;
- u32 vlan_table[3];
- u16 vid;
- bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
-
- for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
- if (get_vlan_table(ds, vid, vlan_table)) {
- dev_dbg(dev->dev, "Failed to get vlan table\n");
- return;
- }
-
- vlan_table[0] = VLAN_VALID | (vid & VLAN_FID_M);
- if (untagged)
- vlan_table[1] |= BIT(port);
- else
- vlan_table[1] &= ~BIT(port);
- vlan_table[1] &= ~(BIT(dev->cpu_port));
-
- vlan_table[2] |= BIT(port) | BIT(dev->cpu_port);
-
- if (set_vlan_table(ds, vid, vlan_table)) {
- dev_dbg(dev->dev, "Failed to set vlan table\n");
- return;
- }
-
- /* change PVID */
- if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
- ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vid);
- }
-}
-
-static int ksz_port_vlan_del(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan)
-{
- struct ksz_device *dev = ds->priv;
- bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
- u32 vlan_table[3];
- u16 vid;
- u16 pvid;
-
- ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid);
- pvid = pvid & 0xFFF;
-
- for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
- if (get_vlan_table(ds, vid, vlan_table)) {
- dev_dbg(dev->dev, "Failed to get vlan table\n");
- return -ETIMEDOUT;
- }
-
- vlan_table[2] &= ~BIT(port);
-
- if (pvid == vid)
- pvid = 1;
-
- if (untagged)
- vlan_table[1] &= ~BIT(port);
-
- if (set_vlan_table(ds, vid, vlan_table)) {
- dev_dbg(dev->dev, "Failed to set vlan table\n");
- return -ETIMEDOUT;
- }
- }
-
- ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid);
-
- return 0;
-}
-
-struct alu_struct {
- /* entry 1 */
- u8 is_static:1;
- u8 is_src_filter:1;
- u8 is_dst_filter:1;
- u8 prio_age:3;
- u32 _reserv_0_1:23;
- u8 mstp:3;
- /* entry 2 */
- u8 is_override:1;
- u8 is_use_fid:1;
- u32 _reserv_1_1:23;
- u8 port_forward:7;
- /* entry 3 & 4*/
- u32 _reserv_2_1:9;
- u8 fid:7;
- u8 mac[ETH_ALEN];
-};
-
-static int ksz_port_fdb_add(struct dsa_switch *ds, int port,
- const unsigned char *addr, u16 vid)
-{
- struct ksz_device *dev = ds->priv;
- u32 alu_table[4];
- u32 data;
- int ret = 0;
-
- mutex_lock(&dev->alu_mutex);
-
- /* find any entry with mac & vid */
- data = vid << ALU_FID_INDEX_S;
- data |= ((addr[0] << 8) | addr[1]);
- ksz_write32(dev, REG_SW_ALU_INDEX_0, data);
-
- data = ((addr[2] << 24) | (addr[3] << 16));
- data |= ((addr[4] << 8) | addr[5]);
- ksz_write32(dev, REG_SW_ALU_INDEX_1, data);
-
- /* start read operation */
- ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);
-
- /* wait to be finished */
- ret = wait_alu_ready(dev, ALU_START, 1000);
- if (ret < 0) {
- dev_dbg(dev->dev, "Failed to read ALU\n");
- goto exit;
- }
-
- /* read ALU entry */
- read_table(ds, alu_table);
-
- /* update ALU entry */
- alu_table[0] = ALU_V_STATIC_VALID;
- alu_table[1] |= BIT(port);
- if (vid)
- alu_table[1] |= ALU_V_USE_FID;
- alu_table[2] = (vid << ALU_V_FID_S);
- alu_table[2] |= ((addr[0] << 8) | addr[1]);
- alu_table[3] = ((addr[2] << 24) | (addr[3] << 16));
- alu_table[3] |= ((addr[4] << 8) | addr[5]);
-
- write_table(ds, alu_table);
-
- ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);
-
- /* wait to be finished */
- ret = wait_alu_ready(dev, ALU_START, 1000);
- if (ret < 0)
- dev_dbg(dev->dev, "Failed to write ALU\n");
-
-exit:
- mutex_unlock(&dev->alu_mutex);
-
- return ret;
-}
-
-static int ksz_port_fdb_del(struct dsa_switch *ds, int port,
- const unsigned char *addr, u16 vid)
-{
- struct ksz_device *dev = ds->priv;
- u32 alu_table[4];
- u32 data;
- int ret = 0;
-
- mutex_lock(&dev->alu_mutex);
-
- /* read any entry with mac & vid */
- data = vid << ALU_FID_INDEX_S;
- data |= ((addr[0] << 8) | addr[1]);
- ksz_write32(dev, REG_SW_ALU_INDEX_0, data);
-
- data = ((addr[2] << 24) | (addr[3] << 16));
- data |= ((addr[4] << 8) | addr[5]);
- ksz_write32(dev, REG_SW_ALU_INDEX_1, data);
-
- /* start read operation */
- ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);
-
- /* wait to be finished */
- ret = wait_alu_ready(dev, ALU_START, 1000);
- if (ret < 0) {
- dev_dbg(dev->dev, "Failed to read ALU\n");
- goto exit;
- }
-
- ksz_read32(dev, REG_SW_ALU_VAL_A, &alu_table[0]);
- if (alu_table[0] & ALU_V_STATIC_VALID) {
- ksz_read32(dev, REG_SW_ALU_VAL_B, &alu_table[1]);
- ksz_read32(dev, REG_SW_ALU_VAL_C, &alu_table[2]);
- ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]);
-
- /* clear forwarding port */
- alu_table[2] &= ~BIT(port);
-
- /* if there is no port to forward, clear table */
- if ((alu_table[2] & ALU_V_PORT_MAP) == 0) {
- alu_table[0] = 0;
- alu_table[1] = 0;
- alu_table[2] = 0;
- alu_table[3] = 0;
- }
- } else {
- alu_table[0] = 0;
- alu_table[1] = 0;
- alu_table[2] = 0;
- alu_table[3] = 0;
- }
-
- write_table(ds, alu_table);
-
- ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);
-
- /* wait to be finished */
- ret = wait_alu_ready(dev, ALU_START, 1000);
- if (ret < 0)
- dev_dbg(dev->dev, "Failed to write ALU\n");
-
-exit:
- mutex_unlock(&dev->alu_mutex);
-
- return ret;
-}
-
-static void convert_alu(struct alu_struct *alu, u32 *alu_table) -{
- alu->is_static = !!(alu_table[0] & ALU_V_STATIC_VALID);
- alu->is_src_filter = !!(alu_table[0] & ALU_V_SRC_FILTER);
- alu->is_dst_filter = !!(alu_table[0] & ALU_V_DST_FILTER);
- alu->prio_age = (alu_table[0] >> ALU_V_PRIO_AGE_CNT_S) &
- ALU_V_PRIO_AGE_CNT_M;
- alu->mstp = alu_table[0] & ALU_V_MSTP_M;
-
- alu->is_override = !!(alu_table[1] & ALU_V_OVERRIDE);
- alu->is_use_fid = !!(alu_table[1] & ALU_V_USE_FID);
- alu->port_forward = alu_table[1] & ALU_V_PORT_MAP;
-
- alu->fid = (alu_table[2] >> ALU_V_FID_S) & ALU_V_FID_M;
-
- alu->mac[0] = (alu_table[2] >> 8) & 0xFF;
- alu->mac[1] = alu_table[2] & 0xFF;
- alu->mac[2] = (alu_table[3] >> 24) & 0xFF;
- alu->mac[3] = (alu_table[3] >> 16) & 0xFF;
- alu->mac[4] = (alu_table[3] >> 8) & 0xFF;
- alu->mac[5] = alu_table[3] & 0xFF;
-}
-
-static int ksz_port_fdb_dump(struct dsa_switch *ds, int port,
- dsa_fdb_dump_cb_t *cb, void *data)
-{
- struct ksz_device *dev = ds->priv;
- int ret = 0;
- u32 ksz_data;
- u32 alu_table[4];
- struct alu_struct alu;
- int timeout;
-
- mutex_lock(&dev->alu_mutex);
-
- /* start ALU search */
- ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH);
-
- do {
- timeout = 1000;
- do {
- ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data);
- if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START))
- break;
- usleep_range(1, 10);
- } while (timeout-- > 0);
-
- if (!timeout) {
- dev_dbg(dev->dev, "Failed to search ALU\n");
- ret = -ETIMEDOUT;
- goto exit;
- }
-
- /* read ALU table */
- read_table(ds, alu_table);
-
- convert_alu(&alu, alu_table);
-
- if (alu.port_forward & BIT(port)) {
- ret = cb(alu.mac, alu.fid, alu.is_static, data);
- if (ret)
- goto exit;
- }
- } while (ksz_data & ALU_START);
-
-exit:
-
- /* stop ALU search */
- ksz_write32(dev, REG_SW_ALU_CTRL__4, 0);
-
- mutex_unlock(&dev->alu_mutex);
-
- return ret;
-}
-
-static int ksz_port_mdb_prepare(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_mdb *mdb,
- struct switchdev_trans *trans)
-{
- /* nothing to do */
- return 0;
-}
-
-static void ksz_port_mdb_add(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_mdb *mdb,
- struct switchdev_trans *trans)
-{
- struct ksz_device *dev = ds->priv;
- u32 static_table[4];
- u32 data;
- int index;
- u32 mac_hi, mac_lo;
-
- mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
- mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
- mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);
-
- mutex_lock(&dev->alu_mutex);
-
- for (index = 0; index < dev->num_statics; index++) {
- /* find empty slot first */
- data = (index << ALU_STAT_INDEX_S) |
- ALU_STAT_READ | ALU_STAT_START;
- ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
-
- /* wait to be finished */
- if (wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0) {
- dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
- goto exit;
- }
-
- /* read ALU static table */
- read_table(ds, static_table);
-
- if (static_table[0] & ALU_V_STATIC_VALID) {
- /* check this has same vid & mac address */
- if (((static_table[2] >> ALU_V_FID_S) == (mdb->vid)) &&
- ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
- (static_table[3] == mac_lo)) {
- /* found matching one */
- break;
- }
- } else {
- /* found empty one */
- break;
- }
- }
-
- /* no available entry */
- if (index == dev->num_statics)
- goto exit;
-
- /* add entry */
- static_table[0] = ALU_V_STATIC_VALID;
- static_table[1] |= BIT(port);
- if (mdb->vid)
- static_table[1] |= ALU_V_USE_FID;
- static_table[2] = (mdb->vid << ALU_V_FID_S);
- static_table[2] |= mac_hi;
- static_table[3] = mac_lo;
-
- write_table(ds, static_table);
-
- data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START;
- ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
-
- /* wait to be finished */
- if (wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0)
- dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
-
-exit:
- mutex_unlock(&dev->alu_mutex);
-}
-
-static int ksz_port_mdb_del(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_mdb *mdb)
-{
- struct ksz_device *dev = ds->priv;
- u32 static_table[4];
- u32 data;
- int index;
- int ret = 0;
- u32 mac_hi, mac_lo;
-
- mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
- mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
- mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);
-
- mutex_lock(&dev->alu_mutex);
-
- for (index = 0; index < dev->num_statics; index++) {
- /* find empty slot first */
- data = (index << ALU_STAT_INDEX_S) |
- ALU_STAT_READ | ALU_STAT_START;
- ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
-
- /* wait to be finished */
- ret = wait_alu_sta_ready(dev, ALU_STAT_START, 1000);
- if (ret < 0) {
- dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
- goto exit;
- }
-
- /* read ALU static table */
- read_table(ds, static_table);
-
- if (static_table[0] & ALU_V_STATIC_VALID) {
- /* check this has same vid & mac address */
-
- if (((static_table[2] >> ALU_V_FID_S) == (mdb->vid)) &&
- ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
- (static_table[3] == mac_lo)) {
- /* found matching one */
- break;
- }
- }
- }
-
- /* no available entry */
- if (index == dev->num_statics) {
- ret = -EINVAL;
- goto exit;
- }
-
- /* clear port */
- static_table[1] &= ~BIT(port);
-
- if ((static_table[1] & ALU_V_PORT_MAP) == 0) {
- /* delete entry */
- static_table[0] = 0;
- static_table[1] = 0;
- static_table[2] = 0;
- static_table[3] = 0;
- }
-
- write_table(ds, static_table);
-
- data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START;
- ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
-
- /* wait to be finished */
- ret = wait_alu_sta_ready(dev, ALU_STAT_START, 1000);
- if (ret < 0)
- dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
-
-exit:
- mutex_unlock(&dev->alu_mutex);
-
- return ret;
-}
-
-static int ksz_port_mirror_add(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror,
- bool ingress)
-{
- struct ksz_device *dev = ds->priv;
-
- if (ingress)
- ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
- else
- ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);
-
- ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false);
-
- /* configure mirror port */
- ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
- PORT_MIRROR_SNIFFER, true);
-
- ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
-
- return 0;
-}
-
-static void ksz_port_mirror_del(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror)
-{
- struct ksz_device *dev = ds->priv;
- u8 data;
-
- if (mirror->ingress)
- ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
- else
- ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);
-
- ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
-
- if (!(data & (PORT_MIRROR_RX | PORT_MIRROR_TX)))
- ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
- PORT_MIRROR_SNIFFER, false);
-}
-
-static const struct dsa_switch_ops ksz_switch_ops = {
- .get_tag_protocol = ksz_get_tag_protocol,
- .setup = ksz_setup,
- .phy_read = ksz_phy_read16,
- .phy_write = ksz_phy_write16,
- .port_enable = ksz_enable_port,
- .port_disable = ksz_disable_port,
- .get_strings = ksz_get_strings,
- .get_ethtool_stats = ksz_get_ethtool_stats,
- .get_sset_count = ksz_sset_count,
- .port_stp_state_set = ksz_port_stp_state_set,
- .port_fast_age = ksz_port_fast_age,
- .port_vlan_filtering = ksz_port_vlan_filtering,
- .port_vlan_prepare = ksz_port_vlan_prepare,
- .port_vlan_add = ksz_port_vlan_add,
- .port_vlan_del = ksz_port_vlan_del,
- .port_fdb_dump = ksz_port_fdb_dump,
- .port_fdb_add = ksz_port_fdb_add,
- .port_fdb_del = ksz_port_fdb_del,
- .port_mdb_prepare = ksz_port_mdb_prepare,
- .port_mdb_add = ksz_port_mdb_add,
- .port_mdb_del = ksz_port_mdb_del,
- .port_mirror_add = ksz_port_mirror_add,
- .port_mirror_del = ksz_port_mirror_del,
-};
-
-struct ksz_chip_data {
- u32 chip_id;
- const char *dev_name;
- int num_vlans;
- int num_alus;
- int num_statics;
- int cpu_ports;
- int port_cnt;
-};
-
-static const struct ksz_chip_data ksz_switch_chips[] = {
- {
- .chip_id = 0x00947700,
- .dev_name = "KSZ9477",
- .num_vlans = 4096,
- .num_alus = 4096,
- .num_statics = 16,
- .cpu_ports = 0x7F, /* can be configured as cpu port */
- .port_cnt = 7, /* total physical port count */
- },
-};
-
-static int ksz_switch_init(struct ksz_device *dev) -{
- int i;
-
- mutex_init(&dev->reg_mutex);
- mutex_init(&dev->stats_mutex);
- mutex_init(&dev->alu_mutex);
- mutex_init(&dev->vlan_mutex);
-
- dev->ds->ops = &ksz_switch_ops;
-
- for (i = 0; i < ARRAY_SIZE(ksz_switch_chips); i++) {
- const struct ksz_chip_data *chip = &ksz_switch_chips[i];
-
- if (dev->chip_id == chip->chip_id) {
- dev->name = chip->dev_name;
- dev->num_vlans = chip->num_vlans;
- dev->num_alus = chip->num_alus;
- dev->num_statics = chip->num_statics;
- dev->port_cnt = chip->port_cnt;
- dev->cpu_ports = chip->cpu_ports;
-
- break;
- }
- }
-
- /* no switch found */
- if (!dev->port_cnt)
- return -ENODEV;
-
- return 0;
-}
-
struct ksz_device *ksz_switch_alloc(struct device *base,
const struct ksz_io_ops *ops,
void *priv)
@@ -1159,53 +57,43 @@ struct ksz_device *ksz_switch_alloc(struct device *base, } EXPORT_SYMBOL(ksz_switch_alloc);
-int ksz_switch_detect(struct ksz_device *dev) -{
- u8 data8;
- u32 id32;
- int ret;
-
- /* turn off SPI DO Edge select */
- ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8);
- if (ret)
- return ret;
-
- data8 &= ~SPI_AUTO_EDGE_DETECTION;
- ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8);
- if (ret)
- return ret;
-
- /* read chip id */
- ret = ksz_read32(dev, REG_CHIP_ID0__1, &id32);
- if (ret)
- return ret;
-
- dev->chip_id = id32;
-
- return 0;
-}
-EXPORT_SYMBOL(ksz_switch_detect);
-
-int ksz_switch_register(struct ksz_device *dev)
+int ksz_switch_register(struct ksz_device *dev,
+ const struct ksz_dev_ops *ops)
{
int ret;
if (dev->pdata)
dev->chip_id = dev->pdata->chip_id;
- if (ksz_switch_detect(dev))
+ dev->dev_ops = ops;
+
+ if (dev->dev_ops->detect(dev))
return -EINVAL;
- ret = ksz_switch_init(dev);
+ ret = dev->dev_ops->init(dev);
if (ret)
return ret;
- return dsa_register_switch(dev->ds);
+ dev->interface = PHY_INTERFACE_MODE_MII;
+ if (dev->dev->of_node) {
+ ret = of_get_phy_mode(dev->dev->of_node);
+ if (ret >= 0)
+ dev->interface = ret;
+ }
+
+ ret = dsa_register_switch(dev->ds);
+ if (ret) {
+ dev->dev_ops->exit(dev);
+ return ret;
+ }
+
+ return 0;
}
EXPORT_SYMBOL(ksz_switch_register);
void ksz_switch_remove(struct ksz_device *dev) {
+ dev->dev_ops->exit(dev);
dsa_unregister_switch(dev->ds);
}
EXPORT_SYMBOL(ksz_switch_remove);
^ permalink raw reply related
* [PATCH RFC 2/6] Create new file ksz9477.c from KSZ9477 code in ksz_common.c
From: Tristram.Ha @ 2017-09-07 21:09 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218CF@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Create new ksz9477.c file from original ksz_common.c.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c
new file mode 100644
index 0000000..bc722b4
--- /dev/null
+++ b/drivers/net/dsa/microchip/ksz9477.c
@@ -0,0 +1,1317 @@
+/*
+ * Microchip switch driver main logic
+ *
+ * Copyright (C) 2017
+ *
+ * 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/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_data/microchip-ksz.h>
+#include <linux/phy.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <net/dsa.h>
+#include <net/switchdev.h>
+
+#include "ksz_priv.h"
+#include "ksz_9477_reg.h"
+
+static const struct {
+ int index;
+ char string[ETH_GSTRING_LEN];
+} mib_names[TOTAL_SWITCH_COUNTER_NUM] = {
+ { 0x00, "rx_hi" },
+ { 0x01, "rx_undersize" },
+ { 0x02, "rx_fragments" },
+ { 0x03, "rx_oversize" },
+ { 0x04, "rx_jabbers" },
+ { 0x05, "rx_symbol_err" },
+ { 0x06, "rx_crc_err" },
+ { 0x07, "rx_align_err" },
+ { 0x08, "rx_mac_ctrl" },
+ { 0x09, "rx_pause" },
+ { 0x0A, "rx_bcast" },
+ { 0x0B, "rx_mcast" },
+ { 0x0C, "rx_ucast" },
+ { 0x0D, "rx_64_or_less" },
+ { 0x0E, "rx_65_127" },
+ { 0x0F, "rx_128_255" },
+ { 0x10, "rx_256_511" },
+ { 0x11, "rx_512_1023" },
+ { 0x12, "rx_1024_1522" },
+ { 0x13, "rx_1523_2000" },
+ { 0x14, "rx_2001" },
+ { 0x15, "tx_hi" },
+ { 0x16, "tx_late_col" },
+ { 0x17, "tx_pause" },
+ { 0x18, "tx_bcast" },
+ { 0x19, "tx_mcast" },
+ { 0x1A, "tx_ucast" },
+ { 0x1B, "tx_deferred" },
+ { 0x1C, "tx_total_col" },
+ { 0x1D, "tx_exc_col" },
+ { 0x1E, "tx_single_col" },
+ { 0x1F, "tx_mult_col" },
+ { 0x80, "rx_total" },
+ { 0x81, "tx_total" },
+ { 0x82, "rx_discards" },
+ { 0x83, "tx_discards" },
+};
+
+static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool
+set) {
+ u8 data;
+
+ ksz_read8(dev, addr, &data);
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+ ksz_write8(dev, addr, data);
+}
+
+static void ksz_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool
+set) {
+ u32 data;
+
+ ksz_read32(dev, addr, &data);
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+ ksz_write32(dev, addr, data);
+}
+
+static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
+ bool set)
+{
+ u32 addr;
+ u8 data;
+
+ addr = PORT_CTRL_ADDR(port, offset);
+ ksz_read8(dev, addr, &data);
+
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+
+ ksz_write8(dev, addr, data);
+}
+
+static void ksz_port_cfg32(struct ksz_device *dev, int port, int offset,
+ u32 bits, bool set)
+{
+ u32 addr;
+ u32 data;
+
+ addr = PORT_CTRL_ADDR(port, offset);
+ ksz_read32(dev, addr, &data);
+
+ if (set)
+ data |= bits;
+ else
+ data &= ~bits;
+
+ ksz_write32(dev, addr, data);
+}
+
+static int wait_vlan_ctrl_ready(struct ksz_device *dev, u32 waiton, int
+timeout) {
+ u8 data;
+
+ do {
+ ksz_read8(dev, REG_SW_VLAN_CTRL, &data);
+ if (!(data & waiton))
+ break;
+ usleep_range(1, 10);
+ } while (timeout-- > 0);
+
+ if (timeout <= 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int get_vlan_table(struct dsa_switch *ds, u16 vid, u32
+*vlan_table) {
+ struct ksz_device *dev = ds->priv;
+ int ret;
+
+ mutex_lock(&dev->vlan_mutex);
+
+ ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
+ ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START);
+
+ /* wait to be cleared */
+ ret = wait_vlan_ctrl_ready(dev, VLAN_START, 1000);
+ if (ret < 0) {
+ dev_dbg(dev->dev, "Failed to read vlan table\n");
+ goto exit;
+ }
+
+ ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]);
+ ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]);
+ ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]);
+
+ ksz_write8(dev, REG_SW_VLAN_CTRL, 0);
+
+exit:
+ mutex_unlock(&dev->vlan_mutex);
+
+ return ret;
+}
+
+static int set_vlan_table(struct dsa_switch *ds, u16 vid, u32
+*vlan_table) {
+ struct ksz_device *dev = ds->priv;
+ int ret;
+
+ mutex_lock(&dev->vlan_mutex);
+
+ ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]);
+ ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]);
+ ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_table[2]);
+
+ ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
+ ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE);
+
+ /* wait to be cleared */
+ ret = wait_vlan_ctrl_ready(dev, VLAN_START, 1000);
+ if (ret < 0) {
+ dev_dbg(dev->dev, "Failed to write vlan table\n");
+ goto exit;
+ }
+
+ ksz_write8(dev, REG_SW_VLAN_CTRL, 0);
+
+ /* update vlan cache table */
+ dev->vlan_cache[vid].table[0] = vlan_table[0];
+ dev->vlan_cache[vid].table[1] = vlan_table[1];
+ dev->vlan_cache[vid].table[2] = vlan_table[2];
+
+exit:
+ mutex_unlock(&dev->vlan_mutex);
+
+ return ret;
+}
+
+static void read_table(struct dsa_switch *ds, u32 *table) {
+ struct ksz_device *dev = ds->priv;
+
+ ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]);
+ ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]);
+ ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]);
+ ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]); }
+
+static void write_table(struct dsa_switch *ds, u32 *table) {
+ struct ksz_device *dev = ds->priv;
+
+ ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]);
+ ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]);
+ ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]);
+ ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]); }
+
+static int wait_alu_ready(struct ksz_device *dev, u32 waiton, int
+timeout) {
+ u32 data;
+
+ do {
+ ksz_read32(dev, REG_SW_ALU_CTRL__4, &data);
+ if (!(data & waiton))
+ break;
+ usleep_range(1, 10);
+ } while (timeout-- > 0);
+
+ if (timeout <= 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int wait_alu_sta_ready(struct ksz_device *dev, u32 waiton, int
+timeout) {
+ u32 data;
+
+ do {
+ ksz_read32(dev, REG_SW_ALU_STAT_CTRL__4, &data);
+ if (!(data & waiton))
+ break;
+ usleep_range(1, 10);
+ } while (timeout-- > 0);
+
+ if (timeout <= 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ksz_reset_switch(struct ksz_device *dev) {
+ u8 data8;
+ u16 data16;
+ u32 data32;
+
+ /* reset switch */
+ ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true);
+
+ /* turn off SPI DO Edge select */
+ ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8);
+ data8 &= ~SPI_AUTO_EDGE_DETECTION;
+ ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8);
+
+ /* default configuration */
+ ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8);
+ data8 = SW_AGING_ENABLE | SW_LINK_AUTO_AGING |
+ SW_SRC_ADDR_FILTER | SW_FLUSH_STP_TABLE | SW_FLUSH_MSTP_TABLE;
+ ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
+
+ /* disable interrupts */
+ ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK);
+ ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F);
+ ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32);
+
+ /* set broadcast storm protection 10% rate */
+ ksz_read16(dev, REG_SW_MAC_CTRL_2, &data16);
+ data16 &= ~BROADCAST_STORM_RATE;
+ data16 |= (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100;
+ ksz_write16(dev, REG_SW_MAC_CTRL_2, data16);
+
+ return 0;
+}
+
+static void port_setup(struct ksz_device *dev, int port, bool cpu_port)
+{
+ u8 data8;
+ u16 data16;
+
+ /* enable tag tail for host port */
+ if (cpu_port)
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE,
+ true);
+
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false);
+
+ /* set back pressure */
+ ksz_port_cfg(dev, port, REG_PORT_MAC_CTRL_1, PORT_BACK_PRESSURE,
+true);
+
+ /* set flow control */
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0,
+ PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, true);
+
+ /* enable broadcast storm limit */
+ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM,
+true);
+
+ /* disable DiffServ priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_PRIO_ENABLE,
+false);
+
+ /* replace priority */
+ ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING,
+ false);
+ ksz_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4,
+ MTI_PVID_REPLACE, false);
+
+ /* enable 802.1p priority */
+ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_PRIO_ENABLE, true);
+
+ /* configure MAC to 1G & RGMII mode */
+ ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8);
+ data8 |= PORT_RGMII_ID_EG_ENABLE;
+ data8 &= ~PORT_MII_NOT_1GBIT;
+ data8 &= ~PORT_MII_SEL_M;
+ data8 |= PORT_RGMII_SEL;
+ ksz_pwrite8(dev, port, REG_PORT_XMII_CTRL_1, data8);
+
+ /* clear pending interrupts */
+ ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16); }
+
+static void ksz_config_cpu_port(struct dsa_switch *ds) {
+ struct ksz_device *dev = ds->priv;
+ int i;
+
+ ds->num_ports = dev->port_cnt;
+
+ for (i = 0; i < ds->num_ports; i++) {
+ if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) {
+ dev->cpu_port = i;
+
+ /* enable cpu port */
+ port_setup(dev, i, true);
+ dev->HOST_MASK = (1 << dev->cpu_port);
+ dev->PORT_MASK |= dev->HOST_MASK;
+ }
+ }
+}
+
+static int ksz_setup(struct dsa_switch *ds) {
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+
+ ret = ksz_reset_switch(dev);
+ if (ret) {
+ dev_err(ds->dev, "failed to reset switch\n");
+ return ret;
+ }
+
+ /* accept packet up to 2000bytes */
+ ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_LEGAL_PACKET_DISABLE, true);
+
+ ksz_config_cpu_port(ds);
+
+ ksz_cfg(dev, REG_SW_MAC_CTRL_1, MULTICAST_STORM_DISABLE, true);
+
+ /* queue based egress rate limit */
+ ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true);
+
+ /* start switch */
+ ksz_cfg(dev, REG_SW_OPERATION, SW_START, true);
+
+ return 0;
+}
+
+static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch
+*ds) {
+ return DSA_TAG_PROTO_KSZ;
+}
+
+static int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg) {
+ struct ksz_device *dev = ds->priv;
+ u16 val = 0;
+
+ ksz_pread16(dev, addr, 0x100 + (reg << 1), &val);
+
+ return val;
+}
+
+static int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg,
+u16 val) {
+ struct ksz_device *dev = ds->priv;
+
+ ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val);
+
+ return 0;
+}
+
+static int ksz_enable_port(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct ksz_device *dev = ds->priv;
+
+ /* setup slave port */
+ port_setup(dev, port, false);
+
+ return 0;
+}
+
+static void ksz_disable_port(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct ksz_device *dev = ds->priv;
+
+ /* there is no port disable */
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, true); }
+
+static int ksz_sset_count(struct dsa_switch *ds) {
+ return TOTAL_SWITCH_COUNTER_NUM;
+}
+
+static void ksz_get_strings(struct dsa_switch *ds, int port, uint8_t
+*buf) {
+ int i;
+
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
+ ETH_GSTRING_LEN);
+ }
+}
+
+static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
+ uint64_t *buf)
+{
+ struct ksz_device *dev = ds->priv;
+ int i;
+ u32 data;
+ int timeout;
+
+ mutex_lock(&dev->stats_mutex);
+
+ for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) {
+ data = MIB_COUNTER_READ;
+ data |= ((mib_names[i].index & 0xFF) << MIB_COUNTER_INDEX_S);
+ ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
+
+ timeout = 1000;
+ do {
+ ksz_pread32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
+ &data);
+ usleep_range(1, 10);
+ if (!(data & MIB_COUNTER_READ))
+ break;
+ } while (timeout-- > 0);
+
+ /* failed to read MIB. get out of loop */
+ if (!timeout) {
+ dev_dbg(dev->dev, "Failed to get MIB\n");
+ break;
+ }
+
+ /* count resets upon read */
+ ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
+
+ dev->ports[port].mib.info[i].counter += (uint64_t)data;
+ buf[i] = dev->ports[port].mib.info[i].counter;
+ }
+
+ mutex_unlock(&dev->stats_mutex);
+}
+
+static void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8
+state) {
+ struct ksz_device *dev = ds->priv;
+ u8 data;
+
+ ksz_pread8(dev, port, P_STP_CTRL, &data);
+ data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ data |= PORT_LEARN_DISABLE;
+ break;
+ case BR_STATE_LISTENING:
+ data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE);
+ break;
+ case BR_STATE_LEARNING:
+ data |= PORT_RX_ENABLE;
+ break;
+ case BR_STATE_FORWARDING:
+ data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
+ break;
+ case BR_STATE_BLOCKING:
+ data |= PORT_LEARN_DISABLE;
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ ksz_pwrite8(dev, port, P_STP_CTRL, data); }
+
+static void ksz_port_fast_age(struct dsa_switch *ds, int port) {
+ struct ksz_device *dev = ds->priv;
+ u8 data8;
+
+ ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8);
+ data8 |= SW_FAST_AGING;
+ ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
+
+ data8 &= ~SW_FAST_AGING;
+ ksz_write8(dev, REG_SW_LUE_CTRL_1, data8); }
+
+static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port,
+bool flag) {
+ struct ksz_device *dev = ds->priv;
+
+ if (flag) {
+ ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
+ PORT_VLAN_LOOKUP_VID_0, true);
+ ksz_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, true);
+ ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, true);
+ } else {
+ ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, false);
+ ksz_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, false);
+ ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
+ PORT_VLAN_LOOKUP_VID_0, false);
+ }
+
+ return 0;
+}
+
+static int ksz_port_vlan_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans)
+{
+ /* nothing needed */
+
+ return 0;
+}
+
+static void ksz_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans) {
+ struct ksz_device *dev = ds->priv;
+ u32 vlan_table[3];
+ u16 vid;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ if (get_vlan_table(ds, vid, vlan_table)) {
+ dev_dbg(dev->dev, "Failed to get vlan table\n");
+ return;
+ }
+
+ vlan_table[0] = VLAN_VALID | (vid & VLAN_FID_M);
+ if (untagged)
+ vlan_table[1] |= BIT(port);
+ else
+ vlan_table[1] &= ~BIT(port);
+ vlan_table[1] &= ~(BIT(dev->cpu_port));
+
+ vlan_table[2] |= BIT(port) | BIT(dev->cpu_port);
+
+ if (set_vlan_table(ds, vid, vlan_table)) {
+ dev_dbg(dev->dev, "Failed to set vlan table\n");
+ return;
+ }
+
+ /* change PVID */
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
+ ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vid);
+ }
+}
+
+static int ksz_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan) {
+ struct ksz_device *dev = ds->priv;
+ bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+ u32 vlan_table[3];
+ u16 vid;
+ u16 pvid;
+
+ ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid);
+ pvid = pvid & 0xFFF;
+
+ for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
+ if (get_vlan_table(ds, vid, vlan_table)) {
+ dev_dbg(dev->dev, "Failed to get vlan table\n");
+ return -ETIMEDOUT;
+ }
+
+ vlan_table[2] &= ~BIT(port);
+
+ if (pvid == vid)
+ pvid = 1;
+
+ if (untagged)
+ vlan_table[1] &= ~BIT(port);
+
+ if (set_vlan_table(ds, vid, vlan_table)) {
+ dev_dbg(dev->dev, "Failed to set vlan table\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid);
+
+ return 0;
+}
+
+struct alu_struct {
+ /* entry 1 */
+ u8 is_static:1;
+ u8 is_src_filter:1;
+ u8 is_dst_filter:1;
+ u8 prio_age:3;
+ u32 _reserv_0_1:23;
+ u8 mstp:3;
+ /* entry 2 */
+ u8 is_override:1;
+ u8 is_use_fid:1;
+ u32 _reserv_1_1:23;
+ u8 port_forward:7;
+ /* entry 3 & 4*/
+ u32 _reserv_2_1:9;
+ u8 fid:7;
+ u8 mac[ETH_ALEN];
+};
+
+static int ksz_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid) {
+ struct ksz_device *dev = ds->priv;
+ u32 alu_table[4];
+ u32 data;
+ int ret = 0;
+
+ mutex_lock(&dev->alu_mutex);
+
+ /* find any entry with mac & vid */
+ data = vid << ALU_FID_INDEX_S;
+ data |= ((addr[0] << 8) | addr[1]);
+ ksz_write32(dev, REG_SW_ALU_INDEX_0, data);
+
+ data = ((addr[2] << 24) | (addr[3] << 16));
+ data |= ((addr[4] << 8) | addr[5]);
+ ksz_write32(dev, REG_SW_ALU_INDEX_1, data);
+
+ /* start read operation */
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);
+
+ /* wait to be finished */
+ ret = wait_alu_ready(dev, ALU_START, 1000);
+ if (ret < 0) {
+ dev_dbg(dev->dev, "Failed to read ALU\n");
+ goto exit;
+ }
+
+ /* read ALU entry */
+ read_table(ds, alu_table);
+
+ /* update ALU entry */
+ alu_table[0] = ALU_V_STATIC_VALID;
+ alu_table[1] |= BIT(port);
+ if (vid)
+ alu_table[1] |= ALU_V_USE_FID;
+ alu_table[2] = (vid << ALU_V_FID_S);
+ alu_table[2] |= ((addr[0] << 8) | addr[1]);
+ alu_table[3] = ((addr[2] << 24) | (addr[3] << 16));
+ alu_table[3] |= ((addr[4] << 8) | addr[5]);
+
+ write_table(ds, alu_table);
+
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);
+
+ /* wait to be finished */
+ ret = wait_alu_ready(dev, ALU_START, 1000);
+ if (ret < 0)
+ dev_dbg(dev->dev, "Failed to write ALU\n");
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
+static int ksz_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid) {
+ struct ksz_device *dev = ds->priv;
+ u32 alu_table[4];
+ u32 data;
+ int ret = 0;
+
+ mutex_lock(&dev->alu_mutex);
+
+ /* read any entry with mac & vid */
+ data = vid << ALU_FID_INDEX_S;
+ data |= ((addr[0] << 8) | addr[1]);
+ ksz_write32(dev, REG_SW_ALU_INDEX_0, data);
+
+ data = ((addr[2] << 24) | (addr[3] << 16));
+ data |= ((addr[4] << 8) | addr[5]);
+ ksz_write32(dev, REG_SW_ALU_INDEX_1, data);
+
+ /* start read operation */
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);
+
+ /* wait to be finished */
+ ret = wait_alu_ready(dev, ALU_START, 1000);
+ if (ret < 0) {
+ dev_dbg(dev->dev, "Failed to read ALU\n");
+ goto exit;
+ }
+
+ ksz_read32(dev, REG_SW_ALU_VAL_A, &alu_table[0]);
+ if (alu_table[0] & ALU_V_STATIC_VALID) {
+ ksz_read32(dev, REG_SW_ALU_VAL_B, &alu_table[1]);
+ ksz_read32(dev, REG_SW_ALU_VAL_C, &alu_table[2]);
+ ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]);
+
+ /* clear forwarding port */
+ alu_table[2] &= ~BIT(port);
+
+ /* if there is no port to forward, clear table */
+ if ((alu_table[2] & ALU_V_PORT_MAP) == 0) {
+ alu_table[0] = 0;
+ alu_table[1] = 0;
+ alu_table[2] = 0;
+ alu_table[3] = 0;
+ }
+ } else {
+ alu_table[0] = 0;
+ alu_table[1] = 0;
+ alu_table[2] = 0;
+ alu_table[3] = 0;
+ }
+
+ write_table(ds, alu_table);
+
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);
+
+ /* wait to be finished */
+ ret = wait_alu_ready(dev, ALU_START, 1000);
+ if (ret < 0)
+ dev_dbg(dev->dev, "Failed to write ALU\n");
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
+static void convert_alu(struct alu_struct *alu, u32 *alu_table) {
+ alu->is_static = !!(alu_table[0] & ALU_V_STATIC_VALID);
+ alu->is_src_filter = !!(alu_table[0] & ALU_V_SRC_FILTER);
+ alu->is_dst_filter = !!(alu_table[0] & ALU_V_DST_FILTER);
+ alu->prio_age = (alu_table[0] >> ALU_V_PRIO_AGE_CNT_S) &
+ ALU_V_PRIO_AGE_CNT_M;
+ alu->mstp = alu_table[0] & ALU_V_MSTP_M;
+
+ alu->is_override = !!(alu_table[1] & ALU_V_OVERRIDE);
+ alu->is_use_fid = !!(alu_table[1] & ALU_V_USE_FID);
+ alu->port_forward = alu_table[1] & ALU_V_PORT_MAP;
+
+ alu->fid = (alu_table[2] >> ALU_V_FID_S) & ALU_V_FID_M;
+
+ alu->mac[0] = (alu_table[2] >> 8) & 0xFF;
+ alu->mac[1] = alu_table[2] & 0xFF;
+ alu->mac[2] = (alu_table[3] >> 24) & 0xFF;
+ alu->mac[3] = (alu_table[3] >> 16) & 0xFF;
+ alu->mac[4] = (alu_table[3] >> 8) & 0xFF;
+ alu->mac[5] = alu_table[3] & 0xFF;
+}
+
+static int ksz_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data) {
+ struct ksz_device *dev = ds->priv;
+ int ret = 0;
+ u32 ksz_data;
+ u32 alu_table[4];
+ struct alu_struct alu;
+ int timeout;
+
+ mutex_lock(&dev->alu_mutex);
+
+ /* start ALU search */
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH);
+
+ do {
+ timeout = 1000;
+ do {
+ ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data);
+ if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START))
+ break;
+ usleep_range(1, 10);
+ } while (timeout-- > 0);
+
+ if (!timeout) {
+ dev_dbg(dev->dev, "Failed to search ALU\n");
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+
+ /* read ALU table */
+ read_table(ds, alu_table);
+
+ convert_alu(&alu, alu_table);
+
+ if (alu.port_forward & BIT(port)) {
+ ret = cb(alu.mac, alu.fid, alu.is_static, data);
+ if (ret)
+ goto exit;
+ }
+ } while (ksz_data & ALU_START);
+
+exit:
+
+ /* stop ALU search */
+ ksz_write32(dev, REG_SW_ALU_CTRL__4, 0);
+
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
+static int ksz_port_mdb_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct switchdev_trans *trans)
+{
+ /* nothing to do */
+ return 0;
+}
+
+static void ksz_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct switchdev_trans *trans)
+{
+ struct ksz_device *dev = ds->priv;
+ u32 static_table[4];
+ u32 data;
+ int index;
+ u32 mac_hi, mac_lo;
+
+ mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
+ mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
+ mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);
+
+ mutex_lock(&dev->alu_mutex);
+
+ for (index = 0; index < dev->num_statics; index++) {
+ /* find empty slot first */
+ data = (index << ALU_STAT_INDEX_S) |
+ ALU_STAT_READ | ALU_STAT_START;
+ ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
+
+ /* wait to be finished */
+ if (wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0) {
+ dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
+ goto exit;
+ }
+
+ /* read ALU static table */
+ read_table(ds, static_table);
+
+ if (static_table[0] & ALU_V_STATIC_VALID) {
+ /* check this has same vid & mac address */
+ if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) &&
+ ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
+ static_table[3] == mac_lo) {
+ /* found matching one */
+ break;
+ }
+ } else {
+ /* found empty one */
+ break;
+ }
+ }
+
+ /* no available entry */
+ if (index == dev->num_statics)
+ goto exit;
+
+ /* add entry */
+ static_table[0] = ALU_V_STATIC_VALID;
+ static_table[1] |= BIT(port);
+ if (mdb->vid)
+ static_table[1] |= ALU_V_USE_FID;
+ static_table[2] = (mdb->vid << ALU_V_FID_S);
+ static_table[2] |= mac_hi;
+ static_table[3] = mac_lo;
+
+ write_table(ds, static_table);
+
+ data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START;
+ ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
+
+ /* wait to be finished */
+ if (wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0)
+ dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+}
+
+static int ksz_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb) {
+ struct ksz_device *dev = ds->priv;
+ u32 static_table[4];
+ u32 data;
+ int index;
+ int ret = 0;
+ u32 mac_hi, mac_lo;
+
+ mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
+ mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
+ mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);
+
+ mutex_lock(&dev->alu_mutex);
+
+ for (index = 0; index < dev->num_statics; index++) {
+ /* find empty slot first */
+ data = (index << ALU_STAT_INDEX_S) |
+ ALU_STAT_READ | ALU_STAT_START;
+ ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
+
+ /* wait to be finished */
+ ret = wait_alu_sta_ready(dev, ALU_STAT_START, 1000);
+ if (ret < 0) {
+ dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
+ goto exit;
+ }
+
+ /* read ALU static table */
+ read_table(ds, static_table);
+
+ if (static_table[0] & ALU_V_STATIC_VALID) {
+ /* check this has same vid & mac address */
+
+ if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) &&
+ ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
+ static_table[3] == mac_lo) {
+ /* found matching one */
+ break;
+ }
+ }
+ }
+
+ /* no available entry */
+ if (index == dev->num_statics) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* clear port */
+ static_table[1] &= ~BIT(port);
+
+ if ((static_table[1] & ALU_V_PORT_MAP) == 0) {
+ /* delete entry */
+ static_table[0] = 0;
+ static_table[1] = 0;
+ static_table[2] = 0;
+ static_table[3] = 0;
+ }
+
+ write_table(ds, static_table);
+
+ data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START;
+ ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
+
+ /* wait to be finished */
+ ret = wait_alu_sta_ready(dev, ALU_STAT_START, 1000);
+ if (ret < 0)
+ dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
+
+exit:
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
+static int ksz_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (ingress)
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
+ else
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);
+
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false);
+
+ /* configure mirror port */
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, true);
+
+ ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
+
+ return 0;
+}
+
+static void ksz_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror) {
+ struct ksz_device *dev = ds->priv;
+ u8 data;
+
+ if (mirror->ingress)
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
+ else
+ ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);
+
+ ksz_pread8(dev, port, P_MIRROR_CTRL, &data);
+
+ if (!(data & (PORT_MIRROR_RX | PORT_MIRROR_TX)))
+ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
+ PORT_MIRROR_SNIFFER, false);
+}
+
+static const struct dsa_switch_ops ksz_switch_ops = {
+ .get_tag_protocol = ksz_get_tag_protocol,
+ .setup = ksz_setup,
+ .phy_read = ksz_phy_read16,
+ .phy_write = ksz_phy_write16,
+ .port_enable = ksz_enable_port,
+ .port_disable = ksz_disable_port,
+ .get_strings = ksz_get_strings,
+ .get_ethtool_stats = ksz_get_ethtool_stats,
+ .get_sset_count = ksz_sset_count,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_fast_age = ksz_port_fast_age,
+ .port_vlan_filtering = ksz_port_vlan_filtering,
+ .port_vlan_prepare = ksz_port_vlan_prepare,
+ .port_vlan_add = ksz_port_vlan_add,
+ .port_vlan_del = ksz_port_vlan_del,
+ .port_fdb_dump = ksz_port_fdb_dump,
+ .port_fdb_add = ksz_port_fdb_add,
+ .port_fdb_del = ksz_port_fdb_del,
+ .port_mdb_prepare = ksz_port_mdb_prepare,
+ .port_mdb_add = ksz_port_mdb_add,
+ .port_mdb_del = ksz_port_mdb_del,
+ .port_mirror_add = ksz_port_mirror_add,
+ .port_mirror_del = ksz_port_mirror_del,
+};
+
+static u32 ksz_get_port_addr(int port, int offset) {
+ return PORT_CTRL_ADDR(port, offset);
+}
+
+/* For Ingress (Host -> KSZ), 2 bytes are added before FCS.
+ *
+-----------------------------------------------------------------------
+----
+ *
+DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4by
+tes)
+ *
+-----------------------------------------------------------------------
+----
+ * tag0 : Prioritization (not used now)
+ * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2,
+0x10=port5)
+ *
+ * For Egress (KSZ -> Host), 1 byte is added before FCS.
+ *
+-----------------------------------------------------------------------
+----
+ * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes)
+ *
+-----------------------------------------------------------------------
+----
+ * tag0 : zero-based value represents port
+ * (eg, 0x00=port1, 0x02=port3, 0x06=port7)
+ */
+
+#define KSZ_INGRESS_TAG_LEN 2
+#define KSZ_EGRESS_TAG_LEN 1
+#define KSZ_PTP_TAG_LEN 4
+#define KSZ_PTP_TAG_INDICATION 0x80
+
+/**
+ * This information needs to be communicated to the MAC driver so that
+it can
+ * receive the frames correctly from the switch as some MAC drivers
+adhere to
+ * receive exactly 1518 bytes.
+ */
+static int ksz_get_rx_len(struct ksz_device *dev) {
+ int len = KSZ_EGRESS_TAG_LEN;
+
+ /* PTP function is turned on. */
+ if (dev->overrides & PTP_TAG)
+ len += KSZ_PTP_TAG_LEN;
+ return len;
+}
+
+/**
+ * This information tells how many more bytes need to be allocated to
+ * accommodate the tail tag.
+ */
+static int ksz_get_tx_len(struct ksz_device *dev) {
+ int len = KSZ_INGRESS_TAG_LEN;
+
+ /* PTP function is turned on. */
+ if (dev->overrides & PTP_TAG)
+ len += KSZ_PTP_TAG_LEN;
+ return len;
+}
+
+static void ksz_add_tail_tag(struct ksz_device *dev, struct sk_buff *skb,
+ int port)
+{
+ struct {
+ u32 timestamp;
+ u16 ports;
+ } tx_tag;
+ u8 *trailer;
+ u8 *tag;
+ int len = KSZ_INGRESS_TAG_LEN;
+ int ptp_len = 0;
+
+ port = 1 << port;
+
+ /* PTP function is turned on. */
+ if (dev->overrides & PTP_TAG)
+ ptp_len = KSZ_PTP_TAG_LEN;
+ len += ptp_len;
+ trailer = skb_put(skb, len);
+ memset(&tx_tag, 0, sizeof(tx_tag));
+ tx_tag.ports = port & dev->PORT_MASK;
+ tx_tag.ports = cpu_to_be16(tx_tag.ports);
+ tag = (u8 *)&tx_tag;
+ memcpy(trailer, &tag[4 - ptp_len], ptp_len + KSZ_INGRESS_TAG_LEN); }
+
+static int ksz_get_tail_tag(struct ksz_device *dev, struct sk_buff *skb,
+ int *port)
+{
+ u8 *trailer;
+ int len = KSZ_EGRESS_TAG_LEN;
+
+ trailer = skb_tail_pointer(skb) - len;
+ if (*trailer & KSZ_PTP_TAG_INDICATION)
+ len += KSZ_PTP_TAG_LEN;
+ *trailer &= ~KSZ_PTP_TAG_INDICATION;
+ *port = *trailer;
+ if (*port >= 0 && *port < dev->mib_port_cnt) {
+ struct ksz_port *p = &dev->ports[*port];
+
+ /**
+ * Switch is forwarding when port membership includes other
+ * ports.
+ */
+ if (p->member & ~((1 << *port) | dev->HOST_MASK))
+ skb->offload_fwd_mark = 1;
+ }
+ return len;
+}
+
+static int ksz_switch_detect(struct ksz_device *dev) {
+ u8 data8;
+ u32 id32;
+ int ret;
+
+ /* turn off SPI DO Edge select */
+ ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8);
+ if (ret)
+ return ret;
+
+ data8 &= ~SPI_AUTO_EDGE_DETECTION;
+ ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8);
+ if (ret)
+ return ret;
+
+ /* read chip id */
+ ret = ksz_read32(dev, REG_CHIP_ID0__1, &id32);
+ if (ret)
+ return ret;
+
+ /* Number of ports can be reduced depending on chip. */
+ dev->mib_port_cnt = TOTAL_PORT_NUM;
+
+ dev->chip_id = id32;
+
+ dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM;
+
+ /* Try to read MIB counters in all ports every second. */
+ dev->MIB_READ_INTERVAL = msecs_to_jiffies(1000 / dev->mib_port_cnt);
+
+ return 0;
+}
+
+struct ksz_chip_data {
+ u32 chip_id;
+ const char *dev_name;
+ int num_vlans;
+ int num_alus;
+ int num_statics;
+ int cpu_ports;
+ int port_cnt;
+};
+
+static const struct ksz_chip_data ksz_switch_chips[] = {
+ {
+ .chip_id = 0x00947700,
+ .dev_name = "KSZ9477",
+ .num_vlans = 4096,
+ .num_alus = 4096,
+ .num_statics = 16,
+ .cpu_ports = 0x7F, /* can be configured as cpu port */
+ .port_cnt = 7, /* total physical port count */
+ },
+};
+
+static int ksz_switch_init(struct ksz_device *dev) {
+ int i;
+
+ mutex_init(&dev->reg_mutex);
+ mutex_init(&dev->stats_mutex);
+ mutex_init(&dev->alu_mutex);
+ mutex_init(&dev->vlan_mutex);
+
+ dev->ds->ops = &ksz_switch_ops;
+
+ for (i = 0; i < ARRAY_SIZE(ksz_switch_chips); i++) {
+ const struct ksz_chip_data *chip = &ksz_switch_chips[i];
+
+ if (dev->chip_id == chip->chip_id) {
+ dev->name = chip->dev_name;
+ dev->num_vlans = chip->num_vlans;
+ dev->num_alus = chip->num_alus;
+ dev->num_statics = chip->num_statics;
+ dev->port_cnt = chip->port_cnt;
+ dev->cpu_ports = chip->cpu_ports;
+
+ break;
+ }
+ }
+
+ /* no switch found */
+ if (!dev->port_cnt)
+ return -ENODEV;
+
+ dev->PORT_MASK = (1 << dev->port_cnt) - 1;
+ dev->PORT_MASK |= dev->HOST_MASK;
+
+ i = dev->mib_port_cnt;
+ dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i,
+ GFP_KERNEL);
+ if (!dev->ports)
+ return -ENOMEM;
+ for (i = 0; i < dev->mib_port_cnt; i++) {
+ dev->ports[i].mib.info =
+ devm_kzalloc(dev->dev,
+ sizeof(struct ksz_port_mib_info) *
+ TOTAL_SWITCH_COUNTER_NUM, GFP_KERNEL);
+ if (!dev->ports[i].mib.info)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void ksz_switch_exit(struct ksz_device *dev) { }
+
+static const struct ksz_dev_ops ksz9477_dev_ops = {
+ .get_port_addr = ksz_get_port_addr,
+ .reset = ksz_reset_switch,
+ .get_rx_len = ksz_get_rx_len,
+ .get_tx_len = ksz_get_tx_len,
+ .add_tail_tag = ksz_add_tail_tag,
+ .get_tail_tag = ksz_get_tail_tag,
+ .detect = ksz_switch_detect,
+ .init = ksz_switch_init,
+ .exit = ksz_switch_exit,
+};
+
+int ksz9477_switch_register(struct ksz_device *dev) {
+ return ksz_switch_register(dev, &ksz9477_dev_ops); }
+EXPORT_SYMBOL(ksz9477_switch_register);
+
+MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>");
+MODULE_DESCRIPTION("Microchip KSZ Series Switch DSA Driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
* [PATCH RFC 1/6] The file ksz_common.c will be used by other KSZ switch drivers.
From: Tristram.Ha @ 2017-09-07 21:08 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218C4@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
Break ksz_common.c into 2 files so that the common code can be used by other KSZ switch drivers.
Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
---
diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
index ed335e2..0961c30 100644
--- a/drivers/net/dsa/microchip/Makefile
+++ b/drivers/net/dsa/microchip/Makefile
@@ -1,2 +1,2 @@
-obj-$(CONFIG_MICROCHIP_KSZ) += ksz_common.o
+obj-$(CONFIG_MICROCHIP_KSZ) += ksz9477.o ksz_common.o
obj-$(CONFIG_MICROCHIP_KSZ_SPI_DRIVER) += ksz_spi.o
^ permalink raw reply related
* [PATCH RFC 0/6] Modify KSZ9477 DSA driver in preparation to add other KSZ switch drivers.
From: Tristram.Ha @ 2017-09-07 21:08 UTC (permalink / raw)
To: andrew, muvarov, pavel, nathan.leigh.conrad, vivien.didelot,
f.fainelli, netdev, linux-kernel, Woojung.Huh
In-Reply-To: <93AF473E2DA327428DE3D46B72B1E9FD411218B7@CHN-SV-EXMX02.mchp-main.com>
From: Tristram Ha <Tristram.Ha@microchip.com>
This series of patches is to modify the original KSZ9477 DSA driver so that other KSZ switch drivers can be added and use the common code.
This patch set is against net-next.
drivers/net/dsa/microchip/Makefile | 2 +-
drivers/net/dsa/microchip/ksz9477.c | 1317 ++++++++++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz_common.c | 1156 +---------------------------
drivers/net/dsa/microchip/ksz_priv.h | 105 ++-
drivers/net/dsa/microchip/ksz_spi.c | 13 +-
net/dsa/tag_ksz.c | 40 +-
6 files changed, 1458 insertions(+), 1175 deletions(-) create mode 100644 drivers/net/dsa/microchip/ksz9477.c
^ permalink raw reply
* Re: [Patch net v2 2/2] net_sched: fix all the madness of tc filter chain
From: Jiri Pirko @ 2017-09-07 20:52 UTC (permalink / raw)
To: Cong Wang; +Cc: Linux Kernel Network Developers, Jakub Kicinski, Jiri Pirko
In-Reply-To: <CAM_iQpXsYc1cfZmuFKGH1O-FPJr9+NJqZTTB5z1oJVGYgvkV+Q@mail.gmail.com>
Thu, Sep 07, 2017 at 07:45:49PM CEST, xiyou.wangcong@gmail.com wrote:
>On Wed, Sep 6, 2017 at 11:32 PM, Jiri Pirko <jiri@resnulli.us> wrote:
>> Thu, Sep 07, 2017 at 06:26:07AM CEST, xiyou.wangcong@gmail.com wrote:
>>>This patch fixes the following madness of tc filter chain:
>>
>> Could you avoid expressive words like "madness" and such?
>> Please be technical.
>>
>
>If the following 2a) 2b) 2c) 2d) are not enough to show the madness,
>I don't know any other to show it. Madness is for code, not for you
>or any other person, so 100% technical.
We hav eto agree to disagree. You want to be expressive, apparently
there is no way to talk you out of that. Sigh...
>
>>
>>>
>>>1) tcf_chain_destroy() is called by both tcf_block_put() and
>>> tcf_chain_put(). tcf_chain_put() is correctly refcnt'ed and paired
>>> with tcf_chain_get(), but tcf_block_put() is not, it should be paired
>>> with tcf_block_get() which means we still need to decrease the refcnt.
>>> Think it in another way: if we call tcf_bock_put() immediately after
>>> tcf_block_get(), could we get effectively a nop? This causes a memory
>>> leak as reported by Jakub.
>>>
>>>2) tp proto should hold a refcnt to the chain too. This significantly
>>> simplifies the logic:
>>>
>>>2a) Chain 0 is no longer special, it is created and refcnted by tp
>>> like any other chains. All the ugliness in tcf_chain_put() can be
>>> gone!
>>>
>>>2b) No need to handle the flushing oddly, because block still holds
>>> chain 0, it can not be released, this guarantees block is the last
>>> user.
>>>
>>>2c) The race condition with RCU callbacks is easier to handle with just
>>> a rcu_barrier()! Much easier to understand, nothing to hide! Thanks
>>> to the previous patch. Please see also the comments in code.
>>>
>>>2d) Make the code understandable by humans, much less error-prone.
>>>
>>>Fixes: 744a4cf63e52 ("net: sched: fix use after free when tcf_chain_destroy is called multiple times")
>>>Fixes: 5bc1701881e3 ("net: sched: introduce multichain support for filters")
>>>Reported-by: Jakub Kicinski <jakub.kicinski@netronome.com>
>>>Cc: Jiri Pirko <jiri@mellanox.com>
>>>Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
>>>---
>>> net/sched/cls_api.c | 38 ++++++++++++++++++++++----------------
>>> 1 file changed, 22 insertions(+), 16 deletions(-)
>>>
>>>diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
>>>index 6c5ea84d2682..e9060dc36519 100644
>>>--- a/net/sched/cls_api.c
>>>+++ b/net/sched/cls_api.c
>>>@@ -209,21 +209,20 @@ static void tcf_chain_flush(struct tcf_chain *chain)
>>> RCU_INIT_POINTER(*chain->p_filter_chain, NULL);
>>> while ((tp = rtnl_dereference(chain->filter_chain)) != NULL) {
>>> RCU_INIT_POINTER(chain->filter_chain, tp->next);
>>>+ tcf_chain_put(chain);
>>> tcf_proto_destroy(tp);
>>> }
>>> }
>>>
>>> static void tcf_chain_destroy(struct tcf_chain *chain)
>>> {
>>>- /* May be already removed from the list by the previous call. */
>>>- if (!list_empty(&chain->list))
>>>- list_del_init(&chain->list);
>>>+ list_del(&chain->list);
>>>+ kfree(chain);
>>>+}
>>>
>>>- /* There might still be a reference held when we got here from
>>>- * tcf_block_put. Wait for the user to drop reference before free.
>>>- */
>>>- if (!chain->refcnt)
>>>- kfree(chain);
>>>+static void tcf_chain_hold(struct tcf_chain *chain)
>>>+{
>>>+ ++chain->refcnt;
>>> }
>>>
>>> struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
>>>@@ -233,7 +232,7 @@ struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
>>>
>>> list_for_each_entry(chain, &block->chain_list, list) {
>>> if (chain->index == chain_index) {
>>>- chain->refcnt++;
>>>+ tcf_chain_hold(chain);
>>> return chain;
>>> }
>>> }
>>>@@ -246,10 +245,7 @@ EXPORT_SYMBOL(tcf_chain_get);
>>>
>>> void tcf_chain_put(struct tcf_chain *chain)
>>> {
>>>- /* Destroy unused chain, with exception of chain 0, which is the
>>>- * default one and has to be always present.
>>>- */
>>>- if (--chain->refcnt == 0 && !chain->filter_chain && chain->index != 0)
>>>+ if (--chain->refcnt == 0)
>>
>> Okay, so you take the reference for every goto_chain action and every
>> tp, right? Note that for chain 0, you hold one more reference (due to
>> the creation). That is probably ok as we need chain 0 not to go away
>> even if all tps and goto_chain actions are gone.
>
>Yeah, this is the core of the patch.
>
>>
>>
>>> tcf_chain_destroy(chain);
>>> }
>>> EXPORT_SYMBOL(tcf_chain_put);
>>>@@ -294,10 +290,18 @@ void tcf_block_put(struct tcf_block *block)
>>> if (!block)
>>> return;
>>>
>>>- list_for_each_entry_safe(chain, tmp, &block->chain_list, list) {
>>>+ /* Standalone actions are not allowed to jump to any chain, and
>>>+ * bound actions should be all removed after flushing. However,
>>>+ * filters are destroyed in RCU callbacks, we have to flush and wait
>>>+ * for them before releasing this refcnt, otherwise we race with RCU
>>>+ * callbacks!!!
>>
>> Why the "!!!"? Please avoid that. Not necessary at all.
>
>
>Because we don't have lock here, we have to pay more extra
>attention to it. Playing list without lock is like playing fire. I use "!!!" to
>draw people's attention when they touch it, perhaps "XXX" is better?
>
>
>>
>>
>>>+ */
>>>+ list_for_each_entry(chain, &block->chain_list, list)
>>> tcf_chain_flush(chain);
>>>- tcf_chain_destroy(chain);
>>>- }
>>>+ rcu_barrier();
>>
>> This actually tries to fix another bug I discovered yesterday. Good.
>>
>
>This on the other hand proves we should make the code clean
>and understandable asap, not just wait for net-next.
>
>
>
>>
>>>+
>>>+ list_for_each_entry_safe(chain, tmp, &block->chain_list, list)
>>>+ tcf_chain_put(chain);
>>
>> Which reference are you putting here? For chain 0, that is the original
>> reference due to creation from block_get. But how about the other
>> chains? If you do flush all in the previous list iteration, they are
>> removed there. Also note that they are removed from the list while
>> iterating it.
>
>
>Yes it is for chain 0, because block holds a reference to chain 0 during
>creation. Non-0 chains are created with refcnt==1 too but paired with
>tcf_chain_put() rather than tcf_block_put(). This is what makes chain 0
>not special w.r.t. refcnt.
So you need to tcf_chain_put only chain 0 here, right? The rest of the
chains get destroyed by the previous list_for_each_entry iteration when
flush happens and actions destroy happens what decrements recnt to 0
there.
What do I miss, who would still hold reference for non-0 chains when all
tps and all goto_chain actions are gone?
>
>
>>
>> I believe that you need to add tcf_chain_hold(chain) to the start of the
>> previous list iteration to ensure all existing chains will stay, then
>> you can put them here.
>
>We don't need it, it is already perfectly paired with tcf_block_get().
>Think about the following:
>
>1)
>tcf_block_get(...); // create chain 0 with refcnt=1
>tcf_block_put(); // no tp, only chain 0 in chain_list
>// chain 0 is put, refcnt=0, it is gone
>
>2)
>tcf_block_get(); // create chain 0 with refcnt=1
>...
>tcf_chain_get(11); // create chain 11 with refcnt=1
>// one tp is inserted to chain 11, now refcnt==2
>// in tc_ctl_tfilter()
>tcf_chain_put(11); // paired with above get, refcnt==1
>...
>tcf_block_put(); // flush chain 11, tp removed, refcnt==0
>// put chain 0 too, paired with block get, refcnt == 0
>// both chain 0 and chain11 are gone
>
>
>
>>
>> Did you test this? I believe we need some simple test script.
>
>Of course I did. I verified memleak is gone and tested basic
>filters and gact actions with chain 0 and chain 11, everything
>works as expected, I also added a printk() to verify the chain
>is really gone as soon as all references are gone.
>
>I will contribute my test cases back after I figure out how.
>Also net-next is already closed.
^ permalink raw reply
* Re: [RFC PATCH v3 0/6] Configuring traffic classes via new hardware offload mechanism in tc/mqprio
From: Nambiar, Amritha @ 2017-09-07 20:09 UTC (permalink / raw)
To: Florian Fainelli, intel-wired-lan, jeffrey.t.kirsher
Cc: alexander.h.duyck, netdev
In-Reply-To: <3ecacf3d-1f02-fbc6-0e90-8e84dcd15a4e@gmail.com>
On 9/7/2017 11:34 AM, Florian Fainelli wrote:
> On 09/07/2017 04:00 AM, Amritha Nambiar wrote:
>> The following series introduces a new hardware offload mode in
>> tc/mqprio where the TCs, the queue configurations and bandwidth
>> rate limits are offloaded to the hardware. The existing mqprio
>> framework is extended to configure the queue counts and layout and
>> also added support for rate limiting. This is achieved through new
>> netlink attributes for the 'mode' option which takes values such
>> as 'dcb' (default) and 'channel' and a 'shaper' option for QoS
>> attributes such as bandwidth rate limits in hw mode 1.
>
> So "dcb" defines a default priorities to queue mapping?
In the default offload implementation, only the basic hw offload was
supported factoring only the 'number of TCs' values. The rest of the
queue configuration was being ignored. This is the legacy behavior with
hw mode set to 1.
Example:
# tc qdisc add dev eth0 root mqprio num_tc 4 map 0 0 0 0 1 1 1 1 queues
4@0 4@4 hw 1
I just named this default behavior to 'dcb' while introducing a new
offload mechanism.
>
>> Legacy devices can fall back to the existing setup supporting hw
>> mode 1 without these additional options where only the TCs are
>> offloaded and then the 'mode' and 'shaper' options defaults to DCB
>> support.
>
> That's the last part that confuses me, see below.
As I introduced new options for 'mode' and 'shaper', I set the defaults
for these options to 'dcb' so existing offloaders can continue to work
without supporting these new options. Patch 1 has a detailed description
on how this is done.
>
>> The i40e driver enables the new mqprio hardware offload mechanism
>> factoring the TCs, queue configuration and bandwidth rates by
>> creating HW channel VSIs.
>
> I am really confused by what you call hw_mode 1, as I understand it
> there are really 3 different modes:
There are actually 2 modes now with 'hw' option set to 1, legacy/dcb and
channel.
>
> - legacy: you don't define any traffic class mapping, but you can
> still chain this scheduler with a match + action (like what
> Documentation/networking/multiqueue.txt) you can optionally also add
> "shaper" arguments, but there should not be any default DCB queue
> mapping either?
>
> - dcb: a default mapping for traffic classes to queues is defined,
> optional "shaper" arguments
The legacy mode now becomes the dcb mode. In this mode, although the TC
values, the queue configurations, prio-tc-mapping are all offloaded to
the device, the existing implementation in current drivers support only
a basic hw offload factoring only the TC values.
Examples:
# ... num_tc 2 map 0 0 0 0 1 1 1 1 queues 4@0 4@4 hw 1
# ... num_tc 2 map 0 0 0 0 1 1 1 1 queues 4@0 4@4 hw 1 mode dcb
# ... num_tc 2 map 0 0 0 0 1 1 1 1 queues 4@0 4@4 hw 1 mode dcb\
shaper dcb
>
> - channel: (maybe calling that "custom_tc_map" would be clearer?)
> where you express the exact traffic classes to queue mapping and
> optional "shaper" arguments
In the channel mode, a full hw offload is supported, the TC values, the
queue configurations and additionally QoS attributes (optional) are all
used in the new implementation.
Examples:
# ... num_tc 2 map 0 0 0 0 1 1 1 1 queues 4@0 4@4 hw 1 mode channel
# ... num_tc 2 map 0 0 0 0 1 1 1 1 queues 4@0 4@4 hw 1 mode channel\
shaper bw_rlimit max_rate 4Gbit 5Gbit
>
> I think that's what you are doing, but I just got confused by the
> cover letter.
>
>>
>> In this new mode, the priority to traffic class mapping and the
>> user specified queue ranges are used to configure the traffic class
>> when the 'mode' option is set to 'channel'. This is achieved by
>> creating HW channels(VSI). A new channel is created for each of the
>> traffic class configuration offloaded via mqprio framework except
>> for the first TC (TC0) which is for the main VSI. TC0 for the main
>> VSI is also reconfigured as per user provided queue parameters.
>> Finally, bandwidth rate limits are set on these traffic classes
>> through the shaper attribute by sending these rates in addition to
>> the number of TCs and the queue configurations.
>>
>> Example: # tc qdisc add dev eth0 root mqprio num_tc 2 map 0 0 0 0 1
>> 1 1 1\ queues 4@0 4@4 hw 1 mode channel shaper bw_rlimit\
>
> Do you see a case where you can declare a different number of
> traffic classes say 4 and map them onto just 2 hardware queues? If
> not, it seems a tiny bit redundant to have to specify both the map
> and the queue mapping should be sufficient, right?
This will be subjected to validation of the user input and will be
treated as invalid configuration. The 'map' specifies the mapping
between user priorities and traffic classes while the queue mapping is
the queue layout specifying the queue count and offsets.
>
>> min_rate 1Gbit 2Gbit max_rate 4Gbit 5Gbit
>>
>> To dump the bandwidth rates:
>>
>> # tc qdisc show dev eth0
>>
>> qdisc mqprio 804a: root tc 2 map 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
>> queues:(0:3) (4:7) mode:channel shaper:bw_rlimit min_rate:1Gbit
>> 2Gbit max_rate:4Gbit 5Gbit
>
> I am not well versed into tc, but being able to specify "shaper"
> arguments has actually value outside of just the multiq scheduler and
> it could probably be an action on its own?
The mqprio scheduler already supports configuring the traffic classes
and enabling support for HW shapers was just a matter to extending it
with new netlink based attributes.
>
>>
>> ---
>>
>> Amritha Nambiar (6): mqprio: Introduce new hardware offload mode
>> and shaper in mqprio i40e: Add macro for PF reset bit i40e: Add
>> infrastructure for queue channel support i40e: Enable 'channel'
>> mode in mqprio for TC configs i40e: Refactor VF BW rate limiting
>> i40e: Add support setting TC max bandwidth rates
>>
>>
>> drivers/net/ethernet/intel/i40e/i40e.h | 44 +
>> drivers/net/ethernet/intel/i40e/i40e_debugfs.c | 3
>> drivers/net/ethernet/intel/i40e/i40e_ethtool.c | 8
>> drivers/net/ethernet/intel/i40e/i40e_main.c | 1463
>> +++++++++++++++++--- drivers/net/ethernet/intel/i40e/i40e_txrx.h |
>> 2 drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c | 50 -
>> include/net/pkt_cls.h | 9
>> include/uapi/linux/pkt_sched.h | 32
>> net/sched/sch_mqprio.c | 183 ++- 9
>> files changed, 1551 insertions(+), 243 deletions(-)
>>
>> --
>>
>
>
^ permalink raw reply
* Re: [RFC net-next 0/5] TSN: Add qdisc-based config interfaces for traffic shapers
From: Guedes, Andre @ 2017-09-07 19:58 UTC (permalink / raw)
To: Gomes, Vinicius, henrik@austad.us
Cc: jiri@resnulli.us, jhs@mojatatu.com, Ong, Boon Leong,
xiyou.wangcong@gmail.com, Sanchez-Palencia, Jesus,
richardcochran@gmail.com, netdev@vger.kernel.org, Briano, Ivan,
intel-wired-lan@lists.osuosl.org
In-Reply-To: <20170907053411.GA6580@sisyphus.home.austad.us>
[-- Attachment #1: Type: text/plain, Size: 3398 bytes --]
Hi Henrik,
Thanks for your feedback! I'll address some of your comments below.
On Thu, 2017-09-07 at 07:34 +0200, Henrik Austad wrote:
> > As for the shapers config interface:
> >
> > * CBS (802.1Qav)
> >
> > This patchset is proposing a new qdisc called 'cbs'. Its 'tc' cmd line
> > is:
> > $ tc qdisc add dev IFACE parent ID cbs locredit N hicredit M sendslope S
> > \
> > idleslope I
>
> So this confuses me a bit, why specify sendSlope?
>
> sendSlope = portTransmitRate - idleSlope
>
> and portTransmitRate is the speed of the MAC (which you get from the
> driver). Adding sendSlope here is just redundant I think.
Yes, this was something we've spent quite a few time discussing before this RFC
series. After reading the Annex L from 802.1Q-2014 (operation of CBS algorithm)
so many times, we've came up with the rationale explained below.
The rationale here is that sendSlope is just another parameter from CBS
algorithm like idleSlope, hiCredit and loCredit. As such, its calculation
should be done at the same "layer" as the others parameters (in this case, user
space) in order to keep consistency. Moreover, in this design, the driver layer
is dead simple: all the device driver has to do is applying CBS parameters to
hardware. Having any CBS parameter calculation in the driver layer means all
device drivers must implement that calculation.
> Also, does this mean that when you create the qdisc, you have locked the
> bandwidth for the scheduler? Meaning, if I later want to add another
> stream that requires more bandwidth, I have to close all active streams,
> reconfigure the qdisc and then restart?
If we want to reserve more bandwidth to "accommodate" a new stream, we don't
need to close all active streams. All we have to do is changing the CBS qdisc
and pass the new CBS parameters. Here is what the command-line would look like:
$ tc qdisc change dev enp0s4 parent 8001:5 cbs locredit -1470 hicredit 30
sendslope -980000 idleslope 20000
No application/stream is interrupted while new CBS parameters are applied.
> > Note that the parameters for this qdisc are the ones defined by the
> > 802.1Q-2014 spec, so no hardware specific functionality is exposed here.
>
> You do need to know if the link is brought up as 100 or 1000 though - which
> the driver already knows.
User space knows that information via ethtool or /sys.
> > Testing this RFC
> > ================
> >
> > For testing the patches of this RFC only, you can refer to the samples and
> > helper script being added to samples/tsn/ and the use the 'mqprio' qdisc to
> > setup the priorities to Tx queues mapping, together with the 'cbs' qdisc to
> > configure the HW shaper of the i210 controller:
>
> I will test it, feedback will be provided soon! :)
That's great! Please let us know if you find any issue and thanks for you
support.
> > 8) You can also run a Talker for class B (prio 2 here)
> > $ ./talker -i enp3s0 -p 2
> >
> > * The bandwidth displayed on the listener output now should increase to
> > very
> > close to the one configured for class A + class B.
>
> Because you grab both class A *and* B, or because B will eat what A does
> not use?
Because the listener application grabs both class A and B traffic.
Regards,
Andre
[-- Attachment #2: smime.p7s --]
[-- Type: application/x-pkcs7-signature, Size: 3262 bytes --]
^ permalink raw reply
* [PATCH] net: tulip: Constify tulip_tbl
From: Kees Cook @ 2017-09-07 19:35 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Jarod Wilson, Gustavo A. R. Silva, linux-parisc,
linux-kernel
It looks like all users of tulip_tbl are reads, so mark this table
as read-only.
$ git grep tulip_tbl # edited to avoid line-wraps...
interrupt.c: iowrite32(tulip_tbl[tp->chip_id].valid_intrs, ...
interrupt.c: iowrite32(tulip_tbl[tp->chip_id].valid_intrs&~RxPollInt, ...
interrupt.c: iowrite32(tulip_tbl[tp->chip_id].valid_intrs, ...
interrupt.c: iowrite32(tulip_tbl[tp->chip_id].valid_intrs | TimerInt,
pnic.c: iowrite32(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
tulip.h: extern struct tulip_chip_table tulip_tbl[];
tulip_core.c:struct tulip_chip_table tulip_tbl[] = {
tulip_core.c:iowrite32(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR5);
tulip_core.c:iowrite32(tulip_tbl[tp->chip_id].valid_intrs, ioaddr + CSR7);
tulip_core.c:setup_timer(&tp->timer, tulip_tbl[tp->chip_id].media_timer,
tulip_core.c:const char *chip_name = tulip_tbl[chip_idx].chip_name;
tulip_core.c:if (pci_resource_len (pdev, 0) < tulip_tbl[chip_idx].io_size)
tulip_core.c:ioaddr = pci_iomap(..., tulip_tbl[chip_idx].io_size);
tulip_core.c:tp->flags = tulip_tbl[chip_idx].flags;
tulip_core.c:setup_timer(&tp->timer, tulip_tbl[tp->chip_id].media_timer,
tulip_core.c:INIT_WORK(&tp->media_work, tulip_tbl[tp->chip_id].media_task);
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Jarod Wilson <jarod@redhat.com>
Cc: "Gustavo A. R. Silva" <gustavo@embeddedor.com>
Cc: netdev@vger.kernel.org
Cc: linux-parisc@vger.kernel.org
Signed-off-by: Kees Cook <keescook@chromium.org>
---
drivers/net/ethernet/dec/tulip/tulip.h | 2 +-
drivers/net/ethernet/dec/tulip/tulip_core.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/dec/tulip/tulip.h b/drivers/net/ethernet/dec/tulip/tulip.h
index 38431a155f09..06660dbc44b7 100644
--- a/drivers/net/ethernet/dec/tulip/tulip.h
+++ b/drivers/net/ethernet/dec/tulip/tulip.h
@@ -515,7 +515,7 @@ void comet_timer(unsigned long data);
extern int tulip_debug;
extern const char * const medianame[];
extern const char tulip_media_cap[];
-extern struct tulip_chip_table tulip_tbl[];
+extern const struct tulip_chip_table tulip_tbl[];
void oom_timer(unsigned long data);
extern u8 t21040_csr13[];
diff --git a/drivers/net/ethernet/dec/tulip/tulip_core.c b/drivers/net/ethernet/dec/tulip/tulip_core.c
index 84394b43c0a1..851b6d1f5a42 100644
--- a/drivers/net/ethernet/dec/tulip/tulip_core.c
+++ b/drivers/net/ethernet/dec/tulip/tulip_core.c
@@ -138,7 +138,7 @@ static void tulip_timer(unsigned long data)
* It is indexed via the values in 'enum chips'
*/
-struct tulip_chip_table tulip_tbl[] = {
+const struct tulip_chip_table tulip_tbl[] = {
{ }, /* placeholder for array, slot unused currently */
{ }, /* placeholder for array, slot unused currently */
--
2.7.4
--
Kees Cook
Pixel Security
^ permalink raw reply related
* Re: [PATCH net-next v2 01/10] net: dsa: add debugfs interface
From: Greg KH @ 2017-09-07 19:34 UTC (permalink / raw)
To: Vivien Didelot
Cc: netdev, linux-kernel, kernel, David S. Miller, Florian Fainelli,
Andrew Lunn, Egil Hjelmeland, John Crispin, Woojung Huh,
Sean Wang, Nikita Yushchenko, Chris Healy
In-Reply-To: <20170828191748.19492-2-vivien.didelot@savoirfairelinux.com>
I agree you shouldn't be using debugfs for this, but in the future, if
you do write debugfs code, please take the following review into
account:
On Mon, Aug 28, 2017 at 03:17:39PM -0400, Vivien Didelot wrote:
> +static int dsa_debugfs_create_port(struct dsa_switch *ds, int port)
> +{
> + struct dentry *dir;
> + char name[32];
> +
> + snprintf(name, sizeof(name), DSA_PORT_FMT, port);
> +
> + dir = debugfs_create_dir(name, ds->debugfs_dir);
> + if (IS_ERR_OR_NULL(dir))
> + return -EFAULT;
You should _never_ care about the return value of a debugfs call, and
you should not need to ever propagate the error upward. The api was
written to not need this.
Just call the function, and return, that's it. If you need to save the
return value (i.e. it's a dentry), you also don't care, just save it and
pass it to some other debugfs call, and all will still be fine. Your
code should never do anything different if a debugfs call succeeds or
fails.
> +static int dsa_debugfs_create_switch(struct dsa_switch *ds)
> +{
> + char name[32];
> + int i, err;
> +
> + /* skip if there is no debugfs support */
> + if (!dsa_debugfs_dir)
> + return 0;
Again, you don't care, all of these functions should return void.
> + snprintf(name, sizeof(name), DSA_SWITCH_FMT, ds->index);
> +
> + ds->debugfs_dir = debugfs_create_dir(name, dsa_debugfs_dir);
> + if (IS_ERR_OR_NULL(ds->debugfs_dir))
> + return -EFAULT;
See, that's horrid, you should never need to make such a bad check.
Also, even if it were the correct way to do this you never return EFAULT
unless there is a memory copy error to/from userspace. That is not the
case here, or in any of this code, right?
> +static void dsa_debugfs_destroy_switch(struct dsa_switch *ds)
> +{
> + /* handles NULL */
> + debugfs_remove_recursive(ds->debugfs_dir);
Of course it handles NULL, why comment that? That's the whole goal of
debugfs, to be dirt simple, allow you to do anything you want, in almost
no lines of code.
Also, it will never be mounted on a "real" system, so you better not
rely on it for anything "real".
> + err = dsa_debugfs_create_switch(ds);
> + if (err) {
> + pr_warn("DSA: failed to create debugfs interface for switch %d (%d)\n",
> + ds->index, err);
Never complain to the syslog about a debugfs issue.
> +void dsa_debugfs_destroy_module(void)
> +{
> + /* handles NULL */
> + debugfs_remove_recursive(dsa_debugfs_dir);
again, of course it does, do you think we don't know how to write an
api? :)
thanks,
greg k-h
^ permalink raw reply
* Re: [RFC PATCH v3 0/6] Configuring traffic classes via new hardware offload mechanism in tc/mqprio
From: Florian Fainelli @ 2017-09-07 18:34 UTC (permalink / raw)
To: Amritha Nambiar, intel-wired-lan, jeffrey.t.kirsher
Cc: alexander.h.duyck, netdev
In-Reply-To: <150478158684.24662.17975701233699487888.stgit@anamdev.jf.intel.com>
On 09/07/2017 04:00 AM, Amritha Nambiar wrote:
> The following series introduces a new hardware offload mode in
> tc/mqprio where the TCs, the queue configurations and
> bandwidth rate limits are offloaded to the hardware. The existing
> mqprio framework is extended to configure the queue counts and
> layout and also added support for rate limiting. This is achieved
> through new netlink attributes for the 'mode' option which takes
> values such as 'dcb' (default) and 'channel' and a 'shaper' option
> for QoS attributes such as bandwidth rate limits in hw mode 1.
So "dcb" defines a default priorities to queue mapping?
> Legacy devices can fall back to the existing setup supporting hw mode
> 1 without these additional options where only the TCs are offloaded
> and then the 'mode' and 'shaper' options defaults to DCB support.
That's the last part that confuses me, see below.
> The i40e driver enables the new mqprio hardware offload mechanism
> factoring the TCs, queue configuration and bandwidth rates by
> creating HW channel VSIs.
I am really confused by what you call hw_mode 1, as I understand it
there are really 3 different modes:
- legacy: you don't define any traffic class mapping, but you can still
chain this scheduler with a match + action (like what
Documentation/networking/multiqueue.txt) you can optionally also add
"shaper" arguments, but there should not be any default DCB queue
mapping either?
- dcb: a default mapping for traffic classes to queues is defined,
optional "shaper" arguments
- channel: (maybe calling that "custom_tc_map" would be clearer?) where
you express the exact traffic classes to queue mapping and optional
"shaper" arguments
I think that's what you are doing, but I just got confused by the cover
letter.
>
> In this new mode, the priority to traffic class mapping and the
> user specified queue ranges are used to configure the traffic
> class when the 'mode' option is set to 'channel'. This is achieved by
> creating HW channels(VSI). A new channel is created for each of the
> traffic class configuration offloaded via mqprio framework except for
> the first TC (TC0) which is for the main VSI. TC0 for the main VSI is
> also reconfigured as per user provided queue parameters. Finally,
> bandwidth rate limits are set on these traffic classes through the
> shaper attribute by sending these rates in addition to the number of
> TCs and the queue configurations.
>
> Example:
> # tc qdisc add dev eth0 root mqprio num_tc 2 map 0 0 0 0 1 1 1 1\
> queues 4@0 4@4 hw 1 mode channel shaper bw_rlimit\
Do you see a case where you can declare a different number of traffic
classes say 4 and map them onto just 2 hardware queues? If not, it seems
a tiny bit redundant to have to specify both the map and the queue
mapping should be sufficient, right?
> min_rate 1Gbit 2Gbit max_rate 4Gbit 5Gbit
>
> To dump the bandwidth rates:
>
> # tc qdisc show dev eth0
>
> qdisc mqprio 804a: root tc 2 map 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
> queues:(0:3) (4:7)
> mode:channel
> shaper:bw_rlimit min_rate:1Gbit 2Gbit max_rate:4Gbit 5Gbit
I am not well versed into tc, but being able to specify "shaper"
arguments has actually value outside of just the multiq scheduler and it
could probably be an action on its own?
>
> ---
>
> Amritha Nambiar (6):
> mqprio: Introduce new hardware offload mode and shaper in mqprio
> i40e: Add macro for PF reset bit
> i40e: Add infrastructure for queue channel support
> i40e: Enable 'channel' mode in mqprio for TC configs
> i40e: Refactor VF BW rate limiting
> i40e: Add support setting TC max bandwidth rates
>
>
> drivers/net/ethernet/intel/i40e/i40e.h | 44 +
> drivers/net/ethernet/intel/i40e/i40e_debugfs.c | 3
> drivers/net/ethernet/intel/i40e/i40e_ethtool.c | 8
> drivers/net/ethernet/intel/i40e/i40e_main.c | 1463 +++++++++++++++++---
> drivers/net/ethernet/intel/i40e/i40e_txrx.h | 2
> drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c | 50 -
> include/net/pkt_cls.h | 9
> include/uapi/linux/pkt_sched.h | 32
> net/sched/sch_mqprio.c | 183 ++-
> 9 files changed, 1551 insertions(+), 243 deletions(-)
>
> --
>
--
Florian
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox