// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2022 Schneider-Electric
*
* Clément Léger <clement.leger@bootlin.com>
*/
#include <linux/clk.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_ether.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <net/dsa.h>
#include "rzn1_a5psw.h"
struct a5psw_stats {
u16 offset;
const char name[ETH_GSTRING_LEN];
};
#define STAT_DESC(_offset) { \
.offset = A5PSW_##_offset, \
.name = __stringify(_offset), \
}
static const struct a5psw_stats a5psw_stats[] = {
STAT_DESC(aFramesTransmittedOK),
STAT_DESC(aFramesReceivedOK),
STAT_DESC(aFrameCheckSequenceErrors),
STAT_DESC(aAlignmentErrors),
STAT_DESC(aOctetsTransmittedOK),
STAT_DESC(aOctetsReceivedOK),
STAT_DESC(aTxPAUSEMACCtrlFrames),
STAT_DESC(aRxPAUSEMACCtrlFrames),
STAT_DESC(ifInErrors),
STAT_DESC(ifOutErrors),
STAT_DESC(ifInUcastPkts),
STAT_DESC(ifInMulticastPkts),
STAT_DESC(ifInBroadcastPkts),
STAT_DESC(ifOutDiscards),
STAT_DESC(ifOutUcastPkts),
STAT_DESC(ifOutMulticastPkts),
STAT_DESC(ifOutBroadcastPkts),
STAT_DESC(etherStatsDropEvents),
STAT_DESC(etherStatsOctets),
STAT_DESC(etherStatsPkts),
STAT_DESC(etherStatsUndersizePkts),
STAT_DESC(etherStatsOversizePkts),
STAT_DESC(etherStatsPkts64Octets),
STAT_DESC(etherStatsPkts65to127Octets),
STAT_DESC(etherStatsPkts128to255Octets),
STAT_DESC(etherStatsPkts256to511Octets),
STAT_DESC(etherStatsPkts1024to1518Octets),
STAT_DESC(etherStatsPkts1519toXOctets),
STAT_DESC(etherStatsJabbers),
STAT_DESC(etherStatsFragments),
STAT_DESC(VLANReceived),
STAT_DESC(VLANTransmitted),
STAT_DESC(aDeferred),
STAT_DESC(aMultipleCollisions),
STAT_DESC(aSingleCollisions),
STAT_DESC(aLateCollisions),
STAT_DESC(aExcessiveCollisions),
STAT_DESC(aCarrierSenseErrors),
};
static void a5psw_reg_writel(struct a5psw *a5psw, int offset, u32 value)
{
writel(value, a5psw->base + offset);
}
static u32 a5psw_reg_readl(struct a5psw *a5psw, int offset)
{
return readl(a5psw->base + offset);
}
static void a5psw_reg_rmw(struct a5psw *a5psw, int offset, u32 mask, u32 val)
{
u32 reg;
spin_lock(&a5psw->reg_lock);
reg = a5psw_reg_readl(a5psw, offset);
reg &= ~mask;
reg |= val;
a5psw_reg_writel(a5psw, offset, reg);
spin_unlock(&a5psw->reg_lock);
}
static enum dsa_tag_protocol a5psw_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
{
return DSA_TAG_PROTO_RZN1_A5PSW;
}
static void a5psw_port_pattern_set(struct a5psw *a5psw, int port, int pattern,
bool enable)
{
u32 rx_match = 0;
if (enable)
rx_match |= A5PSW_RXMATCH_CONFIG_PATTERN(pattern);
a5psw_reg_rmw(a5psw, A5PSW_RXMATCH_CONFIG(port),
A5PSW_RXMATCH_CONFIG_PATTERN(pattern), rx_match);
}
static void a5psw_port_mgmtfwd_set(struct a5psw *a5psw, int port, bool enable)
{
/* Enable "management forward" pattern matching, this will forward
* packets from this port only towards the management port and thus
* isolate the port.
*/
a5psw_port_pattern_set(a5psw, port, A5PSW_PATTERN_MGMTFWD, enable);
}
static void a5psw_port_enable_set(struct a5psw *a5psw, int port, bool enable)
{
u32 port_ena = 0;
if (enable)
port_ena |= A5PSW_PORT_ENA_TX_RX(port);
a5psw_reg_rmw(a5psw, A5PSW_PORT_ENA, A5PSW_PORT_ENA_TX_RX(port),
port_ena);
}
static int a5psw_lk_execute_ctrl(struct a5psw *a5psw, u32 *ctrl)
{
int ret;
a5psw_reg_writel(a5psw, A5PSW_LK_ADDR_CTRL, *ctrl);
ret = readl_poll_timeout(a5psw->base + A5PSW_LK_ADDR_CTRL, *ctrl,
!(*ctrl & A5PSW_LK_ADDR_CTRL_BUSY),
A5PSW_LK_BUSY_USEC_POLL, A5PSW_CTRL_TIMEOUT);
if (ret)
dev_err(a5psw->dev, "LK_CTRL timeout waiting for BUSY bit\n");
return ret;
}
static void a5psw_port_fdb_flush(struct a5psw *a5psw, int port)
{
u32 ctrl = A5PSW_LK_ADDR_CTRL_DELETE_PORT | BIT(port);
mutex_lock(&a5psw->lk_lock);
a5psw_lk_execute_ctrl(a5psw, &ctrl);
mutex_unlock(&a5psw->lk_lock);
}
static void a5psw_port_authorize_set(struct a5psw *a5psw, int port,
bool authorize)
{
u32 reg = a5psw_reg_readl(a5psw, A5PSW_AUTH_PORT(port));
if (authorize)
reg |= A5PSW_AUTH_PORT_AUTHORIZED;
else
reg &= ~A5PSW_AUTH_PORT_AUTHORIZED;
a5psw_reg_writel(a5psw, A5PSW_AUTH_PORT(port), reg);
}
static void a5psw_port_disable(struct dsa_switch *ds, int port)
{
struct a5psw *a5psw = ds->priv;
a5psw_port_authorize_set(a5psw, port, false);
a5psw_port_enable_set(a5psw, port, false);
}
static int a5psw_port_enable(