mirror of
https://github.com/tbsdtv/linux_media.git
synced 2025-07-23 04:33:26 +02:00
The call path of a switchdev VLAN addition to the bridge looks something like this today: nbp_vlan_init | __br_vlan_set_default_pvid | | | | | br_afspec | | | | | | | v | | | br_process_vlan_info | | | | | | | v | | | br_vlan_info | | | / \ / | | / \ / | | / \ / | | / \ / v v v v v nbp_vlan_add br_vlan_add ------+ | ^ ^ | | | / | | | | / / / | \ br_vlan_get_master/ / v \ ^ / / br_vlan_add_existing \ | / / | \ | / / / \ | / / / \ | / / / \ | / / / v | | v / __vlan_add / / | / / | / v | / __vlan_vid_add | / \ | / v v v br_switchdev_port_vlan_add The ranges UAPI was introduced to the bridge in commitbdced7ef78
("bridge: support for multiple vlans and vlan ranges in setlink and dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec) have always been passed one by one, through struct bridge_vlan_info tmp_vinfo, to br_vlan_info. So the range never went too far in depth. Then Scott Feldman introduced the switchdev_port_bridge_setlink function in commit47f8328bb1
("switchdev: add new switchdev bridge setlink"). That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made full use of the range. But switchdev_port_bridge_setlink was called like this: br_setlink -> br_afspec -> switchdev_port_bridge_setlink Basically, the switchdev and the bridge code were not tightly integrated. Then commit41c498b935
("bridge: restore br_setlink back to original") came, and switchdev drivers were required to implement .ndo_bridge_setlink = switchdev_port_bridge_setlink for a while. In the meantime, commits such as0944d6b5a2
("bridge: try switchdev op first in __vlan_vid_add/del") finally made switchdev penetrate the br_vlan_info() barrier and start to develop the call path we have today. But remember, br_vlan_info() still receives VLANs one by one. Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit29ab586c3d
("net: switchdev: Remove bridge bypass support from switchdev") so that drivers would not implement .ndo_bridge_setlink any longer. The switchdev_port_bridge_setlink also got deleted. This refactoring removed the parallel bridge_setlink implementation from switchdev, and left the only switchdev VLAN objects to be the ones offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add (the latter coming from commit9c86ce2c1a
("net: bridge: Notify about bridge VLANs")). That is to say, today the switchdev VLAN object ranges are not used in the kernel. Refactoring the above call path is a bit complicated, when the bridge VLAN call path is already a bit complicated. Let's go off and finish the job of commit29ab586c3d
by deleting the bogus iteration through the VLAN ranges from the drivers. Some aspects of this feature never made too much sense in the first place. For example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID flag supposed to mean, when a port can obviously have a single pvid? This particular configuration _is_ denied as of commit6623c60dc2
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API perspective, the driver still has to play pretend, and only offload the vlan->vid_end as pvid. And the addition of a switchdev VLAN object can modify the flags of another, completely unrelated, switchdev VLAN object! (a VLAN that is PVID will invalidate the PVID flag from whatever other VLAN had previously been offloaded with switchdev and had that flag. Yet switchdev never notifies about that change, drivers are supposed to guess). Nonetheless, having a VLAN range in the API makes error handling look scarier than it really is - unwinding on errors and all of that. When in reality, no one really calls this API with more than one VLAN. It is all unnecessary complexity. And despite appearing pretentious (two-phase transactional model and all), the switchdev API is really sloppy because the VLAN addition and removal operations are not paired with one another (you can add a VLAN 100 times and delete it just once). The bridge notifies through switchdev of a VLAN addition not only when the flags of an existing VLAN change, but also when nothing changes. There are switchdev drivers out there who don't like adding a VLAN that has already been added, and those checks don't really belong at driver level. But the fact that the API contains ranges is yet another factor that prevents this from being addressed in the future. Of the existing switchdev pieces of hardware, it appears that only Mellanox Spectrum supports offloading more than one VLAN at a time, through mlxsw_sp_port_vlan_set. I have kept that code internal to the driver, because there is some more bookkeeping that makes use of it, but I deleted it from the switchdev API. But since the switchdev support for ranges has already been de facto deleted by a Mellanox employee and nobody noticed for 4 years, I'm going to assume it's not a biggie. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1522 lines
37 KiB
C
1522 lines
37 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
|
|
* Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
|
|
* Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2016 John Crispin <john@phrozen.org>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/netdevice.h>
|
|
#include <net/dsa.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/mdio.h>
|
|
#include <linux/phylink.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include "qca8k.h"
|
|
|
|
#define MIB_DESC(_s, _o, _n) \
|
|
{ \
|
|
.size = (_s), \
|
|
.offset = (_o), \
|
|
.name = (_n), \
|
|
}
|
|
|
|
static const struct qca8k_mib_desc ar8327_mib[] = {
|
|
MIB_DESC(1, 0x00, "RxBroad"),
|
|
MIB_DESC(1, 0x04, "RxPause"),
|
|
MIB_DESC(1, 0x08, "RxMulti"),
|
|
MIB_DESC(1, 0x0c, "RxFcsErr"),
|
|
MIB_DESC(1, 0x10, "RxAlignErr"),
|
|
MIB_DESC(1, 0x14, "RxRunt"),
|
|
MIB_DESC(1, 0x18, "RxFragment"),
|
|
MIB_DESC(1, 0x1c, "Rx64Byte"),
|
|
MIB_DESC(1, 0x20, "Rx128Byte"),
|
|
MIB_DESC(1, 0x24, "Rx256Byte"),
|
|
MIB_DESC(1, 0x28, "Rx512Byte"),
|
|
MIB_DESC(1, 0x2c, "Rx1024Byte"),
|
|
MIB_DESC(1, 0x30, "Rx1518Byte"),
|
|
MIB_DESC(1, 0x34, "RxMaxByte"),
|
|
MIB_DESC(1, 0x38, "RxTooLong"),
|
|
MIB_DESC(2, 0x3c, "RxGoodByte"),
|
|
MIB_DESC(2, 0x44, "RxBadByte"),
|
|
MIB_DESC(1, 0x4c, "RxOverFlow"),
|
|
MIB_DESC(1, 0x50, "Filtered"),
|
|
MIB_DESC(1, 0x54, "TxBroad"),
|
|
MIB_DESC(1, 0x58, "TxPause"),
|
|
MIB_DESC(1, 0x5c, "TxMulti"),
|
|
MIB_DESC(1, 0x60, "TxUnderRun"),
|
|
MIB_DESC(1, 0x64, "Tx64Byte"),
|
|
MIB_DESC(1, 0x68, "Tx128Byte"),
|
|
MIB_DESC(1, 0x6c, "Tx256Byte"),
|
|
MIB_DESC(1, 0x70, "Tx512Byte"),
|
|
MIB_DESC(1, 0x74, "Tx1024Byte"),
|
|
MIB_DESC(1, 0x78, "Tx1518Byte"),
|
|
MIB_DESC(1, 0x7c, "TxMaxByte"),
|
|
MIB_DESC(1, 0x80, "TxOverSize"),
|
|
MIB_DESC(2, 0x84, "TxByte"),
|
|
MIB_DESC(1, 0x8c, "TxCollision"),
|
|
MIB_DESC(1, 0x90, "TxAbortCol"),
|
|
MIB_DESC(1, 0x94, "TxMultiCol"),
|
|
MIB_DESC(1, 0x98, "TxSingleCol"),
|
|
MIB_DESC(1, 0x9c, "TxExcDefer"),
|
|
MIB_DESC(1, 0xa0, "TxDefer"),
|
|
MIB_DESC(1, 0xa4, "TxLateCol"),
|
|
};
|
|
|
|
/* The 32bit switch registers are accessed indirectly. To achieve this we need
|
|
* to set the page of the register. Track the last page that was set to reduce
|
|
* mdio writes
|
|
*/
|
|
static u16 qca8k_current_page = 0xffff;
|
|
|
|
static void
|
|
qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
|
|
{
|
|
regaddr >>= 1;
|
|
*r1 = regaddr & 0x1e;
|
|
|
|
regaddr >>= 5;
|
|
*r2 = regaddr & 0x7;
|
|
|
|
regaddr >>= 3;
|
|
*page = regaddr & 0x3ff;
|
|
}
|
|
|
|
static u32
|
|
qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = bus->read(bus, phy_id, regnum);
|
|
if (ret >= 0) {
|
|
val = ret;
|
|
ret = bus->read(bus, phy_id, regnum + 1);
|
|
val |= ret << 16;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to read qca8k 32bit register\n");
|
|
return ret;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
|
|
{
|
|
u16 lo, hi;
|
|
int ret;
|
|
|
|
lo = val & 0xffff;
|
|
hi = (u16)(val >> 16);
|
|
|
|
ret = bus->write(bus, phy_id, regnum, lo);
|
|
if (ret >= 0)
|
|
ret = bus->write(bus, phy_id, regnum + 1, hi);
|
|
if (ret < 0)
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to write qca8k 32bit register\n");
|
|
}
|
|
|
|
static void
|
|
qca8k_set_page(struct mii_bus *bus, u16 page)
|
|
{
|
|
if (page == qca8k_current_page)
|
|
return;
|
|
|
|
if (bus->write(bus, 0x18, 0, page) < 0)
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to set qca8k page\n");
|
|
qca8k_current_page = page;
|
|
}
|
|
|
|
static u32
|
|
qca8k_read(struct qca8k_priv *priv, u32 reg)
|
|
{
|
|
u16 r1, r2, page;
|
|
u32 val;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
qca8k_set_page(priv->bus, page);
|
|
val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
|
|
|
|
mutex_unlock(&priv->bus->mdio_lock);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
|
|
{
|
|
u16 r1, r2, page;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
qca8k_set_page(priv->bus, page);
|
|
qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
|
|
|
|
mutex_unlock(&priv->bus->mdio_lock);
|
|
}
|
|
|
|
static u32
|
|
qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val)
|
|
{
|
|
u16 r1, r2, page;
|
|
u32 ret;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
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);
|
|
|
|
mutex_unlock(&priv->bus->mdio_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val)
|
|
{
|
|
qca8k_rmw(priv, reg, 0, val);
|
|
}
|
|
|
|
static void
|
|
qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val)
|
|
{
|
|
qca8k_rmw(priv, reg, val, 0);
|
|
}
|
|
|
|
static int
|
|
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;
|
|
}
|
|
|
|
static int
|
|
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;
|
|
}
|
|
|
|
static const struct regmap_range qca8k_readable_ranges[] = {
|
|
regmap_reg_range(0x0000, 0x00e4), /* Global control */
|
|
regmap_reg_range(0x0100, 0x0168), /* EEE control */
|
|
regmap_reg_range(0x0200, 0x0270), /* Parser control */
|
|
regmap_reg_range(0x0400, 0x0454), /* ACL */
|
|
regmap_reg_range(0x0600, 0x0718), /* Lookup */
|
|
regmap_reg_range(0x0800, 0x0b70), /* QM */
|
|
regmap_reg_range(0x0c00, 0x0c80), /* PKT */
|
|
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 const struct regmap_access_table qca8k_readable_table = {
|
|
.yes_ranges = qca8k_readable_ranges,
|
|
.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
|
|
};
|
|
|
|
static struct regmap_config qca8k_regmap_config = {
|
|
.reg_bits = 16,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = 0x16ac, /* end MIB - Port6 range */
|
|
.reg_read = qca8k_regmap_read,
|
|
.reg_write = qca8k_regmap_write,
|
|
.rd_table = &qca8k_readable_table,
|
|
};
|
|
|
|
static int
|
|
qca8k_busy_wait(struct qca8k_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 = qca8k_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
|
|
qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
|
|
{
|
|
u32 reg[4];
|
|
int i;
|
|
|
|
/* load the ARL table into an array */
|
|
for (i = 0; i < 4; i++)
|
|
reg[i] = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4));
|
|
|
|
/* vid - 83:72 */
|
|
fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M;
|
|
/* aging - 67:64 */
|
|
fdb->aging = reg[2] & QCA8K_ATU_STATUS_M;
|
|
/* portmask - 54:48 */
|
|
fdb->port_mask = (reg[1] >> QCA8K_ATU_PORT_S) & QCA8K_ATU_PORT_M;
|
|
/* mac - 47:0 */
|
|
fdb->mac[0] = (reg[1] >> QCA8K_ATU_ADDR0_S) & 0xff;
|
|
fdb->mac[1] = reg[1] & 0xff;
|
|
fdb->mac[2] = (reg[0] >> QCA8K_ATU_ADDR2_S) & 0xff;
|
|
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;
|
|
}
|
|
|
|
static void
|
|
qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
|
|
u8 aging)
|
|
{
|
|
u32 reg[3] = { 0 };
|
|
int i;
|
|
|
|
/* vid - 83:72 */
|
|
reg[2] = (vid & QCA8K_ATU_VID_M) << QCA8K_ATU_VID_S;
|
|
/* aging - 67:64 */
|
|
reg[2] |= aging & QCA8K_ATU_STATUS_M;
|
|
/* portmask - 54:48 */
|
|
reg[1] = (port_mask & QCA8K_ATU_PORT_M) << QCA8K_ATU_PORT_S;
|
|
/* mac - 47:0 */
|
|
reg[1] |= mac[0] << QCA8K_ATU_ADDR0_S;
|
|
reg[1] |= mac[1];
|
|
reg[0] |= mac[2] << QCA8K_ATU_ADDR2_S;
|
|
reg[0] |= mac[3] << QCA8K_ATU_ADDR3_S;
|
|
reg[0] |= mac[4] << QCA8K_ATU_ADDR4_S;
|
|
reg[0] |= mac[5];
|
|
|
|
/* load the array into the ARL table */
|
|
for (i = 0; i < 3; i++)
|
|
qca8k_write(priv, QCA8K_REG_ATU_DATA0 + (i * 4), reg[i]);
|
|
}
|
|
|
|
static int
|
|
qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
|
|
{
|
|
u32 reg;
|
|
|
|
/* Set the command and FDB index */
|
|
reg = QCA8K_ATU_FUNC_BUSY;
|
|
reg |= cmd;
|
|
if (port >= 0) {
|
|
reg |= QCA8K_ATU_FUNC_PORT_EN;
|
|
reg |= (port & QCA8K_ATU_FUNC_PORT_M) << QCA8K_ATU_FUNC_PORT_S;
|
|
}
|
|
|
|
/* Write the function register triggering the table access */
|
|
qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
|
|
|
|
/* wait for completion */
|
|
if (qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY))
|
|
return -1;
|
|
|
|
/* Check for table full violation when adding an entry */
|
|
if (cmd == QCA8K_FDB_LOAD) {
|
|
reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC);
|
|
if (reg & QCA8K_ATU_FUNC_FULL)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
|
|
{
|
|
int ret;
|
|
|
|
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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
|
|
u16 vid, u8 aging)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
qca8k_fdb_write(priv, vid, port_mask, mac, aging);
|
|
ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
qca8k_fdb_write(priv, vid, port_mask, mac, 0);
|
|
ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
qca8k_fdb_flush(struct qca8k_priv *priv)
|
|
{
|
|
mutex_lock(&priv->reg_mutex);
|
|
qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
|
|
mutex_unlock(&priv->reg_mutex);
|
|
}
|
|
|
|
static int
|
|
qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
|
|
{
|
|
u32 reg;
|
|
|
|
/* Set the command and VLAN index */
|
|
reg = QCA8K_VTU_FUNC1_BUSY;
|
|
reg |= cmd;
|
|
reg |= vid << QCA8K_VTU_FUNC1_VID_S;
|
|
|
|
/* Write the function register triggering the table access */
|
|
qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
|
|
|
|
/* wait for completion */
|
|
if (qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY))
|
|
return -ETIMEDOUT;
|
|
|
|
/* Check for table full violation when adding an entry */
|
|
if (cmd == QCA8K_VLAN_LOAD) {
|
|
reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC1);
|
|
if (reg & QCA8K_VTU_FUNC1_FULL)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged)
|
|
{
|
|
u32 reg;
|
|
int ret;
|
|
|
|
/*
|
|
We do the right thing with VLAN 0 and treat it as untagged while
|
|
preserving the tag on egress.
|
|
*/
|
|
if (vid == 0)
|
|
return 0;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0);
|
|
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)
|
|
reg |= QCA8K_VTU_FUNC0_EG_MODE_UNTAG <<
|
|
QCA8K_VTU_FUNC0_EG_MODE_S(port);
|
|
else
|
|
reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG <<
|
|
QCA8K_VTU_FUNC0_EG_MODE_S(port);
|
|
|
|
qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
|
|
ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
|
|
|
|
out:
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid)
|
|
{
|
|
u32 reg, mask;
|
|
int ret, i;
|
|
bool del;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0);
|
|
reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port));
|
|
reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT <<
|
|
QCA8K_VTU_FUNC0_EG_MODE_S(port);
|
|
|
|
/* Check if we're the last member to be removed */
|
|
del = true;
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
mask = QCA8K_VTU_FUNC0_EG_MODE_NOT;
|
|
mask <<= QCA8K_VTU_FUNC0_EG_MODE_S(i);
|
|
|
|
if ((reg & mask) != mask) {
|
|
del = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (del) {
|
|
ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
|
|
} else {
|
|
qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
|
|
ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
qca8k_mib_init(struct qca8k_priv *priv)
|
|
{
|
|
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);
|
|
mutex_unlock(&priv->reg_mutex);
|
|
}
|
|
|
|
static void
|
|
qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
|
|
{
|
|
u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
|
|
|
|
/* Port 0 and 6 have no internal PHY */
|
|
if (port > 0 && port < 6)
|
|
mask |= QCA8K_PORT_STATUS_LINK_AUTO;
|
|
|
|
if (enable)
|
|
qca8k_reg_set(priv, QCA8K_REG_PORT_STATUS(port), mask);
|
|
else
|
|
qca8k_reg_clear(priv, QCA8K_REG_PORT_STATUS(port), mask);
|
|
}
|
|
|
|
static u32
|
|
qca8k_port_to_phy(int port)
|
|
{
|
|
/* From Andrew Lunn:
|
|
* Port 0 has no internal phy.
|
|
* Port 1 has an internal PHY at MDIO address 0.
|
|
* Port 2 has an internal PHY at MDIO address 1.
|
|
* ...
|
|
* Port 5 has an internal PHY at MDIO address 4.
|
|
* Port 6 has no internal PHY.
|
|
*/
|
|
|
|
return port - 1;
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_write(struct qca8k_priv *priv, int port, u32 regnum, u16 data)
|
|
{
|
|
u32 phy, val;
|
|
|
|
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);
|
|
|
|
return qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_BUSY);
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_read(struct qca8k_priv *priv, int port, u32 regnum)
|
|
{
|
|
u32 phy, val;
|
|
|
|
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);
|
|
|
|
if (qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_BUSY))
|
|
return -ETIMEDOUT;
|
|
|
|
val = (qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL) &
|
|
QCA8K_MDIO_MASTER_DATA_MASK);
|
|
|
|
return val;
|
|
}
|
|
|
|
static int
|
|
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);
|
|
}
|
|
|
|
static int
|
|
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);
|
|
|
|
if (ret < 0)
|
|
return 0xffff;
|
|
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
int err;
|
|
|
|
ports = of_get_child_by_name(priv->dev->of_node, "ports");
|
|
if (!ports)
|
|
return -EINVAL;
|
|
|
|
for_each_available_child_of_node(ports, port) {
|
|
err = of_property_read_u32(port, "reg", ®);
|
|
if (err) {
|
|
of_node_put(port);
|
|
of_node_put(ports);
|
|
return err;
|
|
}
|
|
|
|
if (!dsa_is_user_port(priv->ds, reg))
|
|
continue;
|
|
|
|
if (of_property_read_bool(port, "phy-handle"))
|
|
external_mdio_mask |= BIT(reg);
|
|
else
|
|
internal_mdio_mask |= BIT(reg);
|
|
}
|
|
|
|
of_node_put(ports);
|
|
if (!external_mdio_mask && !internal_mdio_mask) {
|
|
dev_err(priv->dev, "no PHYs are defined.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
|
|
* the MDIO_MASTER register also _disconnects_ the external MDC
|
|
* passthrough to the internal PHYs. It's not possible to use both
|
|
* configurations at the same time!
|
|
*
|
|
* Because this came up during the review process:
|
|
* If the external mdio-bus driver is capable magically disabling
|
|
* the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
|
|
* accessors for the time being, it would be possible to pull this
|
|
* off.
|
|
*/
|
|
if (!!external_mdio_mask && !!internal_mdio_mask) {
|
|
dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (external_mdio_mask) {
|
|
/* Make sure to disable the internal mdio bus in cases
|
|
* a dt-overlay and driver reload changed the configuration
|
|
*/
|
|
|
|
qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_EN);
|
|
return 0;
|
|
}
|
|
|
|
priv->ops.phy_read = qca8k_phy_read;
|
|
priv->ops.phy_write = qca8k_phy_write;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_setup(struct dsa_switch *ds)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
int ret, i;
|
|
|
|
/* 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");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_init(&priv->reg_mutex);
|
|
|
|
/* Start by setting up the register mapping */
|
|
priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
|
|
&qca8k_regmap_config);
|
|
if (IS_ERR(priv->regmap))
|
|
pr_warn("regmap initialization failed");
|
|
|
|
ret = qca8k_setup_mdio_bus(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Enable CPU Port */
|
|
qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
|
|
QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
|
|
|
|
/* Enable MIB counters */
|
|
qca8k_mib_init(priv);
|
|
|
|
/* 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);
|
|
|
|
/* 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);
|
|
|
|
/* 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);
|
|
|
|
/* 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));
|
|
}
|
|
|
|
/* 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));
|
|
|
|
/* Enable ARP Auto-learning by default */
|
|
qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
QCA8K_PORT_LOOKUP_LEARN);
|
|
|
|
/* 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));
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Flush the FDB table */
|
|
qca8k_fdb_flush(priv);
|
|
|
|
/* We don't have interrupts for link changes, so we need to poll */
|
|
ds->pcs_poll = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
|
|
const struct phylink_link_state *state)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
u32 reg, val;
|
|
|
|
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_SGMII)
|
|
return;
|
|
|
|
reg = QCA8K_REG_PORT0_PAD_CTRL;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
/* Internal PHY, nothing to do */
|
|
return;
|
|
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_SGMII &&
|
|
state->interface != PHY_INTERFACE_MODE_1000BASEX)
|
|
return;
|
|
|
|
reg = QCA8K_REG_PORT6_PAD_CTRL;
|
|
break;
|
|
default:
|
|
dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
|
|
return;
|
|
}
|
|
|
|
if (port != 6 && phylink_autoneg_inband(mode)) {
|
|
dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
switch (state->interface) {
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
/* RGMII mode means no delay so don't enable the delay */
|
|
qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
|
|
break;
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
/* 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_RX_DELAY_EN);
|
|
break;
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
/* Enable SGMII on the port */
|
|
qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
|
|
|
|
/* Enable/disable SerDes auto-negotiation as necessary */
|
|
val = qca8k_read(priv, QCA8K_REG_PWS);
|
|
if (phylink_autoneg_inband(mode))
|
|
val &= ~QCA8K_PWS_SERDES_AEN_DIS;
|
|
else
|
|
val |= QCA8K_PWS_SERDES_AEN_DIS;
|
|
qca8k_write(priv, QCA8K_REG_PWS, val);
|
|
|
|
/* Configure the SGMII parameters */
|
|
val = qca8k_read(priv, QCA8K_REG_SGMII_CTRL);
|
|
|
|
val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
|
|
QCA8K_SGMII_EN_TX | QCA8K_SGMII_EN_SD;
|
|
|
|
if (dsa_is_cpu_port(ds, port)) {
|
|
/* CPU port, we're talking to the CPU MAC, be a PHY */
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_PHY;
|
|
} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_MAC;
|
|
} else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) {
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_BASEX;
|
|
}
|
|
|
|
qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
|
|
break;
|
|
default:
|
|
dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
|
|
phy_modes(state->interface), port);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_validate(struct dsa_switch *ds, int port,
|
|
unsigned long *supported,
|
|
struct phylink_link_state *state)
|
|
{
|
|
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
|
|
|
|
switch (port) {
|
|
case 0: /* 1st CPU 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_SGMII)
|
|
goto unsupported;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
/* Internal PHY */
|
|
if (state->interface != PHY_INTERFACE_MODE_NA &&
|
|
state->interface != PHY_INTERFACE_MODE_GMII)
|
|
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_SGMII &&
|
|
state->interface != PHY_INTERFACE_MODE_1000BASEX)
|
|
goto unsupported;
|
|
break;
|
|
default:
|
|
unsupported:
|
|
linkmode_zero(supported);
|
|
return;
|
|
}
|
|
|
|
phylink_set_port_modes(mask);
|
|
phylink_set(mask, Autoneg);
|
|
|
|
phylink_set(mask, 1000baseT_Full);
|
|
phylink_set(mask, 10baseT_Half);
|
|
phylink_set(mask, 10baseT_Full);
|
|
phylink_set(mask, 100baseT_Half);
|
|
phylink_set(mask, 100baseT_Full);
|
|
|
|
if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
|
|
phylink_set(mask, 1000baseX_Full);
|
|
|
|
phylink_set(mask, Pause);
|
|
phylink_set(mask, Asym_Pause);
|
|
|
|
linkmode_and(supported, supported, mask);
|
|
linkmode_and(state->advertising, state->advertising, mask);
|
|
}
|
|
|
|
static int
|
|
qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port,
|
|
struct phylink_link_state *state)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
u32 reg;
|
|
|
|
reg = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port));
|
|
|
|
state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
|
|
state->an_complete = state->link;
|
|
state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO);
|
|
state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
|
|
DUPLEX_HALF;
|
|
|
|
switch (reg & QCA8K_PORT_STATUS_SPEED) {
|
|
case QCA8K_PORT_STATUS_SPEED_10:
|
|
state->speed = SPEED_10;
|
|
break;
|
|
case QCA8K_PORT_STATUS_SPEED_100:
|
|
state->speed = SPEED_100;
|
|
break;
|
|
case QCA8K_PORT_STATUS_SPEED_1000:
|
|
state->speed = SPEED_1000;
|
|
break;
|
|
default:
|
|
state->speed = SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
state->pause = MLO_PAUSE_NONE;
|
|
if (reg & QCA8K_PORT_STATUS_RXFLOW)
|
|
state->pause |= MLO_PAUSE_RX;
|
|
if (reg & QCA8K_PORT_STATUS_TXFLOW)
|
|
state->pause |= MLO_PAUSE_TX;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
|
|
phy_interface_t interface)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
qca8k_port_set_status(priv, port, 0);
|
|
}
|
|
|
|
static void
|
|
qca8k_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 qca8k_priv *priv = ds->priv;
|
|
u32 reg;
|
|
|
|
if (phylink_autoneg_inband(mode)) {
|
|
reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
} else {
|
|
switch (speed) {
|
|
case SPEED_10:
|
|
reg = QCA8K_PORT_STATUS_SPEED_10;
|
|
break;
|
|
case SPEED_100:
|
|
reg = QCA8K_PORT_STATUS_SPEED_100;
|
|
break;
|
|
case SPEED_1000:
|
|
reg = QCA8K_PORT_STATUS_SPEED_1000;
|
|
break;
|
|
default:
|
|
reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
break;
|
|
}
|
|
|
|
if (duplex == DUPLEX_FULL)
|
|
reg |= QCA8K_PORT_STATUS_DUPLEX;
|
|
|
|
if (rx_pause || dsa_is_cpu_port(ds, port))
|
|
reg |= QCA8K_PORT_STATUS_RXFLOW;
|
|
|
|
if (tx_pause || dsa_is_cpu_port(ds, port))
|
|
reg |= QCA8K_PORT_STATUS_TXFLOW;
|
|
}
|
|
|
|
reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
|
|
|
|
qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
|
|
}
|
|
|
|
static void
|
|
qca8k_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(ar8327_mib); i++)
|
|
strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name,
|
|
ETH_GSTRING_LEN);
|
|
}
|
|
|
|
static void
|
|
qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
|
|
uint64_t *data)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
const struct qca8k_mib_desc *mib;
|
|
u32 reg, i;
|
|
u64 hi;
|
|
|
|
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);
|
|
if (mib->size == 2) {
|
|
hi = qca8k_read(priv, reg + 4);
|
|
data[i] |= hi << 32;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
|
|
{
|
|
if (sset != ETH_SS_STATS)
|
|
return 0;
|
|
|
|
return ARRAY_SIZE(ar8327_mib);
|
|
}
|
|
|
|
static int
|
|
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;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
reg = qca8k_read(priv, QCA8K_REG_EEE_CTRL);
|
|
if (eee->eee_enabled)
|
|
reg |= lpi_en;
|
|
else
|
|
reg &= ~lpi_en;
|
|
qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e)
|
|
{
|
|
/* Nothing to do on the port's MAC */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
u32 stp_state;
|
|
|
|
switch (state) {
|
|
case BR_STATE_DISABLED:
|
|
stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
|
|
break;
|
|
case BR_STATE_BLOCKING:
|
|
stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
|
|
break;
|
|
case BR_STATE_LISTENING:
|
|
stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
|
|
break;
|
|
case BR_STATE_LEARNING:
|
|
stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
|
|
break;
|
|
case BR_STATE_FORWARDING:
|
|
default:
|
|
stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
|
|
break;
|
|
}
|
|
|
|
qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
|
|
QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
|
|
}
|
|
|
|
static int
|
|
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;
|
|
|
|
for (i = 1; i < QCA8K_NUM_PORTS; i++) {
|
|
if (dsa_to_port(ds, i)->bridge_dev != br)
|
|
continue;
|
|
/* 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));
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *br)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
int i;
|
|
|
|
for (i = 1; i < QCA8K_NUM_PORTS; i++) {
|
|
if (dsa_to_port(ds, i)->bridge_dev != br)
|
|
continue;
|
|
/* Remove this port to the portvlan mask of the other ports
|
|
* in the bridge
|
|
*/
|
|
qca8k_reg_clear(priv,
|
|
QCA8K_PORT_LOOKUP_CTRL(i),
|
|
BIT(port));
|
|
}
|
|
|
|
/* Set the cpu port to be the only one in the portvlan mask of
|
|
* this port
|
|
*/
|
|
qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
|
|
QCA8K_PORT_LOOKUP_MEMBER, BIT(QCA8K_CPU_PORT));
|
|
}
|
|
|
|
static int
|
|
qca8k_port_enable(struct dsa_switch *ds, int port,
|
|
struct phy_device *phy)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
|
|
qca8k_port_set_status(priv, port, 1);
|
|
priv->port_sts[port].enabled = 1;
|
|
|
|
if (dsa_is_user_port(ds, port))
|
|
phy_support_asym_pause(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_port_disable(struct dsa_switch *ds, int port)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
|
|
qca8k_port_set_status(priv, port, 0);
|
|
priv->port_sts[port].enabled = 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
int i, mtu = 0;
|
|
|
|
priv->port_mtu[port] = new_mtu;
|
|
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++)
|
|
if (priv->port_mtu[i] > 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;
|
|
}
|
|
|
|
static int
|
|
qca8k_port_max_mtu(struct dsa_switch *ds, int port)
|
|
{
|
|
return QCA8K_MAX_MTU;
|
|
}
|
|
|
|
static int
|
|
qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
|
|
u16 port_mask, u16 vid)
|
|
{
|
|
/* Set the vid to the port vlan id if no vid is set */
|
|
if (!vid)
|
|
vid = QCA8K_PORT_VID_DEF;
|
|
|
|
return qca8k_fdb_add(priv, addr, port_mask, vid,
|
|
QCA8K_ATU_STATUS_STATIC);
|
|
}
|
|
|
|
static int
|
|
qca8k_port_fdb_add(struct dsa_switch *ds, int port,
|
|
const unsigned char *addr, u16 vid)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
u16 port_mask = BIT(port);
|
|
|
|
return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
|
|
}
|
|
|
|
static int
|
|
qca8k_port_fdb_del(struct dsa_switch *ds, int port,
|
|
const unsigned char *addr, u16 vid)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
u16 port_mask = BIT(port);
|
|
|
|
if (!vid)
|
|
vid = QCA8K_PORT_VID_DEF;
|
|
|
|
return qca8k_fdb_del(priv, addr, port_mask, vid);
|
|
}
|
|
|
|
static int
|
|
qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
|
|
dsa_fdb_dump_cb_t *cb, void *data)
|
|
{
|
|
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
struct qca8k_fdb _fdb = { 0 };
|
|
int cnt = QCA8K_NUM_FDB_RECORDS;
|
|
bool is_static;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
|
|
if (!_fdb.aging)
|
|
break;
|
|
is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
|
|
ret = cb(_fdb.mac, _fdb.vid, is_static, data);
|
|
if (ret)
|
|
break;
|
|
}
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
|
|
struct switchdev_trans *trans)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
if (switchdev_trans_ph_prepare(trans))
|
|
return 0;
|
|
|
|
if (vlan_filtering) {
|
|
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);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_port_vlan_prepare(struct dsa_switch *ds, int port,
|
|
const struct switchdev_obj_port_vlan *vlan)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_port_vlan_add(struct dsa_switch *ds, int port,
|
|
const struct switchdev_obj_port_vlan *vlan)
|
|
{
|
|
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;
|
|
|
|
ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret);
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
static int
|
|
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;
|
|
|
|
ret = qca8k_vlan_del(priv, port, vlan->vid);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum dsa_tag_protocol
|
|
qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
|
|
enum dsa_tag_protocol mp)
|
|
{
|
|
return DSA_TAG_PROTO_QCA;
|
|
}
|
|
|
|
static const struct dsa_switch_ops qca8k_switch_ops = {
|
|
.get_tag_protocol = qca8k_get_tag_protocol,
|
|
.setup = qca8k_setup,
|
|
.get_strings = qca8k_get_strings,
|
|
.get_ethtool_stats = qca8k_get_ethtool_stats,
|
|
.get_sset_count = qca8k_get_sset_count,
|
|
.get_mac_eee = qca8k_get_mac_eee,
|
|
.set_mac_eee = qca8k_set_mac_eee,
|
|
.port_enable = qca8k_port_enable,
|
|
.port_disable = qca8k_port_disable,
|
|
.port_change_mtu = qca8k_port_change_mtu,
|
|
.port_max_mtu = qca8k_port_max_mtu,
|
|
.port_stp_state_set = qca8k_port_stp_state_set,
|
|
.port_bridge_join = qca8k_port_bridge_join,
|
|
.port_bridge_leave = qca8k_port_bridge_leave,
|
|
.port_fdb_add = qca8k_port_fdb_add,
|
|
.port_fdb_del = qca8k_port_fdb_del,
|
|
.port_fdb_dump = qca8k_port_fdb_dump,
|
|
.port_vlan_filtering = qca8k_port_vlan_filtering,
|
|
.port_vlan_prepare = qca8k_port_vlan_prepare,
|
|
.port_vlan_add = qca8k_port_vlan_add,
|
|
.port_vlan_del = qca8k_port_vlan_del,
|
|
.phylink_validate = qca8k_phylink_validate,
|
|
.phylink_mac_link_state = qca8k_phylink_mac_link_state,
|
|
.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,
|
|
};
|
|
|
|
static int
|
|
qca8k_sw_probe(struct mdio_device *mdiodev)
|
|
{
|
|
struct qca8k_priv *priv;
|
|
u32 id;
|
|
|
|
/* allocate the private data struct so that we can probe the switches
|
|
* ID register
|
|
*/
|
|
priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->bus = mdiodev->bus;
|
|
priv->dev = &mdiodev->dev;
|
|
|
|
priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
|
|
GPIOD_ASIS);
|
|
if (IS_ERR(priv->reset_gpio))
|
|
return PTR_ERR(priv->reset_gpio);
|
|
|
|
if (priv->reset_gpio) {
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
|
/* The active low duration must be greater than 10 ms
|
|
* and checkpatch.pl wants 20 ms.
|
|
*/
|
|
msleep(20);
|
|
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;
|
|
|
|
priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
|
|
if (!priv->ds)
|
|
return -ENOMEM;
|
|
|
|
priv->ds->dev = &mdiodev->dev;
|
|
priv->ds->num_ports = QCA8K_NUM_PORTS;
|
|
priv->ds->configure_vlan_while_not_filtering = true;
|
|
priv->ds->priv = priv;
|
|
priv->ops = qca8k_switch_ops;
|
|
priv->ds->ops = &priv->ops;
|
|
mutex_init(&priv->reg_mutex);
|
|
dev_set_drvdata(&mdiodev->dev, priv);
|
|
|
|
return dsa_register_switch(priv->ds);
|
|
}
|
|
|
|
static void
|
|
qca8k_sw_remove(struct mdio_device *mdiodev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
int i;
|
|
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++)
|
|
qca8k_port_set_status(priv, i, 0);
|
|
|
|
dsa_unregister_switch(priv->ds);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static void
|
|
qca8k_set_pm(struct qca8k_priv *priv, int enable)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
if (!priv->port_sts[i].enabled)
|
|
continue;
|
|
|
|
qca8k_port_set_status(priv, i, enable);
|
|
}
|
|
}
|
|
|
|
static int qca8k_suspend(struct device *dev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(dev);
|
|
|
|
qca8k_set_pm(priv, 0);
|
|
|
|
return dsa_switch_suspend(priv->ds);
|
|
}
|
|
|
|
static int qca8k_resume(struct device *dev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(dev);
|
|
|
|
qca8k_set_pm(priv, 1);
|
|
|
|
return dsa_switch_resume(priv->ds);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
|
|
qca8k_suspend, qca8k_resume);
|
|
|
|
static const struct of_device_id qca8k_of_match[] = {
|
|
{ .compatible = "qca,qca8334" },
|
|
{ .compatible = "qca,qca8337" },
|
|
{ /* sentinel */ },
|
|
};
|
|
|
|
static struct mdio_driver qca8kmdio_driver = {
|
|
.probe = qca8k_sw_probe,
|
|
.remove = qca8k_sw_remove,
|
|
.mdiodrv.driver = {
|
|
.name = "qca8k",
|
|
.of_match_table = qca8k_of_match,
|
|
.pm = &qca8k_pm_ops,
|
|
},
|
|
};
|
|
|
|
mdio_module_driver(qca8kmdio_driver);
|
|
|
|
MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
|
|
MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:qca8k");
|