root/drivers/net/ethernet/mscc/ocelot_stats.c
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Statistics for Ocelot switch family
 *
 * Copyright (c) 2017 Microsemi Corporation
 * Copyright 2022 NXP
 */
#include <linux/ethtool_netlink.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include "ocelot.h"

enum ocelot_stat {
        OCELOT_STAT_RX_OCTETS,
        OCELOT_STAT_RX_UNICAST,
        OCELOT_STAT_RX_MULTICAST,
        OCELOT_STAT_RX_BROADCAST,
        OCELOT_STAT_RX_SHORTS,
        OCELOT_STAT_RX_FRAGMENTS,
        OCELOT_STAT_RX_JABBERS,
        OCELOT_STAT_RX_CRC_ALIGN_ERRS,
        OCELOT_STAT_RX_SYM_ERRS,
        OCELOT_STAT_RX_64,
        OCELOT_STAT_RX_65_127,
        OCELOT_STAT_RX_128_255,
        OCELOT_STAT_RX_256_511,
        OCELOT_STAT_RX_512_1023,
        OCELOT_STAT_RX_1024_1526,
        OCELOT_STAT_RX_1527_MAX,
        OCELOT_STAT_RX_PAUSE,
        OCELOT_STAT_RX_CONTROL,
        OCELOT_STAT_RX_LONGS,
        OCELOT_STAT_RX_CLASSIFIED_DROPS,
        OCELOT_STAT_RX_RED_PRIO_0,
        OCELOT_STAT_RX_RED_PRIO_1,
        OCELOT_STAT_RX_RED_PRIO_2,
        OCELOT_STAT_RX_RED_PRIO_3,
        OCELOT_STAT_RX_RED_PRIO_4,
        OCELOT_STAT_RX_RED_PRIO_5,
        OCELOT_STAT_RX_RED_PRIO_6,
        OCELOT_STAT_RX_RED_PRIO_7,
        OCELOT_STAT_RX_YELLOW_PRIO_0,
        OCELOT_STAT_RX_YELLOW_PRIO_1,
        OCELOT_STAT_RX_YELLOW_PRIO_2,
        OCELOT_STAT_RX_YELLOW_PRIO_3,
        OCELOT_STAT_RX_YELLOW_PRIO_4,
        OCELOT_STAT_RX_YELLOW_PRIO_5,
        OCELOT_STAT_RX_YELLOW_PRIO_6,
        OCELOT_STAT_RX_YELLOW_PRIO_7,
        OCELOT_STAT_RX_GREEN_PRIO_0,
        OCELOT_STAT_RX_GREEN_PRIO_1,
        OCELOT_STAT_RX_GREEN_PRIO_2,
        OCELOT_STAT_RX_GREEN_PRIO_3,
        OCELOT_STAT_RX_GREEN_PRIO_4,
        OCELOT_STAT_RX_GREEN_PRIO_5,
        OCELOT_STAT_RX_GREEN_PRIO_6,
        OCELOT_STAT_RX_GREEN_PRIO_7,
        OCELOT_STAT_RX_ASSEMBLY_ERRS,
        OCELOT_STAT_RX_SMD_ERRS,
        OCELOT_STAT_RX_ASSEMBLY_OK,
        OCELOT_STAT_RX_MERGE_FRAGMENTS,
        OCELOT_STAT_RX_PMAC_OCTETS,
        OCELOT_STAT_RX_PMAC_UNICAST,
        OCELOT_STAT_RX_PMAC_MULTICAST,
        OCELOT_STAT_RX_PMAC_BROADCAST,
        OCELOT_STAT_RX_PMAC_SHORTS,
        OCELOT_STAT_RX_PMAC_FRAGMENTS,
        OCELOT_STAT_RX_PMAC_JABBERS,
        OCELOT_STAT_RX_PMAC_CRC_ALIGN_ERRS,
        OCELOT_STAT_RX_PMAC_SYM_ERRS,
        OCELOT_STAT_RX_PMAC_64,
        OCELOT_STAT_RX_PMAC_65_127,
        OCELOT_STAT_RX_PMAC_128_255,
        OCELOT_STAT_RX_PMAC_256_511,
        OCELOT_STAT_RX_PMAC_512_1023,
        OCELOT_STAT_RX_PMAC_1024_1526,
        OCELOT_STAT_RX_PMAC_1527_MAX,
        OCELOT_STAT_RX_PMAC_PAUSE,
        OCELOT_STAT_RX_PMAC_CONTROL,
        OCELOT_STAT_RX_PMAC_LONGS,
        OCELOT_STAT_TX_OCTETS,
        OCELOT_STAT_TX_UNICAST,
        OCELOT_STAT_TX_MULTICAST,
        OCELOT_STAT_TX_BROADCAST,
        OCELOT_STAT_TX_COLLISION,
        OCELOT_STAT_TX_DROPS,
        OCELOT_STAT_TX_PAUSE,
        OCELOT_STAT_TX_64,
        OCELOT_STAT_TX_65_127,
        OCELOT_STAT_TX_128_255,
        OCELOT_STAT_TX_256_511,
        OCELOT_STAT_TX_512_1023,
        OCELOT_STAT_TX_1024_1526,
        OCELOT_STAT_TX_1527_MAX,
        OCELOT_STAT_TX_YELLOW_PRIO_0,
        OCELOT_STAT_TX_YELLOW_PRIO_1,
        OCELOT_STAT_TX_YELLOW_PRIO_2,
        OCELOT_STAT_TX_YELLOW_PRIO_3,
        OCELOT_STAT_TX_YELLOW_PRIO_4,
        OCELOT_STAT_TX_YELLOW_PRIO_5,
        OCELOT_STAT_TX_YELLOW_PRIO_6,
        OCELOT_STAT_TX_YELLOW_PRIO_7,
        OCELOT_STAT_TX_GREEN_PRIO_0,
        OCELOT_STAT_TX_GREEN_PRIO_1,
        OCELOT_STAT_TX_GREEN_PRIO_2,
        OCELOT_STAT_TX_GREEN_PRIO_3,
        OCELOT_STAT_TX_GREEN_PRIO_4,
        OCELOT_STAT_TX_GREEN_PRIO_5,
        OCELOT_STAT_TX_GREEN_PRIO_6,
        OCELOT_STAT_TX_GREEN_PRIO_7,
        OCELOT_STAT_TX_AGED,
        OCELOT_STAT_TX_MM_HOLD,
        OCELOT_STAT_TX_MERGE_FRAGMENTS,
        OCELOT_STAT_TX_PMAC_OCTETS,
        OCELOT_STAT_TX_PMAC_UNICAST,
        OCELOT_STAT_TX_PMAC_MULTICAST,
        OCELOT_STAT_TX_PMAC_BROADCAST,
        OCELOT_STAT_TX_PMAC_PAUSE,
        OCELOT_STAT_TX_PMAC_64,
        OCELOT_STAT_TX_PMAC_65_127,
        OCELOT_STAT_TX_PMAC_128_255,
        OCELOT_STAT_TX_PMAC_256_511,
        OCELOT_STAT_TX_PMAC_512_1023,
        OCELOT_STAT_TX_PMAC_1024_1526,
        OCELOT_STAT_TX_PMAC_1527_MAX,
        OCELOT_STAT_DROP_LOCAL,
        OCELOT_STAT_DROP_TAIL,
        OCELOT_STAT_DROP_YELLOW_PRIO_0,
        OCELOT_STAT_DROP_YELLOW_PRIO_1,
        OCELOT_STAT_DROP_YELLOW_PRIO_2,
        OCELOT_STAT_DROP_YELLOW_PRIO_3,
        OCELOT_STAT_DROP_YELLOW_PRIO_4,
        OCELOT_STAT_DROP_YELLOW_PRIO_5,
        OCELOT_STAT_DROP_YELLOW_PRIO_6,
        OCELOT_STAT_DROP_YELLOW_PRIO_7,
        OCELOT_STAT_DROP_GREEN_PRIO_0,
        OCELOT_STAT_DROP_GREEN_PRIO_1,
        OCELOT_STAT_DROP_GREEN_PRIO_2,
        OCELOT_STAT_DROP_GREEN_PRIO_3,
        OCELOT_STAT_DROP_GREEN_PRIO_4,
        OCELOT_STAT_DROP_GREEN_PRIO_5,
        OCELOT_STAT_DROP_GREEN_PRIO_6,
        OCELOT_STAT_DROP_GREEN_PRIO_7,
        OCELOT_NUM_STATS,
};

