lkml.org 
[lkml]   [2022]   [Oct]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC net-next 2/2] net: dsa: Add driver for Maxlinear GSW1XX switch
Date
Add initial framework for Maxlinear's GSW1xx switch and
currently only GSW145 in MDIO managed mode is supported.

Signed-off-by: Camel Guo <camel.guo@axis.com>
---
MAINTAINERS | 3 +
drivers/net/dsa/Kconfig | 16 +
drivers/net/dsa/Makefile | 2 +
drivers/net/dsa/gsw1xx.h | 27 ++
drivers/net/dsa/gsw1xx_core.c | 823 ++++++++++++++++++++++++++++++++++
drivers/net/dsa/gsw1xx_mdio.c | 128 ++++++
6 files changed, 999 insertions(+)
create mode 100644 drivers/net/dsa/gsw1xx.h
create mode 100644 drivers/net/dsa/gsw1xx_core.c
create mode 100644 drivers/net/dsa/gsw1xx_mdio.c

diff --git a/MAINTAINERS b/MAINTAINERS
index df88faabdb53..40371dc9e2dd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12590,6 +12590,9 @@ M: Camel Guo <camel.guo@axis.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/mxl,gsw.yaml
+F: drivers/net/dsa/gsw1xx.h
+F: drivers/net/dsa/gsw1xx_core.c
+F: drivers/net/dsa/gsw1xx_mdio.c

MCBA MICROCHIP CAN BUS ANALYZER TOOL DRIVER
R: Yasushi SHOJI <yashi@spacecubics.com>
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 07507b4820d7..af57b92f786a 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -122,4 +122,20 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
+
+config NET_DSA_MXL_GSW1XX
+ tristate
+ select REGMAP
+ help
+ This enables support for the Maxlinear GSW1XX integrated ethernet
+ switch chips.
+
+config NET_DSA_MXL_GSW1XX_MDIO
+ tristate "MaxLinear GSW1XX ethernet switch in MDIO managed mode"
+ select NET_DSA_MXL_GSW1XX
+ select FIXED_PHY
+ help
+ This enables access functions if the MaxLinear GSW1XX is configured
+ for MDIO managed mode.
+
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index 16eb879e0cb4..022fc661107b 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -15,6 +15,8 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
+obj-$(CONFIG_NET_DSA_MXL_GSW1XX) += gsw1xx_core.o
+obj-$(CONFIG_NET_DSA_MXL_GSW1XX_MDIO) += gsw1xx_mdio.o
obj-y += b53/
obj-y += hirschmann/
obj-y += microchip/
diff --git a/drivers/net/dsa/gsw1xx.h b/drivers/net/dsa/gsw1xx.h
new file mode 100644
index 000000000000..08b2975e1267
--- /dev/null
+++ b/drivers/net/dsa/gsw1xx.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef GSW1XX_H
+#define GSW1XX_H
+
+#include <linux/regmap.h>
+#include <linux/device.h>
+#include <net/dsa.h>
+
+struct gsw1xx_hw_info {
+ int max_ports;
+ int cpu_port;
+};
+
+struct gsw1xx_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct dsa_switch *ds;
+ const struct gsw1xx_hw_info *hw_info;
+};
+
+extern const struct regmap_access_table gsw1xx_register_set;
+
+int gsw1xx_probe(struct gsw1xx_priv *priv, struct device *dev);
+void gsw1xx_remove(struct gsw1xx_priv *priv);
+void gsw1xx_shutdown(struct gsw1xx_priv *priv);
+
+#endif /* GSW1XX_H */
diff --git a/drivers/net/dsa/gsw1xx_core.c b/drivers/net/dsa/gsw1xx_core.c
new file mode 100644
index 000000000000..1b3cbee4addc
--- /dev/null
+++ b/drivers/net/dsa/gsw1xx_core.c
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/phylink.h>
+#include <linux/regmap.h>
+#include <net/dsa.h>
+
+#include "gsw1xx.h"
+
+/* GSW1XX MDIO Registers */
+#define GSW1XX_MDIO_BASE_ADDR 0xF400
+#define GSW1XX_MDIO_REG_LEN 0x0422
+#define GSW1XX_MDIO_GLOB 0x00
+#define GSW1XX_MDIO_GLOB_ENABLE BIT(15)
+#define GSW1XX_MDIO_CTRL 0x08
+#define GSW1XX_MDIO_CTRL_BUSY BIT(12)
+#define GSW1XX_MDIO_CTRL_RD BIT(11)
+#define GSW1XX_MDIO_CTRL_WR BIT(10)
+#define GSW1XX_MDIO_CTRL_PHYAD_MASK 0x1F
+#define GSW1XX_MDIO_CTRL_PHYAD_SHIFT 5
+#define GSW1XX_MDIO_CTRL_REGAD_MASK 0x1F
+#define GSW1XX_MDIO_READ 0x09
+#define GSW1XX_MDIO_WRITE 0x0A
+#define GSW1XX_MDIO_MDC_CFG0 0x0B
+#define GSW1XX_MDIO_PHYp(p) (0x15 - (p))
+#define GSW1XX_MDIO_PHY_LINK_MASK 0x6000
+#define GSW1XX_MDIO_PHY_LINK_AUTO 0x0000
+#define GSW1XX_MDIO_PHY_LINK_DOWN 0x4000
+#define GSW1XX_MDIO_PHY_LINK_UP 0x2000
+#define GSW1XX_MDIO_PHY_SPEED_MASK 0x1800
+#define GSW1XX_MDIO_PHY_SPEED_AUTO 0x1800
+#define GSW1XX_MDIO_PHY_SPEED_M10 0x0000
+#define GSW1XX_MDIO_PHY_SPEED_M100 0x0800
+#define GSW1XX_MDIO_PHY_SPEED_G1 0x1000
+#define GSW1XX_MDIO_PHY_FDUP_MASK 0x0600
+#define GSW1XX_MDIO_PHY_FDUP_AUTO 0x0000
+#define GSW1XX_MDIO_PHY_FDUP_EN 0x0200
+#define GSW1XX_MDIO_PHY_FDUP_DIS 0x0600
+#define GSW1XX_MDIO_PHY_FCONTX_MASK 0x0180
+#define GSW1XX_MDIO_PHY_FCONTX_AUTO 0x0000
+#define GSW1XX_MDIO_PHY_FCONTX_EN 0x0100
+#define GSW1XX_MDIO_PHY_FCONTX_DIS 0x0180
+#define GSW1XX_MDIO_PHY_FCONRX_MASK 0x0060
+#define GSW1XX_MDIO_PHY_FCONRX_AUTO 0x0000
+#define GSW1XX_MDIO_PHY_FCONRX_EN 0x0020
+#define GSW1XX_MDIO_PHY_FCONRX_DIS 0x0060
+#define GSW1XX_MDIO_PHY_ADDR_MASK 0x001F
+#define GSW1XX_MDIO_PHY_MASK (GSW1XX_MDIO_PHY_ADDR_MASK | \
+ GSW1XX_MDIO_PHY_FCONRX_MASK | \
+ GSW1XX_MDIO_PHY_FCONTX_MASK | \
+ GSW1XX_MDIO_PHY_LINK_MASK | \
+ GSW1XX_MDIO_PHY_SPEED_MASK | \
+ GSW1XX_MDIO_PHY_FDUP_MASK)
+
+/* GSW1XX_IP Core Registers */
+#define GSW1XX_IP_BASE_ADDR 0xE000
+#define GSW1XX_IP_REG_LEN 0x0D46
+#define GSW1XX_IP_SWRES 0x000
+#define GSW1XX_IP_SWRES_R1 BIT(1) /* Software reset */
+#define GSW1XX_IP_SWRES_R0 BIT(0) /* Hardware reset */
+#define GSW1XX_IP_VERSION 0x013
+#define GSW1XX_IP_VERSION_REV_SHIFT 0
+#define GSW1XX_IP_VERSION_REV_MASK GENMASK(7, 0)
+#define GSW1XX_IP_VERSION_MOD_SHIFT 8
+#define GSW1XX_IP_VERSION_MOD_MASK GENMASK(15, 8)
+#define GSW1XX_IP_VERSION_2_3 0x023
+
+#define GSW1XX_IP_BM_RAM_VAL(x) (0x043 - (x))
+#define GSW1XX_IP_BM_RAM_ADDR 0x044
+#define GSW1XX_IP_BM_RAM_CTRL 0x045
+#define GSW1XX_IP_BM_RAM_CTRL_BAS BIT(15)
+#define GSW1XX_IP_BM_RAM_CTRL_OPMOD BIT(5)
+#define GSW1XX_IP_BM_RAM_CTRL_ADDR_MASK GENMASK(4, 0)
+#define GSW1XX_IP_BM_QUEUE_GCTRL 0x04A
+#define GSW1XX_IP_BM_QUEUE_GCTRL_GL_MOD BIT(10)
+/* buffer management Port Configuration Register */
+#define GSW1XX_IP_BM_PCFGp(p) (0x080 + ((p) * 2))
+#define GSW1XX_IP_BM_PCFG_CNTEN BIT(0) /* RMON Counter Enable */
+/* PCE */
+#define GSW1XX_IP_PCE_PMAP2 0x454 /* Default Multicast port map */
+#define GSW1XX_IP_PCE_PMAP3 0x455 /* Default Unknown Unicast port map */
+#define GSW1XX_IP_PCE_GCTRL_0 0x456
+#define GSW1XX_IP_PCE_GCTRL_0_MTFL BIT(0) /* MAC Table Flushing */
+#define GSW1XX_IP_PCE_GCTRL_0_MC_VALID BIT(3)
+#define GSW1XX_IP_PCE_PCTRL_0p(p) (0x480 + ((p) * 0xA))
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_LISTEN 0x0
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_RX 0x1
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_TX 0x2
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_LEARNING 0x3
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_FORWARDING 0x7
+#define GSW1XX_IP_PCE_PCTRL_0_PSTATE_MASK GENMASK(2, 0)
+
+#define GSW1XX_IP_MAC_FLEN 0x8C5
+#define GSW1XX_IP_MAC_CTRL_0p(p) (0x903 + ((p) * 0xC))
+#define GSW1XX_IP_MAC_CTRL_0_PADEN BIT(8)
+#define GSW1XX_IP_MAC_CTRL_0_FCS_EN BIT(7)
+#define GSW1XX_IP_MAC_CTRL_0_FCON_MASK 0x0070
+#define GSW1XX_IP_MAC_CTRL_0_FCON_AUTO 0x0000
+#define GSW1XX_IP_MAC_CTRL_0_FCON_RX 0x0010
+#define GSW1XX_IP_MAC_CTRL_0_FCON_TX 0x0020
+#define GSW1XX_IP_MAC_CTRL_0_FCON_RXTX 0x0030
+#define GSW1XX_IP_MAC_CTRL_0_FCON_NONE 0x0040
+#define GSW1XX_IP_MAC_CTRL_0_FDUP_MASK 0x000C
+#define GSW1XX_IP_MAC_CTRL_0_FDUP_AUTO 0x0000
+#define GSW1XX_IP_MAC_CTRL_0_FDUP_EN 0x0004
+#define GSW1XX_IP_MAC_CTRL_0_FDUP_DIS 0x000C
+#define GSW1XX_IP_MAC_CTRL_0_GMII_MASK 0x0003
+#define GSW1XX_IP_MAC_CTRL_0_GMII_AUTO 0x0000
+#define GSW1XX_IP_MAC_CTRL_0_GMII_MII 0x0001
+#define GSW1XX_IP_MAC_CTRL_0_GMII_RGMII 0x0002
+#define GSW1XX_IP_MAC_CTRL_2p(p) (0x905 + ((p) * 0xC))
+#define GSW1XX_IP_MAC_CTRL_2_MLEN BIT(3) /* Maximum Untagged Frame Lnegth */
+#define GSW1XX_IP_MAC_CTRL_4p(p) (0x907 + ((p) * 0xC))
+#define GSW1XX_IP_MAC_CTRL_4_LPIEN BIT(7)
+
+/* Ethernet Switch Fetch DMA Port Control Register */
+#define GSW1XX_IP_FDMA_PCTRLp(p) (0xA80 + ((p) * 0x6))
+#define GSW1XX_IP_FDMA_PCTRL_EN BIT(0) /* FDMA Port Enable */
+
+/* Ethernet Switch Store DMA Port Control Register */
+#define GSW1XX_IP_SDMA_PCTRLp(p) (0xBC0 + ((p) * 0x6))
+#define GSW1XX_IP_SDMA_PCTRL_EN BIT(0) /* SDMA Port Enable */
+#define GSW1XX_IP_SDMA_PCTRL_FCEN BIT(1) /* Flow Control Enable */
+#define GSW1XX_IP_SDMA_PCTRL_PAUFWD BIT(3) /* Pause Frame Forwarding */
+
+struct gsw1xx_rmon_cnt_desc {
+ unsigned int size;
+ unsigned int offset;
+ const char *name;
+};
+
+#define MIB_DESC(_size, _offset, _name) \
+ { \
+ .size = _size, .offset = _offset, .name = _name \
+ }
+
+static const struct gsw1xx_rmon_cnt_desc gsw1xx_rmon_cnt[] = {
+ /** Receive Packet Count (only packets that are accepted and not discarded). */
+ MIB_DESC(1, 0x1F, "RxGoodPkts"),
+ MIB_DESC(1, 0x23, "RxUnicastPkts"),
+ MIB_DESC(1, 0x22, "RxMulticastPkts"),
+ MIB_DESC(1, 0x21, "RxFCSErrorPkts"),
+ MIB_DESC(1, 0x1D, "RxUnderSizeGoodPkts"),
+ MIB_DESC(1, 0x1E, "RxUnderSizeErrorPkts"),
+ MIB_DESC(1, 0x1B, "RxOversizeGoodPkts"),
+ MIB_DESC(1, 0x1C, "RxOversizeErrorPkts"),
+ MIB_DESC(1, 0x20, "RxGoodPausePkts"),
+ MIB_DESC(1, 0x1A, "RxAlignErrorPkts"),
+ MIB_DESC(1, 0x12, "Rx64BytePkts"),
+ MIB_DESC(1, 0x13, "Rx127BytePkts"),
+ MIB_DESC(1, 0x14, "Rx255BytePkts"),
+ MIB_DESC(1, 0x15, "Rx511BytePkts"),
+ MIB_DESC(1, 0x16, "Rx1023BytePkts"),
+ /** Receive Size 1024-1522 (or more, if configured) Packet Count. */
+ MIB_DESC(1, 0x17, "RxMaxBytePkts"),
+ MIB_DESC(1, 0x18, "RxDroppedPkts"),
+ MIB_DESC(1, 0x19, "RxFilteredPkts"),
+ MIB_DESC(2, 0x24, "RxGoodBytes"),
+ MIB_DESC(2, 0x26, "RxBadBytes"),
+ MIB_DESC(1, 0x11, "TxAcmDroppedPkts"),
+ MIB_DESC(1, 0x0C, "TxGoodPkts"),
+ MIB_DESC(1, 0x06, "TxUnicastPkts"),
+ MIB_DESC(1, 0x07, "TxMulticastPkts"),
+ MIB_DESC(1, 0x00, "Tx64BytePkts"),
+ MIB_DESC(1, 0x01, "Tx127BytePkts"),
+ MIB_DESC(1, 0x02, "Tx255BytePkts"),
+ MIB_DESC(1, 0x03, "Tx511BytePkts"),
+ MIB_DESC(1, 0x04, "Tx1023BytePkts"),
+ /** Transmit Size 1024-1522 (or more, if configured) Packet Count. */
+ MIB_DESC(1, 0x05, "TxMaxBytePkts"),
+ MIB_DESC(1, 0x08, "TxSingleCollCount"),
+ MIB_DESC(1, 0x09, "TxMultCollCount"),
+ MIB_DESC(1, 0x0A, "TxLateCollCount"),
+ MIB_DESC(1, 0x0B, "TxExcessCollCount"),
+ MIB_DESC(1, 0x0D, "TxPauseCount"),
+ MIB_DESC(1, 0x10, "TxDroppedPkts"),
+ MIB_DESC(2, 0x0E, "TxGoodBytes"),
+};
+
+static u32 gsw1xx_switch_r(struct gsw1xx_priv *priv, u32 offset)
+{
+ int ret = 0;
+ u32 val = 0;
+
+ ret = regmap_read(priv->regmap, GSW1XX_IP_BASE_ADDR + offset, &val);
+
+ return ret < 0 ? (u32)ret : val;
+}
+
+static void gsw1xx_switch_w(struct gsw1xx_priv *priv, u32 val, u32 offset)
+{
+ regmap_write(priv->regmap, GSW1XX_IP_BASE_ADDR + offset, val);
+}
+
+static u32 gsw1xx_mdio_r(struct gsw1xx_priv *priv, u32 offset)
+{
+ int ret = 0;
+ u32 val = 0;
+
+ ret = regmap_read(priv->regmap, GSW1XX_MDIO_BASE_ADDR + offset, &val);
+
+ return ret < 0 ? (u32)ret : val;
+}
+
+static void gsw1xx_mdio_w(struct gsw1xx_priv *priv, u32 val, u32 offset)
+{
+ regmap_write(priv->regmap, GSW1XX_MDIO_BASE_ADDR + offset, val);
+}
+
+static void gsw1xx_mdio_mask(struct gsw1xx_priv *priv, u32 clear, u32 set,
+ u32 offset)
+{
+ u32 val = gsw1xx_mdio_r(priv, offset);
+
+ val &= ~(clear);
+ val |= set;
+ gsw1xx_mdio_w(priv, val, offset);
+}
+
+static void gsw1xx_switch_mask(struct gsw1xx_priv *priv, u32 clear, u32 set,
+ u32 offset)
+{
+ u32 val = gsw1xx_switch_r(priv, offset);
+
+ val &= ~(clear);
+ val |= set;
+ gsw1xx_switch_w(priv, val, offset);
+}
+
+static u32 gsw1xx_switch_r_timeout(struct gsw1xx_priv *priv, u32 offset,
+ u32 cleared)
+{
+ u32 val;
+
+ return read_poll_timeout(gsw1xx_switch_r, val, (val & cleared) == 0, 20,
+ 50000, true, priv, offset);
+}
+
+static int gsw1xx_mdio_poll(struct gsw1xx_priv *priv)
+{
+ int cnt = 100;
+
+ while (likely(cnt--)) {
+ u32 ctrl = gsw1xx_mdio_r(priv, GSW1XX_MDIO_CTRL);
+
+ if ((ctrl & GSW1XX_MDIO_CTRL_BUSY) == 0)
+ return 0;
+ usleep_range(20, 40);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int gsw1xx_mdio_wr(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+ struct gsw1xx_priv *priv = bus->priv;
+ int err;
+
+ err = gsw1xx_mdio_poll(priv);
+ if (err) {
+ dev_err(&bus->dev, "timeout while waiting for MDIO bus\n");
+ return err;
+ }
+
+ gsw1xx_mdio_w(priv, val, GSW1XX_MDIO_WRITE);
+ gsw1xx_mdio_w(priv,
+ GSW1XX_MDIO_CTRL_WR |
+ ((addr & GSW1XX_MDIO_CTRL_PHYAD_MASK)
+ << GSW1XX_MDIO_CTRL_PHYAD_SHIFT) |
+ (reg & GSW1XX_MDIO_CTRL_REGAD_MASK),
+ GSW1XX_MDIO_CTRL);
+
+ return 0;
+}
+
+static int gsw1xx_mdio_rd(struct mii_bus *bus, int addr, int reg)
+{
+ struct gsw1xx_priv *priv = bus->priv;
+ int err;
+
+ err = gsw1xx_mdio_poll(priv);
+ if (err) {
+ dev_err(&bus->dev, "timeout while waiting for MDIO bus\n");
+ return err;
+ }
+
+ gsw1xx_mdio_w(priv,
+ GSW1XX_MDIO_CTRL_RD |
+ ((addr & GSW1XX_MDIO_CTRL_PHYAD_MASK)
+ << GSW1XX_MDIO_CTRL_PHYAD_SHIFT) |
+ (reg & GSW1XX_MDIO_CTRL_REGAD_MASK),
+ GSW1XX_MDIO_CTRL);
+
+ err = gsw1xx_mdio_poll(priv);
+ if (err) {
+ dev_err(&bus->dev, "timeout while waiting for MDIO bus\n");
+ return err;
+ }
+
+ return gsw1xx_mdio_r(priv, GSW1XX_MDIO_READ);
+}
+
+static int gsw1xx_mdio(struct gsw1xx_priv *priv, struct device_node *mdio_np)
+{
+ struct dsa_switch *ds = priv->ds;
+ int err;
+
+ ds->slave_mii_bus = mdiobus_alloc();
+ if (!ds->slave_mii_bus)
+ return -ENOMEM;
+
+ ds->slave_mii_bus->priv = priv;
+ ds->slave_mii_bus->read = gsw1xx_mdio_rd;
+ ds->slave_mii_bus->write = gsw1xx_mdio_wr;
+ ds->slave_mii_bus->name = "mxl,gsw1xx-mdio";
+ snprintf(ds->slave_mii_bus->id, MII_BUS_ID_SIZE, "%s-mii",
+ dev_name(priv->dev));
+ ds->slave_mii_bus->parent = priv->dev;
+ ds->slave_mii_bus->phy_mask = ~ds->phys_mii_mask;
+
+ err = of_mdiobus_register(ds->slave_mii_bus, mdio_np);
+ if (err)
+ mdiobus_free(ds->slave_mii_bus);
+
+ return err;
+}
+
+static int gsw1xx_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phydev)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+
+ if (!dsa_is_user_port(ds, port))
+ return 0;
+
+ /* RMON Counter Enable for port */
+ gsw1xx_switch_w(priv, GSW1XX_IP_BM_PCFG_CNTEN,
+ GSW1XX_IP_BM_PCFGp(port));
+
+ /* enable port fetch/store dma */
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_FDMA_PCTRL_EN,
+ GSW1XX_IP_FDMA_PCTRLp(port));
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_SDMA_PCTRL_EN,
+ GSW1XX_IP_SDMA_PCTRLp(port));
+
+ if (!dsa_is_cpu_port(ds, port)) {
+ u32 mdio_phy = 0;
+
+ if (phydev)
+ mdio_phy =
+ phydev->mdio.addr & GSW1XX_MDIO_PHY_ADDR_MASK;
+
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_PHY_ADDR_MASK, mdio_phy,
+ GSW1XX_MDIO_PHYp(port));
+ }
+
+ return 0;
+}
+
+static void gsw1xx_port_disable(struct dsa_switch *ds, int port)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+
+ if (!dsa_is_user_port(ds, port))
+ return;
+
+ gsw1xx_switch_mask(priv, GSW1XX_IP_FDMA_PCTRL_EN, 0,
+ GSW1XX_IP_FDMA_PCTRLp(port));
+ gsw1xx_switch_mask(priv, GSW1XX_IP_SDMA_PCTRL_EN, 0,
+ GSW1XX_IP_SDMA_PCTRLp(port));
+}
+
+static int gsw1xx_setup(struct dsa_switch *ds)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+ unsigned int cpu_port = priv->hw_info->cpu_port;
+ int i;
+ int err;
+
+ gsw1xx_switch_w(priv, GSW1XX_IP_SWRES_R0, GSW1XX_IP_SWRES);
+ usleep_range(5000, 10000);
+ gsw1xx_switch_w(priv, 0, GSW1XX_IP_SWRES);
+
+ /* disable port fetch/store dma on all ports */
+ for (i = 0; i < priv->hw_info->max_ports; i++)
+ gsw1xx_port_disable(ds, i);
+
+ /* enable Switch */
+ gsw1xx_mdio_mask(priv, 0, GSW1XX_MDIO_GLOB_ENABLE, GSW1XX_MDIO_GLOB);
+
+ gsw1xx_switch_w(priv, 0x7F, GSW1XX_IP_PCE_PMAP2);
+ gsw1xx_switch_w(priv, 0x7F, GSW1XX_IP_PCE_PMAP3);
+
+ /* Deactivate MDIO PHY auto polling since it affects mmd read/write.
+ */
+ gsw1xx_mdio_w(priv, 0x0, GSW1XX_MDIO_MDC_CFG0);
+
+ gsw1xx_switch_mask(priv, 1, GSW1XX_IP_MAC_CTRL_2_MLEN,
+ GSW1XX_IP_MAC_CTRL_2p(cpu_port));
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_BM_QUEUE_GCTRL_GL_MOD,
+ GSW1XX_IP_BM_QUEUE_GCTRL);
+
+ /* Flush MAC Table */
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_PCE_GCTRL_0_MTFL,
+ GSW1XX_IP_PCE_GCTRL_0);
+ err = gsw1xx_switch_r_timeout(priv, GSW1XX_IP_PCE_GCTRL_0,
+ GSW1XX_IP_PCE_GCTRL_0_MTFL);
+ if (err) {
+ dev_err(priv->dev, "MAC flushing didn't finish\n");
+ return err;
+ }
+
+ gsw1xx_port_enable(ds, cpu_port, NULL);
+
+ return 0;
+}
+
+static enum dsa_tag_protocol gsw1xx_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_NONE;
+}
+
+static void gsw1xx_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+ u32 stp_state;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ gsw1xx_switch_mask(priv, GSW1XX_IP_SDMA_PCTRL_EN, 0,
+ GSW1XX_IP_SDMA_PCTRLp(port));
+ return;
+ case BR_STATE_BLOCKING:
+ case BR_STATE_LISTENING:
+ stp_state = GSW1XX_IP_PCE_PCTRL_0_PSTATE_LISTEN;
+ break;
+ case BR_STATE_LEARNING:
+ stp_state = GSW1XX_IP_PCE_PCTRL_0_PSTATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ stp_state = GSW1XX_IP_PCE_PCTRL_0_PSTATE_FORWARDING;
+ break;
+ default:
+ dev_err(priv->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_SDMA_PCTRL_EN,
+ GSW1XX_IP_SDMA_PCTRLp(port));
+ gsw1xx_switch_mask(priv, GSW1XX_IP_PCE_PCTRL_0_PSTATE_MASK, stp_state,
+ GSW1XX_IP_PCE_PCTRL_0p(port));
+}
+
+static void gsw1xx_port_set_link(struct gsw1xx_priv *priv, int port, bool link)
+{
+ u32 mdio_phy;
+
+ if (link)
+ mdio_phy = GSW1XX_MDIO_PHY_LINK_UP;
+ else
+ mdio_phy = GSW1XX_MDIO_PHY_LINK_DOWN;
+
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_PHY_LINK_MASK, mdio_phy,
+ GSW1XX_MDIO_PHYp(port));
+}
+
+static void gsw1xx_port_set_speed(struct gsw1xx_priv *priv, int port, int speed,
+ phy_interface_t interface)
+{
+ u32 mdio_phy = 0, mac_ctrl_0 = 0;
+
+ switch (speed) {
+ case SPEED_10:
+ mdio_phy = GSW1XX_MDIO_PHY_SPEED_M10;
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_GMII_MII;
+ break;
+
+ case SPEED_100:
+ mdio_phy = GSW1XX_MDIO_PHY_SPEED_M100;
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_GMII_MII;
+ break;
+
+ case SPEED_1000:
+ mdio_phy = GSW1XX_MDIO_PHY_SPEED_G1;
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_GMII_RGMII;
+ break;
+ }
+
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_PHY_SPEED_MASK, mdio_phy,
+ GSW1XX_MDIO_PHYp(port));
+ gsw1xx_switch_mask(priv, GSW1XX_IP_MAC_CTRL_0_GMII_MASK, mac_ctrl_0,
+ GSW1XX_IP_MAC_CTRL_0p(port));
+}
+
+static void gsw1xx_port_set_duplex(struct gsw1xx_priv *priv, int port,
+ int duplex)
+{
+ u32 mac_ctrl_0, mdio_phy;
+
+ if (duplex == DUPLEX_FULL) {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FDUP_EN;
+ mdio_phy = GSW1XX_MDIO_PHY_FDUP_EN;
+ } else {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FDUP_DIS;
+ mdio_phy = GSW1XX_MDIO_PHY_FDUP_DIS;
+ }
+
+ gsw1xx_switch_mask(priv, GSW1XX_IP_MAC_CTRL_0_FDUP_MASK, mac_ctrl_0,
+ GSW1XX_IP_MAC_CTRL_0p(port));
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_PHY_FDUP_MASK, mdio_phy,
+ GSW1XX_MDIO_PHYp(port));
+}
+
+static void gsw1xx_port_set_pause(struct gsw1xx_priv *priv, int port,
+ bool tx_pause, bool rx_pause)
+{
+ u32 mac_ctrl_0, mdio_phy;
+
+ if (tx_pause && rx_pause) {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FCON_RXTX;
+ mdio_phy =
+ GSW1XX_MDIO_PHY_FCONTX_EN | GSW1XX_MDIO_PHY_FCONRX_EN;
+ } else if (tx_pause) {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FCON_TX;
+ mdio_phy =
+ GSW1XX_MDIO_PHY_FCONTX_EN | GSW1XX_MDIO_PHY_FCONRX_DIS;
+ } else if (rx_pause) {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FCON_RX;
+ mdio_phy =
+ GSW1XX_MDIO_PHY_FCONTX_DIS | GSW1XX_MDIO_PHY_FCONRX_EN;
+ } else {
+ mac_ctrl_0 = GSW1XX_IP_MAC_CTRL_0_FCON_NONE;
+ mdio_phy =
+ GSW1XX_MDIO_PHY_FCONTX_DIS | GSW1XX_MDIO_PHY_FCONRX_DIS;
+ }
+
+ gsw1xx_switch_mask(priv, GSW1XX_IP_MAC_CTRL_0_FCON_MASK, mac_ctrl_0,
+ GSW1XX_IP_MAC_CTRL_0p(port));
+ gsw1xx_mdio_mask(priv,
+ GSW1XX_MDIO_PHY_FCONTX_MASK | GSW1XX_MDIO_PHY_FCONRX_MASK,
+ mdio_phy, GSW1XX_MDIO_PHYp(port));
+}
+
+static void gsw1xx_phylink_mac_link_down(struct dsa_switch *ds, int port,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+
+ if (!dsa_is_cpu_port(ds, port))
+ gsw1xx_port_set_link(priv, port, false);
+}
+
+static void gsw1xx_phylink_mac_link_up(struct dsa_switch *ds, int port,
+ unsigned int mode,
+ phy_interface_t interface,
+ struct phy_device *phydev, int speed,
+ int duplex, bool tx_pause, bool rx_pause)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+
+ if (!dsa_is_cpu_port(ds, port)) {
+ gsw1xx_port_set_link(priv, port, true);
+ gsw1xx_port_set_speed(priv, port, speed, interface);
+ gsw1xx_port_set_duplex(priv, port, duplex);
+ gsw1xx_port_set_pause(priv, port, tx_pause, rx_pause);
+ }
+}
+
+static void gsw1xx_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+ uint8_t *data)
+{
+ int i;
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(gsw1xx_rmon_cnt); i++)
+ strncpy(data + i * ETH_GSTRING_LEN, gsw1xx_rmon_cnt[i].name,
+ ETH_GSTRING_LEN);
+}
+
+static u32 gsw1xx_bcm_ram_entry_read(struct gsw1xx_priv *priv, u32 port,
+ u32 index)
+{
+ u32 result;
+ int err;
+
+ gsw1xx_switch_w(priv, index, GSW1XX_IP_BM_RAM_ADDR);
+ gsw1xx_switch_mask(priv,
+ GSW1XX_IP_BM_RAM_CTRL_ADDR_MASK | GSW1XX_IP_BM_RAM_CTRL_OPMOD,
+ port | GSW1XX_IP_BM_RAM_CTRL_BAS,
+ GSW1XX_IP_BM_RAM_CTRL);
+
+ err = gsw1xx_switch_r_timeout(priv, GSW1XX_IP_BM_RAM_CTRL,
+ GSW1XX_IP_BM_RAM_CTRL_BAS);
+ if (err) {
+ dev_err(priv->dev,
+ "timeout while reading entry: %u from RMON table for port: %u",
+ index, port);
+ return 0;
+ }
+
+ result = gsw1xx_switch_r(priv, GSW1XX_IP_BM_RAM_VAL(0));
+ result |= gsw1xx_switch_r(priv, GSW1XX_IP_BM_RAM_VAL(1)) << 16;
+
+ return result;
+}
+
+static void gsw1xx_get_ethtool_stats(struct dsa_switch *ds, int port,
+ uint64_t *data)
+{
+ struct gsw1xx_priv *priv = ds->priv;
+ const struct gsw1xx_rmon_cnt_desc *rmon_cnt;
+ int i;
+ u64 high;
+
+ for (i = 0; i < ARRAY_SIZE(gsw1xx_rmon_cnt); i++) {
+ rmon_cnt = &gsw1xx_rmon_cnt[i];
+
+ data[i] =
+ gsw1xx_bcm_ram_entry_read(priv, port, rmon_cnt->offset);
+ if (rmon_cnt->size == 2) {
+ high = gsw1xx_bcm_ram_entry_read(priv, port,
+ rmon_cnt->offset + 1);
+ data[i] |= high << 32;
+ }
+ }
+}
+
+static int gsw1xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+ if (sset != ETH_SS_STATS)
+ return 0;
+
+ return ARRAY_SIZE(gsw1xx_rmon_cnt);
+}
+
+static int gsw1xx_get_mac_eee(struct dsa_switch *ds, int port,
+ struct ethtool_eee *e)
+{
+ struct gsw1xx_priv *priv = (struct gsw1xx_priv *)ds->priv;
+ u32 val = 0;
+
+ val = gsw1xx_switch_r(priv, GSW1XX_IP_MAC_CTRL_4p(port));
+ e->tx_lpi_enabled = !!(val & GSW1XX_IP_MAC_CTRL_4_LPIEN);
+
+ e->tx_lpi_timer = 20;
+
+ return 0;
+}
+
+static int gsw1xx_set_mac_eee(struct dsa_switch *ds, int port,
+ struct ethtool_eee *e)
+{
+ struct gsw1xx_priv *priv = (struct gsw1xx_priv *)ds->priv;
+
+ if (e->tx_lpi_enabled) {
+ gsw1xx_switch_mask(priv, 0, GSW1XX_IP_MAC_CTRL_4_LPIEN,
+ GSW1XX_IP_MAC_CTRL_4p(port));
+ } else {
+ gsw1xx_switch_mask(priv, GSW1XX_IP_MAC_CTRL_4_LPIEN, 0,
+ GSW1XX_IP_MAC_CTRL_4p(port));
+ }
+
+ return 0;
+}
+
+static const struct dsa_switch_ops gsw1xx_switch_ops = {
+ .get_tag_protocol = gsw1xx_get_tag_protocol,
+ .setup = gsw1xx_setup,
+ .set_mac_eee = gsw1xx_set_mac_eee,
+ .get_mac_eee = gsw1xx_get_mac_eee,
+ .port_enable = gsw1xx_port_enable,
+ .port_disable = gsw1xx_port_disable,
+ .port_stp_state_set = gsw1xx_port_stp_state_set,
+ .phylink_mac_link_down = gsw1xx_phylink_mac_link_down,
+ .phylink_mac_link_up = gsw1xx_phylink_mac_link_up,
+ .get_strings = gsw1xx_get_strings,
+ .get_ethtool_stats = gsw1xx_get_ethtool_stats,
+ .get_sset_count = gsw1xx_get_sset_count,
+};
+
+int gsw1xx_probe(struct gsw1xx_priv *priv, struct device *dev)
+{
+ struct device_node *np, *mdio_np;
+ int err;
+ u32 version;
+
+ if (!priv->regmap || IS_ERR(priv->regmap))
+ return -EINVAL;
+
+ priv->hw_info = of_device_get_match_data(dev);
+ if (!priv->hw_info)
+ return -EINVAL;
+
+ priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
+ if (!priv->ds)
+ return -ENOMEM;
+
+ priv->ds->dev = dev;
+ priv->ds->num_ports = priv->hw_info->max_ports;
+ priv->ds->priv = priv;
+ priv->ds->ops = &gsw1xx_switch_ops;
+ priv->dev = dev;
+ version = gsw1xx_switch_r(priv, GSW1XX_IP_VERSION);
+
+ np = dev->of_node;
+ switch (version) {
+ case GSW1XX_IP_VERSION_2_3:
+ if (!of_device_is_compatible(np, "mxl,gsw145-mdio"))
+ return -EINVAL;
+ break;
+ default:
+ dev_err(dev, "unknown GSW1XX_IP version: 0x%x", version);
+ return -ENOENT;
+ }
+
+ /* bring up the mdio bus */
+ mdio_np = of_get_child_by_name(np, "mdio");
+ if (!mdio_np) {
+ dev_err(dev, "missing child mdio node\n");
+ return -EINVAL;
+ }
+
+ err = gsw1xx_mdio(priv, mdio_np);
+ if (err) {
+ dev_err(dev, "mdio probe failed\n");
+ goto put_mdio_node;
+ }
+
+ err = dsa_register_switch(priv->ds);
+ if (err) {
+ dev_err(dev, "dsa switch register failed: %i\n", err);
+ goto mdio_bus;
+ }
+ if (!dsa_is_cpu_port(priv->ds, priv->hw_info->cpu_port)) {
+ dev_err(dev,
+ "wrong CPU port defined, HW only supports port: %i",
+ priv->hw_info->cpu_port);
+ err = -EINVAL;
+ goto disable_switch;
+ }
+
+ dev_set_drvdata(dev, priv);
+
+ return 0;
+
+disable_switch:
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_GLOB_ENABLE, 0, GSW1XX_MDIO_GLOB);
+ dsa_unregister_switch(priv->ds);
+mdio_bus:
+ if (mdio_np) {
+ mdiobus_unregister(priv->ds->slave_mii_bus);
+ mdiobus_free(priv->ds->slave_mii_bus);
+ }
+put_mdio_node:
+ of_node_put(mdio_np);
+ return err;
+}
+EXPORT_SYMBOL(gsw1xx_probe);
+
+void gsw1xx_remove(struct gsw1xx_priv *priv)
+{
+ if (!priv)
+ return;
+
+ /* disable the switch */
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_GLOB_ENABLE, 0, GSW1XX_MDIO_GLOB);
+
+ dsa_unregister_switch(priv->ds);
+
+ if (priv->ds->slave_mii_bus) {
+ mdiobus_unregister(priv->ds->slave_mii_bus);
+ of_node_put(priv->ds->slave_mii_bus->dev.of_node);
+ mdiobus_free(priv->ds->slave_mii_bus);
+ }
+
+ dev_set_drvdata(priv->dev, NULL);
+}
+EXPORT_SYMBOL(gsw1xx_remove);
+
+void gsw1xx_shutdown(struct gsw1xx_priv *priv)
+{
+ if (!priv)
+ return;
+
+ /* disable the switch */
+ gsw1xx_mdio_mask(priv, GSW1XX_MDIO_GLOB_ENABLE, 0, GSW1XX_MDIO_GLOB);
+
+ dsa_switch_shutdown(priv->ds);
+
+ dev_set_drvdata(priv->dev, NULL);
+}
+EXPORT_SYMBOL(gsw1xx_shutdown);
+
+static const struct regmap_range gsw1xx_valid_regs[] = {
+ /* GSWIP Core Registers */
+ regmap_reg_range(GSW1XX_IP_BASE_ADDR,
+ GSW1XX_IP_BASE_ADDR + GSW1XX_IP_REG_LEN),
+ /* Top Level PDI Registers, MDIO Master Reigsters */
+ regmap_reg_range(GSW1XX_MDIO_BASE_ADDR,
+ GSW1XX_MDIO_BASE_ADDR + GSW1XX_MDIO_REG_LEN),
+};
+
+const struct regmap_access_table gsw1xx_register_set = {
+ .yes_ranges = gsw1xx_valid_regs,
+ .n_yes_ranges = ARRAY_SIZE(gsw1xx_valid_regs),
+};
+EXPORT_SYMBOL(gsw1xx_register_set);
+
+MODULE_AUTHOR("Camel Guo <camel.guo@axis.com>");
+MODULE_DESCRIPTION("Core Driver for MaxLinear GSM1XX ethernet switch");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/gsw1xx_mdio.c b/drivers/net/dsa/gsw1xx_mdio.c
new file mode 100644
index 000000000000..8328001041ed
--- /dev/null
+++ b/drivers/net/dsa/gsw1xx_mdio.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MaxLinear switch driver for GSW1XX in MDIO managed mode
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#include "gsw1xx.h"
+
+#define GSW1XX_SMDIO_TARGET_BASE_ADDR_REG 0x1F
+
+static int gsw1xx_mdio_write(void *ctx, uint32_t reg, uint32_t val)
+{
+ struct mdio_device *mdiodev = (struct mdio_device *)ctx;
+ int ret = 0;
+
+ mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ ret = mdiodev->bus->write(mdiodev->bus, mdiodev->addr,
+ GSW1XX_SMDIO_TARGET_BASE_ADDR_REG, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = mdiodev->bus->write(mdiodev->bus, mdiodev->addr, 0, val);
+
+out:
+ mutex_unlock(&mdiodev->bus->mdio_lock);
+
+ return ret;
+}
+
+static int gsw1xx_mdio_read(void *ctx, uint32_t reg, uint32_t *val)
+{
+ struct mdio_device *mdiodev = (struct mdio_device *)ctx;
+ int ret = 0;
+
+ mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ ret = mdiodev->bus->write(mdiodev->bus, mdiodev->addr,
+ GSW1XX_SMDIO_TARGET_BASE_ADDR_REG, reg);
+ if (ret < 0)
+ goto out;
+
+ *val = mdiodev->bus->read(mdiodev->bus, mdiodev->addr, 0);
+
+out:
+ mutex_unlock(&mdiodev->bus->mdio_lock);
+
+ return ret;
+}
+
+static const struct regmap_config gsw1xx_mdio_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_stride = 1,
+
+ .disable_locking = true,
+
+ .volatile_table = &gsw1xx_register_set,
+ .wr_table = &gsw1xx_register_set,
+ .rd_table = &gsw1xx_register_set,
+
+ .reg_read = gsw1xx_mdio_read,
+ .reg_write = gsw1xx_mdio_write,
+
+ .cache_type = REGCACHE_NONE,
+};
+
+static int gsw1xx_mdio_probe(struct mdio_device *mdiodev)
+{
+ struct gsw1xx_priv *priv;
+ struct device *dev = &mdiodev->dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init(dev, NULL, mdiodev,
+ &gsw1xx_mdio_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err(dev, "regmap init failed: %d\n", ret);
+ return ret;
+ }
+
+ return gsw1xx_probe(priv, dev);
+}
+
+static void gsw1xx_mdio_remove(struct mdio_device *mdiodev)
+{
+ gsw1xx_remove(dev_get_drvdata(&mdiodev->dev));
+}
+
+static void gsw1xx_mdio_shutdown(struct mdio_device *mdiodev)
+{
+ gsw1xx_shutdown(dev_get_drvdata(&mdiodev->dev));
+}
+
+static const struct gsw1xx_hw_info gsw145_hw_info = {
+ .max_ports = 6,
+ .cpu_port = 5,
+};
+
+static const struct of_device_id gsw1xx_mdio_of_match[] = {
+ { .compatible = "mxl,gsw145-mdio", .data = &gsw145_hw_info },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, gsw1xx_mdio_of_match);
+
+static struct mdio_driver gsw1xx_mdio_driver = {
+ .probe = gsw1xx_mdio_probe,
+ .remove = gsw1xx_mdio_remove,
+ .shutdown = gsw1xx_mdio_shutdown,
+ .mdiodrv.driver = {
+ .name = "GSW1XX_MDIO",
+ .of_match_table = of_match_ptr(gsw1xx_mdio_of_match),
+ },
+};
+
+mdio_module_driver(gsw1xx_mdio_driver);
+
+MODULE_AUTHOR("Camel Guo <camel.guo@axis.com>");
+MODULE_DESCRIPTION("Driver for MaxLinear GSM1XX ethernet switch in MDIO managed mode");
+MODULE_LICENSE("GPL");
--
2.30.2
\
 
 \ /
  Last update: 2022-10-25 15:54    [W:0.139 / U:0.532 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site