/* Copyright (c) 2014, 2015, 2017 The Linux Foundation. All rights reserved. * Copyright (C) 2009 Felix Fietkau * Copyright (C) 2011-2012 Gabor Juhos * Copyright (c) 2016 John Crispin john@phrozen.org * Author: Matus Ujhelyi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* QCA8337 Switch driver */ #include #include #include #include #include #include #include #include #include #include #include static inline void split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) { regaddr >>= 1; *r1 = regaddr & 0x1e; regaddr >>= 5; *r2 = regaddr & 0x7; regaddr >>= 3; *page = regaddr & 0x1ff; } u32 qca8337_read(struct qca8337_priv *priv, u32 reg) { struct phy_device *phy = priv->phy; struct mii_bus *bus = phy->bus; u16 r1, r2, page; u16 lo, hi; mutex_lock(&bus->mdio_lock); split_addr(reg, &r1, &r2, &page); bus->write(bus, 0x18, 0, page); usleep_range(1000, 2000); /* wait for the page switch to propagate */ lo = bus->read(bus, 0x10 | r2, r1); hi = bus->read(bus, 0x10 | r2, r1 + 1); mutex_unlock(&bus->mdio_lock); return (hi << 16) | lo; } EXPORT_SYMBOL(qca8337_read); void qca8337_write(struct qca8337_priv *priv, u32 reg, u32 val) { struct phy_device *phy = priv->phy; struct mii_bus *bus = phy->bus; u16 r1, r2, r3; u16 lo, hi; mutex_lock(&bus->mdio_lock); split_addr(reg, &r1, &r2, &r3); lo = val & 0xffff; hi = (u16)(val >> 16); bus->write(bus, 0x18, 0, r3); usleep_range(1000, 2000); /* wait for the page switch to propagate */ bus->write(bus, 0x10 | r2, r1, lo); bus->write(bus, 0x10 | r2, r1 + 1, hi); mutex_unlock(&bus->mdio_lock); } EXPORT_SYMBOL(qca8337_write); static u32 qca8337_rmw(struct qca8337_priv *priv, u32 reg, u32 mask, u32 val) { u32 ret; ret = priv->ops->read(priv, reg); ret &= ~mask; ret |= val; priv->ops->write(priv, reg, ret); return ret; } static void qca8337_reg_set(struct qca8337_priv *priv, u32 reg, u32 val) { qca8337_rmw(priv, reg, 0, val); } static void qca8337_reset_switch(struct qca8337_priv *priv) { u32 val = 0; int count = 0; qca8337_reg_set(priv, QCA8337_REG_MASK_CTRL, QCA8337_CTRL_RESET); /*Need wait so reset done*/ for (count = 0; count < 100; count++) { usleep_range(5000, 10000); val = priv->ops->read(priv, QCA8337_REG_MASK_CTRL); if (!val && !(val & QCA8337_CTRL_RESET)) break; } } static void qca8337_port_set_status(struct qca8337_priv *priv) { qca8337_write(priv, QCA8337_REG_PORT_STATUS(0), (QCA8337_PORT_SPEED_1000M | QCA8337_PORT_STATUS_TXMAC | QCA8337_PORT_STATUS_RXMAC | QCA8337_PORT_STATUS_TXFLOW | QCA8337_PORT_STATUS_RXFLOW | QCA8337_PORT_STATUS_DUPLEX)); qca8337_write(priv, QCA8337_REG_PORT_STATUS(6), (QCA8337_PORT_SPEED_1000M | QCA8337_PORT_STATUS_TXMAC | QCA8337_PORT_STATUS_RXMAC | QCA8337_PORT_STATUS_TXFLOW | QCA8337_PORT_STATUS_RXFLOW | QCA8337_PORT_STATUS_DUPLEX)); } static int qca8337_busy_wait(struct qca8337_priv *priv, u32 reg, u32 mask) { unsigned long timeout; timeout = jiffies + msecs_to_jiffies(20); /* loop until the busy flag has cleared */ do { u32 val = priv->ops->read(priv, reg); int busy = val & mask; if (!busy) break; cond_resched(); } while (!time_after_eq(jiffies, timeout)); return time_after_eq(jiffies, timeout); } static void qca8337_mib_init(struct qca8337_priv *priv) { qca8337_reg_set(priv, QCA8337_REG_MIB, QCA8337_MIB_FLUSH | QCA8337_MIB_BUSY); qca8337_busy_wait(priv, QCA8337_REG_MIB, QCA8337_MIB_BUSY); qca8337_reg_set(priv, QCA8337_REG_MIB, QCA8337_MIB_CPU_KEEP); priv->ops->write(priv, QCA8337_REG_MODULE_EN, QCA8337_MODULE_EN_MIB); } static void qca8337_vlan_config(struct qca8337_priv *priv) { priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(0), 0x0014007e); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(0), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(1), 0x0014007d); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(1), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(2), 0x0014007b); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(2), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(3), 0x00140077); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(3), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(4), 0x0014006f); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(4), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(5), 0x0014005f); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(5), 0x10001); priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(6), 0x0014001e); priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(6), 0x10001); } static int qca8337_hw_init(struct qca8337_priv *priv) { int i; /* set pad control for cpu port */ qca8337_write(priv, QCA8337_REG_PAD0_CTRL, QCA8337_PAD_SGMII_EN); qca8337_write(priv, QCA8337_REG_PAD5_CTRL, QCA8337_PAD_RGMII_RXCLK_DELAY_EN); qca8337_write(priv, QCA8337_REG_PAD6_CTRL, (QCA8337_PAD_RGMII_EN | QCA8337_PAD_RGMII_RXCLK_DELAY_EN | (0x1 << QCA8337_PAD_RGMII_TXCLK_DELAY_SEL_S) | (0x2 << QCA8337_PAD_RGMII_RXCLK_DELAY_SEL_S))); /* Enable CPU Port */ qca8337_reg_set(priv, QCA8337_REG_GLOBAL_FW_CTRL0, QCA8337_GLOBAL_FW_CTRL0_CPU_PORT_EN); qca8337_port_set_status(priv); /* Enable MIB counters */ qca8337_mib_init(priv); /* Disable QCA header mode on the cpu port */ priv->ops->write(priv, QCA8337_REG_PORT_HEADER(priv->cpu_port), 0); /* Disable forwarding by default on all ports */ for (i = 0; i < priv->ports; i++) qca8337_rmw(priv, QCA8337_REG_PORT_LOOKUP(i), QCA8337_PORT_LOOKUP_MEMBER, 0); qca8337_write(priv, QCA8337_REG_GLOBAL_FW_CTRL1, (QCA8337_IGMP_JOIN_LEAVE_DPALL | QCA8337_BROAD_DPALL | QCA8337_MULTI_FLOOD_DPALL | QCA8337_UNI_FLOOD_DPALL)); /* Setup connection between CPU port & user ports */ qca8337_vlan_config(priv); /* Disable AZ */ priv->ops->write(priv, QCA8337_REG_EEE_CTRL, QCA8337_EEE_CTRL_DISABLE); return 0; } static void qca8337_reg_init_lan(struct qca8337_priv *priv) { priv->ops->write(priv, QCA8337_REG_POWER_ON_STRIP, QCA8337_REG_POS_VAL); priv->ops->write(priv, QCA8337_MAC_PWR_SEL , QCA8337_MAC_PWR_SEL_VAL); priv->ops->write(priv, QCA8337_SGMII_CTRL_REG, QCA8337_SGMII_CTRL_VAL); } static void qca8337_read_port_link(struct qca8337_priv *priv, int port, struct port_link_info *port_link) { u32 status; u32 speed; memset(port_link, '\0', sizeof(*port_link)); status = priv->ops->read(priv, QCA8337_REG_PORT_STATUS(port)); port_link->aneg = !!(status & QCA8337_PORT_STATUS_LINK_AUTO); if (port_link->aneg || (port != priv->cpu_port)) { port_link->link = !!(status & QCA8337_PORT_STATUS_LINK_UP); if (!port_link->link) return; } else { port_link->link = true; } port_link->duplex = !!(status & QCA8337_PORT_STATUS_DUPLEX); port_link->tx_flow = !!(status & QCA8337_PORT_STATUS_TXFLOW); port_link->rx_flow = !!(status & QCA8337_PORT_STATUS_RXFLOW); speed = (status & QCA8337_PORT_STATUS_SPEED) >> QCA8337_PORT_STATUS_SPEED_S; switch (speed) { case QCA8337_PORT_SPEED_10M: port_link->speed = SPEED_10; break; case QCA8337_PORT_SPEED_100M: port_link->speed = SPEED_100; break; case QCA8337_PORT_SPEED_1000M: port_link->speed = SPEED_1000; break; default: port_link->speed = SPEED_UNKNOWN; break; } } static void qca8337_phy_enable(struct phy_device *phydev) { int phyid = 0; ushort phy_val; struct mii_bus *bus; struct qca8337_priv *priv = phydev->priv; bus = priv->phy->bus; if (AUTONEG_ENABLE == phydev->autoneg) { int port; for (port = 1; port < priv->ports - 1; port++) qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), 0x1280); for (phyid = 0; phyid < priv->num_phy ; phyid++) { /*enable phy prefer multi-port mode*/ phy_val = mdiobus_read(bus, phyid, MII_CTRL1000); phy_val |= (ADVERTISE_MULTI_PORT_PREFER | ADVERTISE_1000FULL); mdiobus_write(bus, phyid, MII_CTRL1000, phy_val); /*enable extended next page. 0:enable, 1:disable*/ phy_val = mdiobus_read(bus, phyid, MII_ADVERTISE); phy_val &= (~(ADVERTISE_RESV)); mdiobus_write(bus, phyid, MII_ADVERTISE, phy_val); /*Phy power up*/ mdiobus_write(bus, phyid, MII_BMCR, (BMCR_RESET | BMCR_ANENABLE)); /* wait for the page switch to propagate */ usleep_range(100, 200); } } else { int port; u32 status = 0; u32 advertise = 0; phydev->advertising &= phydev->supported; advertise = phydev->advertising; for (port = 1; port < priv->ports - 1; port++) { status = 0; status |= phydev->duplex ? QCA8337_PORT_STATUS_DUPLEX : 0; status |= (advertise & ADVERTISED_Asym_Pause) ? QCA8337_PORT_STATUS_TXFLOW : 0; status |= (advertise & ADVERTISED_Pause) ? QCA8337_PORT_STATUS_RXFLOW : 0; if (SPEED_1000 == phydev->speed) status |= QCA8337_PORT_SPEED_1000M; else if (SPEED_100 == phydev->speed) status |= QCA8337_PORT_SPEED_100M; else if (SPEED_10 == phydev->speed) status |= QCA8337_PORT_SPEED_10M; qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), status); /* wait for the page switch to propagate */ usleep_range(100, 200); status |= QCA8337_PORT_STATUS_TXMAC | QCA8337_PORT_STATUS_RXMAC; qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), status); } for (phyid = 0; phyid < priv->num_phy ; phyid++) { phydev->addr = phyid; genphy_setup_forced(phydev); } for (phyid = 0; phyid < priv->num_phy ; phyid++) { phydev->addr = phyid; genphy_update_link(phydev); if (phydev->link) break; } } } static int qca8337_config_aneg(struct phy_device *phydev) { qca8337_phy_enable(phydev); return 0; } static int qca8337_read_status(struct phy_device *phydev) { struct qca8337_priv *priv = phydev->priv; struct port_link_info port_link; int ret = 0; int i, port_status = 0; int speed = -1, duplex = 0; for (i = 1; i < priv->ports - 1; i++) { qca8337_read_port_link(priv, i, &port_link); if (port_link.link) { speed = (speed < port_link.speed) ? port_link.speed : speed; duplex = (duplex < port_link.duplex) ? port_link.duplex : duplex; port_status |= 1 << i; } } qca8337_read_port_link(priv, priv->cpu_port, &port_link); phydev->link = (port_status) ? !!port_link.link : 0; phydev->speed = speed; phydev->duplex = duplex; return ret; } static int qca8337_aneg_done(struct phy_device *phydev) { int phyid = 0; int retval = 0; int aneg_status = 0; struct qca8337_priv *priv = phydev->priv; struct mii_bus *bus = priv->phy->bus; for (phyid = 0; phyid < priv->num_phy ; phyid++) { retval = mdiobus_read(bus, phyid, MII_BMSR); if (retval < 0) return retval; (retval & BMSR_ANEGCOMPLETE) ? (aneg_status |= 1 << phyid) : (aneg_status |= 0 << phyid); } return aneg_status; } static int qca8337_regmap_read(void *ctx, uint32_t reg, uint32_t *val) { struct qca8337_priv *priv = (struct qca8337_priv *)ctx; if (!priv->phy->link) return -EPERM; *val = priv->ops->read(priv, reg); return 0; } static int qca8337_regmap_write(void *ctx, uint32_t reg, uint32_t val) { struct qca8337_priv *priv = (struct qca8337_priv *)ctx; if (!priv->phy->link) return -EPERM; priv->ops->write(priv, reg, val); return 0; } static const struct regmap_range qca8337_readable_ranges[] = { regmap_reg_range(0x0000, 0x00e4), /* Global control registers */ regmap_reg_range(0x0100, 0x0168), /* EEE control registers */ regmap_reg_range(0x0200, 0x0270), /* Parser control registers */ regmap_reg_range(0x0400, 0x0454), /* ACL control registers */ regmap_reg_range(0x0600, 0x0718), /* Lookup control registers */ regmap_reg_range(0x0800, 0x0b70), /* QM control registers */ regmap_reg_range(0x0c00, 0x0c80), /* PKT edit control registers */ regmap_reg_range(0x0e00, 0x0e98), /* L3 */ regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */ regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */ regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */ regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */ regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */ regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */ regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */ }; static struct regmap_access_table qca8337_readable_table = { .yes_ranges = qca8337_readable_ranges, .n_yes_ranges = ARRAY_SIZE(qca8337_readable_ranges), }; static struct regmap_config qca8337_regmap_config = { .reg_bits = 16, .val_bits = 32, .reg_stride = 4, .max_register = 0x16ac, /* end MIB - Port6 range */ .reg_read = qca8337_regmap_read, .reg_write = qca8337_regmap_write, .rd_table = &qca8337_readable_table, }; static int qca8337_config_init(struct phy_device *phydev) { struct qca8337_priv *priv = phydev->priv; int ret = 0; /*Software reset*/ priv->ops->reset_switch(priv); /* Add delay to settle reset */ usleep_range(100, 200); ret = priv->ops->hw_init(priv); if (ret) return ret; qca8337_reg_init_lan(priv); return 0; } static struct qca8337_switch_ops switch_ops = { .hw_init = qca8337_hw_init, .reset_switch = qca8337_reset_switch, .read = qca8337_read, .write = qca8337_write, }; static int qca8337_probe(struct phy_device *phydev) { struct device *dev = &phydev->dev; struct qca8337_priv *priv = NULL; u32 val = 0; u16 id = 0; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->phy = phydev; priv->dev = &phydev->dev; priv->cpu_port = QCA8337_CPU_PORT; priv->vlans = QCA8337_MAX_VLANS; priv->ports = QCA8337_NUM_PORTS; priv->num_phy = QCA8337_NUM_PHYS; priv->ops = &switch_ops; /* Setup the register mapping */ priv->regmap = devm_regmap_init(priv->dev, NULL, priv, &qca8337_regmap_config); if (IS_ERR(priv->regmap)) pr_warn("regmap initialization failed"); /* read the switches ID register */ val = qca8337_read(priv, QCA8337_REG_MASK_CTRL); id = val & (QCA8337_CTRL_REVISION | QCA8337_CTRL_VERSION); priv->chip_ver = (id & QCA8337_CTRL_VERSION) >> QCA8337_CTRL_VERSION_S; priv->chip_rev = (id & QCA8337_CTRL_REVISION); if (priv->chip_ver != QCA8337_ID_QCA8337) { dev_err(dev, "qca8337: unknown Atheros device "); dev_err(dev, "[ver=%d, rev=%d, phy_id=%04x%04x]\n", priv->chip_ver, priv->chip_rev, mdiobus_read(priv->phy->bus, priv->phy->addr, 2), mdiobus_read(priv->phy->bus, priv->phy->addr, 3)); return -ENODEV; } dev_dbg(dev, "qca8337: Switch probed successfully "); dev_dbg(dev, "[ver=%d, rev=%d, phy_id=%04x%04x]\n", priv->chip_ver, priv->chip_rev, mdiobus_read(priv->phy->bus, priv->phy->addr, 2), mdiobus_read(priv->phy->bus, priv->phy->addr, 3)); phydev->priv = priv; return 0; } static void qca8337_remove(struct phy_device *phydev) { struct qca8337_priv *priv = phydev->priv; if (!priv) return; kfree(priv); } static struct phy_driver qca8337_driver = { .phy_id = QCA8337_PHY_ID, .name = "Atheros QCA8337", .phy_id_mask = 0xffffffef, .probe = qca8337_probe, .config_init = qca8337_config_init, .features = PHY_GBIT_FEATURES, .flags = PHY_HAS_INTERRUPT, .config_aneg = qca8337_config_aneg, .read_status = qca8337_read_status, .aneg_done = qca8337_aneg_done, .remove = qca8337_remove, .driver = { .owner = THIS_MODULE }, }; static int __init qca8337_init(void) { return phy_driver_register(&qca8337_driver); } static void __exit qca8337_exit(void) { phy_driver_unregister(&qca8337_driver); } module_init(qca8337_init); module_exit(qca8337_exit); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:qca8337");