struct ocelot_stat_layout {
        enum ocelot_reg reg;
        char name[ETH_GSTRING_LEN];
};

/* 32-bit counter checked for wraparound by ocelot_port_update_stats()
 * and copied to ocelot->stats.
 */
#define OCELOT_STAT(kind) \
        [OCELOT_STAT_ ## kind] = { .reg = SYS_COUNT_ ## kind }
/* Same as above, except also exported to ethtool -S. Standard counters should
 * only be exposed to more specific interfaces rather than by their string name.
 */
#define OCELOT_STAT_ETHTOOL(kind, ethtool_name) \
        [OCELOT_STAT_ ## kind] = { .reg = SYS_COUNT_ ## kind, .name = ethtool_name }

#define OCELOT_COMMON_STATS \
        OCELOT_STAT_ETHTOOL(RX_OCTETS, "rx_octets"), \
        OCELOT_STAT_ETHTOOL(RX_UNICAST, "rx_unicast"), \
        OCELOT_STAT_ETHTOOL(RX_MULTICAST, "rx_multicast"), \
        OCELOT_STAT_ETHTOOL(RX_BROADCAST, "rx_broadcast"), \
        OCELOT_STAT_ETHTOOL(RX_SHORTS, "rx_shorts"), \
        OCELOT_STAT_ETHTOOL(RX_FRAGMENTS, "rx_fragments"), \
        OCELOT_STAT_ETHTOOL(RX_JABBERS, "rx_jabbers"), \
        OCELOT_STAT_ETHTOOL(RX_CRC_ALIGN_ERRS, "rx_crc_align_errs"), \
        OCELOT_STAT_ETHTOOL(RX_SYM_ERRS, "rx_sym_errs"), \
        OCELOT_STAT_ETHTOOL(RX_64, "rx_frames_below_65_octets"), \
        OCELOT_STAT_ETHTOOL(RX_65_127, "rx_frames_65_to_127_octets"), \
        OCELOT_STAT_ETHTOOL(RX_128_255, "rx_frames_128_to_255_octets"), \
        OCELOT_STAT_ETHTOOL(RX_256_511, "rx_frames_256_to_511_octets"), \
        OCELOT_STAT_ETHTOOL(RX_512_1023, "rx_frames_512_to_1023_octets"), \
        OCELOT_STAT_ETHTOOL(RX_1024_1526, "rx_frames_1024_to_1526_octets"), \
        OCELOT_STAT_ETHTOOL(RX_1527_MAX, "rx_frames_over_1526_octets"), \
        OCELOT_STAT_ETHTOOL(RX_PAUSE, "rx_pause"), \
        OCELOT_STAT_ETHTOOL(RX_CONTROL, "rx_control"), \
        OCELOT_STAT_ETHTOOL(RX_LONGS, "rx_longs"), \
        OCELOT_STAT_ETHTOOL(RX_CLASSIFIED_DROPS, "rx_classified_drops"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_0, "rx_red_prio_0"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_1, "rx_red_prio_1"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_2, "rx_red_prio_2"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_3, "rx_red_prio_3"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_4, "rx_red_prio_4"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_5, "rx_red_prio_5"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_6, "rx_red_prio_6"), \
        OCELOT_STAT_ETHTOOL(RX_RED_PRIO_7, "rx_red_prio_7"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_0, "rx_yellow_prio_0"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_1, "rx_yellow_prio_1"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_2, "rx_yellow_prio_2"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_3, "rx_yellow_prio_3"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_4, "rx_yellow_prio_4"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_5, "rx_yellow_prio_5"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_6, "rx_yellow_prio_6"), \
        OCELOT_STAT_ETHTOOL(RX_YELLOW_PRIO_7, "rx_yellow_prio_7"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_0, "rx_green_prio_0"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_1, "rx_green_prio_1"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_2, "rx_green_prio_2"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_3, "rx_green_prio_3"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_4, "rx_green_prio_4"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_5, "rx_green_prio_5"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_6, "rx_green_prio_6"), \
        OCELOT_STAT_ETHTOOL(RX_GREEN_PRIO_7, "rx_green_prio_7"), \
        OCELOT_STAT_ETHTOOL(TX_OCTETS, "tx_octets"), \
        OCELOT_STAT_ETHTOOL(TX_UNICAST, "tx_unicast"), \
        OCELOT_STAT_ETHTOOL(TX_MULTICAST, "tx_multicast"), \
        OCELOT_STAT_ETHTOOL(TX_BROADCAST, "tx_broadcast"), \
        OCELOT_STAT_ETHTOOL(TX_COLLISION, "tx_collision"), \
        OCELOT_STAT_ETHTOOL(TX_DROPS, "tx_drops"), \
        OCELOT_STAT_ETHTOOL(TX_PAUSE, "tx_pause"), \
        OCELOT_STAT_ETHTOOL(TX_64, "tx_frames_below_65_octets"), \
        OCELOT_STAT_ETHTOOL(TX_65_127, "tx_frames_65_to_127_octets"), \
        OCELOT_STAT_ETHTOOL(TX_128_255, "tx_frames_128_255_octets"), \
        OCELOT_STAT_ETHTOOL(TX_256_511, "tx_frames_256_511_octets"), \
        OCELOT_STAT_ETHTOOL(TX_512_1023, "tx_frames_512_1023_octets"), \
        OCELOT_STAT_ETHTOOL(TX_1024_1526, "tx_frames_1024_1526_octets"), \
        OCELOT_STAT_ETHTOOL(TX_1527_MAX, "tx_frames_over_1526_octets"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_0, "tx_yellow_prio_0"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_1, "tx_yellow_prio_1"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_2, "tx_yellow_prio_2"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_3, "tx_yellow_prio_3"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_4, "tx_yellow_prio_4"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_5, "tx_yellow_prio_5"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_6, "tx_yellow_prio_6"), \
        OCELOT_STAT_ETHTOOL(TX_YELLOW_PRIO_7, "tx_yellow_prio_7"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_0, "tx_green_prio_0"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_1, "tx_green_prio_1"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_2, "tx_green_prio_2"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_3, "tx_green_prio_3"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_4, "tx_green_prio_4"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_5, "tx_green_prio_5"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_6, "tx_green_prio_6"), \
        OCELOT_STAT_ETHTOOL(TX_GREEN_PRIO_7, "tx_green_prio_7"), \
        OCELOT_STAT_ETHTOOL(TX_AGED, "tx_aged"), \
        OCELOT_STAT_ETHTOOL(DROP_LOCAL, "drop_local"), \
        OCELOT_STAT_ETHTOOL(DROP_TAIL, "drop_tail"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_0, "drop_yellow_prio_0"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_1, "drop_yellow_prio_1"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_2, "drop_yellow_prio_2"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_3, "drop_yellow_prio_3"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_4, "drop_yellow_prio_4"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_5, "drop_yellow_prio_5"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_6, "drop_yellow_prio_6"), \
        OCELOT_STAT_ETHTOOL(DROP_YELLOW_PRIO_7, "drop_yellow_prio_7"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_0, "drop_green_prio_0"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_1, "drop_green_prio_1"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_2, "drop_green_prio_2"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_3, "drop_green_prio_3"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_4, "drop_green_prio_4"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_5, "drop_green_prio_5"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_6, "drop_green_prio_6"), \
        OCELOT_STAT_ETHTOOL(DROP_GREEN_PRIO_7, "drop_green_prio_7")

struct ocelot_stats_region {
        struct list_head node;
        enum ocelot_reg base;
        enum ocelot_stat first_stat;
        int count;
        u32 *buf;
};

static const struct ocelot_stat_layout ocelot_stats_layout[OCELOT_NUM_STATS] = {
        OCELOT_COMMON_STATS,
};

static const struct ocelot_stat_layout ocelot_mm_stats_layout[OCELOT_NUM_STATS] = {
        OCELOT_COMMON_STATS,
        OCELOT_STAT(RX_ASSEMBLY_ERRS),
        OCELOT_STAT(RX_SMD_ERRS),
        OCELOT_STAT(RX_ASSEMBLY_OK),
        OCELOT_STAT(RX_MERGE_FRAGMENTS),
        OCELOT_STAT(TX_MERGE_FRAGMENTS),
        OCELOT_STAT(TX_MM_HOLD),
        OCELOT_STAT(RX_PMAC_OCTETS),
        OCELOT_STAT(RX_PMAC_UNICAST),
        OCELOT_STAT(RX_PMAC_MULTICAST),
        OCELOT_STAT(RX_PMAC_BROADCAST),
        OCELOT_STAT(RX_PMAC_SHORTS),
        OCELOT_STAT(RX_PMAC_FRAGMENTS),
        OCELOT_STAT(RX_PMAC_JABBERS),
        OCELOT_STAT(RX_PMAC_CRC_ALIGN_ERRS),
        OCELOT_STAT(RX_PMAC_SYM_ERRS),
        OCELOT_STAT(RX_PMAC_64),
        OCELOT_STAT(RX_PMAC_65_127),
        OCELOT_STAT(RX_PMAC_128_255),
        OCELOT_STAT(RX_PMAC_256_511),
        OCELOT_STAT(RX_PMAC_512_1023),
        OCELOT_STAT(RX_PMAC_1024_1526),
        OCELOT_STAT(RX_PMAC_1527_MAX),
        OCELOT_STAT(RX_PMAC_PAUSE),
        OCELOT_STAT(RX_PMAC_CONTROL),
        OCELOT_STAT(RX_PMAC_LONGS),
        OCELOT_STAT(TX_PMAC_OCTETS),
        OCELOT_STAT(TX_PMAC_UNICAST),
        OCELOT_STAT(TX_PMAC_MULTICAST),
        OCELOT_STAT(TX_PMAC_BROADCAST),
        OCELOT_STAT(TX_PMAC_PAUSE),
        OCELOT_STAT(TX_PMAC_64),
        OCELOT_STAT(TX_PMAC_65_127),
        OCELOT_STAT(TX_PMAC_128_255),
        OCELOT_STAT(TX_PMAC_256_511),
        OCELOT_STAT(TX_PMAC_512_1023),
        OCELOT_STAT(TX_PMAC_1024_1526),
        OCELOT_STAT(TX_PMAC_1527_MAX),
};

static const struct ocelot_stat_layout *
ocelot_get_stats_layout(struct ocelot *ocelot)
{
        if (ocelot->mm_supported)
                return ocelot_mm_stats_layout;

        return ocelot_stats_layout;
}

/* Read the counters from hardware and keep them in region->buf.
 * Caller must hold &ocelot->stat_view_lock.
 */
static int ocelot_port_update_stats(struct ocelot *ocelot, int port)
{
        struct ocelot_stats_region *region;
        int err;

        /* Configure the port to read the stats from */
        ocelot_write(ocelot, SYS_STAT_CFG_STAT_VIEW(port), SYS_STAT_CFG);

        list_for_each_entry(region, &ocelot->stats_regions, node) {
                err = ocelot_bulk_read(ocelot, region->base, region->buf,
                                       region->count);
                if (err)
                        return err;
        }

        return 0;
}

/* Transfer the counters from region->buf to ocelot->stats.
 * Caller must hold &ocelot->stat_view_lock and &ocelot->stats_lock.
 */
static void ocelot_port_transfer_stats(struct ocelot *ocelot, int port)
{
        struct ocelot_stats_region *region;
        int j;

        list_for_each_entry(region, &ocelot->stats_regions, node) {
                unsigned int idx = port * OCELOT_NUM_STATS + region->first_stat;

                for (j = 0; j < region->count; j++) {
                        u64 *stat = &ocelot->stats[idx + j];
                        u64 val = region->buf[j];

                        if (val < (*stat & U32_MAX))
                                *stat += (u64)1 << 32;

                        *stat = (*stat & ~(u64)U32_MAX) + val;
                }
        }
}

static void ocelot_check_stats_work(struct work_struct *work)
{
        struct delayed_work *del_work = to_delayed_work(work);
        struct ocelot *ocelot = container_of(del_work, struct ocelot,
                                             stats_work);
        int port, err;

        mutex_lock(&ocelot->stat_view_lock);

        for (port = 0; port < ocelot->num_phys_ports; port++) {
                err = ocelot_port_update_stats(ocelot, port);
                if (err)
                        break;

                spin_lock(&ocelot->stats_lock);
                ocelot_port_transfer_stats(ocelot, port);
                spin_unlock(&ocelot->stats_lock);
        }

        if (!err && ocelot->ops->update_stats)
                ocelot->ops->update_stats(ocelot);

        mutex_unlock(&ocelot->stat_view_lock);

        if (err)
                dev_err(ocelot->dev, "Error %d updating ethtool stats\n",  err);

        queue_delayed_work(ocelot->stats_queue, &ocelot->stats_work,
                           OCELOT_STATS_CHECK_DELAY);
}

void ocelot_get_strings(struct ocelot *ocelot, int port, u32 sset, u8 *data)
{
        const struct ocelot_stat_layout *layout;
        enum ocelot_stat i;

        if (sset != ETH_SS_STATS)
                return;

        layout = ocelot_get_stats_layout(ocelot);

        for (i = 0; i < OCELOT_NUM_STATS; i++) {
                if (layout[i].name[0] == '\0')
                        continue;

                memcpy(data, layout[i].name, ETH_GSTRING_LEN);
                data += ETH_GSTRING_LEN;
        }
}
EXPORT_SYMBOL(ocelot_get_strings);

/* Update ocelot->stats for the given port and run the given callback */
static void ocelot_port_stats_run(struct ocelot *ocelot, int port, void *priv,
                                  void (*cb)(struct ocelot *ocelot, int port,
                                             void *priv))
{
        int err;

        mutex_lock(&ocelot->stat_view_lock);

        err = ocelot_port_update_stats(ocelot, port);
        if (err) {
                dev_err(ocelot->dev, "Failed to update port %d stats: %pe\n",
                        port, ERR_PTR(err));
                goto out_unlock;
        }

        spin_lock(&ocelot->stats_lock);

        ocelot_port_transfer_stats(ocelot, port);
        cb(ocelot, port, priv);

        spin_unlock(&ocelot->stats_lock);

out_unlock:
        mutex_unlock(&ocelot->stat_view_lock);
}

int ocelot_get_sset_count(struct ocelot *ocelot, int port, int sset)
{
        const struct ocelot_stat_layout *layout;
        enum ocelot_stat i;
        int num_stats = 0;

        if (sset != ETH_SS_STATS)
                return -EOPNOTSUPP;

        layout = ocelot_get_stats_layout(ocelot);

        for (i = 0; i < OCELOT_NUM_STATS; i++)
                if (layout[i].name[0] != '\0')
                        num_stats++;

        return num_stats;
}
EXPORT_SYMBOL(ocelot_get_sset_count);

static void ocelot_port_ethtool_stats_cb(struct ocelot *ocelot, int port,
                                         void *priv)
{
        const struct ocelot_stat_layout *layout;
        enum ocelot_stat i;
        u64 *data = priv;

        layout = ocelot_get_stats_layout(ocelot);

        /* Copy all supported counters */
        for (i = 0; i < OCELOT_NUM_STATS; i++) {
                int index = port * OCELOT_NUM_STATS + i;

                if (layout[i].name[0] == '\0')
                        continue;

                *data++ = ocelot->stats[index];
        }
}

void ocelot_get_ethtool_stats(struct ocelot *ocelot, int port, u64 *data)
{
        ocelot_port_stats_run(ocelot, port, data, ocelot_port_ethtool_stats_cb);
}
EXPORT_SYMBOL(ocelot_get_ethtool_stats);

static void ocelot_port_pause_stats_cb(struct ocelot *ocelot, int port, void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_pause_stats *pause_stats = priv;

        pause_stats->tx_pause_frames = s[OCELOT_STAT_TX_PAUSE];
        pause_stats->rx_pause_frames = s[OCELOT_STAT_RX_PAUSE];
}

static void ocelot_port_pmac_pause_stats_cb(struct ocelot *ocelot, int port,
                                            void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_pause_stats *pause_stats = priv;

        pause_stats->tx_pause_frames = s[OCELOT_STAT_TX_PMAC_PAUSE];
        pause_stats->rx_pause_frames = s[OCELOT_STAT_RX_PMAC_PAUSE];
}

static void ocelot_port_mm_stats_cb(struct ocelot *ocelot, int port,
                                    void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_mm_stats *stats = priv;

        stats->MACMergeFrameAssErrorCount = s[OCELOT_STAT_RX_ASSEMBLY_ERRS];
        stats->MACMergeFrameSmdErrorCount = s[OCELOT_STAT_RX_SMD_ERRS];
        stats->MACMergeFrameAssOkCount = s[OCELOT_STAT_RX_ASSEMBLY_OK];
        stats->MACMergeFragCountRx = s[OCELOT_STAT_RX_MERGE_FRAGMENTS];
        stats->MACMergeFragCountTx = s[OCELOT_STAT_TX_MERGE_FRAGMENTS];
        stats->MACMergeHoldCount = s[OCELOT_STAT_TX_MM_HOLD];
}

void ocelot_port_get_pause_stats(struct ocelot *ocelot, int port,
                                 struct ethtool_pause_stats *pause_stats)
{
        struct net_device *dev;

        switch (pause_stats->src) {
        case ETHTOOL_MAC_STATS_SRC_EMAC:
                ocelot_port_stats_run(ocelot, port, pause_stats,
                                      ocelot_port_pause_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_PMAC:
                if (ocelot->mm_supported)
                        ocelot_port_stats_run(ocelot, port, pause_stats,
                                              ocelot_port_pmac_pause_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
                dev = ocelot->ops->port_to_netdev(ocelot, port);
                ethtool_aggregate_pause_stats(dev, pause_stats);
                break;
        }
}
EXPORT_SYMBOL_GPL(ocelot_port_get_pause_stats);

void ocelot_port_get_mm_stats(struct ocelot *ocelot, int port,
                              struct ethtool_mm_stats *stats)
{
        if (!ocelot->mm_supported)
                return;

        ocelot_port_stats_run(ocelot, port, stats, ocelot_port_mm_stats_cb);
}
EXPORT_SYMBOL_GPL(ocelot_port_get_mm_stats);

static const struct ethtool_rmon_hist_range ocelot_rmon_ranges[] = {
        {   64,    64 },
        {   65,   127 },
        {  128,   255 },
        {  256,   511 },
        {  512,  1023 },
        { 1024,  1526 },
        { 1527, 65535 },
        {},
};

static void ocelot_port_rmon_stats_cb(struct ocelot *ocelot, int port, void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_rmon_stats *rmon_stats = priv;

        rmon_stats->undersize_pkts = s[OCELOT_STAT_RX_SHORTS];
        rmon_stats->oversize_pkts = s[OCELOT_STAT_RX_LONGS];
        rmon_stats->fragments = s[OCELOT_STAT_RX_FRAGMENTS];
        rmon_stats->jabbers = s[OCELOT_STAT_RX_JABBERS];

        rmon_stats->hist[0] = s[OCELOT_STAT_RX_64];
        rmon_stats->hist[1] = s[OCELOT_STAT_RX_65_127];
        rmon_stats->hist[2] = s[OCELOT_STAT_RX_128_255];
        rmon_stats->hist[3] = s[OCELOT_STAT_RX_256_511];
        rmon_stats->hist[4] = s[OCELOT_STAT_RX_512_1023];
        rmon_stats->hist[5] = s[OCELOT_STAT_RX_1024_1526];
        rmon_stats->hist[6] = s[OCELOT_STAT_RX_1527_MAX];

        rmon_stats->hist_tx[0] = s[OCELOT_STAT_TX_64];
        rmon_stats->hist_tx[1] = s[OCELOT_STAT_TX_65_127];
        rmon_stats->hist_tx[2] = s[OCELOT_STAT_TX_128_255];
        rmon_stats->hist_tx[3] = s[OCELOT_STAT_TX_256_511];
        rmon_stats->hist_tx[4] = s[OCELOT_STAT_TX_512_1023];
        rmon_stats->hist_tx[5] = s[OCELOT_STAT_TX_1024_1526];
        rmon_stats->hist_tx[6] = s[OCELOT_STAT_TX_1527_MAX];
}

static void ocelot_port_pmac_rmon_stats_cb(struct ocelot *ocelot, int port,
                                           void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_rmon_stats *rmon_stats = priv;

        rmon_stats->undersize_pkts = s[OCELOT_STAT_RX_PMAC_SHORTS];
        rmon_stats->oversize_pkts = s[OCELOT_STAT_RX_PMAC_LONGS];
        rmon_stats->fragments = s[OCELOT_STAT_RX_PMAC_FRAGMENTS];
        rmon_stats->jabbers = s[OCELOT_STAT_RX_PMAC_JABBERS];

        rmon_stats->hist[0] = s[OCELOT_STAT_RX_PMAC_64];
        rmon_stats->hist[1] = s[OCELOT_STAT_RX_PMAC_65_127];
        rmon_stats->hist[2] = s[OCELOT_STAT_RX_PMAC_128_255];
        rmon_stats->hist[3] = s[OCELOT_STAT_RX_PMAC_256_511];
        rmon_stats->hist[4] = s[OCELOT_STAT_RX_PMAC_512_1023];
        rmon_stats->hist[5] = s[OCELOT_STAT_RX_PMAC_1024_1526];
        rmon_stats->hist[6] = s[OCELOT_STAT_RX_PMAC_1527_MAX];

        rmon_stats->hist_tx[0] = s[OCELOT_STAT_TX_PMAC_64];
        rmon_stats->hist_tx[1] = s[OCELOT_STAT_TX_PMAC_65_127];
        rmon_stats->hist_tx[2] = s[OCELOT_STAT_TX_PMAC_128_255];
        rmon_stats->hist_tx[3] = s[OCELOT_STAT_TX_PMAC_256_511];
        rmon_stats->hist_tx[4] = s[OCELOT_STAT_TX_PMAC_512_1023];
        rmon_stats->hist_tx[5] = s[OCELOT_STAT_TX_PMAC_1024_1526];
        rmon_stats->hist_tx[6] = s[OCELOT_STAT_TX_PMAC_1527_MAX];
}

void ocelot_port_get_rmon_stats(struct ocelot *ocelot, int port,
                                struct ethtool_rmon_stats *rmon_stats,
                                const struct ethtool_rmon_hist_range **ranges)
{
        struct net_device *dev;

        *ranges = ocelot_rmon_ranges;

        switch (rmon_stats->src) {
        case ETHTOOL_MAC_STATS_SRC_EMAC:
                ocelot_port_stats_run(ocelot, port, rmon_stats,
                                      ocelot_port_rmon_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_PMAC:
                if (ocelot->mm_supported)
                        ocelot_port_stats_run(ocelot, port, rmon_stats,
                                              ocelot_port_pmac_rmon_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
                dev = ocelot->ops->port_to_netdev(ocelot, port);
                ethtool_aggregate_rmon_stats(dev, rmon_stats);
                break;
        }
}
EXPORT_SYMBOL_GPL(ocelot_port_get_rmon_stats);

static void ocelot_port_ctrl_stats_cb(struct ocelot *ocelot, int port, void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_ctrl_stats *ctrl_stats = priv;

        ctrl_stats->MACControlFramesReceived = s[OCELOT_STAT_RX_CONTROL];
}

static void ocelot_port_pmac_ctrl_stats_cb(struct ocelot *ocelot, int port,
                                           void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_ctrl_stats *ctrl_stats = priv;

        ctrl_stats->MACControlFramesReceived = s[OCELOT_STAT_RX_PMAC_CONTROL];
}

void ocelot_port_get_eth_ctrl_stats(struct ocelot *ocelot, int port,
                                    struct ethtool_eth_ctrl_stats *ctrl_stats)
{
        struct net_device *dev;

        switch (ctrl_stats->src) {
        case ETHTOOL_MAC_STATS_SRC_EMAC:
                ocelot_port_stats_run(ocelot, port, ctrl_stats,
                                      ocelot_port_ctrl_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_PMAC:
                if (ocelot->mm_supported)
                        ocelot_port_stats_run(ocelot, port, ctrl_stats,
                                              ocelot_port_pmac_ctrl_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
                dev = ocelot->ops->port_to_netdev(ocelot, port);
                ethtool_aggregate_ctrl_stats(dev, ctrl_stats);
                break;
        }
}
EXPORT_SYMBOL_GPL(ocelot_port_get_eth_ctrl_stats);

static void ocelot_port_mac_stats_cb(struct ocelot *ocelot, int port, void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_mac_stats *mac_stats = priv;

        mac_stats->OctetsTransmittedOK = s[OCELOT_STAT_TX_OCTETS];
        mac_stats->FramesTransmittedOK = s[OCELOT_STAT_TX_64] +
                                         s[OCELOT_STAT_TX_65_127] +
                                         s[OCELOT_STAT_TX_128_255] +
                                         s[OCELOT_STAT_TX_256_511] +
                                         s[OCELOT_STAT_TX_512_1023] +
                                         s[OCELOT_STAT_TX_1024_1526] +
                                         s[OCELOT_STAT_TX_1527_MAX];
        mac_stats->OctetsReceivedOK = s[OCELOT_STAT_RX_OCTETS];
        mac_stats->FramesReceivedOK = s[OCELOT_STAT_RX_GREEN_PRIO_0] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_1] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_2] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_3] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_4] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_5] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_6] +
                                      s[OCELOT_STAT_RX_GREEN_PRIO_7] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_0] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_1] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_2] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_3] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_4] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_5] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_6] +
                                      s[OCELOT_STAT_RX_YELLOW_PRIO_7];
        mac_stats->MulticastFramesXmittedOK = s[OCELOT_STAT_TX_MULTICAST];
        mac_stats->BroadcastFramesXmittedOK = s[OCELOT_STAT_TX_BROADCAST];
        mac_stats->MulticastFramesReceivedOK = s[OCELOT_STAT_RX_MULTICAST];
        mac_stats->BroadcastFramesReceivedOK = s[OCELOT_STAT_RX_BROADCAST];
        mac_stats->FrameTooLongErrors = s[OCELOT_STAT_RX_LONGS];
        /* Sadly, C_RX_CRC is the sum of FCS and alignment errors, they are not
         * counted individually.
         */
        mac_stats->FrameCheckSequenceErrors = s[OCELOT_STAT_RX_CRC_ALIGN_ERRS];
        mac_stats->AlignmentErrors = s[OCELOT_STAT_RX_CRC_ALIGN_ERRS];
}

