// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include "sja1105.h"
#define SJA1105_SIZE_MAC_AREA (0x02 * 4)
#define SJA1105_SIZE_HL1_AREA (0x10 * 4)
#define SJA1105_SIZE_HL2_AREA (0x4 * 4)
#define SJA1105_SIZE_QLEVEL_AREA (0x8 * 4) /* 0x4 to 0xB */
struct sja1105_port_status_mac {
u64 n_runt;
u64 n_soferr;
u64 n_alignerr;
u64 n_miierr;
u64 typeerr;
u64 sizeerr;
u64 tctimeout;
u64 priorerr;
u64 nomaster;
u64 memov;
u64 memerr;
u64 invtyp;
u64 intcyov;
u64 domerr;
u64 pcfbagdrop;
u64 spcprior;
u64 ageprior;
u64 portdrop;
u64 lendrop;
u64 bagdrop;
u64 policeerr;
u64 drpnona664err;
u64 spcerr;
u64 agedrp;
};
struct sja1105_port_status_hl1 {
u64 n_n664err;
u64 n_vlanerr;
u64 n_unreleased;
u64 n_sizeerr;
u64 n_crcerr;
u64 n_vlnotfound;
u64 n_ctpolerr;
u64 n_polerr;
u64 n_rxfrmsh;
u64 n_rxfrm;
u64 n_rxbytesh;
u64 n_rxbyte;
u64 n_txfrmsh;
u64 n_txfrm;
u64 n_txbytesh;
u64 n_txbyte;
};
struct sja1105_port_status_hl2 {
u64 n_qfull;
u64 n_part_drop;
u64 n_egr_disabled;
u64 n_not_reach;
u64 qlevel_hwm[8]; /* Only for P/Q/R/S */
u64 qlevel[8]; /* Only for P/Q/R/S */
};
struct sja1105_port_status {
struct sja1105_port_status_mac mac;
struct sja1105_port_status_hl1 hl1;
struct sja1105_port_status_hl2 hl2;
};
static void
sja1105_port_status_mac_unpack(void *buf,
struct sja1105_port_status_mac *status)
{
/* Make pointer arithmetic work on 4 bytes */
u32 *p = buf;
sja1105_unpack(p + 0x0, &status->n_runt, 31, 24, 4);
sja1105_unpack(p + 0x0, &status->n_soferr, 23, 16, 4);
sja1105_unpack(p + 0x0, &status->n_alignerr, 15, 8, 4);
sja1105_unpack(p + 0x0, &status->n_miierr, 7, 0, 4);
sja1105_unpack(p + 0x1, &status->typeerr, 27, 27, 4);
sja1105_unpack(p + 0x1, &status->sizeerr, 26, 26, 4);
sja1105_unpack(p + 0x1, &status->tctimeout, 25, 25, 4);
sja1105_unpack(p + 0x1, &status->priorerr, 24, 24, 4);
sja1105_unpack(p + 0x1, &status->nomaster, 23, 23, 4);
sja1105_unpack(p + 0x1, &status->memov, 22, 22, 4);
sja1105_unpack(p + 0x1, &status->memerr, 21, 21, 4);
sja1105_unpack(p + 0x1, &status->invtyp, 19, 19, 4);
sja1105_unpack(p + 0x1, &status->intcyov, 18, 18, 4);
sja1105_unpack(p + 0x1, &status->domerr, 17, 17, 4);
sja1105_unpack(p + 0x1, &status->pcfbagdrop, 16, 16, 4);
sja1105_unpack(p + 0x1, &status->spcprior, 15, 12, 4);
sja1105_unpack(p + 0x1, &status->ageprior, 11, 8, 4);
sja1105_unpack(p + 0x1, &status->portdrop, 6, 6, 4);
sja1105_unpack(p + 0x1, &status->lendrop, 5, 5, 4);
sja1105_unpack(p + 0x1, &status->bagdrop, 4, 4, 4);
sja1105_unpack(p + 0x1, &status->policeerr, 3, 3, 4);
sja1105_unpack(p + 0x1, &status->drpnona664err, 2, 2, 4);
sja1105_unpack(p + 0x1, &status->spcerr, 1, 1, 4);
sja1105_unpack(p + 0x1, &status->agedrp, 0, 0, 4);
}
static void
sja1105_port_status_hl1_unpack(void *buf,
struct sja1105_port_status_hl1 *status)
{
/* Make pointer arithmetic work on 4 bytes */
u32 *p = buf;
sja1105_unpack(p + 0xF, &status->n_n664err, 31, 0, 4);
sja1105_unpack(p + 0xE, &status->n_vlanerr, 31, 0, 4);
sja1105_unpack(p + 0xD, &status->n_unreleased, 31, 0, 4);
sja1105_unpack(p + 0xC, &status->n_sizeerr, 31, 0, 4);
sja1105_unpack(p + 0xB, &status->n_crcerr, 31, 0, 4);
sja1105_unpack(p + 0xA, &status->n_vlnotfound, 31, 0, 4);
sja1105_unpack(p + 0x9, &status->n_ctpolerr, 31, 0, 4);
sja1105_unpack(p + 0x8, &status->n_polerr, 31, 0, 4);
sja1105_unpack(p + 0x7, &status->n_rxfrmsh, 31, 0, 4);
sja1105_unpack(p + 0x6, &status->n_rxfrm, 31, 0, 4);
sja1105_unpack(p + 0x5, &status->n_rxbytesh, 31, 0, 4);
sja1105_unpack(p + 0x4, &status->n_rxbyte, 31, 0, 4);
sja1105_unpack(p + 0x3, &status->n_txfrmsh, 31, 0, 4);
sja1105_unpack(p + 0x2, &status->n_txfrm, 31, 0, 4);
sja1105_unpack(p + 0x1, &status->n_txbytesh, 31, 0, 4);
sja1105_unpack(p + 0x0, &status->n_txbyte, 31, 0, 4);
status->n_rxfrm += status->n_rxfrmsh << 32;
status->n_rxbyte += status->n_rxbytesh << 32;
status->n_txfrm += status->n_txfrmsh << 32;
status->n_txbyte += status->n_txbytesh << 32;
}
static void
sja1105_port_status_hl2_unpack(void *buf,
struct sja1105_port_status_hl2 *status)
{
/* Make pointer arithmetic work on 4 bytes */
u32 *p = buf;
sja1105_unpack(p + 0x3, &status->n_qfull, 31, 0, 4);
sja1105_unpack(p + 0x2, &status->n_part_drop, 31, 0, 4);
sja1105_unpack(p + 0x1, &status->n_egr_disabled, 31, 0, 4);
sja1105_unpack(p + 0x0, &status->n_not_reach, 31, 0, 4);
}
static void
sja1105pqrs_port_status_qlevel_unpack(void *buf,
struct sja1105_port_status_hl2 *status)
{
/* Make pointer arithmetic work on 4 bytes */
u32 *p = buf;
int i;
for (i = 0; i < 8; i++) {
sja1105_unpack(p + i, &status->qlevel_hwm[i], 24, 16, 4);
sja1105_unpack(p + i, &status->qlevel[i], 8, 0, 4);
}
}
static int sja1105_port_status_get_mac(struct sja1105_private *priv,
struct sja1105_port_status_mac *status,
int port)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 packed_buf[SJA1105_SIZE_MAC_AREA] = {0};
int rc;
/* MAC area */
rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac[port],
packed_buf, SJA1105_SIZE_MAC_AREA);
if (rc < 0)
return rc;
sja1105_port_status_mac_unpack(packed_buf, status);
return 0;
}
static int sja1105_port_status_get_hl1(struct sja1105_private *priv,
struct sja1105_port_status_hl1 *status,
int port)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 packed_buf[SJA1105_SIZE_HL1_AREA] = {0};
int rc;
rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl1[port],
packed_buf, SJA1105_SIZE_HL1_AREA);
if (rc < 0)
return rc;
sja1105_port_status_hl1_unpack(packed_buf, status);
return 0;
}
static int sja1105_port_status_get_hl2(struct sja1105_private *priv,
struct sja1105_port_status_hl2 *status,
int port)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 packed_buf[SJA1105_SIZE_QLEVEL_AREA] = {0};
int rc;
rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl2[port],
packed_buf, SJA1105_SIZE_HL2_AREA);
if (rc < 0)
return rc;
sja1105_port_status_hl2_unpack(packed_buf, status);
/* Code below is strictly P/Q/R/S specific. */
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
return 0;
rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->qlevel[port],
packed_buf, SJA1105_SIZE_QLEVEL_AREA);
if (rc < 0)
return rc;
sja1105pqrs_port_status_qlevel_unpack(packed_buf, status);
return 0;
}
static int sja1105_port_status_get(struct sja1105_private *priv,
struct sja1105_port_status *status,
int port)
{
int rc;
rc = sja1105_port_status_get_mac(priv, &status->mac, port);
if (rc < 0)
return rc;
rc = sja1105_port_status_get_hl1(priv, &status->hl1, port);
if (rc < 0)
return rc;
rc = sja1105_port_status_get_hl2(priv, &status->hl2, port);
if (rc < 0)
return rc;
return 0;
}
static char sja1105_port_stats[][ETH_GSTRING_LEN] = {
/* MAC-Level Diagnostic Counters */
"n_runt",
"n_soferr",
"n_alignerr",
"n_miierr",
/* MAC-Level Diagnostic Flags */
"typeerr",
"sizeerr",
"tctimeout",
"priorerr",
"nomaster",
"memov",
"memerr",
"invtyp",
"intcyov",
"domerr",
"pcfbagdrop",
"spcprior",
"ageprior",
"portdrop",
"lendrop",
"bagdrop",
"policeerr",
"drpnona664err",
"spcerr",
"agedrp",
/* High-Level Diagnostic Counters */
"n_n664err",
"n_vlanerr",
"n_unreleased",
"n_sizeerr",
"n_crcerr",
"n_vlnotfound",
"n_ctpolerr",
"n_polerr",
"n_rxfrm",
"n_rxbyte",
"n_txfrm",
"n_txbyte",
"n_qfull",
"n_part_drop",
"n_egr_disabled",
"n_not_reach",
};
static char sja1105pqrs_extra_port_stats[][ETH_GSTRING_LEN] = {
/* Queue Levels */
"qlevel_hwm_0",
"qlevel_hwm_1",
"qlevel_hwm_2",
"qlevel_hwm_3",
"qlevel_hwm_4",
"qlevel_hwm_5",
"qlevel_hwm_6",
"qlevel_hwm_7",
"qlevel_0",
"qlevel_1",
"qlevel_2",
"qlevel_3",
"qlevel_4",
"qlevel_5",
"qlevel_6",
"qlevel_7",
};
void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_port_status status;
int rc, i, k = 0;
memset(&status, 0, sizeof(status));
rc = sja1105_port_status_get(priv, &status, port);
if (rc < 0) {
dev_err(ds->dev, "Failed to read port %d counters: %d\n",
port, rc);
return;
}
memset(data, 0, ARRAY_SIZE(sja1105_port_stats) * sizeof(u64));
data[k++] = status.mac.n_runt;
data[k++] = status.mac.n_soferr;
data[k++] = status.mac.n_alignerr;
data[k++] = status.mac.n_miierr;
data[k++] = status.mac.typeerr;
data[k++] = status.mac.sizeerr;
data[k++] = status.mac.tctimeout;
data[k++] = status.mac.priorerr;
data[k++] = status.mac.nomaster;
data[k++] = status.mac.memov;
data[k++] = status.mac.memerr;
data[k++] = status.mac.invtyp;
data[k++] = status.mac.intcyov;
data[k++] = status.mac.domerr;
data[k++] = status.mac.pcfbagdrop;
data[k++] = status.mac.spcprior;
data[k++] = status.mac.ageprior;
data[k++] = status.mac.portdrop;
data[k++] = status.mac.lendrop;
data[k++] = status.mac.bagdrop;
data[k++] = status.mac.policeerr;
data[k++] = status.mac.drpnona664err;
data[k++] = status.mac.spcerr;
data[k++] = status.mac.agedrp;
data[k++] = status.hl1.n_n664err;
data[k++] = status.hl1.n_vlanerr;
data[k++] = status.hl1.n_unreleased;
data[k++] = status.hl1.n_sizeerr;
data[k++] = status.hl1.n_crcerr;
data[k++] = status.hl1.n_vlnotfound;
data[k++] = status.hl1.n_ctpolerr;
data[k++] = status.hl1.n_polerr;
data[k++] = status.hl1.n_rxfrm;
data[k++] = status.hl1.n_rxbyte;
data[k++] = status.hl1.n_txfrm;
data[k++] = status.hl1.n_txbyte;
data[k++] = status.hl2.n_qfull;
data[k++] = status.hl2.n_part_drop;
data[k++] = status.hl2.n_egr_disabled;
data[k++] = status.hl2.n_not_reach;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
return;
memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) *
sizeof(u64));
for (i = 0; i < 8; i++) {
data[k++] = status.hl2.qlevel_hwm[i];
data[k++] = status.hl2.qlevel[i];
}
}
void sja1105_get_strings(struct dsa_switch *ds, int port,
u32 stringset, u8 *data)
{
struct sja1105_private *priv = ds->priv;
u8 *p = data;
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < ARRAY_SIZE(sja1105_port_stats); i++) {
strlcpy(p, sja1105_port_stats[i], ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
return;
for (i = 0; i < ARRAY_SIZE(sja1105pqrs_extra_port_stats); i++) {
strlcpy(p, sja1105pqrs_extra_port_stats[i],
ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
break;
}
}
int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
int count = ARRAY_SIZE(sja1105_port_stats);
struct sja1105_private *priv = ds->priv;
if (sset != ETH_SS_STATS)
return -EOPNOTSUPP;
if (priv->info->device_id == SJA1105PR_DEVICE_ID ||
priv->info->device_id == SJA1105QS_DEVICE_ID)
count += ARRAY_SIZE(sja1105pqrs_extra_port_stats);
return count;
}