Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config NET_DSA_MXL862
tristate "MaxLinear MxL862xx"
depends on NET_DSA
select CRC16
select NET_DSA_TAG_MXL_862XX
help
This enables support for the MaxLinear MxL862xx switch family.
These switches have two 10GE SerDes interfaces, one typically
used as CPU port.
- MxL86282 has eight 2.5 Gigabit PHYs
- MxL86252 has five 2.5 Gigabit PHYs
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __MXL862XX_CMD_H
#define __MXL862XX_CMD_H
#define MXL862XX_MMD_DEV 30
#define MXL862XX_MMD_REG_CTRL 0
#define MXL862XX_MMD_REG_LEN_RET 1
#define MXL862XX_MMD_REG_DATA_FIRST 2
#define MXL862XX_MMD_REG_DATA_LAST 95
#define MXL862XX_MMD_REG_DATA_MAX_SIZE \
(MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
#define MXL862XX_COMMON_MAGIC 0x100
#define MXL862XX_BRDG_MAGIC 0x300
#define MXL862XX_BRDGPORT_MAGIC 0x400
#define MXL862XX_CTP_MAGIC 0x500
#define MXL862XX_QOS_MAGIC 0x600
#define MXL862XX_RMON_MAGIC 0x700
#define MXL862XX_SWMAC_MAGIC 0xa00
#define MXL862XX_EXTVLAN_MAGIC 0xb00
#define MXL862XX_VLANFILTER_MAGIC 0xc00
#define MXL862XX_STP_MAGIC 0xf00
#define MXL862XX_SS_MAGIC 0x1600
#define GPY_GPY2XX_MAGIC 0x1800
#define SYS_MISC_MAGIC 0x1900
#define MXL862XX_XPCS_MAGIC 0x1a00
#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9)
#define MXL862XX_COMMON_CFGSET (MXL862XX_COMMON_MAGIC + 0xa)
#define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11)
#define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1)
#define MXL862XX_BRIDGE_CONFIGSET (MXL862XX_BRDG_MAGIC + 0x2)
#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3)
#define MXL862XX_BRIDGE_FREE (MXL862XX_BRDG_MAGIC + 0x4)
#define MXL862XX_BRIDGEPORT_ALLOC (MXL862XX_BRDGPORT_MAGIC + 0x1)
#define MXL862XX_BRIDGEPORT_CONFIGSET (MXL862XX_BRDGPORT_MAGIC + 0x2)
#define MXL862XX_BRIDGEPORT_CONFIGGET (MXL862XX_BRDGPORT_MAGIC + 0x3)
#define MXL862XX_BRIDGEPORT_FREE (MXL862XX_BRDGPORT_MAGIC + 0x4)
#define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3)
#define MXL862XX_QOS_METERCFGSET (MXL862XX_QOS_MAGIC + 0x2)
#define MXL862XX_QOS_METERALLOC (MXL862XX_QOS_MAGIC + 0x2a)
#define MXL862XX_RMON_PORT_GET (MXL862XX_RMON_MAGIC + 0x1)
#define MXL862XX_MAC_TABLEENTRYADD (MXL862XX_SWMAC_MAGIC + 0x2)
#define MXL862XX_MAC_TABLEENTRYREAD (MXL862XX_SWMAC_MAGIC + 0x3)
#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
#define MXL862XX_MAC_TABLEENTRYREMOVE (MXL862XX_SWMAC_MAGIC + 0x5)
#define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8)
#define MXL862XX_EXTENDEDVLAN_ALLOC (MXL862XX_EXTVLAN_MAGIC + 0x1)
#define MXL862XX_EXTENDEDVLAN_SET (MXL862XX_EXTVLAN_MAGIC + 0x2)
#define MXL862XX_EXTENDEDVLAN_GET (MXL862XX_EXTVLAN_MAGIC + 0x3)
#define MXL862XX_EXTENDEDVLAN_FREE (MXL862XX_EXTVLAN_MAGIC + 0x4)
#define MXL862XX_VLANFILTER_ALLOC (MXL862XX_VLANFILTER_MAGIC + 0x1)
#define MXL862XX_VLANFILTER_SET (MXL862XX_VLANFILTER_MAGIC + 0x2)
#define MXL862XX_VLANFILTER_GET (MXL862XX_VLANFILTER_MAGIC + 0x3)
#define MXL862XX_VLANFILTER_FREE (MXL862XX_VLANFILTER_MAGIC + 0x4)
#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x2)
#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
#define MXL862XX_XPCS_PCS_GET_STATE (MXL862XX_XPCS_MAGIC + 0x2)
#define MXL862XX_XPCS_PCS_DISABLE (MXL862XX_XPCS_MAGIC + 0x4)
#define MXL862XX_XPCS_AN_RESTART (MXL862XX_XPCS_MAGIC + 0x5)
#define MXL862XX_XPCS_PCS_LINK_UP (MXL862XX_XPCS_MAGIC + 0x7)
#define MXL862XX_XPCS_LOOPBACK (MXL862XX_XPCS_MAGIC + 0x8)
#define MXL862XX_XPCS_RESET (MXL862XX_XPCS_MAGIC + 0x9)
#define MMD_API_MAXIMUM_ID 0x7fff
#endif /* __MXL862XX_CMD_H */
@@ -0,0 +1,506 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Based upon the MaxLinear SDK driver
*
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
* Copyright (C) 2025 John Crispin <john@phrozen.org>
* Copyright (C) 2024 MaxLinear Inc.
*/
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/crc16.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
#include <linux/unaligned.h>
#include <net/dsa.h>
#include "mxl862xx.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
#define CTRL_CRC_FLAG BIT(14)
#define LEN_RET_LEN_MASK GENMASK(9, 0)
#define MXL862XX_MMD_REG_CTRL 0
#define MXL862XX_MMD_REG_LEN_RET 1
#define MXL862XX_MMD_REG_DATA_FIRST 2
#define MXL862XX_MMD_REG_DATA_LAST 95
#define MXL862XX_MMD_REG_DATA_MAX_SIZE \
(MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
#define MMD_API_SET_DATA_0 2
#define MMD_API_GET_DATA_0 5
#define MMD_API_RST_DATA 8
#define MXL862XX_SWITCH_RESET 0x9907
static void mxl862xx_crc_err_work_fn(struct work_struct *work)
{
struct mxl862xx_priv *priv = container_of(work, struct mxl862xx_priv,
crc_err_work);
struct dsa_port *dp;
rtnl_lock();
if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) {
dev_warn(&priv->mdiodev->dev,
"MDIO CRC error detected, shutting down all ports\n");
dsa_switch_for_each_cpu_port(dp, priv->ds)
dev_close(dp->conduit);
}
rtnl_unlock();
clear_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags);
}
/* Firmware CRC error codes (outside normal Zephyr errno range). */
#define MXL862XX_FW_CRC6_ERR (-1024)
#define MXL862XX_FW_CRC16_ERR (-1023)
/* 3GPP CRC-6 lookup table (polynomial 0x6F).
* Matches the firmware's default CRC-6 implementation.
*/
static const u8 mxl862xx_crc6_table[256] = {
0x00, 0x2f, 0x31, 0x1e, 0x0d, 0x22, 0x3c, 0x13,
0x1a, 0x35, 0x2b, 0x04, 0x17, 0x38, 0x26, 0x09,
0x34, 0x1b, 0x05, 0x2a, 0x39, 0x16, 0x08, 0x27,
0x2e, 0x01, 0x1f, 0x30, 0x23, 0x0c, 0x12, 0x3d,
0x07, 0x28, 0x36, 0x19, 0x0a, 0x25, 0x3b, 0x14,
0x1d, 0x32, 0x2c, 0x03, 0x10, 0x3f, 0x21, 0x0e,
0x33, 0x1c, 0x02, 0x2d, 0x3e, 0x11, 0x0f, 0x20,
0x29, 0x06, 0x18, 0x37, 0x24, 0x0b, 0x15, 0x3a,
0x0e, 0x21, 0x3f, 0x10, 0x03, 0x2c, 0x32, 0x1d,
0x14, 0x3b, 0x25, 0x0a, 0x19, 0x36, 0x28, 0x07,
0x3a, 0x15, 0x0b, 0x24, 0x37, 0x18, 0x06, 0x29,
0x20, 0x0f, 0x11, 0x3e, 0x2d, 0x02, 0x1c, 0x33,
0x09, 0x26, 0x38, 0x17, 0x04, 0x2b, 0x35, 0x1a,
0x13, 0x3c, 0x22, 0x0d, 0x1e, 0x31, 0x2f, 0x00,
0x3d, 0x12, 0x0c, 0x23, 0x30, 0x1f, 0x01, 0x2e,
0x27, 0x08, 0x16, 0x39, 0x2a, 0x05, 0x1b, 0x34,
0x1c, 0x33, 0x2d, 0x02, 0x11, 0x3e, 0x20, 0x0f,
0x06, 0x29, 0x37, 0x18, 0x0b, 0x24, 0x3a, 0x15,
0x28, 0x07, 0x19, 0x36, 0x25, 0x0a, 0x14, 0x3b,
0x32, 0x1d, 0x03, 0x2c, 0x3f, 0x10, 0x0e, 0x21,
0x1b, 0x34, 0x2a, 0x05, 0x16, 0x39, 0x27, 0x08,
0x01, 0x2e, 0x30, 0x1f, 0x0c, 0x23, 0x3d, 0x12,
0x2f, 0x00, 0x1e, 0x31, 0x22, 0x0d, 0x13, 0x3c,
0x35, 0x1a, 0x04, 0x2b, 0x38, 0x17, 0x09, 0x26,
0x12, 0x3d, 0x23, 0x0c, 0x1f, 0x30, 0x2e, 0x01,
0x08, 0x27, 0x39, 0x16, 0x05, 0x2a, 0x34, 0x1b,
0x26, 0x09, 0x17, 0x38, 0x2b, 0x04, 0x1a, 0x35,
0x3c, 0x13, 0x0d, 0x22, 0x31, 0x1e, 0x00, 0x2f,
0x15, 0x3a, 0x24, 0x0b, 0x18, 0x37, 0x29, 0x06,
0x0f, 0x20, 0x3e, 0x11, 0x02, 0x2d, 0x33, 0x1c,
0x21, 0x0e, 0x10, 0x3f, 0x2c, 0x03, 0x1d, 0x32,
0x3b, 0x14, 0x0a, 0x25, 0x36, 0x19, 0x07, 0x28,
};
/* Compute 3GPP CRC-6 over the ctrl register (16 bits) and the lower
* 10 bits of the len_ret register. The 26-bit input is packed as
* { len_ret[9:0], ctrl[15:0] } and processed LSB-first through the
* lookup table.
*/
static u8 mxl862xx_crc6(u16 ctrl, u16 len_ret)
{
u32 data = ((u32)(len_ret & LEN_RET_LEN_MASK) << 16) | ctrl;
u8 crc = 0;
int i;
for (i = 0; i < sizeof(data); i++, data >>= 8)
crc = mxl862xx_crc6_table[(crc << 2) ^ (data & 0xff)] & 0x3f;
return crc;
}
/* Encode CRC-6 into the ctrl and len_ret registers before writing them
* to MDIO. The caller must set ctrl = API_ID | CTRL_BUSY_MASK |
* CTRL_CRC_FLAG, and len_ret = parameter length (bits 0-9 only).
*
* After encoding:
* ctrl[12:0] = API ID (unchanged)
* ctrl[14:13] = CRC-6 bits 5-4
* ctrl[15] = busy flag (unchanged)
* len_ret[9:0] = parameter length (unchanged)
* len_ret[13:10] = CRC-6 bits 3-0
* len_ret[14] = original ctrl[14] (CRC check flag, forwarded to FW)
* len_ret[15] = original ctrl[13] (magic bit, always 1)
*/
static void mxl862xx_crc6_encode(u16 *pctrl, u16 *plen_ret)
{
u16 crc, ctrl, len_ret;
/* Set magic bit before CRC computation */
*pctrl |= BIT(13);
crc = mxl862xx_crc6(*pctrl, *plen_ret);
/* Place CRC MSB (bits 5-4) into ctrl bits 13-14 */
ctrl = (*pctrl & ~GENMASK(14, 13));
ctrl |= (crc & 0x30) << 9;
/* Place CRC LSB (bits 3-0) into len_ret bits 10-13 */
len_ret = *plen_ret | ((crc & 0x0f) << 10);
/* Forward ctrl[14] (CRC check flag) to len_ret[14],
* and ctrl[13] (magic, always 1) to len_ret[15].
*/
len_ret |= (*pctrl & BIT(14)) | ((*pctrl & BIT(13)) << 2);
*pctrl = ctrl;
*plen_ret = len_ret;
}
/* Verify CRC-6 on a firmware response and extract the return value.
*
* The firmware encodes the return value as a signed 11-bit integer:
* - Sign bit (bit 10) in ctrl[14]
* - Magnitude (bits 9-0) in len_ret[9:0]
* These are recoverable after CRC-6 verification by restoring the
* original ctrl from the auxiliary copies in len_ret[15:14].
*
* Return: 0 on CRC match (with *result set), or -EIO on mismatch.
*/
static int mxl862xx_crc6_verify(u16 ctrl, u16 len_ret, int *result)
{
u16 crc_recv, crc_calc;
/* Extract the received CRC-6 */
crc_recv = ((ctrl >> 9) & 0x30) | ((len_ret >> 10) & 0x0f);
/* Reconstruct the original ctrl for re-computation:
* ctrl[14] = len_ret[14] (sign bit / CRC check flag)
* ctrl[13] = len_ret[15] >> 2 (magic bit)
*/
ctrl &= ~GENMASK(14, 13);
ctrl |= len_ret & BIT(14);
ctrl |= (len_ret & BIT(15)) >> 2;
crc_calc = mxl862xx_crc6(ctrl, len_ret);
if (crc_recv != crc_calc)
return -EIO;
/* Extract signed 11-bit return value:
* bit 10 (sign) from ctrl[14], bits 9-0 from len_ret[9:0]
*/
*result = sign_extend32((len_ret & LEN_RET_LEN_MASK) |
((ctrl & CTRL_CRC_FLAG) >> 4), 10);
return 0;
}
static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr)
{
return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr);
}
static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data)
{
return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data);
}
static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv)
{
return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
}
static int mxl862xx_busy_wait(struct mxl862xx_priv *priv)
{
int val;
return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
!(val & CTRL_BUSY_MASK), 15, 500000);
}
/* Issue a firmware command with CRC-6 protection on the ctrl and len_ret
* registers, wait for completion, and verify the response CRC-6.
*
* Return: firmware result value (>= 0) on success, or negative errno.
*/
static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)
{
u16 ctrl_enc, len_enc;
int ret, fw_result;
ctrl_enc = cmd | CTRL_BUSY_MASK | CTRL_CRC_FLAG;
len_enc = len;
mxl862xx_crc6_encode(&ctrl_enc, &len_enc);
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, len_enc);
if (ret < 0)
return ret;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, ctrl_enc);
if (ret < 0)
return ret;
ret = mxl862xx_busy_wait(priv);
if (ret < 0)
return ret;
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
if (ret < 0)
return ret;
ctrl_enc = ret;
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
if (ret < 0)
return ret;
len_enc = ret;
ret = mxl862xx_crc6_verify(ctrl_enc, len_enc, &fw_result);
if (ret) {
if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
return -EIO;
}
return fw_result;
}
static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words)
{
u16 cmd;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1;
if (!(cmd < 2))
return -EINVAL;
cmd += MMD_API_SET_DATA_0;
return mxl862xx_issue_cmd(priv, cmd,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
}
static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words)
{
u16 cmd;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (!(cmd > 0 && cmd < 3))
return -EINVAL;
cmd += MMD_API_GET_DATA_0;
return mxl862xx_issue_cmd(priv, cmd,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
}
static int mxl862xx_rst_data(struct mxl862xx_priv *priv)
{
return mxl862xx_issue_cmd(priv, MMD_API_RST_DATA, 0);
}
/* Minimum number of zero words in the data payload before issuing a
* RST_DATA command is worthwhile. RST_DATA costs one full command
* round-trip (~5 MDIO transactions), so the threshold must offset that.
*/
#define RST_DATA_THRESHOLD 5
static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
bool quiet)
{
int ret;
ret = mxl862xx_issue_cmd(priv, cmd, size);
/* Handle errors returned by the firmware as -EIO.
* The firmware is based on Zephyr OS and uses the errors as
* defined in errno.h of Zephyr OS. See
* https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h
*
* The firmware signals CRC validation failures with dedicated
* error codes outside the normal Zephyr errno range:
* -1024: CRC-6 mismatch on ctrl/len_ret registers
* -1023: CRC-16 mismatch on data payload
*/
if (ret < 0) {
if ((ret == MXL862XX_FW_CRC6_ERR ||
ret == MXL862XX_FW_CRC16_ERR) &&
!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
if (!quiet)
dev_err(&priv->mdiodev->dev,
"CMD %04x returned error %d\n", cmd, ret);
return -EIO;
}
return ret;
}
int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
u16 size, bool read, bool quiet)
{
__le16 *data = _data;
bool use_rst = false;
unsigned int zeros;
int ret, cmd_ret;
u16 max, crc, i;
dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
max = (size + 1) / 2;
ret = mxl862xx_busy_wait(priv);
if (ret < 0)
goto out;
/* If the data contains enough zero words, issue RST_DATA to zero
* both the firmware buffer and MMD registers, then skip writing
* zero words individually.
*/
for (i = 0, zeros = 0; i < size / 2 && zeros < RST_DATA_THRESHOLD; i++)
if (!get_unaligned_le16(&data[i]))
zeros++;
if (zeros < RST_DATA_THRESHOLD && (size & 1) && !*(u8 *)&data[i])
zeros++;
if (zeros >= RST_DATA_THRESHOLD) {
ret = mxl862xx_rst_data(priv);
if (ret < 0)
goto out;
use_rst = true;
}
/* Compute CRC-16 over the data payload; written as an extra word
* after the data so the firmware can verify the transfer.
*/
crc = crc16(0xffff, (const u8 *)data, size);
for (i = 0; i < max + 1; i++) {
u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
u16 val;
if (i && off == 0) {
/* Send command to set data when every
* MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written.
*/
ret = mxl862xx_set_data(priv, i);
if (ret < 0)
goto out;
}
if (i == max) {
/* Even size: full CRC word.
* Odd size: only CRC high byte remains (low byte
* was packed into the previous word).
*/
val = (size & 1) ? crc >> 8 : crc;
} else if ((i * 2 + 1) == size) {
/* Special handling for last BYTE if it's not WORD
* aligned to avoid reading beyond the allocated data
* structure. Pack the CRC low byte into the high
* byte of this word so it sits at byte offset 'size'
* in the firmware's contiguous buffer.
*/
val = *(u8 *)&data[i] | ((crc & 0xff) << 8);
} else {
val = get_unaligned_le16(&data[i]);
}
/* After RST_DATA, skip zero data words as the registers
* already contain zeros, but never skip the CRC word at the
* final word.
*/
if (use_rst && i < max && val == 0)
continue;
ret = mxl862xx_reg_write(priv,
MXL862XX_MMD_REG_DATA_FIRST + off,
val);
if (ret < 0)
goto out;
}
ret = mxl862xx_send_cmd(priv, cmd, size, quiet);
if (ret < 0 || !read)
goto out;
/* store result of mxl862xx_send_cmd() */
cmd_ret = ret;
for (i = 0; i < max + 1; i++) {
u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (i && off == 0) {
/* Send command to fetch next batch of data when every
* MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read.
*/
ret = mxl862xx_get_data(priv, i);
if (ret < 0)
goto out;
}
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off);
if (ret < 0)
goto out;
if (i == max) {
/* Even size: full CRC word.
* Odd size: only CRC high byte remains (low byte
* was in the previous word).
*/
if (size & 1)
crc = (crc & 0x00ff) |
(((u16)ret & 0xff) << 8);
else
crc = (u16)ret;
} else if ((i * 2 + 1) == size) {
/* Special handling for last BYTE if it's not WORD
* aligned to avoid writing beyond the allocated data
* structure. The high byte carries the CRC low byte.
*/
*(uint8_t *)&data[i] = ret & 0xff;
crc = (ret >> 8) & 0xff;
} else {
put_unaligned_le16((u16)ret, &data[i]);
}
}
if (crc16(0xffff, (const u8 *)data, size) != crc) {
if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
ret = -EIO;
goto out;
}
/* on success return the result of the mxl862xx_send_cmd() */
ret = cmd_ret;
dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data);
out:
mutex_unlock(&priv->mdiodev->bus->mdio_lock);
return ret;
}
int mxl862xx_reset(struct mxl862xx_priv *priv)
{
int ret;
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
/* Software reset */
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0);
if (ret)
goto out;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET);
out:
mutex_unlock(&priv->mdiodev->bus->mdio_lock);
return ret;
}
void mxl862xx_host_init(struct mxl862xx_priv *priv)
{
INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
}
void mxl862xx_host_shutdown(struct mxl862xx_priv *priv)
{
cancel_work_sync(&priv->crc_err_work);
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __MXL862XX_HOST_H
#define __MXL862XX_HOST_H
#include "mxl862xx.h"
void mxl862xx_host_init(struct mxl862xx_priv *priv);
void mxl862xx_host_shutdown(struct mxl862xx_priv *priv);
int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
bool read, bool quiet);
#define MXL862XX_API_WRITE(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
#define MXL862XX_API_READ(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
int mxl862xx_reset(struct mxl862xx_priv *priv);
#endif /* __MXL862XX_HOST_H */
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Phylink and PCS support for MaxLinear MxL862xx switch family
*
* Copyright (C) 2024 MaxLinear Inc.
* Copyright (C) 2025 John Crispin <john@phrozen.org>
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
*/
#include <linux/bitfield.h>
#include <linux/mutex.h>
#include <linux/phylink.h>
#include <net/dsa.h>
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
struct mxl862xx_priv *priv = ds->priv;
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
MAC_100 | MAC_1000 | MAC_2500FD;
switch (port) {
case 1 ... 8:
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 9:
case 13:
/* Advertised also on old firmware lacking the XPCS API:
* there the SerDes runs in its flash-configured mode
* without host control (mac_select_pcs returns NULL),
* keeping the CPU port working.
*/
__set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10GKR, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
fallthrough;
case 10 ... 12:
case 14 ... 16:
if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
break;
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
break;
default:
break;
}
if (port == 9 || port == 13)
config->mac_capabilities |= MAC_10000FD | MAC_5000FD;
}
static struct mxl862xx_pcs *pcs_to_mxl862xx_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mxl862xx_pcs, pcs);
}
static int mxl862xx_xpcs_if_mode(phy_interface_t interface)
{
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
return MXL862XX_XPCS_IF_SGMII;
case PHY_INTERFACE_MODE_QSGMII:
return MXL862XX_XPCS_IF_QSGMII;
case PHY_INTERFACE_MODE_1000BASEX:
return MXL862XX_XPCS_IF_1000BASEX;
case PHY_INTERFACE_MODE_2500BASEX:
return MXL862XX_XPCS_IF_2500BASEX;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
return MXL862XX_XPCS_IF_USXGMII;
case PHY_INTERFACE_MODE_10GBASER:
return MXL862XX_XPCS_IF_10GBASER;
case PHY_INTERFACE_MODE_10GKR:
return MXL862XX_XPCS_IF_10GKR;
default:
return -EINVAL;
}
}
static int mxl862xx_xpcs_neg_mode(unsigned int neg_mode)
{
if (!(neg_mode & PHYLINK_PCS_NEG_INBAND))
return MXL862XX_XPCS_NEG_NONE;
if (neg_mode & PHYLINK_PCS_NEG_ENABLED)
return MXL862XX_XPCS_NEG_INBAND_AN_ON;
return MXL862XX_XPCS_NEG_INBAND_AN_OFF;
}
static int mxl862xx_pcs_enable(struct phylink_pcs *pcs)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
/* Bringup is done idempotently by pcs_config; just account this
* sub-port so pcs_disable powers the shared XPCS down only after
* the last sub-port has been released.
*/
mutex_lock(&mpcs->priv->serdes_lock);
mpcs->priv->serdes_refcount[mpcs->serdes_id]++;
mutex_unlock(&mpcs->priv->serdes_lock);
return 0;
}
static void mxl862xx_pcs_disable(struct phylink_pcs *pcs)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
struct mxl862xx_xpcs_pcs_disable dis = {};
struct mxl862xx_priv *priv = mpcs->priv;
dis.port_id = mpcs->serdes_id;
/* The SerDes is shared across QSGMII/QUSXGMII sub-ports; only
* power it down once the last active sub-port goes away. Hold
* serdes_lock across the count and the power-down so a sibling
* sub-port enable cannot race the transition to zero.
*/
mutex_lock(&priv->serdes_lock);
if (--priv->serdes_refcount[mpcs->serdes_id] == 0)
MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_DISABLE, dis);
mutex_unlock(&priv->serdes_lock);
}
/* The XPCS firmware reports failures in the result field using its own
* libc errno values; ENOTSUP (134) in particular has no kernel errno.
* Translate the codes the firmware can actually return.
*/
static int mxl862xx_xpcs_errno(int result)
{
switch (result) {
case -5: /* firmware -EIO */
return -EIO;
case -134: /* firmware -ENOTSUP */
return -EOPNOTSUPP;
default: /* firmware -EINVAL and anything unexpected */
return -EINVAL;
}
}
static int mxl862xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
struct mxl862xx_priv *priv = mpcs->priv;
struct mxl862xx_xpcs_pcs_cfg cfg = {};
int if_mode, lane, ret, adv;
if_mode = mxl862xx_xpcs_if_mode(interface);
if (if_mode < 0) {
dev_err(priv->ds->dev, "unsupported interface: %s\n",
phy_modes(interface));
return if_mode;
}
/* The XPCS bringup is per-instance and idempotent in the
* firmware: every QSGMII/QUSXGMII sub-port may call pcs_config
* and the firmware will skip the bringup if the requested mode
* matches the cached one, then update MAC pause for the
* sub-port indicated by @usx_subport. No serdes_lock is needed
* here: the refcount held since pcs_enable keeps a sibling
* pcs_disable from powering the XPCS down, and pcs_disable
* invalidates the firmware's cached mode so the next pcs_config
* redoes the bringup.
*/
lane = (interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
cfg.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_CFG_PORT_ID,
mpcs->serdes_id) |
FIELD_PREP(MXL862XX_XPCS_CFG_USX_SUBPORT,
mpcs->slot) |
FIELD_PREP(MXL862XX_XPCS_CFG_USX_LANE_MODE, lane) |
FIELD_PREP(MXL862XX_XPCS_CFG_INTERFACE, if_mode) |
FIELD_PREP(MXL862XX_XPCS_CFG_NEG_MODE,
mxl862xx_xpcs_neg_mode(neg_mode)) |
FIELD_PREP(MXL862XX_XPCS_CFG_ROLE,
MXL862XX_XPCS_ROLE_MAC) |
FIELD_PREP(MXL862XX_XPCS_CFG_PERMIT_PAUSE,
permit_pause_to_mac));
if (neg_mode & PHYLINK_PCS_NEG_INBAND) {
adv = phylink_mii_c22_pcs_encode_advertisement(interface,
advertising);
if (adv >= 0)
cfg.advertising.cl37 = cpu_to_le16(adv);
}
ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_CONFIG, cfg);
if (ret)
return ret;
ret = (s16)le16_to_cpu(cfg.result);
if (ret < 0)
return mxl862xx_xpcs_errno(ret);
mpcs->interface = interface;
return ret > 0 ? 1 : 0;
}
static void mxl862xx_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
struct mxl862xx_priv *priv = mpcs->priv;
struct mxl862xx_xpcs_pcs_state st = {};
int if_mode, lane, ret;
u32 mode;
u16 bmsr;
if_mode = mxl862xx_xpcs_if_mode(state->interface);
if (if_mode < 0) {
state->link = false;
return;
}
lane = (state->interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
st.mode = cpu_to_le32(FIELD_PREP(MXL862XX_XPCS_ST_PORT_ID,
mpcs->serdes_id) |
FIELD_PREP(MXL862XX_XPCS_ST_INTERFACE, if_mode) |
FIELD_PREP(MXL862XX_XPCS_ST_USX_SUBPORT,
mpcs->slot) |
FIELD_PREP(MXL862XX_XPCS_ST_USX_LANE_MODE, lane));
ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_GET_STATE, st);
if (ret) {
state->link = false;
return;
}
mode = le32_to_cpu(st.mode);
state->link = FIELD_GET(MXL862XX_XPCS_ST_LINK, mode) &&
!FIELD_GET(MXL862XX_XPCS_ST_PCS_FAULT, mode);
state->an_complete = FIELD_GET(MXL862XX_XPCS_ST_AN_COMPLETE, mode);
switch (state->interface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
bmsr = (state->link ? BMSR_LSTATUS : 0) |
(state->an_complete ? BMSR_ANEGCOMPLETE : 0);
phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr,
le16_to_cpu(st.lpa.cl37));
break;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
if (state->link)
phylink_decode_usxgmii_word(state,
le16_to_cpu(st.lpa.usx));
break;
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_10GKR:
if (state->link) {
state->speed = SPEED_10000;
state->duplex = DUPLEX_FULL;
}
break;
default:
state->link = false;
break;
}
}
static void mxl862xx_pcs_an_restart(struct phylink_pcs *pcs)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
struct mxl862xx_priv *priv = mpcs->priv;
struct mxl862xx_xpcs_an_restart an = {};
int if_mode, lane;
if_mode = mxl862xx_xpcs_if_mode(mpcs->interface);
if (if_mode < 0)
return;
lane = (mpcs->interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
an.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_ANR_PORT_ID,
mpcs->serdes_id) |
FIELD_PREP(MXL862XX_XPCS_ANR_INTERFACE, if_mode) |
FIELD_PREP(MXL862XX_XPCS_ANR_USX_SUBPORT,
mpcs->slot) |
FIELD_PREP(MXL862XX_XPCS_ANR_USX_LANE_MODE, lane));
MXL862XX_API_WRITE(priv, MXL862XX_XPCS_AN_RESTART, an);
}
static void mxl862xx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface, int speed,
int duplex)
{
struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
struct mxl862xx_xpcs_pcs_link_up lu = {};
struct mxl862xx_priv *priv = mpcs->priv;
int if_mode, lane, dup;
/* With inband-AN enabled (role=MAC), the XPCS auto-resolves
* speed/duplex from the partner's AN word and the firmware
* short-circuits link_up. Skip the firmware round-trip, same
* as pcs-mtk-lynxi.
*/
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
return;
if_mode = mxl862xx_xpcs_if_mode(interface);
if (if_mode < 0)
return;
lane = (interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
dup = (duplex == DUPLEX_FULL) ? MXL862XX_XPCS_DUPLEX_FULL :
MXL862XX_XPCS_DUPLEX_HALF;
lu.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_LU_PORT_ID,
mpcs->serdes_id) |
FIELD_PREP(MXL862XX_XPCS_LU_INTERFACE, if_mode) |
FIELD_PREP(MXL862XX_XPCS_LU_USX_SUBPORT,
mpcs->slot) |
FIELD_PREP(MXL862XX_XPCS_LU_USX_LANE_MODE, lane) |
FIELD_PREP(MXL862XX_XPCS_LU_DUPLEX, dup));
lu.speed = cpu_to_le16(speed);
MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_LINK_UP, lu);
}
static unsigned int mxl862xx_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
case PHY_INTERFACE_MODE_10GKR:
return LINK_INBAND_ENABLE;
case PHY_INTERFACE_MODE_10GBASER:
return LINK_INBAND_DISABLE;
default:
return 0;
}
}
static const struct phylink_pcs_ops mxl862xx_pcs_ops = {
.pcs_enable = mxl862xx_pcs_enable,
.pcs_disable = mxl862xx_pcs_disable,
.pcs_config = mxl862xx_pcs_config,
.pcs_get_state = mxl862xx_pcs_get_state,
.pcs_an_restart = mxl862xx_pcs_an_restart,
.pcs_link_up = mxl862xx_pcs_link_up,
.pcs_inband_caps = mxl862xx_pcs_inband_caps,
};
void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
int port)
{
pcs->priv = priv;
pcs->serdes_id = MXL862XX_SERDES_PORT_ID(port);
pcs->slot = MXL862XX_SERDES_SLOT(port);
pcs->interface = PHY_INTERFACE_MODE_NA;
pcs->pcs.ops = &mxl862xx_pcs_ops;
pcs->pcs.poll = true;
__set_bit(PHY_INTERFACE_MODE_QSGMII, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, pcs->pcs.supported_interfaces);
if (pcs->slot != 0)
return;
__set_bit(PHY_INTERFACE_MODE_SGMII, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10GKR, pcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_USXGMII, pcs->pcs.supported_interfaces);
}
static struct phylink_pcs *
mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct mxl862xx_priv *priv = dp->ds->priv;
int port = dp->index;
switch (port) {
case 9 ... 16:
if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) {
dev_warn_once(dp->ds->dev,
"SerDes PCS unsupported on old firmware.\n");
return NULL;
}
return &priv->serdes_ports[port - 9].pcs;
default:
return NULL;
}
}
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
}
static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
}
static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phydev,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
}
const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
.mac_config = mxl862xx_phylink_mac_config,
.mac_link_down = mxl862xx_phylink_mac_link_down,
.mac_link_up = mxl862xx_phylink_mac_link_up,
.mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
};
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __MXL862XX_PHYLINK_H
#define __MXL862XX_PHYLINK_H
#include <linux/phylink.h>
#include "mxl862xx.h"
#define MXL862XX_SERDES_SLOT(port) \
(((port) - MXL862XX_FIRST_SERDES_PORT) % MXL862XX_SERDES_SLOTS)
#define MXL862XX_SERDES_PORT_ID(port) \
(((port) - MXL862XX_FIRST_SERDES_PORT) / MXL862XX_SERDES_SLOTS)
extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config);
void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
int port);
#endif /* __MXL862XX_PHYLINK_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,343 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __MXL862XX_H
#define __MXL862XX_H
#include <asm/byteorder.h>
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
struct mxl862xx_priv;
#define MXL862XX_MAX_PORTS 17
#define MXL862XX_FIRST_SERDES_PORT 9
#define MXL862XX_SERDES_SLOTS 4
#define MXL862XX_DEFAULT_BRIDGE 0
#define MXL862XX_MAX_BRIDGES 48
#define MXL862XX_MAX_BRIDGE_PORTS 128
#define MXL862XX_TOTAL_EVLAN_ENTRIES 1024
#define MXL862XX_TOTAL_VF_ENTRIES 1024
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
/**
* mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
* @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
* @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
*/
static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port)
{
map[port / 16] |= cpu_to_le16(BIT(port % 16));
}
/**
* mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware portmap
* @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
* @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
*/
static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port)
{
map[port / 16] &= ~cpu_to_le16(BIT(port % 16));
}
/**
* mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no
* bits set
* @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
*
* Return: true if every word in @map is zero.
*/
static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map)
{
int i;
for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
if (map[i])
return false;
return true;
}
/**
* struct mxl862xx_vf_vid - Per-VID entry within a VLAN Filter block
* @list: Linked into &mxl862xx_vf_block.vids
* @vid: VLAN ID
* @index: Entry index within the VLAN Filter HW block
* @untagged: Strip tag on egress for this VID (drives EVLAN tag-stripping)
*/
struct mxl862xx_vf_vid {
struct list_head list;
u16 vid;
u16 index;
bool untagged;
};
/**
* struct mxl862xx_vf_block - Per-port VLAN Filter block
* @allocated: Whether the HW block has been allocated via VLANFILTER_ALLOC
* @block_id: HW VLAN Filter block ID from VLANFILTER_ALLOC
* @block_size: Total entries allocated in this block
* @active_count: Number of ALLOW entries at indices [0, active_count).
* The bridge port config sends max(active_count, 1) as
* block_size to narrow the HW scan window.
* discard_unmatched_tagged handles frames outside this range.
* @vids: List of &mxl862xx_vf_vid entries programmed in this block
*/
struct mxl862xx_vf_block {
bool allocated;
u16 block_id;
u16 block_size;
u16 active_count;
struct list_head vids;
};
/**
* struct mxl862xx_evlan_block - Per-port per-direction extended VLAN block
* @allocated: Whether the HW block has been allocated via EXTENDEDVLAN_ALLOC.
* Guards alloc/free idempotency--the block_id is only valid
* while allocated is true.
* @in_use: Whether the EVLAN engine should be enabled for this block
* on the bridge port (sent as the enable flag in
* set_bridge_port). Can be false while allocated is still
* true -- e.g. when all egress VIDs are removed (idx == 0 in
* evlan_program_egress) the block stays allocated for
* potential reuse, but the engine is disabled so an empty
* rule set does not discard all traffic.
* @block_id: HW block ID from EXTENDEDVLAN_ALLOC
* @block_size: Total entries allocated
* @n_active: Number of HW entries currently written. The bridge port
* config sends this as the egress scan window, so entries
* beyond n_active are never scanned. Always equals
* block_size for ingress blocks (fixed catchall rules).
*/
struct mxl862xx_evlan_block {
bool allocated;
bool in_use;
u16 block_id;
u16 block_size;
u16 n_active;
};
/**
* struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
* @rx_packets: total received packets
* @tx_packets: total transmitted packets
* @rx_bytes: total received bytes
* @tx_bytes: total transmitted bytes
* @rx_errors: total receive errors
* @tx_errors: total transmit errors
* @rx_dropped: total received packets dropped
* @tx_dropped: total transmitted packets dropped
* @multicast: total received multicast packets
* @collisions: total transmit collisions
* @rx_length_errors: received length errors (undersize + oversize)
* @rx_crc_errors: received FCS errors
* @rx_frame_errors: received alignment errors
* @prev_rx_good_pkts: previous snapshot of rx good packet counter
* @prev_tx_good_pkts: previous snapshot of tx good packet counter
* @prev_rx_good_bytes: previous snapshot of rx good byte counter
* @prev_tx_good_bytes: previous snapshot of tx good byte counter
* @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
* @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
* error counter
* @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
* error counter
* @prev_rx_align_error_pkts: previous snapshot of rx alignment
* error counter
* @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
* @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
* @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
* discard counter
* @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
* discard counter
* @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
* counter
* @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
* @prev_tx_coll_count: previous snapshot of tx collision counter
*
* The firmware RMON counters are 32-bit free-running (64-bit for byte
* counters). This structure holds 64-bit accumulators alongside the
* previous raw snapshot so that deltas can be computed across polls,
* handling 32-bit wrap correctly via unsigned subtraction.
*/
struct mxl862xx_port_stats {
u64 rx_packets;
u64 tx_packets;
u64 rx_bytes;
u64 tx_bytes;
u64 rx_errors;
u64 tx_errors;
u64 rx_dropped;
u64 tx_dropped;
u64 multicast;
u64 collisions;
u64 rx_length_errors;
u64 rx_crc_errors;
u64 rx_frame_errors;
u32 prev_rx_good_pkts;
u32 prev_tx_good_pkts;
u64 prev_rx_good_bytes;
u64 prev_tx_good_bytes;
u32 prev_rx_fcserror_pkts;
u32 prev_rx_under_size_error_pkts;
u32 prev_rx_oversize_error_pkts;
u32 prev_rx_align_error_pkts;
u32 prev_tx_dropped_pkts;
u32 prev_rx_dropped_pkts;
u32 prev_rx_evlan_discard_pkts;
u32 prev_mtu_exceed_discard_pkts;
u32 prev_tx_acm_dropped_pkts;
u32 prev_rx_multicast_pkts;
u32 prev_tx_coll_count;
};
/**
* struct mxl862xx_port - per-port state tracked by the driver
* @priv: back-pointer to switch private data; needed by
* deferred work handlers to access ds and priv
* @fid: firmware FID for the permanent single-port bridge;
* kept alive for the lifetime of the port so traffic is
* never forwarded while the port is unbridged
* @flood_block: bitmask of firmware meter indices that are currently
* rate-limiting flood traffic on this port (zero-rate
* meters used to block flooding)
* @learning: true when address learning is enabled on this port
* @setup_done: set at end of port_setup, cleared at start of
* port_teardown; guards deferred work against
* acting on torn-down state
* @pvid: port VLAN ID (native VLAN) assigned to untagged traffic
* @vlan_filtering: true when VLAN filtering is enabled on this port
* @vf: per-port VLAN Filter block state
* @ingress_evlan: ingress extended VLAN block state
* @egress_evlan: egress extended VLAN block state
* @host_flood_uc: desired host unicast flood state (true = flood);
* updated atomically by port_set_host_flood, consumed
* by the deferred host_flood_work
* @host_flood_mc: desired host multicast flood state (true = flood)
* @host_flood_work: deferred work for applying host flood changes;
* port_set_host_flood runs in atomic context (under
* netif_addr_lock) so firmware calls must be deferred.
* The worker acquires rtnl_lock() to serialize with
* DSA callbacks and checks @setup_done to avoid
* acting on torn-down ports.
* @stats: 64-bit accumulated hardware statistics; updated
* periodically by the stats polling work
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
u16 fid;
unsigned long flood_block;
bool learning;
bool setup_done;
u16 pvid;
bool vlan_filtering;
struct mxl862xx_vf_block vf;
struct mxl862xx_evlan_block ingress_evlan;
struct mxl862xx_evlan_block egress_evlan;
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
struct mxl862xx_port_stats stats;
spinlock_t stats_lock; /* protects stats accumulators */
};
/**
* struct mxl862xx_pcs - link SerDes interfaces to bridge ports
* @pcs: &struct phylink_pcs instance
* @priv: pointer to &struct mxl862xx_priv
* @serdes_id: SerDes instance index (0 or 1)
* @slot: slot within the SerDes (0-3 for QSGMII/QUSXGMII, 0 otherwise)
* @interface: cached PHY interface, last value passed to pcs_config().
* %PHY_INTERFACE_MODE_NA before the first successful
* pcs_config(). Used by pcs_an_restart() to populate the
* firmware command and by pcs_disable() to skip the
* firmware power-down for shared (QSGMII/QUSXGMII) modes.
*/
struct mxl862xx_pcs {
struct phylink_pcs pcs;
struct mxl862xx_priv *priv;
int serdes_id;
int slot;
phy_interface_t interface;
};
/**
* struct mxl862xx_fw_version - firmware version for comparison and display
* @major: firmware major version
* @minor: firmware minor version
* @revision: firmware revision number
*/
struct mxl862xx_fw_version {
u8 major;
u8 minor;
u16 revision;
};
#define MXL862XX_FW_VER(maj, min, rev) \
(((u32)(maj) << 24) | ((u32)(min) << 16) | (rev))
#define MXL862XX_FW_VER_MIN(priv, maj, min, rev) \
(MXL862XX_FW_VER((priv)->fw_version.major, (priv)->fw_version.minor, \
(priv)->fw_version.revision) >= \
MXL862XX_FW_VER(maj, min, rev))
/* Bit indices for struct mxl862xx_priv::flags */
#define MXL862XX_FLAG_CRC_ERR 0
#define MXL862XX_FLAG_WORK_STOPPED 1
/**
* struct mxl862xx_priv - driver private data for an MxL862xx switch
* @ds: pointer to the DSA switch instance
* @mdiodev: MDIO device used to communicate with the switch firmware
* @crc_err_work: deferred work for shutting down all ports on MDIO CRC
* errors
* @flags: atomic status flags; %MXL862XX_FLAG_CRC_ERR is set
* before CRC-triggered shutdown and cleared after;
* %MXL862XX_FLAG_WORK_STOPPED is set before cancelling
* stats_work to prevent rescheduling during teardown
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
* @fw_version: cached firmware version, populated at probe and
* compared with MXL862XX_FW_VER_MIN()
* @serdes_ports: SerDes interfaces incl. sub-interfaces in case of
* 10G_QXGMII or QSGMII
* @serdes_refcount: per-XPCS count of sub-ports enabled by phylink;
* pcs_disable powers an XPCS down when the count
* reaches zero. Protected by @serdes_lock.
* @serdes_lock: serializes the @serdes_refcount transitions with
* the XPCS power-down so a sibling sub-port enable
* cannot race a power-down to zero
* @ports: per-port state, indexed by switch port number
* @bridges: maps DSA bridge number to firmware bridge ID;
* zero means no firmware bridge allocated for that
* DSA bridge number. Indexed by dsa_bridge.num
* (0 .. ds->max_num_bridges).
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
struct mxl862xx_priv {
struct dsa_switch *ds;
struct mdio_device *mdiodev;
struct work_struct crc_err_work;
unsigned long flags;
u16 drop_meter;
struct mxl862xx_fw_version fw_version;
struct mxl862xx_pcs serdes_ports[8];
int serdes_refcount[2];
struct mutex serdes_lock;
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
u16 evlan_ingress_size;
u16 evlan_egress_size;
u16 vf_block_size;
struct delayed_work stats_work;
};
#endif /* __MXL862XX_H */