static void ocelot_port_pmac_mac_stats_cb(struct ocelot *ocelot, int port,
                                          void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_mac_stats *mac_stats = priv;

        mac_stats->OctetsTransmittedOK = s[OCELOT_STAT_TX_PMAC_OCTETS];
        mac_stats->FramesTransmittedOK = s[OCELOT_STAT_TX_PMAC_64] +
                                         s[OCELOT_STAT_TX_PMAC_65_127] +
                                         s[OCELOT_STAT_TX_PMAC_128_255] +
                                         s[OCELOT_STAT_TX_PMAC_256_511] +
                                         s[OCELOT_STAT_TX_PMAC_512_1023] +
                                         s[OCELOT_STAT_TX_PMAC_1024_1526] +
                                         s[OCELOT_STAT_TX_PMAC_1527_MAX];
        mac_stats->OctetsReceivedOK = s[OCELOT_STAT_RX_PMAC_OCTETS];
        mac_stats->FramesReceivedOK = s[OCELOT_STAT_RX_PMAC_64] +
                                      s[OCELOT_STAT_RX_PMAC_65_127] +
                                      s[OCELOT_STAT_RX_PMAC_128_255] +
                                      s[OCELOT_STAT_RX_PMAC_256_511] +
                                      s[OCELOT_STAT_RX_PMAC_512_1023] +
                                      s[OCELOT_STAT_RX_PMAC_1024_1526] +
                                      s[OCELOT_STAT_RX_PMAC_1527_MAX];
        mac_stats->MulticastFramesXmittedOK = s[OCELOT_STAT_TX_PMAC_MULTICAST];
        mac_stats->BroadcastFramesXmittedOK = s[OCELOT_STAT_TX_PMAC_BROADCAST];
        mac_stats->MulticastFramesReceivedOK = s[OCELOT_STAT_RX_PMAC_MULTICAST];
        mac_stats->BroadcastFramesReceivedOK = s[OCELOT_STAT_RX_PMAC_BROADCAST];
        mac_stats->FrameTooLongErrors = s[OCELOT_STAT_RX_PMAC_LONGS];
        /* Sadly, C_RX_CRC is the sum of FCS and alignment errors, they are not
         * counted individually.
         */
        mac_stats->FrameCheckSequenceErrors = s[OCELOT_STAT_RX_PMAC_CRC_ALIGN_ERRS];
        mac_stats->AlignmentErrors = s[OCELOT_STAT_RX_PMAC_CRC_ALIGN_ERRS];
}

void ocelot_port_get_eth_mac_stats(struct ocelot *ocelot, int port,
                                   struct ethtool_eth_mac_stats *mac_stats)
{
        struct net_device *dev;

        switch (mac_stats->src) {
        case ETHTOOL_MAC_STATS_SRC_EMAC:
                ocelot_port_stats_run(ocelot, port, mac_stats,
                                      ocelot_port_mac_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_PMAC:
                if (ocelot->mm_supported)
                        ocelot_port_stats_run(ocelot, port, mac_stats,
                                              ocelot_port_pmac_mac_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
                dev = ocelot->ops->port_to_netdev(ocelot, port);
                ethtool_aggregate_mac_stats(dev, mac_stats);
                break;
        }
}
EXPORT_SYMBOL_GPL(ocelot_port_get_eth_mac_stats);

static void ocelot_port_phy_stats_cb(struct ocelot *ocelot, int port, void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_phy_stats *phy_stats = priv;

        phy_stats->SymbolErrorDuringCarrier = s[OCELOT_STAT_RX_SYM_ERRS];
}

static void ocelot_port_pmac_phy_stats_cb(struct ocelot *ocelot, int port,
                                          void *priv)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];
        struct ethtool_eth_phy_stats *phy_stats = priv;

        phy_stats->SymbolErrorDuringCarrier = s[OCELOT_STAT_RX_PMAC_SYM_ERRS];
}

void ocelot_port_get_eth_phy_stats(struct ocelot *ocelot, int port,
                                   struct ethtool_eth_phy_stats *phy_stats)
{
        struct net_device *dev;

        switch (phy_stats->src) {
        case ETHTOOL_MAC_STATS_SRC_EMAC:
                ocelot_port_stats_run(ocelot, port, phy_stats,
                                      ocelot_port_phy_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_PMAC:
                if (ocelot->mm_supported)
                        ocelot_port_stats_run(ocelot, port, phy_stats,
                                              ocelot_port_pmac_phy_stats_cb);
                break;
        case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
                dev = ocelot->ops->port_to_netdev(ocelot, port);
                ethtool_aggregate_phy_stats(dev, phy_stats);
                break;
        }
}
EXPORT_SYMBOL_GPL(ocelot_port_get_eth_phy_stats);

void ocelot_port_get_ts_stats(struct ocelot *ocelot, int port,
                              struct ethtool_ts_stats *ts_stats)
{
        struct ocelot_port *ocelot_port = ocelot->ports[port];
        struct ocelot_ts_stats *stats = ocelot_port->ts_stats;
        unsigned int start;

        if (!ocelot->ptp)
                return;

        do {
                start = u64_stats_fetch_begin(&stats->syncp);
                ts_stats->pkts = stats->pkts;
                ts_stats->onestep_pkts_unconfirmed = stats->onestep_pkts_unconfirmed;
                ts_stats->lost = stats->lost;
                ts_stats->err = stats->err;
        } while (u64_stats_fetch_retry(&stats->syncp, start));
}
EXPORT_SYMBOL_GPL(ocelot_port_get_ts_stats);

void ocelot_port_get_stats64(struct ocelot *ocelot, int port,
                             struct rtnl_link_stats64 *stats)
{
        u64 *s = &ocelot->stats[port * OCELOT_NUM_STATS];

