// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include #include #include #define KSZ8873_CHIP_ID0 0x00 #define KSZ8873_CHIP_ID1 0x01 #define KSZ88_CHIP_ID_M GENMASK(7, 4) #define KSZ88_REV_ID_M GENMASK(3, 1) #define KSZ8873_GLOBAL_CTRL_1 0x03 #define KSZ8873_PASS_ALL_FRAMES BIT(7) #define KSZ8873_P3_TAIL_TAG_EN BIT(6) /* * port specific registers. Should be used with ksz_pwrite/ksz_pread functions */ #define KSZ8873_PORTx_CTRL_1 0x01 #define KSZ8873_PORTx_CTRL_12 0x0c #define PORT_AUTO_NEG_ENABLE BIT(7) #define PORT_FORCE_100_MBIT BIT(6) #define PORT_FORCE_FULL_DUPLEX BIT(5) #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 KSZ8873_PORTx_CTRL_13 0x0d #define PORT_AUTO_NEG_RESTART BIT(5) #define PORT_POWER_DOWN BIT(3) #define KSZ8873_PORTx_STATUS_0 0x0e #define PORT_AUTO_NEG_COMPLETE BIT(6) #define PORT_STAT_LINK_GOOD BIT(5) #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 KSZ8873_PORTx_STATUS_1 0x0f #define KSZ8795_ID_HI 0x0022 #define KSZ8863_ID_LO 0x1430 #define PORT_CTRL_ADDR(port, addr) ((addr) + 0x10 + (port) * 0x10) struct ksz8873_dcfg { unsigned int num_ports; unsigned int phy_port_cnt; u8 id0; u8 id1; }; struct ksz8873_switch { struct phy_device *mdiodev; struct dsa_switch ds; struct device *dev; const struct ksz8873_dcfg *dcfg; struct regmap *regmap; }; /* Serial Management Interface (SMI) uses the following frame format: * * preamble|start|Read/Write| PHY | REG |TA| Data bits | Idle * |frame| OP code |address |address| | | * read | 32x1´s | 01 | 00 | 1xRRR | RRRRR |Z0| 00000000DDDDDDDD | Z * write| 32x1´s | 01 | 00 | 0xRRR | RRRRR |10| xxxxxxxxDDDDDDDD | Z * */ #define SMI_KSZ88XX_READ_PHY BIT(4) static int ksz8873_mdio_read(void *ctx, unsigned int reg, unsigned int *val) { struct ksz8873_switch *priv = ctx; struct phy_device *mdiodev = priv->mdiodev; int ret; ret = mdiobus_read(mdiodev->bus, ((reg & 0xE0) >> 5) | SMI_KSZ88XX_READ_PHY, reg); if (ret < 0) return ret; *val = ret; return 0; } static int ksz8873_mdio_write(void *ctx, unsigned int reg, unsigned int val) { struct ksz8873_switch *priv = ctx; struct phy_device *mdiodev = priv->mdiodev; return mdiobus_write(mdiodev->bus, ((reg & 0xE0) >> 5), reg, val); } static const struct regmap_bus ksz8873_regmap_smi = { .reg_read = ksz8873_mdio_read, .reg_write = ksz8873_mdio_write, }; static const struct regmap_config ksz8873_regmap_config = { .name = "#8", .reg_bits = 8, .pad_bits = 24, .val_bits = 8, }; static int ksz_read8(struct ksz8873_switch *priv, u32 reg, u8 *val) { unsigned int value; int ret = regmap_read(priv->regmap, reg, &value); *val = value & 0xff; return ret; } static int ksz_write8(struct ksz8873_switch *priv, u32 reg, u8 value) { return regmap_write(priv->regmap, reg, value); } static int ksz_pread8(struct ksz8873_switch *priv, int port, int reg, u8 *val) { return ksz_read8(priv, PORT_CTRL_ADDR(port, reg), val); } static int ksz_pwrite8(struct ksz8873_switch *priv, int port, int reg, u8 val) { return ksz_write8(priv, PORT_CTRL_ADDR(port, reg), val); } static void ksz8_r_phy(struct ksz8873_switch *priv, u16 phy, u16 reg, u16 *val) { u8 restart, ctrl, link; int processed = true; u16 data = 0; u8 p = phy; switch (reg) { case MII_BMCR: ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_13, &restart); ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl); if (ctrl & PORT_FORCE_100_MBIT) data |= BMCR_SPEED100; if ((ctrl & PORT_AUTO_NEG_ENABLE)) data |= BMCR_ANENABLE; if (restart & PORT_POWER_DOWN) data |= BMCR_PDOWN; if (restart & PORT_AUTO_NEG_RESTART) data |= BMCR_ANRESTART; if (ctrl & PORT_FORCE_FULL_DUPLEX) data |= BMCR_FULLDPLX; break; case MII_BMSR: ksz_pread8(priv, p, KSZ8873_PORTx_STATUS_0, &link); data = BMSR_100FULL | BMSR_100HALF | BMSR_10FULL | BMSR_10HALF | BMSR_ANEGCAPABLE; if (link & PORT_AUTO_NEG_COMPLETE) data |= BMSR_ANEGCOMPLETE; if (link & PORT_STAT_LINK_GOOD) data |= BMSR_LSTATUS; break; case MII_PHYSID1: data = KSZ8795_ID_HI; break; case MII_PHYSID2: data = KSZ8863_ID_LO; break; case MII_ADVERTISE: ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl); data = ADVERTISE_CSMA; if (ctrl & PORT_AUTO_NEG_100BTX_FD) data |= ADVERTISE_100FULL; if (ctrl & PORT_AUTO_NEG_100BTX) data |= ADVERTISE_100HALF; if (ctrl & PORT_AUTO_NEG_10BT_FD) data |= ADVERTISE_10FULL; if (ctrl & PORT_AUTO_NEG_10BT) data |= ADVERTISE_10HALF; break; case MII_LPA: ksz_pread8(priv, p, KSZ8873_PORTx_STATUS_0, &link); data = LPA_SLCT; if (link & PORT_REMOTE_100BTX_FD) data |= LPA_100FULL; if (link & PORT_REMOTE_100BTX) data |= LPA_100HALF; if (link & PORT_REMOTE_10BT_FD) data |= LPA_10FULL; if (link & PORT_REMOTE_10BT) data |= LPA_10HALF; if (data & ~LPA_SLCT) data |= LPA_LPACK; break; default: processed = false; break; } if (processed) *val = data; } static void ksz8_w_phy(struct ksz8873_switch *priv, u16 phy, u16 reg, u16 val) { u8 restart, ctrl, data; u8 p = phy; switch (reg) { case MII_BMCR: ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl); data = ctrl; if ((val & BMCR_ANENABLE)) data |= PORT_AUTO_NEG_ENABLE; else data &= ~PORT_AUTO_NEG_ENABLE; if (val & BMCR_SPEED100) data |= PORT_FORCE_100_MBIT; else data &= ~PORT_FORCE_100_MBIT; if (val & BMCR_FULLDPLX) data |= PORT_FORCE_FULL_DUPLEX; else data &= ~PORT_FORCE_FULL_DUPLEX; if (data != ctrl) ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_12, data); ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_13, &restart); data = restart; if (val & BMCR_ANRESTART) data |= PORT_AUTO_NEG_RESTART; else data &= ~(PORT_AUTO_NEG_RESTART); if (val & BMCR_PDOWN) data |= PORT_POWER_DOWN; else data &= ~PORT_POWER_DOWN; if (data != restart) ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_13, data); break; case MII_ADVERTISE: ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl); data = ctrl; data &= ~(PORT_AUTO_NEG_100BTX_FD | PORT_AUTO_NEG_100BTX | PORT_AUTO_NEG_10BT_FD | PORT_AUTO_NEG_10BT); if (val & ADVERTISE_100FULL) data |= PORT_AUTO_NEG_100BTX_FD; if (val & ADVERTISE_100HALF) data |= PORT_AUTO_NEG_100BTX; if (val & ADVERTISE_10FULL) data |= PORT_AUTO_NEG_10BT_FD; if (val & ADVERTISE_10HALF) data |= PORT_AUTO_NEG_10BT; if (data != ctrl) ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_12, data); break; default: break; } } static int ksz8873_phy_read16(struct dsa_switch *ds, int addr, int reg) { struct device *dev = ds->dev; struct ksz8873_switch *priv = dev_get_priv(dev); u16 val = 0xffff; if (addr >= priv->dcfg->phy_port_cnt) return val; ksz8_r_phy(priv, addr, reg, &val); return val; } static int ksz8873_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val) { struct device *dev = ds->dev; struct ksz8873_switch *priv = dev_get_priv(dev); /* No real PHY after this. */ if (addr >= priv->dcfg->phy_port_cnt) return 0; ksz8_w_phy(priv, addr, reg, val); return 0; } static void ksz8873_cfg_port_member(struct ksz8873_switch *priv, int port, u8 member) { ksz_pwrite8(priv, port, KSZ8873_PORTx_CTRL_1, member); } static int ksz8873_port_enable(struct dsa_port *dp, int port, struct phy_device *phy) { return 0; } static int ksz8873_xmit(struct dsa_port *dp, int port, void *packet, int length) { u8 *tag = packet + length - dp->ds->needed_tx_tailroom; *tag = BIT(dp->index); return 0; } static int ksz8873_recv(struct dsa_switch *ds, int *port, void *packet, int length) { u8 *tag = packet + length - ds->needed_rx_tailroom; *port = *tag & 7; return 0; }; static const struct dsa_switch_ops ksz8873_dsa_ops = { .port_enable = ksz8873_port_enable, .xmit = ksz8873_xmit, .rcv = ksz8873_recv, .phy_read = ksz8873_phy_read16, .phy_write = ksz8873_phy_write16, }; static int ksz8873_default_setup(struct ksz8873_switch *priv) { int i; ksz_write8(priv, KSZ8873_GLOBAL_CTRL_1, KSZ8873_PASS_ALL_FRAMES | KSZ8873_P3_TAIL_TAG_EN); for (i = 0; i < priv->ds.num_ports; i++) { u8 member; /* isolate all ports by default */ member = BIT(priv->ds.cpu_port); ksz8873_cfg_port_member(priv, i, member); member = dsa_user_ports(&priv->ds); ksz8873_cfg_port_member(priv, priv->ds.cpu_port, member); } return 0; } static int ksz8873_probe_mdio(struct phy_device *mdiodev) { struct device *dev = &mdiodev->dev; const struct ksz8873_dcfg *dcfg; struct ksz8873_switch *priv; struct dsa_switch *ds; int ret, gpio; u8 id0, id1; priv = xzalloc(sizeof(*priv)); dcfg = of_device_get_match_data(dev); if (!dcfg) return -EINVAL; dev->priv = priv; priv->dev = dev; priv->dcfg = dcfg; priv->mdiodev = mdiodev; priv->regmap = regmap_init(dev, &ksz8873_regmap_smi, priv, &ksz8873_regmap_config); if (IS_ERR(priv->regmap)) return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to initialize regmap.\n"); gpio = gpiod_get(dev, "reset", GPIOF_OUT_INIT_ACTIVE); if (gpio_is_valid(gpio)) { mdelay(1); gpio_set_active(gpio, false); } ret = ksz_read8(priv, KSZ8873_CHIP_ID0, &id0); if (ret) return ret; ret = ksz_read8(priv, KSZ8873_CHIP_ID1, &id1); if (ret) return ret; if (id0 != dcfg->id0 || (id1 & (KSZ88_CHIP_ID_M | KSZ88_REV_ID_M)) != dcfg->id1) return -ENODEV; ds = &priv->ds; ds->dev = dev; ds->num_ports = dcfg->num_ports; ds->ops = &ksz8873_dsa_ops; ds->needed_rx_tailroom = 1; ds->needed_tx_tailroom = 1; ds->phys_mii_mask = 0x3; ret = dsa_register_switch(ds); if (ret) return ret; ksz8873_default_setup(priv); return 0; } static const struct ksz8873_dcfg ksz8873_dcfg = { .num_ports = 3, .phy_port_cnt = 2, .id0 = 0x88, .id1 = 0x30, }; static const struct of_device_id ksz8873_dt_ids[] = { { .compatible = "microchip,ksz8873", .data = &ksz8873_dcfg }, { } }; static struct phy_driver ksz8873_driver_mdio = { .drv = { .name = "KSZ8873 MDIO", .of_compatible = DRV_OF_COMPAT(ksz8873_dt_ids), }, .probe = ksz8873_probe_mdio, }; device_mdio_driver(ksz8873_driver_mdio);