difos/target/linux/mediatek/files-6.12/drivers/net/dsa/an8855.c
Daniel Golle 16c1e1b9ba kernel/mediatek: Create kernel files for v6.12 (from v6.6)
This is an automatically generated commit.

When doing `git bisect`, consider `git bisect --skip`.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
2025-05-26 16:58:04 +01:00

2308 lines
64 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Airoha AN8855 DSA Switch driver
* Copyright (C) 2023 Min Yao <min.yao@airoha.com>
* Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/gpio/consumer.h>
#include <linux/if_bridge.h>
#include <linux/iopoll.h>
#include <linux/netdevice.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <net/dsa.h>
#include "an8855.h"
static const struct an8855_mib_desc an8855_mib[] = {
MIB_DESC(1, AN8855_PORT_MIB_TX_DROP, "TxDrop"),
MIB_DESC(1, AN8855_PORT_MIB_TX_CRC_ERR, "TxCrcErr"),
MIB_DESC(1, AN8855_PORT_MIB_TX_COLLISION, "TxCollision"),
MIB_DESC(1, AN8855_PORT_MIB_TX_OVERSIZE_DROP, "TxOversizeDrop"),
MIB_DESC(2, AN8855_PORT_MIB_TX_BAD_PKT_BYTES, "TxBadPktBytes"),
MIB_DESC(1, AN8855_PORT_MIB_RX_DROP, "RxDrop"),
MIB_DESC(1, AN8855_PORT_MIB_RX_FILTERING, "RxFiltering"),
MIB_DESC(1, AN8855_PORT_MIB_RX_CRC_ERR, "RxCrcErr"),
MIB_DESC(1, AN8855_PORT_MIB_RX_CTRL_DROP, "RxCtrlDrop"),
MIB_DESC(1, AN8855_PORT_MIB_RX_INGRESS_DROP, "RxIngressDrop"),
MIB_DESC(1, AN8855_PORT_MIB_RX_ARL_DROP, "RxArlDrop"),
MIB_DESC(1, AN8855_PORT_MIB_FLOW_CONTROL_DROP, "FlowControlDrop"),
MIB_DESC(1, AN8855_PORT_MIB_WRED_DROP, "WredDrop"),
MIB_DESC(1, AN8855_PORT_MIB_MIRROR_DROP, "MirrorDrop"),
MIB_DESC(2, AN8855_PORT_MIB_RX_BAD_PKT_BYTES, "RxBadPktBytes"),
MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_SAMPLING_PKT_DROP, "RxsFlowSamplingPktDrop"),
MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_TOTAL_PKT_DROP, "RxsFlowTotalPktDrop"),
MIB_DESC(1, AN8855_PORT_MIB_PORT_CONTROL_DROP, "PortControlDrop"),
};
static int
an8855_mib_init(struct an8855_priv *priv)
{
int ret;
ret = regmap_write(priv->regmap, AN8855_MIB_CCR,
AN8855_CCR_MIB_ENABLE);
if (ret)
return ret;
return regmap_write(priv->regmap, AN8855_MIB_CCR,
AN8855_CCR_MIB_ACTIVATE);
}
static void an8855_fdb_write(struct an8855_priv *priv, u16 vid,
u8 port_mask, const u8 *mac,
bool add) __must_hold(&priv->reg_mutex)
{
u32 mac_reg[2] = { };
u32 reg;
mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC0, mac[0]);
mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC1, mac[1]);
mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC2, mac[2]);
mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC3, mac[3]);
mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC4, mac[4]);
mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC5, mac[5]);
regmap_bulk_write(priv->regmap, AN8855_ATA1, mac_reg,
ARRAY_SIZE(mac_reg));
reg = AN8855_ATWD_IVL;
if (add)
reg |= AN8855_ATWD_VLD;
reg |= FIELD_PREP(AN8855_ATWD_VID, vid);
reg |= FIELD_PREP(AN8855_ATWD_FID, AN8855_FID_BRIDGED);
regmap_write(priv->regmap, AN8855_ATWD, reg);
regmap_write(priv->regmap, AN8855_ATWD2,
FIELD_PREP(AN8855_ATWD2_PORT, port_mask));
}
static void an8855_fdb_read(struct an8855_priv *priv, struct an8855_fdb *fdb)
{
u32 reg[4];
regmap_bulk_read(priv->regmap, AN8855_ATRD0, reg,
ARRAY_SIZE(reg));
fdb->live = FIELD_GET(AN8855_ATRD0_LIVE, reg[0]);
fdb->type = FIELD_GET(AN8855_ATRD0_TYPE, reg[0]);
fdb->ivl = FIELD_GET(AN8855_ATRD0_IVL, reg[0]);
fdb->vid = FIELD_GET(AN8855_ATRD0_VID, reg[0]);
fdb->fid = FIELD_GET(AN8855_ATRD0_FID, reg[0]);
fdb->aging = FIELD_GET(AN8855_ATRD1_AGING, reg[1]);
fdb->port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, reg[3]);
fdb->mac[0] = FIELD_GET(AN8855_ATRD2_MAC0, reg[2]);
fdb->mac[1] = FIELD_GET(AN8855_ATRD2_MAC1, reg[2]);
fdb->mac[2] = FIELD_GET(AN8855_ATRD2_MAC2, reg[2]);
fdb->mac[3] = FIELD_GET(AN8855_ATRD2_MAC3, reg[2]);
fdb->mac[4] = FIELD_GET(AN8855_ATRD1_MAC4, reg[1]);
fdb->mac[5] = FIELD_GET(AN8855_ATRD1_MAC5, reg[1]);
fdb->noarp = !!FIELD_GET(AN8855_ATRD0_ARP, reg[0]);
}
static int an8855_fdb_cmd(struct an8855_priv *priv, u32 cmd,
u32 *rsp) __must_hold(&priv->reg_mutex)
{
u32 val;
int ret;
/* Set the command operating upon the MAC address entries */
val = AN8855_ATC_BUSY | cmd;
ret = regmap_write(priv->regmap, AN8855_ATC, val);
if (ret)
return ret;
ret = regmap_read_poll_timeout(priv->regmap, AN8855_ATC, val,
!(val & AN8855_ATC_BUSY), 20, 200000);
if (ret)
return ret;
if (rsp)
*rsp = val;
return 0;
}
static void
an8855_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct an8855_priv *priv = ds->priv;
bool learning = false;
u32 stp_state;
switch (state) {
case BR_STATE_DISABLED:
stp_state = AN8855_STP_DISABLED;
break;
case BR_STATE_BLOCKING:
stp_state = AN8855_STP_BLOCKING;
break;
case BR_STATE_LISTENING:
stp_state = AN8855_STP_LISTENING;
break;
case BR_STATE_LEARNING:
stp_state = AN8855_STP_LEARNING;
learning = dp->learning;
break;
case BR_STATE_FORWARDING:
learning = dp->learning;
fallthrough;
default:
stp_state = AN8855_STP_FORWARDING;
break;
}
regmap_update_bits(priv->regmap, AN8855_SSP_P(port),
AN8855_FID_PST_MASK(AN8855_FID_BRIDGED),
AN8855_FID_PST_VAL(AN8855_FID_BRIDGED, stp_state));
regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
learning ? 0 : AN8855_SA_DIS);
}
static void an8855_port_fast_age(struct dsa_switch *ds, int port)
{
struct an8855_priv *priv = ds->priv;
int ret;
/* Set to clean Dynamic entry */
ret = regmap_write(priv->regmap, AN8855_ATA2, AN8855_ATA2_TYPE);
if (ret)
return;
/* Set Port */
ret = regmap_write(priv->regmap, AN8855_ATWD2,
FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
if (ret)
return;
/* Flush Dynamic entry at port */
an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_TYPE_PORT) |
AN8855_FDB_FLUSH, NULL);
}
static int an8855_update_port_member(struct dsa_switch *ds, int port,
const struct net_device *bridge_dev,
bool join)
{
struct an8855_priv *priv = ds->priv;
bool isolated, other_isolated;
struct dsa_port *dp;
u32 port_mask = 0;
int ret;
isolated = !!(priv->port_isolated_map & BIT(port));
dsa_switch_for_each_user_port(dp, ds) {
if (dp->index == port)
continue;
if (!dsa_port_offloads_bridge_dev(dp, bridge_dev))
continue;
other_isolated = !!(priv->port_isolated_map & BIT(dp->index));
port_mask |= BIT(dp->index);
/* Add/remove this port to the portvlan mask of the other
* ports in the bridge
*/
if (join && !(isolated && other_isolated))
ret = regmap_set_bits(priv->regmap,
AN8855_PORTMATRIX_P(dp->index),
FIELD_PREP(AN8855_USER_PORTMATRIX,
BIT(port)));
else
ret = regmap_clear_bits(priv->regmap,
AN8855_PORTMATRIX_P(dp->index),
FIELD_PREP(AN8855_USER_PORTMATRIX,
BIT(port)));
if (ret)
return ret;
}
/* Add/remove all other ports to this port's portvlan mask */
return regmap_update_bits(priv->regmap, AN8855_PORTMATRIX_P(port),
AN8855_USER_PORTMATRIX,
join ? port_mask : ~port_mask);
}
static int an8855_port_pre_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
BR_BCAST_FLOOD | BR_ISOLATED))
return -EINVAL;
return 0;
}
static int an8855_port_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack)
{
struct an8855_priv *priv = ds->priv;
int ret;
if (flags.mask & BR_LEARNING) {
ret = regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
flags.val & BR_LEARNING ? 0 : AN8855_SA_DIS);
if (ret)
return ret;
}
if (flags.mask & BR_FLOOD) {
ret = regmap_update_bits(priv->regmap, AN8855_UNUF, BIT(port),
flags.val & BR_FLOOD ? BIT(port) : 0);
if (ret)
return ret;
}
if (flags.mask & BR_MCAST_FLOOD) {
ret = regmap_update_bits(priv->regmap, AN8855_UNMF, BIT(port),
flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_UNIPMF, BIT(port),
flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
if (ret)
return ret;
}
if (flags.mask & BR_BCAST_FLOOD) {
ret = regmap_update_bits(priv->regmap, AN8855_BCF, BIT(port),
flags.val & BR_BCAST_FLOOD ? BIT(port) : 0);
if (ret)
return ret;
}
if (flags.mask & BR_ISOLATED) {
struct dsa_port *dp = dsa_to_port(ds, port);
struct net_device *bridge_dev = dsa_port_bridge_dev_get(dp);
if (flags.val & BR_ISOLATED)
priv->port_isolated_map |= BIT(port);
else
priv->port_isolated_map &= ~BIT(port);
ret = an8855_update_port_member(ds, port, bridge_dev, true);
if (ret)
return ret;
}
return 0;
}
static int an8855_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
{
struct an8855_priv *priv = ds->priv;
u32 age_count, age_unit, val;
/* Convert msec in AN8855_L2_AGING_MS_CONSTANT counter */
val = msecs / AN8855_L2_AGING_MS_CONSTANT;
/* Derive the count unit */
age_unit = val / FIELD_MAX(AN8855_AGE_UNIT);
/* Get the count in unit, age_unit is always incremented by 1 internally */
age_count = val / (age_unit + 1);
return regmap_update_bits(priv->regmap, AN8855_AAC,
AN8855_AGE_CNT | AN8855_AGE_UNIT,
FIELD_PREP(AN8855_AGE_CNT, age_count) |
FIELD_PREP(AN8855_AGE_UNIT, age_unit));
}
static int an8855_port_bridge_join(struct dsa_switch *ds, int port,
struct dsa_bridge bridge,
bool *tx_fwd_offload,
struct netlink_ext_ack *extack)
{
struct an8855_priv *priv = ds->priv;
int ret;
ret = an8855_update_port_member(ds, port, bridge.dev, true);
if (ret)
return ret;
/* Set to fallback mode for independent VLAN learning if in a bridge */
return regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
AN8855_PORT_VLAN,
FIELD_PREP(AN8855_PORT_VLAN,
AN8855_PORT_FALLBACK_MODE));
}
static void an8855_port_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge)
{
struct an8855_priv *priv = ds->priv;
an8855_update_port_member(ds, port, bridge.dev, false);
/* When a port is removed from the bridge, the port would be set up
* back to the default as is at initial boot which is a VLAN-unaware
* port.
*/
regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
AN8855_PORT_VLAN,
FIELD_PREP(AN8855_PORT_VLAN,
AN8855_PORT_MATRIX_MODE));
}
static int an8855_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
struct an8855_priv *priv = ds->priv;
u8 port_mask = BIT(port);
int ret;
/* Set the vid to the port vlan id if no vid is set */
if (!vid)
vid = AN8855_PORT_VID_DEFAULT;
mutex_lock(&priv->reg_mutex);
an8855_fdb_write(priv, vid, port_mask, addr, true);
ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int an8855_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
struct an8855_priv *priv = ds->priv;
u8 port_mask = BIT(port);
int ret;
/* Set the vid to the port vlan id if no vid is set */
if (!vid)
vid = AN8855_PORT_VID_DEFAULT;
mutex_lock(&priv->reg_mutex);
an8855_fdb_write(priv, vid, port_mask, addr, false);
ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int an8855_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
struct an8855_priv *priv = ds->priv;
int banks, count = 0;
u32 rsp;
int ret;
int i;
mutex_lock(&priv->reg_mutex);
/* Load search port */
ret = regmap_write(priv->regmap, AN8855_ATWD2,
FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
if (ret)
goto exit;
ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
AN8855_FDB_START, &rsp);
if (ret < 0)
goto exit;
do {
/* From response get the number of banks to read, exit if 0 */
banks = FIELD_GET(AN8855_ATC_HIT, rsp);
if (!banks)
break;
/* Each banks have 4 entry */
for (i = 0; i < 4; i++) {
struct an8855_fdb _fdb = { };
count++;
/* Check if bank is present */
if (!(banks & BIT(i)))
continue;
/* Select bank entry index */
ret = regmap_write(priv->regmap, AN8855_ATRDS,
FIELD_PREP(AN8855_ATRD_SEL, i));
if (ret)
break;
/* wait 1ms for the bank entry to be filled */
usleep_range(1000, 1500);
an8855_fdb_read(priv, &_fdb);
if (!_fdb.live)
continue;
ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, data);
if (ret < 0)
break;
}
/* Stop if reached max FDB number */
if (count >= AN8855_NUM_FDB_RECORDS)
break;
/* Read next bank */
ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
AN8855_FDB_NEXT, &rsp);
if (ret < 0)
break;
} while (true);
exit:
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int an8855_vlan_cmd(struct an8855_priv *priv, enum an8855_vlan_cmd cmd,
u16 vid) __must_hold(&priv->reg_mutex)
{
u32 val;
int ret;
val = AN8855_VTCR_BUSY | FIELD_PREP(AN8855_VTCR_FUNC, cmd) |
FIELD_PREP(AN8855_VTCR_VID, vid);
ret = regmap_write(priv->regmap, AN8855_VTCR, val);
if (ret)
return ret;
return regmap_read_poll_timeout(priv->regmap, AN8855_VTCR, val,
!(val & AN8855_VTCR_BUSY), 20, 200000);
}
static int an8855_vlan_add(struct an8855_priv *priv, u8 port, u16 vid,
bool untagged) __must_hold(&priv->reg_mutex)
{
u32 port_mask;
u32 val;
int ret;
/* Fetch entry */
ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
if (ret)
return ret;
ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
if (ret)
return ret;
port_mask = FIELD_GET(AN8855_VA0_PORT, val) | BIT(port);
/* Validate the entry with independent learning, create egress tag per
* VLAN and joining the port as one of the port members.
*/
val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
FIELD_PREP(AN8855_VA0_PORT, port_mask) |
FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED);
ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
if (ret)
return ret;
ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
if (ret)
return ret;
/* CPU port is always taken as a tagged port for serving more than one
* VLANs across and also being applied with egress type stack mode for
* that VLAN tags would be appended after hardware special tag used as
* DSA tag.
*/
if (port == AN8855_CPU_PORT)
val = AN8855_VLAN_EGRESS_STACK;
/* Decide whether adding tag or not for those outgoing packets from the
* port inside the VLAN.
*/
else
val = untagged ? AN8855_VLAN_EGRESS_UNTAG : AN8855_VLAN_EGRESS_TAG;
ret = regmap_update_bits(priv->regmap, AN8855_VAWD0,
AN8855_VA0_ETAG_PORT_MASK(port),
AN8855_VA0_ETAG_PORT_VAL(port, val));
if (ret)
return ret;
/* Flush result to hardware */
return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
}
static int an8855_vlan_del(struct an8855_priv *priv, u8 port,
u16 vid) __must_hold(&priv->reg_mutex)
{
u32 port_mask;
u32 val;
int ret;
/* Fetch entry */
ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
if (ret)
return ret;
ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
if (ret)
return ret;
port_mask = FIELD_GET(AN8855_VA0_PORT, val) & ~BIT(port);
if (!(val & AN8855_VA0_VLAN_VALID)) {
dev_err(priv->dev, "Cannot be deleted due to invalid entry\n");
return -EINVAL;
}
if (port_mask) {
val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
FIELD_PREP(AN8855_VA0_PORT, port_mask);
ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
if (ret)
return ret;
} else {
ret = regmap_write(priv->regmap, AN8855_VAWD0, 0);
if (ret)
return ret;
}
ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
if (ret)
return ret;
/* Flush result to hardware */
return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
}
static int an8855_port_set_vlan_mode(struct an8855_priv *priv, int port,
enum an8855_port_mode port_mode,
enum an8855_vlan_port_eg_tag eg_tag,
enum an8855_vlan_port_attr vlan_attr,
enum an8855_vlan_port_acc_frm acc_frm)
{
int ret;
ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
AN8855_PORT_VLAN,
FIELD_PREP(AN8855_PORT_VLAN, port_mode));
if (ret)
return ret;
return regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
AN8855_PVC_EG_TAG | AN8855_VLAN_ATTR | AN8855_ACC_FRM,
FIELD_PREP(AN8855_PVC_EG_TAG, eg_tag) |
FIELD_PREP(AN8855_VLAN_ATTR, vlan_attr) |
FIELD_PREP(AN8855_ACC_FRM, acc_frm));
}
static int an8855_port_set_pid(struct an8855_priv *priv, int port,
u16 pid)
{
int ret;
ret = regmap_update_bits(priv->regmap, AN8855_PPBV1_P(port),
AN8855_PPBV_G0_PORT_VID,
FIELD_PREP(AN8855_PPBV_G0_PORT_VID, pid));
if (ret)
return ret;
return regmap_update_bits(priv->regmap, AN8855_PVID_P(port),
AN8855_G0_PORT_VID,
FIELD_PREP(AN8855_G0_PORT_VID, pid));
}
static int an8855_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering,
struct netlink_ext_ack *extack)
{
struct an8855_priv *priv = ds->priv;
u32 val;
int ret;
/* The port is being kept as VLAN-unaware port when bridge is
* set up with vlan_filtering not being set, Otherwise, the
* port and the corresponding CPU port is required the setup
* for becoming a VLAN-aware port.
*/
if (vlan_filtering) {
u32 acc_frm;
/* CPU port is set to fallback mode to let untagged
* frames pass through.
*/
ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
AN8855_PORT_FALLBACK_MODE,
AN8855_VLAN_EG_CONSISTENT,
AN8855_VLAN_USER,
AN8855_VLAN_ACC_ALL);
if (ret)
return ret;
ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
if (ret)
return ret;
/* Only accept tagged frames if PVID is not set */
if (FIELD_GET(AN8855_G0_PORT_VID, val) != AN8855_PORT_VID_DEFAULT)
acc_frm = AN8855_VLAN_ACC_TAGGED;
else
acc_frm = AN8855_VLAN_ACC_ALL;
/* Trapped into security mode allows packet forwarding through VLAN
* table lookup.
* Set the port as a user port which is to be able to recognize VID
* from incoming packets before fetching entry within the VLAN table.
*/
ret = an8855_port_set_vlan_mode(priv, port,
AN8855_PORT_SECURITY_MODE,
AN8855_VLAN_EG_DISABLED,
AN8855_VLAN_USER,
acc_frm);
if (ret)
return ret;
} else {
bool disable_cpu_vlan = true;
struct dsa_port *dp;
u32 port_mode;
/* This is called after .port_bridge_leave when leaving a VLAN-aware
* bridge. Don't set standalone ports to fallback mode.
*/
if (dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
port_mode = AN8855_PORT_FALLBACK_MODE;
else
port_mode = AN8855_PORT_MATRIX_MODE;
/* When a port is removed from the bridge, the port would be set up
* back to the default as is at initial boot which is a VLAN-unaware
* port.
*/
ret = an8855_port_set_vlan_mode(priv, port, port_mode,
AN8855_VLAN_EG_CONSISTENT,
AN8855_VLAN_TRANSPARENT,
AN8855_VLAN_ACC_ALL);
if (ret)
return ret;
/* Restore default PVID */
ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
if (ret)
return ret;
dsa_switch_for_each_user_port(dp, ds) {
if (dsa_port_is_vlan_filtering(dp)) {
disable_cpu_vlan = false;
break;
}
}
if (disable_cpu_vlan) {
ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
AN8855_PORT_MATRIX_MODE,
AN8855_VLAN_EG_CONSISTENT,
AN8855_VLAN_USER,
AN8855_VLAN_ACC_ALL);
if (ret)
return ret;
}
}
return 0;
}
static int an8855_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
struct an8855_priv *priv = ds->priv;
u32 val;
int ret;
mutex_lock(&priv->reg_mutex);
ret = an8855_vlan_add(priv, port, vlan->vid, untagged);
mutex_unlock(&priv->reg_mutex);
if (ret)
return ret;
if (pvid) {
/* Accept all frames if PVID is set */
regmap_update_bits(priv->regmap, AN8855_PVC_P(port), AN8855_ACC_FRM,
FIELD_PREP(AN8855_ACC_FRM, AN8855_VLAN_ACC_ALL));
/* Only configure PVID if VLAN filtering is enabled */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
ret = an8855_port_set_pid(priv, port, vlan->vid);
if (ret)
return ret;
}
} else if (vlan->vid) {
ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
if (ret)
return ret;
if (FIELD_GET(AN8855_G0_PORT_VID, val) != vlan->vid)
return 0;
/* This VLAN is overwritten without PVID, so unset it */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
AN8855_ACC_FRM,
FIELD_PREP(AN8855_ACC_FRM,
AN8855_VLAN_ACC_TAGGED));
if (ret)
return ret;
}
ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
if (ret)
return ret;
}
return 0;
}
static int an8855_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct an8855_priv *priv = ds->priv;
u32 val;
int ret;
mutex_lock(&priv->reg_mutex);
ret = an8855_vlan_del(priv, port, vlan->vid);
mutex_unlock(&priv->reg_mutex);
if (ret)
return ret;
ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
if (ret)
return ret;
/* PVID is being restored to the default whenever the PVID port
* is being removed from the VLAN.
*/
if (FIELD_GET(AN8855_G0_PORT_VID, val) == vlan->vid) {
/* Only accept tagged frames if the port is VLAN-aware */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
AN8855_ACC_FRM,
FIELD_PREP(AN8855_ACC_FRM,
AN8855_VLAN_ACC_TAGGED));
if (ret)
return ret;
}
ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
if (ret)
return ret;
}
return 0;
}
static int
an8855_port_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db)
{
struct an8855_priv *priv = ds->priv;
const u8 *addr = mdb->addr;
u16 vid = mdb->vid;
u8 port_mask = 0;
u32 val;
int ret;
/* Set the vid to the port vlan id if no vid is set */
if (!vid)
vid = AN8855_PORT_VID_DEFAULT;
mutex_lock(&priv->reg_mutex);
an8855_fdb_write(priv, vid, 0, addr, false);
if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
if (ret)
goto exit;
port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
}
port_mask |= BIT(port);
an8855_fdb_write(priv, vid, port_mask, addr, true);
ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
exit:
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int
an8855_port_mdb_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db)
{
struct an8855_priv *priv = ds->priv;
const u8 *addr = mdb->addr;
u16 vid = mdb->vid;
u8 port_mask = 0;
u32 val;
int ret;
/* Set the vid to the port vlan id if no vid is set */
if (!vid)
vid = AN8855_PORT_VID_DEFAULT;
mutex_lock(&priv->reg_mutex);
an8855_fdb_write(priv, vid, 0, addr, 0);
if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
if (ret)
goto exit;
port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
}
port_mask &= ~BIT(port);
an8855_fdb_write(priv, vid, port_mask, addr, port_mask ? true : false);
ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
exit:
mutex_unlock(&priv->reg_mutex);
return ret;
}
static int
an8855_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
{
struct an8855_priv *priv = ds->priv;
int length;
u32 val;
/* When a new MTU is set, DSA always set the CPU port's MTU to the
* largest MTU of the slave ports. Because the switch only has a global
* RX length register, only allowing CPU port here is enough.
*/
if (!dsa_is_cpu_port(ds, port))
return 0;
/* RX length also includes Ethernet header, MTK tag, and FCS length */
length = new_mtu + ETH_HLEN + MTK_TAG_LEN + ETH_FCS_LEN;
if (length <= 1522)
val = AN8855_MAX_RX_PKT_1518_1522;
else if (length <= 1536)
val = AN8855_MAX_RX_PKT_1536;
else if (length <= 1552)
val = AN8855_MAX_RX_PKT_1552;
else if (length <= 3072)
val = AN8855_MAX_RX_JUMBO_3K;
else if (length <= 4096)
val = AN8855_MAX_RX_JUMBO_4K;
else if (length <= 5120)
val = AN8855_MAX_RX_JUMBO_5K;
else if (length <= 6144)
val = AN8855_MAX_RX_JUMBO_6K;
else if (length <= 7168)
val = AN8855_MAX_RX_JUMBO_7K;
else if (length <= 8192)
val = AN8855_MAX_RX_JUMBO_8K;
else if (length <= 9216)
val = AN8855_MAX_RX_JUMBO_9K;
else if (length <= 12288)
val = AN8855_MAX_RX_JUMBO_12K;
else if (length <= 15360)
val = AN8855_MAX_RX_JUMBO_15K;
else
val = AN8855_MAX_RX_JUMBO_16K;
/* Enable JUMBO packet */
if (length > 1552)
val |= AN8855_MAX_RX_PKT_JUMBO;
return regmap_update_bits(priv->regmap, AN8855_GMACCR,
AN8855_MAX_RX_JUMBO | AN8855_MAX_RX_PKT_LEN,
val);
}
static int
an8855_port_max_mtu(struct dsa_switch *ds, int port)
{
return AN8855_MAX_MTU;
}
static void
an8855_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(an8855_mib); i++)
ethtool_puts(&data, an8855_mib[i].name);
}
static void
an8855_read_port_stats(struct an8855_priv *priv, int port, u32 offset, u8 size,
uint64_t *data)
{
u32 val, reg = AN8855_PORT_MIB_COUNTER(port) + offset;
regmap_read(priv->regmap, reg, &val);
*data = val;
if (size == 2) {
regmap_read(priv->regmap, reg + 4, &val);
*data |= (u64)val << 32;
}
}
static void
an8855_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
struct an8855_priv *priv = ds->priv;
const struct an8855_mib_desc *mib;
int i;
for (i = 0; i < ARRAY_SIZE(an8855_mib); i++) {
mib = &an8855_mib[i];
an8855_read_port_stats(priv, port, mib->offset, mib->size,
data + i);
}
}
static int
an8855_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
if (sset != ETH_SS_STATS)
return 0;
return ARRAY_SIZE(an8855_mib);
}
static void
an8855_get_eth_mac_stats(struct dsa_switch *ds, int port,
struct ethtool_eth_mac_stats *mac_stats)
{
struct an8855_priv *priv = ds->priv;
/* MIB counter doesn't provide a FramesTransmittedOK but instead
* provide stats for Unicast, Broadcast and Multicast frames separately.
* To simulate a global frame counter, read Unicast and addition Multicast
* and Broadcast later
*/
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_UNICAST, 1,
&mac_stats->FramesTransmittedOK);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_SINGLE_COLLISION, 1,
&mac_stats->SingleCollisionFrames);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTIPLE_COLLISION, 1,
&mac_stats->MultipleCollisionFrames);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNICAST, 1,
&mac_stats->FramesReceivedOK);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BYTES, 2,
&mac_stats->OctetsTransmittedOK);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_ALIGN_ERR, 1,
&mac_stats->AlignmentErrors);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_DEFERRED, 1,
&mac_stats->FramesWithDeferredXmissions);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_LATE_COLLISION, 1,
&mac_stats->LateCollisions);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_EXCESSIVE_COLLISION, 1,
&mac_stats->FramesAbortedDueToXSColls);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BYTES, 2,
&mac_stats->OctetsReceivedOK);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTICAST, 1,
&mac_stats->MulticastFramesXmittedOK);
mac_stats->FramesTransmittedOK += mac_stats->MulticastFramesXmittedOK;
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BROADCAST, 1,
&mac_stats->BroadcastFramesXmittedOK);
mac_stats->FramesTransmittedOK += mac_stats->BroadcastFramesXmittedOK;
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_MULTICAST, 1,
&mac_stats->MulticastFramesReceivedOK);
mac_stats->FramesReceivedOK += mac_stats->MulticastFramesReceivedOK;
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BROADCAST, 1,
&mac_stats->BroadcastFramesReceivedOK);
mac_stats->FramesReceivedOK += mac_stats->BroadcastFramesReceivedOK;
}
static const struct ethtool_rmon_hist_range an8855_rmon_ranges[] = {
{ 0, 64 },
{ 65, 127 },
{ 128, 255 },
{ 256, 511 },
{ 512, 1023 },
{ 1024, 1518 },
{ 1519, AN8855_MAX_MTU },
{}
};
static void an8855_get_rmon_stats(struct dsa_switch *ds, int port,
struct ethtool_rmon_stats *rmon_stats,
const struct ethtool_rmon_hist_range **ranges)
{
struct an8855_priv *priv = ds->priv;
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNDER_SIZE_ERR, 1,
&rmon_stats->undersize_pkts);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_OVER_SZ_ERR, 1,
&rmon_stats->oversize_pkts);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_FRAG_ERR, 1,
&rmon_stats->fragments);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_JABBER_ERR, 1,
&rmon_stats->jabbers);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_64, 1,
&rmon_stats->hist[0]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_65_TO_127, 1,
&rmon_stats->hist[1]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_128_TO_255, 1,
&rmon_stats->hist[2]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_256_TO_511, 1,
&rmon_stats->hist[3]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_512_TO_1023, 1,
&rmon_stats->hist[4]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1024_TO_1518, 1,
&rmon_stats->hist[5]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1519_TO_MAX, 1,
&rmon_stats->hist[6]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_64, 1,
&rmon_stats->hist_tx[0]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_65_TO_127, 1,
&rmon_stats->hist_tx[1]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_128_TO_255, 1,
&rmon_stats->hist_tx[2]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_256_TO_511, 1,
&rmon_stats->hist_tx[3]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_512_TO_1023, 1,
&rmon_stats->hist_tx[4]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1024_TO_1518, 1,
&rmon_stats->hist_tx[5]);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1519_TO_MAX, 1,
&rmon_stats->hist_tx[6]);
*ranges = an8855_rmon_ranges;
}
static void an8855_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
struct ethtool_eth_ctrl_stats *ctrl_stats)
{
struct an8855_priv *priv = ds->priv;
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PAUSE, 1,
&ctrl_stats->MACControlFramesTransmitted);
an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PAUSE, 1,
&ctrl_stats->MACControlFramesReceived);
}
static int an8855_port_mirror_add(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror,
bool ingress,
struct netlink_ext_ack *extack)
{
struct an8855_priv *priv = ds->priv;
int monitor_port;
u32 val;
int ret;
/* Check for existent entry */
if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
return -EEXIST;
ret = regmap_read(priv->regmap, AN8855_MIR, &val);
if (ret)
return ret;
/* AN8855 supports 4 monitor port, but only use first group */
monitor_port = FIELD_GET(AN8855_MIRROR_PORT, val);
if (val & AN8855_MIRROR_EN && monitor_port != mirror->to_local_port)
return -EEXIST;
val = AN8855_MIRROR_EN;
val |= FIELD_PREP(AN8855_MIRROR_PORT, mirror->to_local_port);
ret = regmap_update_bits(priv->regmap, AN8855_MIR,
AN8855_MIRROR_EN | AN8855_MIRROR_PORT,
val);
if (ret)
return ret;
ret = regmap_set_bits(priv->regmap, AN8855_PCR_P(port),
ingress ? AN8855_PORT_RX_MIR : AN8855_PORT_TX_MIR);
if (ret)
return ret;
if (ingress)
priv->mirror_rx |= BIT(port);
else
priv->mirror_tx |= BIT(port);
return 0;
}
static void an8855_port_mirror_del(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror)
{
struct an8855_priv *priv = ds->priv;
if (mirror->ingress)
priv->mirror_rx &= ~BIT(port);
else
priv->mirror_tx &= ~BIT(port);
regmap_clear_bits(priv->regmap, AN8855_PCR_P(port),
mirror->ingress ? AN8855_PORT_RX_MIR :
AN8855_PORT_TX_MIR);
if (!priv->mirror_rx && !priv->mirror_tx)
regmap_clear_bits(priv->regmap, AN8855_MIR, AN8855_MIRROR_EN);
}
static int an8855_port_set_status(struct an8855_priv *priv, int port,
bool enable)
{
if (enable)
return regmap_set_bits(priv->regmap, AN8855_PMCR_P(port),
AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
else
return regmap_clear_bits(priv->regmap, AN8855_PMCR_P(port),
AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
}
static int an8855_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phy)
{
return an8855_port_set_status(ds->priv, port, true);
}
static void an8855_port_disable(struct dsa_switch *ds, int port)
{
an8855_port_set_status(ds->priv, port, false);
}
static u32 en8855_get_phy_flags(struct dsa_switch *ds, int port)
{
struct an8855_priv *priv = ds->priv;
/* PHY doesn't need calibration */
if (!priv->phy_require_calib)
return 0;
/* Use AN8855_PHY_FLAGS_EN_CALIBRATION to signal
* calibration needed.
*/
return AN8855_PHY_FLAGS_EN_CALIBRATION;
}
static enum dsa_tag_protocol
an8855_get_tag_protocol(struct dsa_switch *ds, int port,
enum dsa_tag_protocol mp)
{
return DSA_TAG_PROTO_MTK;
}
/* Similar to MT7530 also trap link local frame and special frame to CPU */
static int an8855_trap_special_frames(struct an8855_priv *priv)
{
int ret;
/* Trap BPDUs to the CPU port(s) and egress them
* VLAN-untagged.
*/
ret = regmap_update_bits(priv->regmap, AN8855_BPC,
AN8855_BPDU_BPDU_FR | AN8855_BPDU_EG_TAG |
AN8855_BPDU_PORT_FW,
AN8855_BPDU_BPDU_FR |
FIELD_PREP(AN8855_BPDU_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_BPDU_PORT_FW, AN8855_BPDU_CPU_ONLY));
if (ret)
return ret;
/* Trap 802.1X PAE frames to the CPU port(s) and egress them
* VLAN-untagged.
*/
ret = regmap_update_bits(priv->regmap, AN8855_PAC,
AN8855_PAE_BPDU_FR | AN8855_PAE_EG_TAG |
AN8855_PAE_PORT_FW,
AN8855_PAE_BPDU_FR |
FIELD_PREP(AN8855_PAE_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_PAE_PORT_FW, AN8855_BPDU_CPU_ONLY));
if (ret)
return ret;
/* Trap frames with :01 MAC DAs to the CPU port(s) and egress
* them VLAN-untagged.
*/
ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
AN8855_R01_BPDU_FR | AN8855_R01_EG_TAG |
AN8855_R01_PORT_FW,
AN8855_R01_BPDU_FR |
FIELD_PREP(AN8855_R01_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_R01_PORT_FW, AN8855_BPDU_CPU_ONLY));
if (ret)
return ret;
/* Trap frames with :02 MAC DAs to the CPU port(s) and egress
* them VLAN-untagged.
*/
ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
AN8855_R02_BPDU_FR | AN8855_R02_EG_TAG |
AN8855_R02_PORT_FW,
AN8855_R02_BPDU_FR |
FIELD_PREP(AN8855_R02_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_R02_PORT_FW, AN8855_BPDU_CPU_ONLY));
if (ret)
return ret;
/* Trap frames with :03 MAC DAs to the CPU port(s) and egress
* them VLAN-untagged.
*/
ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
AN8855_R03_BPDU_FR | AN8855_R03_EG_TAG |
AN8855_R03_PORT_FW,
AN8855_R03_BPDU_FR |
FIELD_PREP(AN8855_R03_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_R03_PORT_FW, AN8855_BPDU_CPU_ONLY));
if (ret)
return ret;
/* Trap frames with :0E MAC DAs to the CPU port(s) and egress
* them VLAN-untagged.
*/
return regmap_update_bits(priv->regmap, AN8855_RGAC1,
AN8855_R0E_BPDU_FR | AN8855_R0E_EG_TAG |
AN8855_R0E_PORT_FW,
AN8855_R0E_BPDU_FR |
FIELD_PREP(AN8855_R0E_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
FIELD_PREP(AN8855_R0E_PORT_FW, AN8855_BPDU_CPU_ONLY));
}
static int
an8855_setup_pvid_vlan(struct an8855_priv *priv)
{
u32 val;
int ret;
/* Validate the entry with independent learning, keep the original
* ingress tag attribute.
*/
val = AN8855_VA0_IVL_MAC | AN8855_VA0_EG_CON |
FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED) |
AN8855_VA0_PORT | AN8855_VA0_VLAN_VALID;
ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
if (ret)
return ret;
return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID,
AN8855_PORT_VID_DEFAULT);
}
static int an8855_setup(struct dsa_switch *ds)
{
struct an8855_priv *priv = ds->priv;
struct dsa_port *dp;
int ret;
/* Enable and reset MIB counters */
ret = an8855_mib_init(priv);
if (ret)
return ret;
dsa_switch_for_each_user_port(dp, ds) {
/* Disable MAC by default on all user ports */
ret = an8855_port_set_status(priv, dp->index, false);
if (ret)
return ret;
/* Individual user ports get connected to CPU port only */
ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(dp->index),
FIELD_PREP(AN8855_PORTMATRIX, BIT(AN8855_CPU_PORT)));
if (ret)
return ret;
/* Disable Broadcast Forward on user ports */
ret = regmap_clear_bits(priv->regmap, AN8855_BCF, BIT(dp->index));
if (ret)
return ret;
/* Disable Unknown Unicast Forward on user ports */
ret = regmap_clear_bits(priv->regmap, AN8855_UNUF, BIT(dp->index));
if (ret)
return ret;
/* Disable Unknown Multicast Forward on user ports */
ret = regmap_clear_bits(priv->regmap, AN8855_UNMF, BIT(dp->index));
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_UNIPMF, BIT(dp->index));
if (ret)
return ret;
/* Set default PVID to on all user ports */
ret = an8855_port_set_pid(priv, dp->index, AN8855_PORT_VID_DEFAULT);
if (ret)
return ret;
}
/* Enable Airoha header mode on the cpu port */
ret = regmap_write(priv->regmap, AN8855_PVC_P(AN8855_CPU_PORT),
AN8855_PORT_SPEC_REPLACE_MODE | AN8855_PORT_SPEC_TAG);
if (ret)
return ret;
/* Unknown multicast frame forwarding to the cpu port */
ret = regmap_write(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
if (ret)
return ret;
/* Set CPU port number */
ret = regmap_update_bits(priv->regmap, AN8855_MFC,
AN8855_CPU_EN | AN8855_CPU_PORT_IDX,
AN8855_CPU_EN |
FIELD_PREP(AN8855_CPU_PORT_IDX, AN8855_CPU_PORT));
if (ret)
return ret;
/* CPU port gets connected to all user ports of
* the switch.
*/
ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(AN8855_CPU_PORT),
FIELD_PREP(AN8855_PORTMATRIX, dsa_user_ports(ds)));
if (ret)
return ret;
/* CPU port is set to fallback mode to let untagged
* frames pass through.
*/
ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(AN8855_CPU_PORT),
AN8855_PORT_VLAN,
FIELD_PREP(AN8855_PORT_VLAN, AN8855_PORT_FALLBACK_MODE));
if (ret)
return ret;
/* Enable Broadcast Forward on CPU port */
ret = regmap_set_bits(priv->regmap, AN8855_BCF, BIT(AN8855_CPU_PORT));
if (ret)
return ret;
/* Enable Unknown Unicast Forward on CPU port */
ret = regmap_set_bits(priv->regmap, AN8855_UNUF, BIT(AN8855_CPU_PORT));
if (ret)
return ret;
/* Enable Unknown Multicast Forward on CPU port */
ret = regmap_set_bits(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
if (ret)
return ret;
ret = regmap_set_bits(priv->regmap, AN8855_UNIPMF, BIT(AN8855_CPU_PORT));
if (ret)
return ret;
/* Setup Trap special frame to CPU rules */
ret = an8855_trap_special_frames(priv);
if (ret)
return ret;
dsa_switch_for_each_port(dp, ds) {
/* Disable Learning on all ports.
* Learning on CPU is disabled for fdb isolation and handled by
* assisted_learning_on_cpu_port.
*/
ret = regmap_set_bits(priv->regmap, AN8855_PSC_P(dp->index),
AN8855_SA_DIS);
if (ret)
return ret;
/* Enable consistent egress tag (for VLAN unware VLAN-passtrough) */
ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(dp->index),
AN8855_PVC_EG_TAG,
FIELD_PREP(AN8855_PVC_EG_TAG, AN8855_VLAN_EG_CONSISTENT));
if (ret)
return ret;
}
/* Setup VLAN for Default PVID */
ret = an8855_setup_pvid_vlan(priv);
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_CKGCR,
AN8855_CKG_LNKDN_GLB_STOP | AN8855_CKG_LNKDN_PORT_STOP);
if (ret)
return ret;
/* Release global PHY power down */
ret = regmap_write(priv->regmap, AN8855_RG_GPHY_AFE_PWD, 0x0);
if (ret)
return ret;
ds->configure_vlan_while_not_filtering = true;
/* Flush the FDB table */
ret = an8855_fdb_cmd(priv, AN8855_FDB_FLUSH, NULL);
if (ret < 0)
return ret;
/* Set min a max ageing value supported */
ds->ageing_time_min = AN8855_L2_AGING_MS_CONSTANT;
ds->ageing_time_max = FIELD_MAX(AN8855_AGE_CNT) *
FIELD_MAX(AN8855_AGE_UNIT) *
AN8855_L2_AGING_MS_CONSTANT;
/* Enable assisted learning for fdb isolation */
ds->assisted_learning_on_cpu_port = true;
return 0;
}
static struct phylink_pcs *an8855_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct an8855_priv *priv = dp->ds->priv;
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_2500BASEX:
return &priv->pcs;
default:
return NULL;
}
}
static void an8855_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct dsa_switch *ds = dp->ds;
struct an8855_priv *priv;
int port = dp->index;
priv = ds->priv;
/* Nothing to configure for internal ports */
if (port != 5)
return;
regmap_update_bits(priv->regmap, AN8855_PMCR_P(port),
AN8855_PMCR_IFG_XMIT | AN8855_PMCR_MAC_MODE |
AN8855_PMCR_BACKOFF_EN | AN8855_PMCR_BACKPR_EN,
FIELD_PREP(AN8855_PMCR_IFG_XMIT, 0x1) |
AN8855_PMCR_MAC_MODE | AN8855_PMCR_BACKOFF_EN |
AN8855_PMCR_BACKPR_EN);
}
static void an8855_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
switch (port) {
case 0:
case 1:
case 2:
case 3:
case 4:
__set_bit(PHY_INTERFACE_MODE_GMII,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 5:
phy_interface_set_rgmii(config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX,
config->supported_interfaces);
break;
}
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
}
static void an8855_phylink_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct an8855_priv *priv = dp->ds->priv;
/* With autoneg just disable TX/RX else also force link down */
if (phylink_autoneg_inband(mode)) {
regmap_clear_bits(priv->regmap, AN8855_PMCR_P(dp->index),
AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
} else {
regmap_update_bits(priv->regmap, AN8855_PMCR_P(dp->index),
AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN |
AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK,
AN8855_PMCR_FORCE_MODE);
}
}
static void an8855_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)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct an8855_priv *priv = dp->ds->priv;
int port = dp->index;
u32 reg;
reg = regmap_read(priv->regmap, AN8855_PMCR_P(port), &reg);
if (phylink_autoneg_inband(mode)) {
reg &= ~AN8855_PMCR_FORCE_MODE;
} else {
reg |= AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK;
reg &= ~AN8855_PMCR_FORCE_SPEED;
switch (speed) {
case SPEED_10:
reg |= AN8855_PMCR_FORCE_SPEED_10;
break;
case SPEED_100:
reg |= AN8855_PMCR_FORCE_SPEED_100;
break;
case SPEED_1000:
reg |= AN8855_PMCR_FORCE_SPEED_1000;
break;
case SPEED_2500:
reg |= AN8855_PMCR_FORCE_SPEED_2500;
break;
case SPEED_5000:
dev_err(priv->dev, "Missing support for 5G speed. Aborting...\n");
return;
}
reg &= ~AN8855_PMCR_FORCE_FDX;
if (duplex == DUPLEX_FULL)
reg |= AN8855_PMCR_FORCE_FDX;
reg &= ~AN8855_PMCR_RX_FC_EN;
if (rx_pause || dsa_port_is_cpu(dp))
reg |= AN8855_PMCR_RX_FC_EN;
reg &= ~AN8855_PMCR_TX_FC_EN;
if (rx_pause || dsa_port_is_cpu(dp))
reg |= AN8855_PMCR_TX_FC_EN;
/* Disable any EEE options */
reg &= ~(AN8855_PMCR_FORCE_EEE5G | AN8855_PMCR_FORCE_EEE2P5G |
AN8855_PMCR_FORCE_EEE1G | AN8855_PMCR_FORCE_EEE100);
}
reg |= AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN;
regmap_write(priv->regmap, AN8855_PMCR_P(port), reg);
}
static unsigned int an8855_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
/* SGMII can be configured to use inband with AN result */
if (interface == PHY_INTERFACE_MODE_SGMII)
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
/* inband is not supported in 2500-baseX and must be disabled */
return LINK_INBAND_DISABLE;
}
static void an8855_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
u32 val;
int ret;
ret = regmap_read(priv->regmap, AN8855_PMSR_P(AN8855_CPU_PORT), &val);
if (ret < 0) {
state->link = false;
return;
}
state->link = !!(val & AN8855_PMSR_LNK);
state->an_complete = state->link;
state->duplex = (val & AN8855_PMSR_DPX) ? DUPLEX_FULL :
DUPLEX_HALF;
switch (val & AN8855_PMSR_SPEED) {
case AN8855_PMSR_SPEED_10:
state->speed = SPEED_10;
break;
case AN8855_PMSR_SPEED_100:
state->speed = SPEED_100;
break;
case AN8855_PMSR_SPEED_1000:
state->speed = SPEED_1000;
break;
case AN8855_PMSR_SPEED_2500:
state->speed = SPEED_2500;
break;
case AN8855_PMSR_SPEED_5000:
dev_err(priv->dev, "Missing support for 5G speed. Setting Unknown.\n");
fallthrough;
default:
state->speed = SPEED_UNKNOWN;
break;
}
if (val & AN8855_PMSR_RX_FC)
state->pause |= MLO_PAUSE_RX;
if (val & AN8855_PMSR_TX_FC)
state->pause |= MLO_PAUSE_TX;
}
static int an8855_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
u32 val;
int ret;
/* !!! WELCOME TO HELL !!! */
/* TX FIR - improve TX EYE */
ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_10,
AN8855_RG_DA_QP_TX_FIR_C2_SEL |
AN8855_RG_DA_QP_TX_FIR_C2_FORCE |
AN8855_RG_DA_QP_TX_FIR_C1_SEL |
AN8855_RG_DA_QP_TX_FIR_C1_FORCE,
AN8855_RG_DA_QP_TX_FIR_C2_SEL |
FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C2_FORCE, 0x4) |
AN8855_RG_DA_QP_TX_FIR_C1_SEL |
FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C1_FORCE, 0x0));
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x0;
else
val = 0xd;
ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_11,
AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
AN8855_RG_DA_QP_TX_FIR_C0B_FORCE,
AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C0B_FORCE, val));
if (ret)
return ret;
/* RX CDR - improve RX Jitter Tolerance */
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x5;
else
val = 0x6;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_BOT_LIM,
AN8855_RG_QP_CDR_LPF_KP_GAIN |
AN8855_RG_QP_CDR_LPF_KI_GAIN,
FIELD_PREP(AN8855_RG_QP_CDR_LPF_KP_GAIN, val) |
FIELD_PREP(AN8855_RG_QP_CDR_LPF_KI_GAIN, val));
if (ret)
return ret;
/* PLL */
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x1;
else
val = 0x0;
ret = regmap_update_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_1,
AN8855_RG_TPHY_SPEED,
FIELD_PREP(AN8855_RG_TPHY_SPEED, val));
if (ret)
return ret;
/* PLL - LPF */
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
AN8855_RG_DA_QP_PLL_FBKSEL_INTF |
AN8855_RG_DA_QP_PLL_BR_INTF |
AN8855_RG_DA_QP_PLL_BPD_INTF |
AN8855_RG_DA_QP_PLL_BPA_INTF |
AN8855_RG_DA_QP_PLL_BC_INTF,
AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
FIELD_PREP(AN8855_RG_DA_QP_PLL_FBKSEL_INTF, 0x0) |
FIELD_PREP(AN8855_RG_DA_QP_PLL_BR_INTF, 0x3) |
FIELD_PREP(AN8855_RG_DA_QP_PLL_BPD_INTF, 0x0) |
FIELD_PREP(AN8855_RG_DA_QP_PLL_BPA_INTF, 0x5) |
FIELD_PREP(AN8855_RG_DA_QP_PLL_BC_INTF, 0x1));
if (ret)
return ret;
/* PLL - ICO */
ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_4,
AN8855_RG_DA_QP_PLL_ICOLP_EN_INTF);
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_ICOIQ_EN_INTF);
if (ret)
return ret;
/* PLL - CHP */
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x6;
else
val = 0x4;
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_IR_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_IR_INTF, val));
if (ret)
return ret;
/* PLL - PFD */
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_PFD_OFFSET_EN_INTRF |
AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF |
AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF, 0x1) |
FIELD_PREP(AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF, 0x1));
if (ret)
return ret;
/* PLL - POSTDIV */
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_POSTDIV_EN_INTF |
AN8855_RG_DA_QP_PLL_PHY_CK_EN_INTF |
AN8855_RG_DA_QP_PLL_PCK_SEL_INTF,
AN8855_RG_DA_QP_PLL_PCK_SEL_INTF);
if (ret)
return ret;
/* PLL - SDM */
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_SDM_HREN_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_SDM_HREN_INTF, 0x0));
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
AN8855_RG_DA_QP_PLL_SDM_IFM_INTF);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_SS_LCPLL_PWCTL_SETTING_2,
AN8855_RG_NCPO_ANA_MSB,
FIELD_PREP(AN8855_RG_NCPO_ANA_MSB, 0x1));
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x7a000000;
else
val = 0x48000000;
ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_2,
FIELD_PREP(AN8855_RG_LCPLL_NCPO_VALUE, val));
if (ret)
return ret;
ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_PCW_1,
FIELD_PREP(AN8855_RG_LCPLL_PON_HRDDS_PCW_NCPO_GPON, val));
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_5,
AN8855_RG_LCPLL_NCPO_CHG);
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
AN8855_RG_DA_QP_PLL_SDM_DI_EN_INTF);
if (ret)
return ret;
/* PLL - SS */
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF, 0x0));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_4,
AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF, 0x0));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF,
FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF, 0x0));
if (ret)
return ret;
/* PLL - TDC */
ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
AN8855_RG_DA_QP_PLL_TDC_TXCK_SEL_INTF);
if (ret)
return ret;
ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
AN8855_RG_QP_PLL_SSC_TRI_EN);
if (ret)
return ret;
ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
AN8855_RG_QP_PLL_SSC_PHASE_INI);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_RX_DAC_EN,
AN8855_RG_QP_SIGDET_HF,
FIELD_PREP(AN8855_RG_QP_SIGDET_HF, 0x2));
if (ret)
return ret;
/* TCL Disable (only for Co-SIM) */
ret = regmap_clear_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_0,
AN8855_RG_QP_EQ_RX500M_CK_SEL);
if (ret)
return ret;
/* TX Init */
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x4;
else
val = 0x0;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_TX_MODE,
AN8855_RG_QP_TX_RESERVE |
AN8855_RG_QP_TX_MODE_16B_EN,
FIELD_PREP(AN8855_RG_QP_TX_RESERVE, val));
if (ret)
return ret;
/* RX Control/Init */
ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_RXAFE_RESERVE,
AN8855_RG_QP_CDR_PD_10B_EN);
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x1;
else
val = 0x2;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_MJV_LIM,
AN8855_RG_QP_CDR_LPF_RATIO,
FIELD_PREP(AN8855_RG_QP_CDR_LPF_RATIO, val));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_SETVALUE,
AN8855_RG_QP_CDR_PR_BUF_IN_SR |
AN8855_RG_QP_CDR_PR_BETA_SEL,
FIELD_PREP(AN8855_RG_QP_CDR_PR_BUF_IN_SR, 0x6) |
FIELD_PREP(AN8855_RG_QP_CDR_PR_BETA_SEL, 0x1));
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0xf;
else
val = 0xc;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
AN8855_RG_QP_CDR_PR_DAC_BAND,
FIELD_PREP(AN8855_RG_QP_CDR_PR_DAC_BAND, val));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
AN8855_RG_QP_CDR_PR_KBAND_PCIE_MODE |
AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK,
FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK, 0x19));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF,
AN8855_RG_QP_CDR_PHYCK_SEL |
AN8855_RG_QP_CDR_PHYCK_RSTB |
AN8855_RG_QP_CDR_PHYCK_DIV,
FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_SEL, 0x2) |
FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_DIV, 0x21));
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
AN8855_RG_QP_CDR_PR_XFICK_EN);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
AN8855_RG_QP_CDR_PR_KBAND_DIV,
FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV, 0x4));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_26,
AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
AN8855_RG_LINK_NE_EN |
AN8855_RG_LINK_ERRO_EN,
AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
AN8855_RG_LINK_ERRO_EN);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_DLY_0,
AN8855_RG_QP_RX_SAOSC_EN_H_DLY |
AN8855_RG_QP_RX_PI_CAL_EN_H_DLY,
FIELD_PREP(AN8855_RG_QP_RX_SAOSC_EN_H_DLY, 0x3f) |
FIELD_PREP(AN8855_RG_QP_RX_PI_CAL_EN_H_DLY, 0x6f));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_42,
AN8855_RG_QP_EQ_EN_DLY,
FIELD_PREP(AN8855_RG_QP_EQ_EN_DLY, 0x150));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_2,
AN8855_RG_QP_RX_EQ_EN_H_DLY,
FIELD_PREP(AN8855_RG_QP_RX_EQ_EN_H_DLY, 0x150));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_9,
AN8855_RG_QP_EQ_LEQOSC_DLYCNT,
FIELD_PREP(AN8855_RG_QP_EQ_LEQOSC_DLYCNT, 0x1));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_8,
AN8855_RG_DA_QP_SAOSC_DONE_TIME |
AN8855_RG_DA_QP_LEQOS_EN_TIME,
FIELD_PREP(AN8855_RG_DA_QP_SAOSC_DONE_TIME, 0x200) |
FIELD_PREP(AN8855_RG_DA_QP_LEQOS_EN_TIME, 0xfff));
if (ret)
return ret;
/* Frequency meter */
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = 0x10;
else
val = 0x28;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_5,
AN8855_RG_FREDET_CHK_CYCLE,
FIELD_PREP(AN8855_RG_FREDET_CHK_CYCLE, val));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_6,
AN8855_RG_FREDET_GOLDEN_CYCLE,
FIELD_PREP(AN8855_RG_FREDET_GOLDEN_CYCLE, 0x64));
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_7,
AN8855_RG_FREDET_TOLERATE_CYCLE,
FIELD_PREP(AN8855_RG_FREDET_TOLERATE_CYCLE, 0x2710));
if (ret)
return ret;
ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_0,
AN8855_RG_PHYA_AUTO_INIT);
if (ret)
return ret;
/* PCS Init */
if (interface == PHY_INTERFACE_MODE_SGMII &&
neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED) {
ret = regmap_clear_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_0,
AN8855_RG_SGMII_MODE | AN8855_RG_SGMII_AN_EN);
if (ret)
return ret;
}
ret = regmap_clear_bits(priv->regmap, AN8855_RG_HSGMII_PCS_CTROL_1,
AN8855_RG_TBI_10B_MODE);
if (ret)
return ret;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
/* Set AN Ability - Interrupt */
ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN_FORCE_CL37,
AN8855_RG_FORCE_AN_DONE);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN_13,
AN8855_SGMII_REMOTE_FAULT_DIS |
AN8855_SGMII_IF_MODE,
AN8855_SGMII_REMOTE_FAULT_DIS |
FIELD_PREP(AN8855_SGMII_IF_MODE, 0xb));
if (ret)
return ret;
}
/* Rate Adaption - GMII path config. */
if (interface == PHY_INTERFACE_MODE_2500BASEX) {
ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
AN8855_RG_P0_DIS_MII_MODE);
if (ret)
return ret;
} else {
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
ret = regmap_set_bits(priv->regmap, AN8855_MII_RA_AN_ENABLE,
AN8855_RG_P0_RA_AN_EN);
if (ret)
return ret;
} else {
ret = regmap_update_bits(priv->regmap, AN8855_RG_AN_SGMII_MODE_FORCE,
AN8855_RG_FORCE_CUR_SGMII_MODE |
AN8855_RG_FORCE_CUR_SGMII_SEL,
AN8855_RG_FORCE_CUR_SGMII_SEL);
if (ret)
return ret;
ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
AN8855_RG_P0_MII_RA_RX_EN |
AN8855_RG_P0_MII_RA_TX_EN |
AN8855_RG_P0_MII_RA_RX_MODE |
AN8855_RG_P0_MII_RA_TX_MODE);
if (ret)
return ret;
}
ret = regmap_set_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
AN8855_RG_P0_MII_MODE);
if (ret)
return ret;
}
ret = regmap_set_bits(priv->regmap, AN8855_RG_RATE_ADAPT_CTRL_0,
AN8855_RG_RATE_ADAPT_RX_BYPASS |
AN8855_RG_RATE_ADAPT_TX_BYPASS |
AN8855_RG_RATE_ADAPT_RX_EN |
AN8855_RG_RATE_ADAPT_TX_EN);
if (ret)
return ret;
/* Disable AN if not in autoneg */
ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN0, BMCR_ANENABLE,
neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ? BMCR_ANENABLE :
0);
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_SGMII) {
/* Follow SDK init flow with restarting AN after AN enable */
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN0,
BMCR_ANRESTART);
if (ret)
return ret;
} else {
ret = regmap_set_bits(priv->regmap, AN8855_PHY_RX_FORCE_CTRL_0,
AN8855_RG_FORCE_TXC_SEL);
if (ret)
return ret;
}
}
/* Force Speed with fixed-link or 2500base-x as doesn't support aneg */
if (interface == PHY_INTERFACE_MODE_2500BASEX ||
neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_2500BASEX)
val = AN8855_RG_LINK_MODE_P0_SPEED_2500;
else
val = AN8855_RG_LINK_MODE_P0_SPEED_1000;
ret = regmap_update_bits(priv->regmap, AN8855_SGMII_STS_CTRL_0,
AN8855_RG_LINK_MODE_P0 |
AN8855_RG_FORCE_SPD_MODE_P0,
val | AN8855_RG_FORCE_SPD_MODE_P0);
if (ret)
return ret;
}
/* bypass flow control to MAC */
ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_0,
AN8855_RG_DPX_STS_P3 | AN8855_RG_DPX_STS_P2 |
AN8855_RG_DPX_STS_P1 | AN8855_RG_TXFC_STS_P0 |
AN8855_RG_RXFC_STS_P0 | AN8855_RG_DPX_STS_P0);
if (ret)
return ret;
ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_2,
AN8855_RG_RXFC_AN_BYPASS_P3 |
AN8855_RG_RXFC_AN_BYPASS_P2 |
AN8855_RG_RXFC_AN_BYPASS_P1 |
AN8855_RG_TXFC_AN_BYPASS_P3 |
AN8855_RG_TXFC_AN_BYPASS_P2 |
AN8855_RG_TXFC_AN_BYPASS_P1 |
AN8855_RG_DPX_AN_BYPASS_P3 |
AN8855_RG_DPX_AN_BYPASS_P2 |
AN8855_RG_DPX_AN_BYPASS_P1 |
AN8855_RG_DPX_AN_BYPASS_P0);
if (ret)
return ret;
return 0;
}
static void an8855_pcs_an_restart(struct phylink_pcs *pcs)
{
return;
}
static const struct phylink_pcs_ops an8855_pcs_ops = {
.pcs_inband_caps = an8855_pcs_inband_caps,
.pcs_get_state = an8855_pcs_get_state,
.pcs_config = an8855_pcs_config,
.pcs_an_restart = an8855_pcs_an_restart,
};
static const struct phylink_mac_ops an8855_phylink_mac_ops = {
.mac_select_pcs = an8855_phylink_mac_select_pcs,
.mac_config = an8855_phylink_mac_config,
.mac_link_down = an8855_phylink_mac_link_down,
.mac_link_up = an8855_phylink_mac_link_up,
};
static const struct dsa_switch_ops an8855_switch_ops = {
.get_tag_protocol = an8855_get_tag_protocol,
.setup = an8855_setup,
.get_phy_flags = en8855_get_phy_flags,
.phylink_get_caps = an8855_phylink_get_caps,
.get_strings = an8855_get_strings,
.get_ethtool_stats = an8855_get_ethtool_stats,
.get_sset_count = an8855_get_sset_count,
.get_eth_mac_stats = an8855_get_eth_mac_stats,
.get_eth_ctrl_stats = an8855_get_eth_ctrl_stats,
.get_rmon_stats = an8855_get_rmon_stats,
.port_enable = an8855_port_enable,
.port_disable = an8855_port_disable,
.set_ageing_time = an8855_set_ageing_time,
.port_bridge_join = an8855_port_bridge_join,
.port_bridge_leave = an8855_port_bridge_leave,
.port_fast_age = an8855_port_fast_age,
.port_stp_state_set = an8855_port_stp_state_set,
.port_pre_bridge_flags = an8855_port_pre_bridge_flags,
.port_bridge_flags = an8855_port_bridge_flags,
.port_vlan_filtering = an8855_port_vlan_filtering,
.port_vlan_add = an8855_port_vlan_add,
.port_vlan_del = an8855_port_vlan_del,
.port_fdb_add = an8855_port_fdb_add,
.port_fdb_del = an8855_port_fdb_del,
.port_fdb_dump = an8855_port_fdb_dump,
.port_mdb_add = an8855_port_mdb_add,
.port_mdb_del = an8855_port_mdb_del,
.port_change_mtu = an8855_port_change_mtu,
.port_max_mtu = an8855_port_max_mtu,
.port_mirror_add = an8855_port_mirror_add,
.port_mirror_del = an8855_port_mirror_del,
};
static int an8855_read_switch_id(struct an8855_priv *priv)
{
u32 id;
int ret;
ret = regmap_read(priv->regmap, AN8855_CREV, &id);
if (ret)
return ret;
if (id != AN8855_ID) {
dev_err(priv->dev,
"Switch id detected %x but expected %x\n",
id, AN8855_ID);
return -ENODEV;
}
return 0;
}
static int an8855_switch_probe(struct platform_device *pdev)
{
struct an8855_priv *priv;
u32 val;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
priv->phy_require_calib = of_property_read_bool(priv->dev->of_node,
"airoha,ext-surge");
priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(priv->reset_gpio))
return PTR_ERR(priv->reset_gpio);
/* Get regmap from MFD */
priv->regmap = dev_get_regmap(priv->dev->parent, NULL);
if (priv->reset_gpio) {
usleep_range(100000, 150000);
gpiod_set_value_cansleep(priv->reset_gpio, 0);
usleep_range(100000, 150000);
gpiod_set_value_cansleep(priv->reset_gpio, 1);
/* Poll HWTRAP reg to wait for Switch to fully Init */
ret = regmap_read_poll_timeout(priv->regmap, AN8855_HWTRAP, val,
val, 20, 200000);
if (ret)
return ret;
}
ret = an8855_read_switch_id(priv);
if (ret)
return ret;
priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL);
if (!priv->ds)
return -ENOMEM;
priv->ds->dev = priv->dev;
priv->ds->num_ports = AN8855_NUM_PORTS;
priv->ds->priv = priv;
priv->ds->ops = &an8855_switch_ops;
devm_mutex_init(priv->dev, &priv->reg_mutex);
priv->ds->phylink_mac_ops = &an8855_phylink_mac_ops;
priv->pcs.ops = &an8855_pcs_ops;
priv->pcs.neg_mode = true;
priv->pcs.poll = true;
dev_set_drvdata(priv->dev, priv);
return dsa_register_switch(priv->ds);
}
static void an8855_switch_remove(struct platform_device *pdev)
{
struct an8855_priv *priv = dev_get_drvdata(&pdev->dev);
if (priv)
dsa_unregister_switch(priv->ds);
}
static const struct of_device_id an8855_switch_of_match[] = {
{ .compatible = "airoha,an8855-switch" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, an8855_switch_of_match);
static struct platform_driver an8855_switch_driver = {
.probe = an8855_switch_probe,
.remove_new = an8855_switch_remove,
.driver = {
.name = "an8855-switch",
.of_match_table = an8855_switch_of_match,
},
};
module_platform_driver(an8855_switch_driver);
MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
MODULE_DESCRIPTION("Driver for Airoha AN8855 Switch");
MODULE_LICENSE("GPL");