        spin_lock(&ocelot->stats_lock);

        /* Get Rx stats */
        stats->rx_bytes = s[OCELOT_STAT_RX_OCTETS];
        stats->rx_packets = s[OCELOT_STAT_RX_SHORTS] +
                            s[OCELOT_STAT_RX_FRAGMENTS] +
                            s[OCELOT_STAT_RX_JABBERS] +
                            s[OCELOT_STAT_RX_LONGS] +
                            s[OCELOT_STAT_RX_64] +
                            s[OCELOT_STAT_RX_65_127] +
                            s[OCELOT_STAT_RX_128_255] +
                            s[OCELOT_STAT_RX_256_511] +
                            s[OCELOT_STAT_RX_512_1023] +
                            s[OCELOT_STAT_RX_1024_1526] +
                            s[OCELOT_STAT_RX_1527_MAX];
        stats->multicast = s[OCELOT_STAT_RX_MULTICAST];
        stats->rx_missed_errors = s[OCELOT_STAT_DROP_TAIL];
        stats->rx_dropped = s[OCELOT_STAT_RX_RED_PRIO_0] +
                            s[OCELOT_STAT_RX_RED_PRIO_1] +
                            s[OCELOT_STAT_RX_RED_PRIO_2] +
                            s[OCELOT_STAT_RX_RED_PRIO_3] +
                            s[OCELOT_STAT_RX_RED_PRIO_4] +
                            s[OCELOT_STAT_RX_RED_PRIO_5] +
                            s[OCELOT_STAT_RX_RED_PRIO_6] +
                            s[OCELOT_STAT_RX_RED_PRIO_7] +
                            s[OCELOT_STAT_DROP_LOCAL] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_0] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_1] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_2] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_3] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_4] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_5] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_6] +
                            s[OCELOT_STAT_DROP_YELLOW_PRIO_7] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_0] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_1] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_2] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_3] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_4] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_5] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_6] +
                            s[OCELOT_STAT_DROP_GREEN_PRIO_7];

        /* Get Tx stats */
        stats->tx_bytes = s[OCELOT_STAT_TX_OCTETS];
        stats->tx_packets = s[OCELOT_STAT_TX_64] +
                            s[OCELOT_STAT_TX_65_127] +
                            s[OCELOT_STAT_TX_128_255] +
                            s[OCELOT_STAT_TX_256_511] +
                            s[OCELOT_STAT_TX_512_1023] +
                            s[OCELOT_STAT_TX_1024_1526] +
                            s[OCELOT_STAT_TX_1527_MAX];
        stats->tx_dropped = s[OCELOT_STAT_TX_DROPS] +
                            s[OCELOT_STAT_TX_AGED];
        stats->collisions = s[OCELOT_STAT_TX_COLLISION];

        spin_unlock(&ocelot->stats_lock);
}
EXPORT_SYMBOL(ocelot_port_get_stats64);

static int ocelot_prepare_stats_regions(struct ocelot *ocelot)
{
        struct ocelot_stats_region *region = NULL;
        const struct ocelot_stat_layout *layout;
        enum ocelot_reg last = 0;
        enum ocelot_stat i;

        INIT_LIST_HEAD(&ocelot->stats_regions);

        layout = ocelot_get_stats_layout(ocelot);

        for (i = 0; i < OCELOT_NUM_STATS; i++) {
                if (!layout[i].reg)
                        continue;

                /* enum ocelot_stat must be kept sorted in the same order
                 * as the addresses behind layout[i].reg in order to have
                 * efficient bulking
                 */
                if (last) {
                        WARN(ocelot->map[SYS][last & REG_MASK] >= ocelot->map[SYS][layout[i].reg & REG_MASK],
                             "reg 0x%x had address 0x%x but reg 0x%x has address 0x%x, bulking broken!",
                             last, ocelot->map[SYS][last & REG_MASK],
                             layout[i].reg, ocelot->map[SYS][layout[i].reg & REG_MASK]);
                }

                if (region && ocelot->map[SYS][layout[i].reg & REG_MASK] ==
                    ocelot->map[SYS][last & REG_MASK] + 4) {
                        region->count++;
                } else {
                        region = devm_kzalloc(ocelot->dev, sizeof(*region),
                                              GFP_KERNEL);
                        if (!region)
                                return -ENOMEM;

                        region->base = layout[i].reg;
                        region->first_stat = i;
                        region->count = 1;
                        list_add_tail(&region->node, &ocelot->stats_regions);
                }

                last = layout[i].reg;
        }

        list_for_each_entry(region, &ocelot->stats_regions, node) {
                enum ocelot_target target;
                u32 addr;

                ocelot_reg_to_target_addr(ocelot, region->base, &target,
                                          &addr);

                dev_dbg(ocelot->dev,
                        "region of %d contiguous counters starting with SYS:STAT:CNT[0x%03x]\n",
                        region->count, addr / 4);
                region->buf = devm_kcalloc(ocelot->dev, region->count,
                                           sizeof(*region->buf), GFP_KERNEL);
                if (!region->buf)
                        return -ENOMEM;
        }

        return 0;
}

