diff options
Diffstat (limited to 'drivers/net/dsa/qca8k.c')
-rw-r--r-- | drivers/net/dsa/qca8k.c | 803 |
1 files changed, 615 insertions, 188 deletions
diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c index cdaf9f85a2cb..1f63f50f73f1 100644 --- a/drivers/net/dsa/qca8k.c +++ b/drivers/net/dsa/qca8k.c @@ -11,6 +11,7 @@ #include <linux/netdevice.h> #include <net/dsa.h> #include <linux/of_net.h> +#include <linux/of_mdio.h> #include <linux/of_platform.h> #include <linux/if_bridge.h> #include <linux/mdio.h> @@ -88,26 +89,26 @@ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) *page = regaddr & 0x3ff; } -static u32 -qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum) +static int +qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val) { - u32 val; int ret; ret = bus->read(bus, phy_id, regnum); if (ret >= 0) { - val = ret; + *val = ret; ret = bus->read(bus, phy_id, regnum + 1); - val |= ret << 16; + *val |= ret << 16; } if (ret < 0) { dev_err_ratelimited(&bus->dev, "failed to read qca8k 32bit register\n"); + *val = 0; return ret; } - return val; + return 0; } static void @@ -127,82 +128,110 @@ qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val) "failed to write qca8k 32bit register\n"); } -static void +static int qca8k_set_page(struct mii_bus *bus, u16 page) { + int ret; + if (page == qca8k_current_page) - return; + return 0; - if (bus->write(bus, 0x18, 0, page) < 0) + ret = bus->write(bus, 0x18, 0, page); + if (ret < 0) { dev_err_ratelimited(&bus->dev, "failed to set qca8k page\n"); + return ret; + } + qca8k_current_page = page; + usleep_range(1000, 2000); + return 0; } -static u32 -qca8k_read(struct qca8k_priv *priv, u32 reg) +static int +qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val) { + struct mii_bus *bus = priv->bus; u16 r1, r2, page; - u32 val; + int ret; qca8k_split_addr(reg, &r1, &r2, &page); - mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED); + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - qca8k_set_page(priv->bus, page); - val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1); + ret = qca8k_set_page(bus, page); + if (ret < 0) + goto exit; - mutex_unlock(&priv->bus->mdio_lock); + ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val); - return val; +exit: + mutex_unlock(&bus->mdio_lock); + return ret; } -static void +static int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) { + struct mii_bus *bus = priv->bus; u16 r1, r2, page; + int ret; qca8k_split_addr(reg, &r1, &r2, &page); - mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED); + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + ret = qca8k_set_page(bus, page); + if (ret < 0) + goto exit; - qca8k_set_page(priv->bus, page); - qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val); + qca8k_mii_write32(bus, 0x10 | r2, r1, val); - mutex_unlock(&priv->bus->mdio_lock); +exit: + mutex_unlock(&bus->mdio_lock); + return ret; } -static u32 -qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val) +static int +qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val) { + struct mii_bus *bus = priv->bus; u16 r1, r2, page; - u32 ret; + u32 val; + int ret; qca8k_split_addr(reg, &r1, &r2, &page); - mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED); + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + ret = qca8k_set_page(bus, page); + if (ret < 0) + goto exit; + + ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val); + if (ret < 0) + goto exit; - qca8k_set_page(priv->bus, page); - ret = qca8k_mii_read32(priv->bus, 0x10 | r2, r1); - ret &= ~mask; - ret |= val; - qca8k_mii_write32(priv->bus, 0x10 | r2, r1, ret); + val &= ~mask; + val |= write_val; + qca8k_mii_write32(bus, 0x10 | r2, r1, val); - mutex_unlock(&priv->bus->mdio_lock); +exit: + mutex_unlock(&bus->mdio_lock); return ret; } -static void +static int qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val) { - qca8k_rmw(priv, reg, 0, val); + return qca8k_rmw(priv, reg, 0, val); } -static void +static int qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val) { - qca8k_rmw(priv, reg, val, 0); + return qca8k_rmw(priv, reg, val, 0); } static int @@ -210,9 +239,7 @@ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val) { struct qca8k_priv *priv = (struct qca8k_priv *)ctx; - *val = qca8k_read(priv, reg); - - return 0; + return qca8k_read(priv, reg, val); } static int @@ -220,9 +247,7 @@ qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val) { struct qca8k_priv *priv = (struct qca8k_priv *)ctx; - qca8k_write(priv, reg, val); - - return 0; + return qca8k_write(priv, reg, val); } static const struct regmap_range qca8k_readable_ranges[] = { @@ -262,32 +287,36 @@ static struct regmap_config qca8k_regmap_config = { static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) { - unsigned long timeout; - - timeout = jiffies + msecs_to_jiffies(20); + int ret, ret1; + u32 val; - /* loop until the busy flag has cleared */ - do { - u32 val = qca8k_read(priv, reg); - int busy = val & mask; + ret = read_poll_timeout(qca8k_read, ret1, !(val & mask), + 0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false, + priv, reg, &val); - if (!busy) - break; - cond_resched(); - } while (!time_after_eq(jiffies, timeout)); + /* Check if qca8k_read has failed for a different reason + * before returning -ETIMEDOUT + */ + if (ret < 0 && ret1 < 0) + return ret1; - return time_after_eq(jiffies, timeout); + return ret; } -static void +static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) { - u32 reg[4]; - int i; + u32 reg[4], val; + int i, ret; /* load the ARL table into an array */ - for (i = 0; i < 4; i++) - reg[i] = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4)); + for (i = 0; i < 4; i++) { + ret = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4), &val); + if (ret < 0) + return ret; + + reg[i] = val; + } /* vid - 83:72 */ fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M; @@ -302,6 +331,8 @@ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) fdb->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff; fdb->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff; fdb->mac[5] = reg[0] & 0xff; + + return 0; } static void @@ -334,6 +365,7 @@ static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port) { u32 reg; + int ret; /* Set the command and FDB index */ reg = QCA8K_ATU_FUNC_BUSY; @@ -344,15 +376,20 @@ qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port) } /* Write the function register triggering the table access */ - qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg); + ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg); + if (ret) + return ret; /* wait for completion */ - if (qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY)) - return -1; + ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY); + if (ret) + return ret; /* Check for table full violation when adding an entry */ if (cmd == QCA8K_FDB_LOAD) { - reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC); + ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®); + if (ret < 0) + return ret; if (reg & QCA8K_ATU_FUNC_FULL) return -1; } @@ -367,10 +404,10 @@ qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port) qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging); ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port); - if (ret >= 0) - qca8k_fdb_read(priv, fdb); + if (ret < 0) + return ret; - return ret; + return qca8k_fdb_read(priv, fdb); } static int @@ -412,6 +449,7 @@ static int qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid) { u32 reg; + int ret; /* Set the command and VLAN index */ reg = QCA8K_VTU_FUNC1_BUSY; @@ -419,15 +457,20 @@ qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid) reg |= vid << QCA8K_VTU_FUNC1_VID_S; /* Write the function register triggering the table access */ - qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); + if (ret) + return ret; /* wait for completion */ - if (qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY)) - return -ETIMEDOUT; + ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY); + if (ret) + return ret; /* Check for table full violation when adding an entry */ if (cmd == QCA8K_VLAN_LOAD) { - reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC1); + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, ®); + if (ret < 0) + return ret; if (reg & QCA8K_VTU_FUNC1_FULL) return -ENOMEM; } @@ -453,7 +496,9 @@ qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged) if (ret < 0) goto out; - reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0); + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); + if (ret < 0) + goto out; reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN; reg &= ~(QCA8K_VTU_FUNC0_EG_MODE_MASK << QCA8K_VTU_FUNC0_EG_MODE_S(port)); if (untagged) @@ -463,7 +508,9 @@ qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged) reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG << QCA8K_VTU_FUNC0_EG_MODE_S(port); - qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + if (ret) + goto out; ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); out: @@ -484,7 +531,9 @@ qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) if (ret < 0) goto out; - reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0); + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); + if (ret < 0) + goto out; reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port)); reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT << QCA8K_VTU_FUNC0_EG_MODE_S(port); @@ -504,7 +553,9 @@ qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) if (del) { ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid); } else { - qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + if (ret) + goto out; ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); } @@ -514,15 +565,29 @@ out: return ret; } -static void +static int qca8k_mib_init(struct qca8k_priv *priv) { + int ret; + mutex_lock(&priv->reg_mutex); - qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY); - qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); - qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); - qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); + ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY); + if (ret) + goto exit; + + ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); + if (ret) + goto exit; + + ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); + if (ret) + goto exit; + + ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); + +exit: mutex_unlock(&priv->reg_mutex); + return ret; } static void @@ -556,54 +621,109 @@ qca8k_port_to_phy(int port) } static int -qca8k_mdio_write(struct qca8k_priv *priv, int port, u32 regnum, u16 data) +qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask) { - u32 phy, val; + u16 r1, r2, page; + u32 val; + int ret, ret1; + + qca8k_split_addr(reg, &r1, &r2, &page); + + ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0, + QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false, + bus, 0x10 | r2, r1, &val); + + /* Check if qca8k_read has failed for a different reason + * before returnting -ETIMEDOUT + */ + if (ret < 0 && ret1 < 0) + return ret1; + + return ret; +} + +static int +qca8k_mdio_write(struct mii_bus *salve_bus, int phy, int regnum, u16 data) +{ + struct qca8k_priv *priv = salve_bus->priv; + struct mii_bus *bus = priv->bus; + u16 r1, r2, page; + u32 val; + int ret; if (regnum >= QCA8K_MDIO_MASTER_MAX_REG) return -EINVAL; - /* callee is responsible for not passing bad ports, - * but we still would like to make spills impossible. - */ - phy = qca8k_port_to_phy(port) % PHY_MAX_ADDR; val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN | QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) | QCA8K_MDIO_MASTER_REG_ADDR(regnum) | QCA8K_MDIO_MASTER_DATA(data); - qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val); + qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page); + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + ret = qca8k_set_page(bus, page); + if (ret) + goto exit; + + qca8k_mii_write32(bus, 0x10 | r2, r1, val); - return qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_BUSY); + ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL, + QCA8K_MDIO_MASTER_BUSY); + +exit: + /* even if the busy_wait timeouts try to clear the MASTER_EN */ + qca8k_mii_write32(bus, 0x10 | r2, r1, 0); + + mutex_unlock(&bus->mdio_lock); + + return ret; } static int -qca8k_mdio_read(struct qca8k_priv *priv, int port, u32 regnum) +qca8k_mdio_read(struct mii_bus *salve_bus, int phy, int regnum) { - u32 phy, val; + struct qca8k_priv *priv = salve_bus->priv; + struct mii_bus *bus = priv->bus; + u16 r1, r2, page; + u32 val; + int ret; if (regnum >= QCA8K_MDIO_MASTER_MAX_REG) return -EINVAL; - /* callee is responsible for not passing bad ports, - * but we still would like to make spills impossible. - */ - phy = qca8k_port_to_phy(port) % PHY_MAX_ADDR; val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN | QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) | QCA8K_MDIO_MASTER_REG_ADDR(regnum); - qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val); + qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page); + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + ret = qca8k_set_page(bus, page); + if (ret) + goto exit; + + qca8k_mii_write32(bus, 0x10 | r2, r1, val); - if (qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_BUSY)) - return -ETIMEDOUT; + ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL, + QCA8K_MDIO_MASTER_BUSY); + if (ret) + goto exit; + + ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val); + +exit: + /* even if the busy_wait timeouts try to clear the MASTER_EN */ + qca8k_mii_write32(bus, 0x10 | r2, r1, 0); - val = (qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL) & - QCA8K_MDIO_MASTER_DATA_MASK); + mutex_unlock(&bus->mdio_lock); + + if (ret >= 0) + ret = val & QCA8K_MDIO_MASTER_DATA_MASK; - return val; + return ret; } static int @@ -611,7 +731,14 @@ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data) { struct qca8k_priv *priv = ds->priv; - return qca8k_mdio_write(priv, port, regnum, data); + /* Check if the legacy mapping should be used and the + * port is not correctly mapped to the right PHY in the + * devicetree + */ + if (priv->legacy_phy_port_mapping) + port = qca8k_port_to_phy(port) % PHY_MAX_ADDR; + + return qca8k_mdio_write(priv->bus, port, regnum, data); } static int @@ -620,7 +747,14 @@ qca8k_phy_read(struct dsa_switch *ds, int port, int regnum) struct qca8k_priv *priv = ds->priv; int ret; - ret = qca8k_mdio_read(priv, port, regnum); + /* Check if the legacy mapping should be used and the + * port is not correctly mapped to the right PHY in the + * devicetree + */ + if (priv->legacy_phy_port_mapping) + port = qca8k_port_to_phy(port) % PHY_MAX_ADDR; + + ret = qca8k_mdio_read(priv->bus, port, regnum); if (ret < 0) return 0xffff; @@ -629,14 +763,44 @@ qca8k_phy_read(struct dsa_switch *ds, int port, int regnum) } static int +qca8k_mdio_register(struct qca8k_priv *priv, struct device_node *mdio) +{ + struct dsa_switch *ds = priv->ds; + struct mii_bus *bus; + + bus = devm_mdiobus_alloc(ds->dev); + + if (!bus) + return -ENOMEM; + + bus->priv = (void *)priv; + bus->name = "qca8k slave mii"; + bus->read = qca8k_mdio_read; + bus->write = qca8k_mdio_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d", + ds->index); + + bus->parent = ds->dev; + bus->phy_mask = ~ds->phys_mii_mask; + + ds->slave_mii_bus = bus; + + return devm_of_mdiobus_register(priv->dev, bus, mdio); +} + +static int qca8k_setup_mdio_bus(struct qca8k_priv *priv) { u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg; - struct device_node *ports, *port; + struct device_node *ports, *port, *mdio; + phy_interface_t mode; int err; ports = of_get_child_by_name(priv->dev->of_node, "ports"); if (!ports) + ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports"); + + if (!ports) return -EINVAL; for_each_available_child_of_node(ports, port) { @@ -650,7 +814,10 @@ qca8k_setup_mdio_bus(struct qca8k_priv *priv) if (!dsa_is_user_port(priv->ds, reg)) continue; - if (of_property_read_bool(port, "phy-handle")) + of_get_phy_mode(port, &mode); + + if (of_property_read_bool(port, "phy-handle") && + mode != PHY_INTERFACE_MODE_INTERNAL) external_mdio_mask |= BIT(reg); else internal_mdio_mask |= BIT(reg); @@ -683,13 +850,89 @@ qca8k_setup_mdio_bus(struct qca8k_priv *priv) * a dt-overlay and driver reload changed the configuration */ - qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_EN); - return 0; + return qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL, + QCA8K_MDIO_MASTER_EN); + } + + /* Check if the devicetree declare the port:phy mapping */ + mdio = of_get_child_by_name(priv->dev->of_node, "mdio"); + if (of_device_is_available(mdio)) { + err = qca8k_mdio_register(priv, mdio); + if (err) + of_node_put(mdio); + + return err; } + /* If a mapping can't be found the legacy mapping is used, + * using the qca8k_port_to_phy function + */ + priv->legacy_phy_port_mapping = true; priv->ops.phy_read = qca8k_phy_read; priv->ops.phy_write = qca8k_phy_write; + + return 0; +} + +static int +qca8k_setup_of_rgmii_delay(struct qca8k_priv *priv) +{ + struct device_node *port_dn; + phy_interface_t mode; + struct dsa_port *dp; + u32 val; + + /* CPU port is already checked */ + dp = dsa_to_port(priv->ds, 0); + + port_dn = dp->dn; + + /* Check if port 0 is set to the correct type */ + of_get_phy_mode(port_dn, &mode); + if (mode != PHY_INTERFACE_MODE_RGMII_ID && + mode != PHY_INTERFACE_MODE_RGMII_RXID && + mode != PHY_INTERFACE_MODE_RGMII_TXID) { + return 0; + } + + switch (mode) { + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + if (of_property_read_u32(port_dn, "rx-internal-delay-ps", &val)) + val = 2; + else + /* Switch regs accept value in ns, convert ps to ns */ + val = val / 1000; + + if (val > QCA8K_MAX_DELAY) { + dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value"); + val = 3; + } + + priv->rgmii_rx_delay = val; + /* Stop here if we need to check only for rx delay */ + if (mode != PHY_INTERFACE_MODE_RGMII_ID) + break; + + fallthrough; + case PHY_INTERFACE_MODE_RGMII_TXID: + if (of_property_read_u32(port_dn, "tx-internal-delay-ps", &val)) + val = 1; + else + /* Switch regs accept value in ns, convert ps to ns */ + val = val / 1000; + + if (val > QCA8K_MAX_DELAY) { + dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value"); + val = 3; + } + + priv->rgmii_tx_delay = val; + break; + default: + return 0; + } + return 0; } @@ -698,10 +941,11 @@ qca8k_setup(struct dsa_switch *ds) { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; int ret, i; + u32 mask; /* Make sure that port 0 is the cpu port */ if (!dsa_is_cpu_port(ds, 0)) { - pr_err("port 0 is not the CPU port\n"); + dev_err(priv->dev, "port 0 is not the CPU port"); return -EINVAL; } @@ -711,76 +955,163 @@ qca8k_setup(struct dsa_switch *ds) priv->regmap = devm_regmap_init(ds->dev, NULL, priv, &qca8k_regmap_config); if (IS_ERR(priv->regmap)) - pr_warn("regmap initialization failed"); + dev_warn(priv->dev, "regmap initialization failed"); ret = qca8k_setup_mdio_bus(priv); if (ret) return ret; + ret = qca8k_setup_of_rgmii_delay(priv); + if (ret) + return ret; + /* Enable CPU Port */ - qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0, - QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN); + ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0, + QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN); + if (ret) { + dev_err(priv->dev, "failed enabling CPU port"); + return ret; + } /* Enable MIB counters */ - qca8k_mib_init(priv); + ret = qca8k_mib_init(priv); + if (ret) + dev_warn(priv->dev, "mib init failed"); /* Enable QCA header mode on the cpu port */ - qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT), - QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | - QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); + ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT), + QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | + QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); + if (ret) { + dev_err(priv->dev, "failed enabling QCA header mode"); + return ret; + } /* Disable forwarding by default on all ports */ - for (i = 0; i < QCA8K_NUM_PORTS; i++) - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_MEMBER, 0); + for (i = 0; i < QCA8K_NUM_PORTS; i++) { + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), + QCA8K_PORT_LOOKUP_MEMBER, 0); + if (ret) + return ret; + } /* Disable MAC by default on all ports */ for (i = 1; i < QCA8K_NUM_PORTS; i++) qca8k_port_set_status(priv, i, 0); /* Forward all unknown frames to CPU port for Linux processing */ - qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, - BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | - BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S | - BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | - BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); + ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S | + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | + BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); + if (ret) + return ret; /* Setup connection between CPU port & user ports */ for (i = 0; i < QCA8K_NUM_PORTS; i++) { /* CPU port gets connected to all user ports of the switch */ if (dsa_is_cpu_port(ds, i)) { - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT), - QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT), + QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); + if (ret) + return ret; } /* Individual user ports get connected to CPU port only */ if (dsa_is_user_port(ds, i)) { int shift = 16 * (i % 2); - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_MEMBER, - BIT(QCA8K_CPU_PORT)); + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), + QCA8K_PORT_LOOKUP_MEMBER, + BIT(QCA8K_CPU_PORT)); + if (ret) + return ret; /* Enable ARP Auto-learning by default */ - qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_LEARN); + ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), + QCA8K_PORT_LOOKUP_LEARN); + if (ret) + return ret; /* For port based vlans to work we need to set the * default egress vid */ - qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), - 0xfff << shift, - QCA8K_PORT_VID_DEF << shift); - qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), - QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) | - QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF)); + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), + 0xfff << shift, + QCA8K_PORT_VID_DEF << shift); + if (ret) + return ret; + + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), + QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) | + QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF)); + if (ret) + return ret; } } + /* The port 5 of the qca8337 have some problem in flood condition. The + * original legacy driver had some specific buffer and priority settings + * for the different port suggested by the QCA switch team. Add this + * missing settings to improve switch stability under load condition. + * This problem is limited to qca8337 and other qca8k switch are not affected. + */ + if (priv->switch_id == QCA8K_ID_QCA8337) { + for (i = 0; i < QCA8K_NUM_PORTS; i++) { + switch (i) { + /* The 2 CPU port and port 5 requires some different + * priority than any other ports. + */ + case 0: + case 5: + case 6: + mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) | + QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) | + QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) | + QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) | + QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) | + QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) | + QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e); + break; + default: + mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) | + QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) | + QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) | + QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) | + QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19); + } + qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask); + + mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) | + QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN | + QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN | + QCA8K_PORT_HOL_CTRL1_WRED_EN; + qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i), + QCA8K_PORT_HOL_CTRL1_ING_BUF | + QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN | + QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN | + QCA8K_PORT_HOL_CTRL1_WRED_EN, + mask); + } + } + + /* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */ + if (priv->switch_id == QCA8K_ID_QCA8327) { + mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) | + QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496); + qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH, + QCA8K_GLOBAL_FC_GOL_XON_THRES_S | + QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S, + mask); + } + /* Setup our port MTUs to match power on defaults */ for (i = 0; i < QCA8K_NUM_PORTS; i++) priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN; - qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN); + ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN); + if (ret) + dev_warn(priv->dev, "failed setting MTU settings"); /* Flush the FDB table */ qca8k_fdb_flush(priv); @@ -797,11 +1128,14 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, { struct qca8k_priv *priv = ds->priv; u32 reg, val; + int ret; switch (port) { case 0: /* 1st CPU port */ if (state->interface != PHY_INTERFACE_MODE_RGMII && state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_RGMII_TXID && + state->interface != PHY_INTERFACE_MODE_RGMII_RXID && state->interface != PHY_INTERFACE_MODE_SGMII) return; @@ -817,6 +1151,8 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, case 6: /* 2nd CPU port / external PHY */ if (state->interface != PHY_INTERFACE_MODE_RGMII && state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_RGMII_TXID && + state->interface != PHY_INTERFACE_MODE_RGMII_RXID && state->interface != PHY_INTERFACE_MODE_SGMII && state->interface != PHY_INTERFACE_MODE_1000BASEX) return; @@ -840,16 +1176,22 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN); break; case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_TXID: + case PHY_INTERFACE_MODE_RGMII_RXID: /* RGMII_ID needs internal delay. This is enabled through * PORT5_PAD_CTRL for all ports, rather than individual port * registers */ qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN | - QCA8K_PORT_PAD_RGMII_TX_DELAY(QCA8K_MAX_DELAY) | - QCA8K_PORT_PAD_RGMII_RX_DELAY(QCA8K_MAX_DELAY)); - qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL, + QCA8K_PORT_PAD_RGMII_TX_DELAY(priv->rgmii_tx_delay) | + QCA8K_PORT_PAD_RGMII_RX_DELAY(priv->rgmii_rx_delay) | + QCA8K_PORT_PAD_RGMII_TX_DELAY_EN | QCA8K_PORT_PAD_RGMII_RX_DELAY_EN); + /* QCA8337 requires to set rgmii rx delay */ + if (priv->switch_id == QCA8K_ID_QCA8337) + qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL, + QCA8K_PORT_PAD_RGMII_RX_DELAY_EN); break; case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_1000BASEX: @@ -857,7 +1199,9 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN); /* Enable/disable SerDes auto-negotiation as necessary */ - val = qca8k_read(priv, QCA8K_REG_PWS); + ret = qca8k_read(priv, QCA8K_REG_PWS, &val); + if (ret) + return; if (phylink_autoneg_inband(mode)) val &= ~QCA8K_PWS_SERDES_AEN_DIS; else @@ -865,7 +1209,9 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, qca8k_write(priv, QCA8K_REG_PWS, val); /* Configure the SGMII parameters */ - val = qca8k_read(priv, QCA8K_REG_SGMII_CTRL); + ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val); + if (ret) + return; val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX | QCA8K_SGMII_EN_TX | QCA8K_SGMII_EN_SD; @@ -903,6 +1249,8 @@ qca8k_phylink_validate(struct dsa_switch *ds, int port, if (state->interface != PHY_INTERFACE_MODE_NA && state->interface != PHY_INTERFACE_MODE_RGMII && state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_RGMII_TXID && + state->interface != PHY_INTERFACE_MODE_RGMII_RXID && state->interface != PHY_INTERFACE_MODE_SGMII) goto unsupported; break; @@ -913,13 +1261,16 @@ qca8k_phylink_validate(struct dsa_switch *ds, int port, case 5: /* Internal PHY */ if (state->interface != PHY_INTERFACE_MODE_NA && - state->interface != PHY_INTERFACE_MODE_GMII) + state->interface != PHY_INTERFACE_MODE_GMII && + state->interface != PHY_INTERFACE_MODE_INTERNAL) goto unsupported; break; case 6: /* 2nd CPU port / external PHY */ if (state->interface != PHY_INTERFACE_MODE_NA && state->interface != PHY_INTERFACE_MODE_RGMII && state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_RGMII_TXID && + state->interface != PHY_INTERFACE_MODE_RGMII_RXID && state->interface != PHY_INTERFACE_MODE_SGMII && state->interface != PHY_INTERFACE_MODE_1000BASEX) goto unsupported; @@ -955,8 +1306,11 @@ qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port, { struct qca8k_priv *priv = ds->priv; u32 reg; + int ret; - reg = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port)); + ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), ®); + if (ret < 0) + return ret; state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP); state->an_complete = state->link; @@ -1057,18 +1411,27 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; const struct qca8k_mib_desc *mib; - u32 reg, i; - u64 hi; + u32 reg, i, val; + u32 hi = 0; + int ret; for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++) { mib = &ar8327_mib[i]; reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset; - data[i] = qca8k_read(priv, reg); + ret = qca8k_read(priv, reg, &val); + if (ret < 0) + continue; + if (mib->size == 2) { - hi = qca8k_read(priv, reg + 4); - data[i] |= hi << 32; + ret = qca8k_read(priv, reg + 4, &hi); + if (ret < 0) + continue; } + + data[i] = val; + if (mib->size == 2) + data[i] |= (u64)hi << 32; } } @@ -1087,17 +1450,22 @@ qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee) struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port); u32 reg; + int ret; mutex_lock(&priv->reg_mutex); - reg = qca8k_read(priv, QCA8K_REG_EEE_CTRL); + ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, ®); + if (ret < 0) + goto exit; + if (eee->eee_enabled) reg |= lpi_en; else reg &= ~lpi_en; - qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg); - mutex_unlock(&priv->reg_mutex); + ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg); - return 0; +exit: + mutex_unlock(&priv->reg_mutex); + return ret; } static int @@ -1141,7 +1509,7 @@ qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br) { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; int port_mask = BIT(QCA8K_CPU_PORT); - int i; + int i, ret; for (i = 1; i < QCA8K_NUM_PORTS; i++) { if (dsa_to_port(ds, i)->bridge_dev != br) @@ -1149,17 +1517,20 @@ qca8k_port_bridge_join(struct dsa_switch *ds, int port, struct net_device *br) /* Add this port to the portvlan mask of the other ports * in the bridge */ - qca8k_reg_set(priv, - QCA8K_PORT_LOOKUP_CTRL(i), - BIT(port)); + ret = qca8k_reg_set(priv, + QCA8K_PORT_LOOKUP_CTRL(i), + BIT(port)); + if (ret) + return ret; if (i != port) port_mask |= BIT(i); } + /* Add all other ports to this ports portvlan mask */ - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_MEMBER, port_mask); + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_MEMBER, port_mask); - return 0; + return ret; } static void @@ -1223,9 +1594,7 @@ qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) mtu = priv->port_mtu[i]; /* Include L2 header / FCS length */ - qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN); - - return 0; + return qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN); } static int @@ -1298,18 +1667,19 @@ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct netlink_ext_ack *extack) { struct qca8k_priv *priv = ds->priv; + int ret; if (vlan_filtering) { - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_VLAN_MODE, - QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE, + QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); } else { - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_VLAN_MODE, - QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE, + QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); } - return 0; + return ret; } static int @@ -1320,7 +1690,7 @@ qca8k_port_vlan_add(struct dsa_switch *ds, int port, bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; struct qca8k_priv *priv = ds->priv; - int ret = 0; + int ret; ret = qca8k_vlan_add(priv, port, vlan->vid, untagged); if (ret) { @@ -1331,14 +1701,17 @@ qca8k_port_vlan_add(struct dsa_switch *ds, int port, if (pvid) { int shift = 16 * (port % 2); - qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), - 0xfff << shift, vlan->vid << shift); - qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), - QCA8K_PORT_VLAN_CVID(vlan->vid) | - QCA8K_PORT_VLAN_SVID(vlan->vid)); + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), + 0xfff << shift, vlan->vid << shift); + if (ret) + return ret; + + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), + QCA8K_PORT_VLAN_CVID(vlan->vid) | + QCA8K_PORT_VLAN_SVID(vlan->vid)); } - return 0; + return ret; } static int @@ -1346,7 +1719,7 @@ qca8k_port_vlan_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { struct qca8k_priv *priv = ds->priv; - int ret = 0; + int ret; ret = qca8k_vlan_del(priv, port, vlan->vid); if (ret) @@ -1355,6 +1728,22 @@ qca8k_port_vlan_del(struct dsa_switch *ds, int port, return ret; } +static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port) +{ + struct qca8k_priv *priv = ds->priv; + + /* Communicate to the phy internal driver the switch revision. + * Based on the switch revision different values needs to be + * set to the dbg and mmd reg on the phy. + * The first 2 bit are used to communicate the switch revision + * to the phy driver. + */ + if (port > 0 && port < 6) + return priv->switch_revision; + + return 0; +} + static enum dsa_tag_protocol qca8k_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) @@ -1388,13 +1777,44 @@ static const struct dsa_switch_ops qca8k_switch_ops = { .phylink_mac_config = qca8k_phylink_mac_config, .phylink_mac_link_down = qca8k_phylink_mac_link_down, .phylink_mac_link_up = qca8k_phylink_mac_link_up, + .get_phy_flags = qca8k_get_phy_flags, }; +static int qca8k_read_switch_id(struct qca8k_priv *priv) +{ + const struct qca8k_match_data *data; + u32 val; + u8 id; + int ret; + + /* get the switches ID from the compatible */ + data = of_device_get_match_data(priv->dev); + if (!data) + return -ENODEV; + + ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); + if (ret < 0) + return -ENODEV; + + id = QCA8K_MASK_CTRL_DEVICE_ID(val & QCA8K_MASK_CTRL_DEVICE_ID_MASK); + if (id != data->id) { + dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id); + return -ENODEV; + } + + priv->switch_id = id; + + /* Save revision to communicate to the internal PHY driver */ + priv->switch_revision = (val & QCA8K_MASK_CTRL_REV_ID_MASK); + + return 0; +} + static int qca8k_sw_probe(struct mdio_device *mdiodev) { struct qca8k_priv *priv; - u32 id; + int ret; /* allocate the private data struct so that we can probe the switches * ID register @@ -1420,12 +1840,10 @@ qca8k_sw_probe(struct mdio_device *mdiodev) gpiod_set_value_cansleep(priv->reset_gpio, 0); } - /* read the switches ID register */ - id = qca8k_read(priv, QCA8K_REG_MASK_CTRL); - id >>= QCA8K_MASK_CTRL_ID_S; - id &= QCA8K_MASK_CTRL_ID_M; - if (id != QCA8K_ID_QCA8337) - return -ENODEV; + /* Check the detected switch id */ + ret = qca8k_read_switch_id(priv); + if (ret) + return ret; priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL); if (!priv->ds) @@ -1490,9 +1908,18 @@ static int qca8k_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(qca8k_pm_ops, qca8k_suspend, qca8k_resume); +static const struct qca8k_match_data qca832x = { + .id = QCA8K_ID_QCA8327, +}; + +static const struct qca8k_match_data qca833x = { + .id = QCA8K_ID_QCA8337, +}; + static const struct of_device_id qca8k_of_match[] = { - { .compatible = "qca,qca8334" }, - { .compatible = "qca,qca8337" }, + { .compatible = "qca,qca8327", .data = &qca832x }, + { .compatible = "qca,qca8334", .data = &qca833x }, + { .compatible = "qca,qca8337", .data = &qca833x }, { /* sentinel */ }, }; |