net: phy: adin: add ethtool get_stats support
authorAlexandru Ardelean <alexandru.ardelean@analog.com>
Fri, 16 Aug 2019 13:10:10 +0000 (16:10 +0300)
committerDavid S. Miller <davem@davemloft.net>
Fri, 16 Aug 2019 18:56:26 +0000 (11:56 -0700)
This change implements retrieving all the error counters from the PHY.

The counters require that the RxErrCnt register (0x0014) be read first,
after which copies of the counters are latched into the registers. This
ensures that all registers read after RxErrCnt are synchronized at the
moment that they are read.

The counter values need to be accumulated by the driver, as each time that
RxErrCnt is read, the values that are latched are the ones that have
incremented from the last read.

Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/adin.c

index 131b577..ac79e16 100644 (file)
@@ -24,6 +24,8 @@
 #define   ADIN1300_AUTO_MDI_EN                 BIT(10)
 #define   ADIN1300_MAN_MDIX_EN                 BIT(9)
 
+#define ADIN1300_RX_ERR_CNT                    0x0014
+
 #define ADIN1300_PHY_CTRL2                     0x0016
 #define   ADIN1300_DOWNSPEED_AN_100_EN         BIT(11)
 #define   ADIN1300_DOWNSPEED_AN_10_EN          BIT(10)
@@ -146,6 +148,33 @@ static struct adin_clause45_mmd_map adin_clause45_mmd_map[] = {
        { MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR,    ADIN1300_LPI_WAKE_ERR_CNT_REG },
 };
 
+struct adin_hw_stat {
+       const char *string;
+       u16 reg1;
+       u16 reg2;
+};
+
+static struct adin_hw_stat adin_hw_stats[] = {
+       { "total_frames_checked_count",         0x940A, 0x940B }, /* hi + lo */
+       { "length_error_frames_count",          0x940C },
+       { "alignment_error_frames_count",       0x940D },
+       { "symbol_error_count",                 0x940E },
+       { "oversized_frames_count",             0x940F },
+       { "undersized_frames_count",            0x9410 },
+       { "odd_nibble_frames_count",            0x9411 },
+       { "odd_preamble_packet_count",          0x9412 },
+       { "dribble_bits_frames_count",          0x9413 },
+       { "false_carrier_events_count",         0x9414 },
+};
+
+/**
+ * struct adin_priv - ADIN PHY driver private data
+ * stats               statistic counters for the PHY
+ */
+struct adin_priv {
+       u64                     stats[ARRAY_SIZE(adin_hw_stats)];
+};
+
 static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg)
 {
        size_t i;
@@ -548,10 +577,102 @@ static int adin_soft_reset(struct phy_device *phydev)
        return rc < 0 ? rc : 0;
 }
 
+static int adin_get_sset_count(struct phy_device *phydev)
+{
+       return ARRAY_SIZE(adin_hw_stats);
+}
+
+static void adin_get_strings(struct phy_device *phydev, u8 *data)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++) {
+               strlcpy(&data[i * ETH_GSTRING_LEN],
+                       adin_hw_stats[i].string, ETH_GSTRING_LEN);
+       }
+}
+
+static int adin_read_mmd_stat_regs(struct phy_device *phydev,
+                                  struct adin_hw_stat *stat,
+                                  u32 *val)
+{
+       int ret;
+
+       ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, stat->reg1);
+       if (ret < 0)
+               return ret;
+
+       *val = (ret & 0xffff);
+
+       if (stat->reg2 == 0)
+               return 0;
+
+       ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, stat->reg2);
+       if (ret < 0)
+               return ret;
+
+       *val <<= 16;
+       *val |= (ret & 0xffff);
+
+       return 0;
+}
+
+static u64 adin_get_stat(struct phy_device *phydev, int i)
+{
+       struct adin_hw_stat *stat = &adin_hw_stats[i];
+       struct adin_priv *priv = phydev->priv;
+       u32 val;
+       int ret;
+
+       if (stat->reg1 > 0x1f) {
+               ret = adin_read_mmd_stat_regs(phydev, stat, &val);
+               if (ret < 0)
+                       return (u64)(~0);
+       } else {
+               ret = phy_read(phydev, stat->reg1);
+               if (ret < 0)
+                       return (u64)(~0);
+               val = (ret & 0xffff);
+       }
+
+       priv->stats[i] += val;
+
+       return priv->stats[i];
+}
+
+static void adin_get_stats(struct phy_device *phydev,
+                          struct ethtool_stats *stats, u64 *data)
+{
+       int i, rc;
+
+       /* latch copies of all the frame-checker counters */
+       rc = phy_read(phydev, ADIN1300_RX_ERR_CNT);
+       if (rc < 0)
+               return;
+
+       for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++)
+               data[i] = adin_get_stat(phydev, i);
+}
+
+static int adin_probe(struct phy_device *phydev)
+{
+       struct device *dev = &phydev->mdio.dev;
+       struct adin_priv *priv;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       phydev->priv = priv;
+
+       return 0;
+}
+
 static struct phy_driver adin_driver[] = {
        {
                PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200),
                .name           = "ADIN1200",
+               .probe          = adin_probe,
                .config_init    = adin_config_init,
                .soft_reset     = adin_soft_reset,
                .config_aneg    = adin_config_aneg,
@@ -560,6 +681,9 @@ static struct phy_driver adin_driver[] = {
                .set_tunable    = adin_set_tunable,
                .ack_interrupt  = adin_phy_ack_intr,
                .config_intr    = adin_phy_config_intr,
+               .get_sset_count = adin_get_sset_count,
+               .get_strings    = adin_get_strings,
+               .get_stats      = adin_get_stats,
                .resume         = genphy_resume,
                .suspend        = genphy_suspend,
                .read_mmd       = adin_read_mmd,
@@ -568,6 +692,7 @@ static struct phy_driver adin_driver[] = {
        {
                PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300),
                .name           = "ADIN1300",
+               .probe          = adin_probe,
                .config_init    = adin_config_init,
                .soft_reset     = adin_soft_reset,
                .config_aneg    = adin_config_aneg,
@@ -576,6 +701,9 @@ static struct phy_driver adin_driver[] = {
                .set_tunable    = adin_set_tunable,
                .ack_interrupt  = adin_phy_ack_intr,
                .config_intr    = adin_phy_config_intr,
+               .get_sset_count = adin_get_sset_count,
+               .get_strings    = adin_get_strings,
+               .get_stats      = adin_get_stats,
                .resume         = genphy_resume,
                .suspend        = genphy_suspend,
                .read_mmd       = adin_read_mmd,