int ocelot_stats_init(struct ocelot *ocelot)
{
        char queue_name[32];
        int ret;

        ocelot->stats = devm_kcalloc(ocelot->dev,
                                     ocelot->num_phys_ports * OCELOT_NUM_STATS,
                                     sizeof(u64), GFP_KERNEL);
        if (!ocelot->stats)
                return -ENOMEM;

        if (ocelot->ptp) {
                for (int port = 0; port < ocelot->num_phys_ports; port++) {
                        struct ocelot_port *ocelot_port = ocelot->ports[port];

                        if (!ocelot_port)
                                continue;

                        ocelot_port->ts_stats = devm_kzalloc(ocelot->dev,
                                                             sizeof(*ocelot_port->ts_stats),
                                                             GFP_KERNEL);
                        if (!ocelot_port->ts_stats)
                                return -ENOMEM;

                        u64_stats_init(&ocelot_port->ts_stats->syncp);
                }
        }

        snprintf(queue_name, sizeof(queue_name), "%s-stats",
                 dev_name(ocelot->dev));
        ocelot->stats_queue = create_singlethread_workqueue(queue_name);
        if (!ocelot->stats_queue)
                return -ENOMEM;

        spin_lock_init(&ocelot->stats_lock);
        mutex_init(&ocelot->stat_view_lock);

        ret = ocelot_prepare_stats_regions(ocelot);
        if (ret) {
                destroy_workqueue(ocelot->stats_queue);
                return ret;
        }

        INIT_DELAYED_WORK(&ocelot->stats_work, ocelot_check_stats_work);
        queue_delayed_work(ocelot->stats_queue, &ocelot->stats_work,
                           OCELOT_STATS_CHECK_DELAY);

        return 0;
}

void ocelot_stats_deinit(struct ocelot *ocelot)
{
        disable_delayed_work_sync(&ocelot->stats_work);
        destroy_workqueue(ocelot->stats_queue);
}