Skip to content

Instantly share code, notes, and snippets.

@Ansuel

Ansuel/qca8k.c Secret

Created October 10, 2021 01:40
Show Gist options
  • Save Ansuel/b0cac703a875e2af80364cf1ee2209a1 to your computer and use it in GitHub Desktop.
Save Ansuel/b0cac703a875e2af80364cf1ee2209a1 to your computer and use it in GitHub Desktop.
// 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_mdio.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 <linux/leds.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 int
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");
*val = 0;
return ret;
}
return 0;
}
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 int
qca8k_set_page(struct mii_bus *bus, u16 page)
{
int ret;
if (page == qca8k_current_page)
return 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 int
qca8k_read(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(&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);
exit:
mutex_unlock(&bus->mdio_lock);
return ret;
}
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(&bus->mdio_lock, MDIO_MUTEX_NESTED);
ret = qca8k_set_page(bus, page);
if (ret < 0)
goto exit;
qca8k_mii_write32(bus, 0x10 | r2, r1, val);
exit:
mutex_unlock(&bus->mdio_lock);
return ret;
}
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 val;
int ret;
qca8k_split_addr(reg, &r1, &r2, &page);
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;
val &= ~mask;
val |= write_val;
qca8k_mii_write32(bus, 0x10 | r2, r1, val);
exit:
mutex_unlock(&bus->mdio_lock);
return ret;
}
static int
qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val)
{
return qca8k_rmw(priv, reg, 0, val);
}
static int
qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val)
{
return 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;
return qca8k_read(priv, reg, val);
}
static int
qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val)
{
struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
return qca8k_write(priv, reg, val);
}
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)
{
int ret, ret1;
u32 val;
ret = read_poll_timeout(qca8k_read, ret1, !(val & mask),
0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
priv, reg, &val);
/* Check if qca8k_read has failed for a different reason
* before returning -ETIMEDOUT
*/
if (ret < 0 && ret1 < 0)
return ret1;
return ret;
}
static int
qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
{
u32 reg[4], val;
int i, ret;
/* load the ARL table into an array */
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;
/* 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;
return 0;
}
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;
int ret;
/* 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 */
ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
if (ret)
return ret;
/* wait for completion */
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) {
ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, &reg);
if (ret < 0)
return ret;
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)
return ret;
return qca8k_fdb_read(priv, fdb);
}
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;
int ret;
/* 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 */
ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
if (ret)
return ret;
/* wait for completion */
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) {
ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, &reg);
if (ret < 0)
return ret;
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;
ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
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)
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);
ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
if (ret)
goto out;
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;
ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, &reg);
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);
/* 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 {
ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
if (ret)
goto out;
ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
}
out:
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int
qca8k_mib_init(struct qca8k_priv *priv)
{
int ret;
mutex_lock(&priv->reg_mutex);
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
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_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
{
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 *bus, int phy, int regnum, u16 data)
{
u16 r1, r2, page;
u32 val;
int ret;
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
return -EINVAL;
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_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);
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 mii_bus *bus, int phy, int regnum)
{
u16 r1, r2, page;
u32 val;
int ret;
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
return -EINVAL;
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_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);
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);
mutex_unlock(&bus->mdio_lock);
if (ret >= 0)
ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
return ret;
}
static int
qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
{
struct qca8k_priv *priv = slave_bus->priv;
struct mii_bus *bus = priv->bus;
return qca8k_mdio_write(bus, phy, regnum, data);
}
static int
qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
{
struct qca8k_priv *priv = slave_bus->priv;
struct mii_bus *bus = priv->bus;
return qca8k_mdio_read(bus, phy, regnum);
}
static int
qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
{
struct qca8k_priv *priv = ds->priv;
/* 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
qca8k_phy_read(struct dsa_switch *ds, int port, int regnum)
{
struct qca8k_priv *priv = ds->priv;
int ret;
/* 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;
return ret;
}
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_internal_mdio_read;
bus->write = qca8k_internal_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, *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) {
err = of_property_read_u32(port, "reg", &reg);
if (err) {
of_node_put(port);
of_node_put(ports);
return err;
}
if (!dsa_is_user_port(priv->ds, reg))
continue;
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);
}
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
*/
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_mac_pwr_sel(struct qca8k_priv *priv)
{
u32 mask = 0;
int ret = 0;
/* SoC specific settings for ipq8064.
* If more device require this consider adding
* a dedicated binding.
*/
if (of_machine_is_compatible("qcom,ipq8064"))
mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
/* SoC specific settings for ipq8065 */
if (of_machine_is_compatible("qcom,ipq8065"))
mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
if (mask) {
ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
QCA8K_MAC_PWR_RGMII0_1_8V |
QCA8K_MAC_PWR_RGMII1_1_8V,
mask);
}
return ret;
}
static int qca8k_find_cpu_port(struct dsa_switch *ds)
{
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
/* Find the connected cpu port. Valid port are 0 or 6 */
if (dsa_is_cpu_port(ds, 0))
return 0;
dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
if (dsa_is_cpu_port(ds, 6))
return 6;
return -EINVAL;
}
static int
qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
{
struct device_node *node = priv->dev->of_node;
const struct qca8k_match_data *data;
u32 val = 0;
int ret;
/* QCA8327 require to set to the correct mode.
* His bigger brother QCA8328 have the 172 pin layout.
* Should be applied by default but we set this just to make sure.
*/
if (priv->switch_id == QCA8K_ID_QCA8327) {
data = of_device_get_match_data(priv->dev);
/* Set the correct package of 148 pin for QCA8327 */
if (data->reduced_package)
val |= QCA8327_PWS_PACKAGE148_EN;
ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
val);
if (ret)
return ret;
}
if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
val |= QCA8K_PWS_POWER_ON_SEL;
if (of_property_read_bool(node, "qca,led-open-drain")) {
if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
return -EINVAL;
}
val |= QCA8K_PWS_LED_OPEN_EN_CSR;
}
return qca8k_rmw(priv, QCA8K_REG_PWS,
QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
val);
}
static int
qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
{
int shift;
switch (port_num) {
case 0:
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = 14;
break;
case 1:
case 2:
case 3:
reg_info->reg = QCA8K_LED_CTRL_REG(3);
shift = 2 * led_num + (6 * (port_num - 1));
reg_info->shift = 8 + shift;
break;
case 4:
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = 30;
break;
default:
return -EINVAL;
}
return 0;
}
static int
qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
{
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
/* 6 total control rule:
* 3 control rules for phy0-3 that applies to all their leds
* 3 control rules for phy4
*/
if (port_num == 4)
reg_info->shift = 16;
else
reg_info->shift = 0;
return 0;
}
static int
qca8k_setup_led_rules(struct qca8k_led *led, struct fwnode_handle *node)
{
struct qca8k_led_pattern_en reg_info;
const char **rules;
int i, count, ret;
const char *rule;
u32 val = 0;
if (!fwnode_property_present(node, "qca,led_rules"))
return 0;
rules = kcalloc(QCA8K_LED_RULE_MAX, sizeof(*rules), GFP_KERNEL);
if (!rules)
return -ENOMEM;
ret = fwnode_property_read_string_array(node, "qca,led_rules", rules, QCA8K_LED_RULE_MAX);
if (ret < 0)
return ret;
count = (unsigned int)ret;
for (i = 0; i < count; i++) {
rule = rules[i];
if (!strcmp(rule, "tx-blink"))
val |= QCA8K_LED_TX_BLINK_MASK;
if (!strcmp(rule, "rx-blink"))
val |= QCA8K_LED_RX_BLINK_MASK;
if (!strcmp(rule, "collision-blink"))
val |= QCA8K_LED_COL_BLINK_MASK;
if (!strcmp(rule, "link-10M"))
val |= QCA8K_LED_LINK_10M_EN_MASK;
if (!strcmp(rule, "link-100M"))
val |= QCA8K_LED_LINK_100M_EN_MASK;
if (!strcmp(rule, "link-1000M"))
val |= QCA8K_LED_LINK_1000M_EN_MASK;
if (!strcmp(rule, "half-duplex"))
val |= QCA8K_LED_HALF_DUPLEX_MASK;
if (!strcmp(rule, "full-duplex"))
val |= QCA8K_LED_FULL_DUPLEX_MASK;
if (!strcmp(rule, "linkup-over"))
val |= QCA8K_LED_LINKUP_OVER_MASK;
if (!strcmp(rule, "power-on-reset"))
val |= QCA8K_LED_POWER_ON_LIGHT_MASK;
if (!(val & QCA8K_LED_BLINK_FREQ_MASK)) {
if (!strcmp(rule, "blink-2hz"))
val |= QCA8K_LED_BLINK_2HZ << QCA8K_LED_BLINK_FREQ_SHITF;
else if (!strcmp(rule, "blink-4hz"))
val |= QCA8K_LED_BLINK_4HZ << QCA8K_LED_BLINK_FREQ_SHITF;
else if (!strcmp(rule, "blink-8hz"))
val |= QCA8K_LED_BLINK_8HZ << QCA8K_LED_BLINK_FREQ_SHITF;
else if (!strcmp(rule, "blink-auto"))
val |= QCA8K_LED_BLINK_AUTO << QCA8K_LED_BLINK_FREQ_SHITF;
}
}
kfree(rules);
qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
ret = qca8k_rmw(led->priv, reg_info.reg,
QCA8K_LED_CTRL_MASK << reg_info.shift,
val << reg_info.shift);
return ret;
}
static ssize_t
control_rule_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qca8k_led *led = container_of(led_cdev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
u32 value;
int ret;
qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
ret = qca8k_read(led->priv, reg_info.reg, &value);
if (ret)
return sprintf(buf, "Error reading control rule\n");
value >>= reg_info.shift;
value &= QCA8K_LED_CTRL_MASK;
return sprintf(buf, "%x\n", value);
}
static ssize_t
control_rule_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qca8k_led *led = container_of(led_cdev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
ssize_t status;
long value;
int ret;
status = kstrtol(buf, 0, &value);
if (status)
return status;
if (value < 0)
return -EINVAL;
qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
value &= QCA8K_LED_CTRL_MASK;
ret = qca8k_rmw(led->priv, reg_info.reg,
QCA8K_LED_CTRL_MASK << reg_info.shift,
value << reg_info.shift);
if (ret)
return ret;
return size;
}
static ssize_t
hw_mode_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qca8k_led *led = container_of(led_cdev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
u32 value;
int ret;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
ret = qca8k_read(led->priv, reg_info.reg, &value);
if (ret)
return sprintf(buf, "Error reading hw mode\n");
value >>= reg_info.shift;
value &= GENMASK(1, 0);
return sprintf(buf, "%x\n", value == QCA8K_LED_RULE_CONTROLLED ? 1 : 0);
}
static ssize_t
hw_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qca8k_led *led = container_of(led_cdev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
ssize_t status;
long value;
int ret;
status = kstrtol(buf, 0, &value);
if (status)
return status;
if (value < 0)
return -EINVAL;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
if (value)
value = QCA8K_LED_RULE_CONTROLLED;
value &= GENMASK(1, 0);
ret = qca8k_rmw(led->priv, reg_info.reg,
GENMASK(1, 0) << reg_info.shift,
value << reg_info.shift);
if (ret)
return ret;
return size;
}
static DEVICE_ATTR_RW(control_rule);
static DEVICE_ATTR_RW(hw_mode);
/* Each led have a enable hw_mode and optionally a way to set control rule */
static struct attribute *qca8k_leds_attrs[] = {
&dev_attr_hw_mode.attr,
&dev_attr_control_rule.attr,
NULL,
};
static struct attribute_group qca8k_leds_rule_group = {
.attrs = qca8k_leds_attrs,
};
static const struct attribute_group *qca8k_leds_groups[] = {
&qca8k_leds_rule_group,
NULL,
};
static void
qca8k_led_brightness_set(struct qca8k_led *led,
enum led_brightness b)
{
struct qca8k_led_pattern_en reg_info;
u32 val = QCA8K_LED_ALWAYS_OFF;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
if (b)
val = QCA8K_LED_ALWAYS_ON;
qca8k_rmw(led->priv, reg_info.reg,
GENMASK(1, 0) << reg_info.shift,
val << reg_info.shift);
}
static void
qca8k_cled_brightness_set(struct led_classdev *ldev,
enum led_brightness b)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
return qca8k_led_brightness_set(led, b);
}
static enum led_brightness
qca8k_led_brightness_get(struct qca8k_led *led)
{
struct qca8k_led_pattern_en reg_info;
u32 val;
int ret;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
ret = qca8k_read(led->priv, reg_info.reg, &val);
if (ret)
return 0;
val >>= reg_info.shift;
val &= GENMASK(1, 0);
return val > 0 ? 1 : 0;
}
static enum led_brightness
qca8k_cled_brightness_get(struct led_classdev *ldev)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
return qca8k_led_brightness_get(led);
}
static int
qca8k_cled_blink_set(struct led_classdev *ldev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
struct qca8k_led_pattern_en reg_info;
if (*delay_on == 0 && *delay_off == 0) {
*delay_on = 125;
*delay_off = 125;
}
if (*delay_on != 125 || *delay_off != 125) {
/* The hardware only supports blinking at 4Hz. Fall back
* to software implementation in other cases.
*/
return -EINVAL;
}
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
qca8k_rmw(led->priv, reg_info.reg,
GENMASK(1, 0) << reg_info.shift,
QCA8K_LED_ALWAYS_BLINK_4HZ << reg_info.shift);
return 0;
}
static int
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
{
struct led_init_data init_data = { };
struct fwnode_handle *led = NULL;
struct qca8k_led *port_led;
int led_num, port_index;
const char *state;
int ret;
fwnode_for_each_child_node(port, led) {
/* Reg rapresent the led number of the port.
* Each port can have at least 3 leds attached
* Commonly:
* 1. is gigabit led
* 2. is mbit led
* 3. additional status led
*/
if (fwnode_property_read_u32(led, "reg", &led_num))
continue;
if (led_num >= QCA8K_LED_PORT_COUNT) {
dev_warn(priv->dev, "Invalid LED reg defined %d", port_num);
continue;
}
port_index = 3 * port_num + led_num;
port_led = &priv->ports_led[port_index];
port_led->port_num = port_num;
port_led->led_num = led_num;
port_led->priv = priv;
ret = fwnode_property_read_string(led, "default-state", &state);
if (!ret) {
if (!strcmp(state, "on")) {
port_led->cdev.brightness = 1;
qca8k_led_brightness_set(port_led, 1);
} else if (!strcmp(state, "off")) {
port_led->cdev.brightness = 0;
qca8k_led_brightness_set(port_led, 0);
} else if (!strcmp(state, "keep")) {
port_led->cdev.brightness =
qca8k_led_brightness_get(port_led);
}
}
/* 3 brightness settings can be applied from Documentation:
* 0 always off
* 1 blink at 4Hz
* 2 always on
* 3 rule controlled
* Suppots only 2 mode: (pcb limitation, with always on and blink
* only the last led is set to this mode)
* 0 always off (sets all leds off)
* 3 rule controlled
*/
port_led->cdev.max_brightness = 1;
port_led->cdev.brightness_set = qca8k_cled_brightness_set;
port_led->cdev.brightness_get = qca8k_cled_brightness_get;
port_led->cdev.blink_set = qca8k_cled_blink_set;
port_led->cdev.groups = qca8k_leds_groups;
init_data.default_label = ":port";
init_data.devicename = "qca8k";
init_data.fwnode = led;
/* Provide control rule first lan port and wan port.
* Lan port 2-3-4 follow first lan port control rule if the hw mode
* is active.
* The control_rule sysfs refer to the same reg for lan port (phy0-3)
*/
ret = qca8k_setup_led_rules(port_led, led);
if (ret)
dev_warn(priv->dev, "Failed to apply led control rules for %s",
port_led->cdev.name);
ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
if (ret)
dev_warn(priv->dev, "Failed to int led");
}
return 0;
}
static int
qca8k_setup_led_ctrl(struct qca8k_priv *priv)
{
struct fwnode_handle *leds, *port;
int port_num;
int ret;
leds = device_get_named_child_node(priv->dev, "leds");
if (!leds) {
dev_info(priv->dev, "No LEDs specified in device tree!\n");
return 0;
}
fwnode_for_each_child_node(leds, port) {
if (fwnode_property_read_u32(port, "reg", &port_num))
continue;
/* Each port can have at least 3 different leds attached */
ret = qca8k_parse_port_leds(priv, port, port_num);
if (ret)
return ret;
}
return 0;
}
static int
qca8k_setup(struct dsa_switch *ds)
{
struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
u8 cpu_port;
int ret, i;
u32 mask;
cpu_port = qca8k_find_cpu_port(ds);
if (cpu_port < 0) {
dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
return cpu_port;
}
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))
dev_warn(priv->dev, "regmap initialization failed");
ret = qca8k_setup_mdio_bus(priv);
if (ret)
return ret;
ret = qca8k_setup_of_pws_reg(priv);
if (ret)
return ret;
ret = qca8k_setup_mac_pwr_sel(priv);
if (ret)
return ret;
ret = qca8k_setup_led_ctrl(priv);
if (ret)
return ret;
/* Enable CPU Port */
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 */
ret = qca8k_mib_init(priv);
if (ret)
dev_warn(priv->dev, "mib init failed");
/* Enable QCA header mode on the cpu port */
ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(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++) {
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 */
ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
BIT(cpu_port) << 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)) {
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(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);
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
QCA8K_PORT_LOOKUP_MEMBER,
BIT(cpu_port));
if (ret)
return ret;
/* Enable ARP Auto-learning by default */
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
*/
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;
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);
/* We don't have interrupts for link changes, so we need to poll */
ds->pcs_poll = true;
return 0;
}
static void
qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, struct dsa_port *dp,
u32 reg, const struct phylink_link_state *state)
{
u32 delay, val = 0;
int ret;
if (state->interface == PHY_INTERFACE_MODE_RGMII_ID ||
state->interface == PHY_INTERFACE_MODE_RGMII_TXID ||
state->interface == PHY_INTERFACE_MODE_SGMII) {
if (of_property_read_u32(dp->dn, "tx-internal-delay-ps", &delay))
delay = 1;
else
/* Switch regs accept value in ns, convert ps to ns */
delay = delay / 1000;
if (delay > QCA8K_MAX_DELAY) {
dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
delay = 3;
}
val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
}
if (state->interface == PHY_INTERFACE_MODE_RGMII_ID ||
state->interface == PHY_INTERFACE_MODE_RGMII_RXID ||
state->interface == PHY_INTERFACE_MODE_SGMII) {
if (of_property_read_u32(dp->dn, "rx-internal-delay-ps", &delay))
delay = 2;
else
/* Switch regs accept value in ns, convert ps to ns */
delay = delay / 1000;
if (delay > QCA8K_MAX_DELAY) {
dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
delay = 3;
}
val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
}
/* Set RGMII delay based on the selected values */
ret = qca8k_rmw(priv, reg,
QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
val);
if (ret)
dev_err(priv->dev, "Failed to set internal delay for CPU port %d", dp->index);
}
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;
struct dsa_port *dp;
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;
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_RGMII_TXID &&
state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
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:
case PHY_INTERFACE_MODE_RGMII_TXID:
case PHY_INTERFACE_MODE_RGMII_RXID:
dp = dsa_to_port(ds, port);
qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
/* Configure rgmii delay from dp or taking advised values */
qca8k_mac_config_setup_internal_delay(priv, dp, reg, state);
/* QCA8337 requires to set rgmii rx delay for all ports.
* This is enabled through PORT5_PAD_CTRL for all ports,
* rather than individual port registers.
*/
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:
dp = dsa_to_port(ds, port);
/* Enable SGMII on the port */
qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
/* Enable/disable SerDes auto-negotiation as necessary */
ret = qca8k_read(priv, QCA8K_REG_PWS, &val);
if (ret)
return;
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 */
ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
if (ret)
return;
val |= QCA8K_SGMII_EN_SD;
if (of_property_read_bool(dp->dn, "qca,sgmii-enable-pll")) {
if (priv->switch_id == QCA8K_ID_QCA8327) {
dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
return;
}
if (priv->switch_revision < 2)
dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
QCA8K_SGMII_EN_TX;
}
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);
/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
* falling edge is set writing in the PORT0 PAD reg
*/
if (priv->switch_id == PHY_ID_QCA8327 ||
priv->switch_id == PHY_ID_QCA8337)
reg = QCA8K_REG_PORT0_PAD_CTRL;
val = 0;
/* SGMII Clock phase configuration */
if (of_property_read_bool(dp->dn, "qca,sgmii-rxclk-falling-edge"))
val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
if (of_property_read_bool(dp->dn, "qca,sgmii-txclk-falling-edge"))
val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
if (val)
ret = qca8k_rmw(priv, reg,
QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
val);
/* From original code is reported port instability as SGMII also
* require delay set. Apply advised values here or take them from DT.
*/
if (state->interface == PHY_INTERFACE_MODE_SGMII)
qca8k_mac_config_setup_internal_delay(priv, dp, reg, state);
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_RGMII_TXID &&
state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
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 &&
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;
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;
int ret;
ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
if (ret < 0)
return ret;
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, 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;
ret = qca8k_read(priv, reg, &val);
if (ret < 0)
continue;
if (mib->size == 2) {
ret = qca8k_read(priv, reg + 4, &hi);
if (ret < 0)
continue;
}
data[i] = val;
if (mib->size == 2)
data[i] |= (u64)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;
int ret;
mutex_lock(&priv->reg_mutex);
ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
if (ret < 0)
goto exit;
if (eee->eee_enabled)
reg |= lpi_en;
else
reg &= ~lpi_en;
ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
exit:
mutex_unlock(&priv->reg_mutex);
return ret;
}
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, cpu_port;
int i, ret;
cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
port_mask = BIT(cpu_port);
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
*/
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 */
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
QCA8K_PORT_LOOKUP_MEMBER, port_mask);
return ret;
}
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 cpu_port, i;
cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
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(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 */
return qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN);
}
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;
u16 vid;
for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid)
ret = qca8k_vlan_add(priv, port, 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_end << shift);
qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port),
QCA8K_PORT_VLAN_CVID(vlan->vid_end) |
QCA8K_PORT_VLAN_SVID(vlan->vid_end));
}
}
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;
u16 vid;
for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid)
ret = qca8k_vlan_del(priv, port, vid);
if (ret)
dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret);
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)
{
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,
.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;
int ret;
/* 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);
}
/* 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)
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 qca8k_match_data qca8327 = {
.id = QCA8K_ID_QCA8327,
.reduced_package = true,
};
static const struct qca8k_match_data qca8328 = {
.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,qca8327", .data = &qca8327 },
{ .compatible = "qca,qca8328", .data = &qca8328 },
{ .compatible = "qca,qca8334", .data = &qca833x },
{ .compatible = "qca,qca8337", .data = &qca833x },
{ /* 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");
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
#ifndef __QCA8K_H
#define __QCA8K_H
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/gpio.h>
#define QCA8K_NUM_PORTS 7
#define QCA8K_MAX_MTU 9000
#define PHY_ID_QCA8327 0x004dd034
#define QCA8K_ID_QCA8327 0x12
#define PHY_ID_QCA8337 0x004dd036
#define QCA8K_ID_QCA8337 0x13
#define QCA8K_BUSY_WAIT_TIMEOUT 2000
#define QCA8K_NUM_FDB_RECORDS 2048
#define QCA8K_PORT_VID_DEF 1
/* Global control registers */
#define QCA8K_REG_MASK_CTRL 0x000
#define QCA8K_MASK_CTRL_REV_ID_MASK GENMASK(7, 0)
#define QCA8K_MASK_CTRL_REV_ID(x) ((x) >> 0)
#define QCA8K_MASK_CTRL_DEVICE_ID_MASK GENMASK(15, 8)
#define QCA8K_MASK_CTRL_DEVICE_ID(x) ((x) >> 8)
#define QCA8K_REG_PORT0_PAD_CTRL 0x004
#define QCA8K_PORT0_PAD_CTRL_MAC06_EXCHG BIT(31)
#define QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE BIT(19)
#define QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE BIT(18)
#define QCA8K_REG_PORT5_PAD_CTRL 0x008
#define QCA8K_REG_PORT6_PAD_CTRL 0x00c
#define QCA8K_PORT_PAD_RGMII_EN BIT(26)
#define QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK GENMASK(23, 22)
#define QCA8K_PORT_PAD_RGMII_TX_DELAY(x) ((x) << 22)
#define QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK GENMASK(21, 20)
#define QCA8K_PORT_PAD_RGMII_RX_DELAY(x) ((x) << 20)
#define QCA8K_PORT_PAD_RGMII_TX_DELAY_EN BIT(25)
#define QCA8K_PORT_PAD_RGMII_RX_DELAY_EN BIT(24)
#define QCA8K_MAX_DELAY 3
#define QCA8K_PORT_PAD_SGMII_EN BIT(7)
#define QCA8K_REG_PWS 0x010
#define QCA8K_PWS_POWER_ON_SEL BIT(31)
/* This reg is only valid for QCA832x and toggle the package
* type from 176 pin (by default) to 148 pin used on QCA8327
*/
#define QCA8327_PWS_PACKAGE148_EN BIT(30)
#define QCA8K_PWS_LED_OPEN_EN_CSR BIT(24)
#define QCA8K_PWS_SERDES_AEN_DIS BIT(7)
#define QCA8K_REG_MODULE_EN 0x030
#define QCA8K_MODULE_EN_MIB BIT(0)
#define QCA8K_REG_MIB 0x034
#define QCA8K_MIB_FLUSH BIT(24)
#define QCA8K_MIB_CPU_KEEP BIT(20)
#define QCA8K_MIB_BUSY BIT(17)
#define QCA8K_MDIO_MASTER_CTRL 0x3c
#define QCA8K_MDIO_MASTER_BUSY BIT(31)
#define QCA8K_MDIO_MASTER_EN BIT(30)
#define QCA8K_MDIO_MASTER_READ BIT(27)
#define QCA8K_MDIO_MASTER_WRITE 0
#define QCA8K_MDIO_MASTER_SUP_PRE BIT(26)
#define QCA8K_MDIO_MASTER_PHY_ADDR(x) ((x) << 21)
#define QCA8K_MDIO_MASTER_REG_ADDR(x) ((x) << 16)
#define QCA8K_MDIO_MASTER_DATA(x) (x)
#define QCA8K_MDIO_MASTER_DATA_MASK GENMASK(15, 0)
#define QCA8K_MDIO_MASTER_MAX_PORTS 5
#define QCA8K_MDIO_MASTER_MAX_REG 32
/* LED control register */
#define QCA8K_LED_COUNT 15
#define QCA8K_LED_PORT_COUNT 3
#define QCA8K_LED_RULE_COUNT 6
#define QCA8K_LED_RULE_MAX 11
#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4)
#define QCA8K_LED_CTRL0_REG 0x50
#define QCA8K_LED_CTRL1_REG 0x54
#define QCA8K_LED_CTRL2_REG 0x58
#define QCA8K_LED_CTRL3_REG 0x5C
#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16)
#define QCA8K_LED_CTRL_MASK GENMASK(15, 0)
#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0)
#define QCA8K_LED_BLINK_FREQ_SHITF 0
#define QCA8K_LED_BLINK_2HZ 0
#define QCA8K_LED_BLINK_4HZ 1
#define QCA8K_LED_BLINK_8HZ 2
#define QCA8K_LED_BLINK_AUTO 3
#define QCA8K_LED_LINKUP_OVER_MASK BIT(2)
#define QCA8K_LED_TX_BLINK_MASK BIT(4)
#define QCA8K_LED_RX_BLINK_MASK BIT(5)
#define QCA8K_LED_COL_BLINK_MASK BIT(7)
#define QCA8K_LED_LINK_10M_EN_MASK BIT(8)
#define QCA8K_LED_LINK_100M_EN_MASK BIT(9)
#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10)
#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11)
#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12)
#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13)
#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14)
#define QCA8K_LED_PATTERN_EN_SHIFT 14
#define QCA8K_LED_ALWAYS_OFF 0
#define QCA8K_LED_ALWAYS_BLINK_4HZ 1
#define QCA8K_LED_ALWAYS_ON 2
#define QCA8K_LED_RULE_CONTROLLED 3
#define QCA8K_GOL_MAC_ADDR0 0x60
#define QCA8K_GOL_MAC_ADDR1 0x64
#define QCA8K_MAX_FRAME_SIZE 0x78
#define QCA8K_REG_PORT_STATUS(_i) (0x07c + (_i) * 4)
#define QCA8K_PORT_STATUS_SPEED GENMASK(1, 0)
#define QCA8K_PORT_STATUS_SPEED_10 0
#define QCA8K_PORT_STATUS_SPEED_100 0x1
#define QCA8K_PORT_STATUS_SPEED_1000 0x2
#define QCA8K_PORT_STATUS_TXMAC BIT(2)
#define QCA8K_PORT_STATUS_RXMAC BIT(3)
#define QCA8K_PORT_STATUS_TXFLOW BIT(4)
#define QCA8K_PORT_STATUS_RXFLOW BIT(5)
#define QCA8K_PORT_STATUS_DUPLEX BIT(6)
#define QCA8K_PORT_STATUS_LINK_UP BIT(8)
#define QCA8K_PORT_STATUS_LINK_AUTO BIT(9)
#define QCA8K_PORT_STATUS_LINK_PAUSE BIT(10)
#define QCA8K_PORT_STATUS_FLOW_AUTO BIT(12)
#define QCA8K_REG_PORT_HDR_CTRL(_i) (0x9c + (_i * 4))
#define QCA8K_PORT_HDR_CTRL_RX_MASK GENMASK(3, 2)
#define QCA8K_PORT_HDR_CTRL_RX_S 2
#define QCA8K_PORT_HDR_CTRL_TX_MASK GENMASK(1, 0)
#define QCA8K_PORT_HDR_CTRL_TX_S 0
#define QCA8K_PORT_HDR_CTRL_ALL 2
#define QCA8K_PORT_HDR_CTRL_MGMT 1
#define QCA8K_PORT_HDR_CTRL_NONE 0
#define QCA8K_REG_SGMII_CTRL 0x0e0
#define QCA8K_SGMII_EN_PLL BIT(1)
#define QCA8K_SGMII_EN_RX BIT(2)
#define QCA8K_SGMII_EN_TX BIT(3)
#define QCA8K_SGMII_EN_SD BIT(4)
#define QCA8K_SGMII_CLK125M_DELAY BIT(7)
#define QCA8K_SGMII_MODE_CTRL_MASK (BIT(22) | BIT(23))
#define QCA8K_SGMII_MODE_CTRL_BASEX (0 << 22)
#define QCA8K_SGMII_MODE_CTRL_PHY (1 << 22)
#define QCA8K_SGMII_MODE_CTRL_MAC (2 << 22)
/* MAC_PWR_SEL registers */
#define QCA8K_REG_MAC_PWR_SEL 0x0e4
#define QCA8K_MAC_PWR_RGMII1_1_8V BIT(18)
#define QCA8K_MAC_PWR_RGMII0_1_8V BIT(19)
/* EEE control registers */
#define QCA8K_REG_EEE_CTRL 0x100
#define QCA8K_REG_EEE_CTRL_LPI_EN(_i) ((_i + 1) * 2)
/* ACL registers */
#define QCA8K_REG_PORT_VLAN_CTRL0(_i) (0x420 + (_i * 8))
#define QCA8K_PORT_VLAN_CVID(x) (x << 16)
#define QCA8K_PORT_VLAN_SVID(x) x
#define QCA8K_REG_PORT_VLAN_CTRL1(_i) (0x424 + (_i * 8))
#define QCA8K_REG_IPV4_PRI_BASE_ADDR 0x470
#define QCA8K_REG_IPV4_PRI_ADDR_MASK 0x474
/* Lookup registers */
#define QCA8K_REG_ATU_DATA0 0x600
#define QCA8K_ATU_ADDR2_S 24
#define QCA8K_ATU_ADDR3_S 16
#define QCA8K_ATU_ADDR4_S 8
#define QCA8K_REG_ATU_DATA1 0x604
#define QCA8K_ATU_PORT_M 0x7f
#define QCA8K_ATU_PORT_S 16
#define QCA8K_ATU_ADDR0_S 8
#define QCA8K_REG_ATU_DATA2 0x608
#define QCA8K_ATU_VID_M 0xfff
#define QCA8K_ATU_VID_S 8
#define QCA8K_ATU_STATUS_M 0xf
#define QCA8K_ATU_STATUS_STATIC 0xf
#define QCA8K_REG_ATU_FUNC 0x60c
#define QCA8K_ATU_FUNC_BUSY BIT(31)
#define QCA8K_ATU_FUNC_PORT_EN BIT(14)
#define QCA8K_ATU_FUNC_MULTI_EN BIT(13)
#define QCA8K_ATU_FUNC_FULL BIT(12)
#define QCA8K_ATU_FUNC_PORT_M 0xf
#define QCA8K_ATU_FUNC_PORT_S 8
#define QCA8K_REG_VTU_FUNC0 0x610
#define QCA8K_VTU_FUNC0_VALID BIT(20)
#define QCA8K_VTU_FUNC0_IVL_EN BIT(19)
#define QCA8K_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2)
#define QCA8K_VTU_FUNC0_EG_MODE_MASK 3
#define QCA8K_VTU_FUNC0_EG_MODE_UNMOD 0
#define QCA8K_VTU_FUNC0_EG_MODE_UNTAG 1
#define QCA8K_VTU_FUNC0_EG_MODE_TAG 2
#define QCA8K_VTU_FUNC0_EG_MODE_NOT 3
#define QCA8K_REG_VTU_FUNC1 0x614
#define QCA8K_VTU_FUNC1_BUSY BIT(31)
#define QCA8K_VTU_FUNC1_VID_S 16
#define QCA8K_VTU_FUNC1_FULL BIT(4)
#define QCA8K_REG_GLOBAL_FW_CTRL0 0x620
#define QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN BIT(10)
#define QCA8K_REG_GLOBAL_FW_CTRL1 0x624
#define QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S 24
#define QCA8K_GLOBAL_FW_CTRL1_BC_DP_S 16
#define QCA8K_GLOBAL_FW_CTRL1_MC_DP_S 8
#define QCA8K_GLOBAL_FW_CTRL1_UC_DP_S 0
#define QCA8K_PORT_LOOKUP_CTRL(_i) (0x660 + (_i) * 0xc)
#define QCA8K_PORT_LOOKUP_MEMBER GENMASK(6, 0)
#define QCA8K_PORT_LOOKUP_VLAN_MODE GENMASK(9, 8)
#define QCA8K_PORT_LOOKUP_VLAN_MODE_NONE (0 << 8)
#define QCA8K_PORT_LOOKUP_VLAN_MODE_FALLBACK (1 << 8)
#define QCA8K_PORT_LOOKUP_VLAN_MODE_CHECK (2 << 8)
#define QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE (3 << 8)
#define QCA8K_PORT_LOOKUP_STATE_MASK GENMASK(18, 16)
#define QCA8K_PORT_LOOKUP_STATE_DISABLED (0 << 16)
#define QCA8K_PORT_LOOKUP_STATE_BLOCKING (1 << 16)
#define QCA8K_PORT_LOOKUP_STATE_LISTENING (2 << 16)
#define QCA8K_PORT_LOOKUP_STATE_LEARNING (3 << 16)
#define QCA8K_PORT_LOOKUP_STATE_FORWARD (4 << 16)
#define QCA8K_PORT_LOOKUP_STATE GENMASK(18, 16)
#define QCA8K_PORT_LOOKUP_LEARN BIT(20)
#define QCA8K_REG_GLOBAL_FC_THRESH 0x800
#define QCA8K_GLOBAL_FC_GOL_XON_THRES(x) ((x) << 16)
#define QCA8K_GLOBAL_FC_GOL_XON_THRES_S GENMASK(24, 16)
#define QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x) ((x) << 0)
#define QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S GENMASK(8, 0)
#define QCA8K_REG_PORT_HOL_CTRL0(_i) (0x970 + (_i) * 0x8)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF GENMASK(3, 0)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI0(x) ((x) << 0)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF GENMASK(7, 4)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI1(x) ((x) << 4)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF GENMASK(11, 8)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI2(x) ((x) << 8)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF GENMASK(15, 12)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI3(x) ((x) << 12)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF GENMASK(19, 16)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI4(x) ((x) << 16)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF GENMASK(23, 20)
#define QCA8K_PORT_HOL_CTRL0_EG_PRI5(x) ((x) << 20)
#define QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF GENMASK(29, 24)
#define QCA8K_PORT_HOL_CTRL0_EG_PORT(x) ((x) << 24)
#define QCA8K_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
#define QCA8K_PORT_HOL_CTRL1_ING_BUF GENMASK(3, 0)
#define QCA8K_PORT_HOL_CTRL1_ING(x) ((x) << 0)
#define QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN BIT(6)
#define QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN BIT(7)
#define QCA8K_PORT_HOL_CTRL1_WRED_EN BIT(8)
#define QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16)
/* Pkt edit registers */
#define QCA8K_EGRESS_VLAN(x) (0x0c70 + (4 * (x / 2)))
/* L3 registers */
#define QCA8K_HROUTER_CONTROL 0xe00
#define QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_M GENMASK(17, 16)
#define QCA8K_HROUTER_CONTROL_GLB_LOCKTIME_S 16
#define QCA8K_HROUTER_CONTROL_ARP_AGE_MODE 1
#define QCA8K_HROUTER_PBASED_CONTROL1 0xe08
#define QCA8K_HROUTER_PBASED_CONTROL2 0xe0c
#define QCA8K_HNAT_CONTROL 0xe38
/* MIB registers */
#define QCA8K_PORT_MIB_COUNTER(_i) (0x1000 + (_i) * 0x100)
/* QCA specific MII registers */
#define MII_ATH_MMD_ADDR 0x0d
#define MII_ATH_MMD_DATA 0x0e
enum {
QCA8K_PORT_SPEED_10M = 0,
QCA8K_PORT_SPEED_100M = 1,
QCA8K_PORT_SPEED_1000M = 2,
QCA8K_PORT_SPEED_ERR = 3,
};
enum qca8k_fdb_cmd {
QCA8K_FDB_FLUSH = 1,
QCA8K_FDB_LOAD = 2,
QCA8K_FDB_PURGE = 3,
QCA8K_FDB_NEXT = 6,
QCA8K_FDB_SEARCH = 7,
};
enum qca8k_vlan_cmd {
QCA8K_VLAN_FLUSH = 1,
QCA8K_VLAN_LOAD = 2,
QCA8K_VLAN_PURGE = 3,
QCA8K_VLAN_REMOVE_PORT = 4,
QCA8K_VLAN_NEXT = 5,
QCA8K_VLAN_READ = 6,
};
struct ar8xxx_port_status {
int enabled;
};
struct qca8k_match_data {
u8 id;
bool reduced_package;
};
struct qca8k_led_pattern_en {
u32 reg;
u8 shift;
};
struct qca8k_led {
u8 port_num;
u8 led_num;
u16 old_rule;
struct qca8k_priv *priv;
struct led_classdev cdev;
};
struct qca8k_priv {
u8 switch_id;
u8 switch_revision;
bool legacy_phy_port_mapping;
struct regmap *regmap;
struct mii_bus *bus;
struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
struct dsa_switch *ds;
struct mutex reg_mutex;
struct device *dev;
struct dsa_switch_ops ops;
struct gpio_desc *reset_gpio;
unsigned int port_mtu[QCA8K_NUM_PORTS];
struct qca8k_led ports_led[QCA8K_LED_COUNT];
};
struct qca8k_mib_desc {
unsigned int size;
unsigned int offset;
const char *name;
};
struct qca8k_fdb {
u16 vid;
u8 port_mask;
u8 aging;
u8 mac[6];
};
#endif /* __QCA8K_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment