root/drivers/net/ethernet/qualcomm/emac/emac-ethtool.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
 */

#include <linux/ethtool.h>
#include <linux/phy.h>

#include "emac.h"

static const char * const emac_ethtool_stat_strings[] = {
        "rx_ok",
        "rx_bcast",
        "rx_mcast",
        "rx_pause",
        "rx_ctrl",
        "rx_fcs_err",
        "rx_len_err",
        "rx_byte_cnt",
        "rx_runt",
        "rx_frag",
        "rx_sz_64",
        "rx_sz_65_127",
        "rx_sz_128_255",
        "rx_sz_256_511",
        "rx_sz_512_1023",
        "rx_sz_1024_1518",
        "rx_sz_1519_max",
        "rx_sz_ov",
        "rx_rxf_ov",
        "rx_align_err",
        "rx_bcast_byte_cnt",
        "rx_mcast_byte_cnt",
        "rx_err_addr",
        "rx_crc_align",
        "rx_jabbers",
        "tx_ok",
        "tx_bcast",
        "tx_mcast",
        "tx_pause",
        "tx_exc_defer",
        "tx_ctrl",
        "tx_defer",
        "tx_byte_cnt",
        "tx_sz_64",
        "tx_sz_65_127",
        "tx_sz_128_255",
        "tx_sz_256_511",
        "tx_sz_512_1023",
        "tx_sz_1024_1518",
        "tx_sz_1519_max",
        "tx_1_col",
        "tx_2_col",
        "tx_late_col",
        "tx_abort_col",
        "tx_underrun",
        "tx_rd_eop",
        "tx_len_err",
        "tx_trunc",
        "tx_bcast_byte",
        "tx_mcast_byte",
        "tx_col",
};

#define EMAC_STATS_LEN  ARRAY_SIZE(emac_ethtool_stat_strings)

static u32 emac_get_msglevel(struct net_device *netdev)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        return adpt->msg_enable;
}

static void emac_set_msglevel(struct net_device *netdev, u32 data)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        adpt->msg_enable = data;
}

static int emac_get_sset_count(struct net_device *netdev, int sset)
{
        switch (sset) {
        case ETH_SS_PRIV_FLAGS:
                return 1;
        case ETH_SS_STATS:
                return EMAC_STATS_LEN;
        default:
                return -EOPNOTSUPP;
        }
}

static void emac_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
        unsigned int i;

        switch (stringset) {
        case ETH_SS_PRIV_FLAGS:
                strcpy(data, "single-pause-mode");
                break;

        case ETH_SS_STATS:
                for (i = 0; i < EMAC_STATS_LEN; i++) {
                        strscpy(data, emac_ethtool_stat_strings[i],
                                ETH_GSTRING_LEN);
                        data += ETH_GSTRING_LEN;
                }
                break;
        }
}

static void emac_get_ethtool_stats(struct net_device *netdev,
                                   struct ethtool_stats *stats,
                                   u64 *data)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        spin_lock(&adpt->stats.lock);

        emac_update_hw_stats(adpt);
        memcpy(data, &adpt->stats, EMAC_STATS_LEN * sizeof(u64));

        spin_unlock(&adpt->stats.lock);
}

static int emac_nway_reset(struct net_device *netdev)
{
        struct phy_device *phydev = netdev->phydev;

        if (!phydev)
                return -ENODEV;

        return genphy_restart_aneg(phydev);
}

static void emac_get_ringparam(struct net_device *netdev,
                               struct ethtool_ringparam *ring,
                               struct kernel_ethtool_ringparam *kernel_ring,
                               struct netlink_ext_ack *extack)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        ring->rx_max_pending = EMAC_MAX_RX_DESCS;
        ring->tx_max_pending = EMAC_MAX_TX_DESCS;
        ring->rx_pending = adpt->rx_desc_cnt;
        ring->tx_pending = adpt->tx_desc_cnt;
}

static int emac_set_ringparam(struct net_device *netdev,
                              struct ethtool_ringparam *ring,
                              struct kernel_ethtool_ringparam *kernel_ring,
                              struct netlink_ext_ack *extack)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        /* We don't have separate queues/rings for small/large frames, so
         * reject any attempt to specify those values separately.
         */
        if (ring->rx_mini_pending || ring->rx_jumbo_pending)
                return -EINVAL;

        adpt->tx_desc_cnt =
                clamp_val(ring->tx_pending, EMAC_MIN_TX_DESCS, EMAC_MAX_TX_DESCS);

        adpt->rx_desc_cnt =
                clamp_val(ring->rx_pending, EMAC_MIN_RX_DESCS, EMAC_MAX_RX_DESCS);

        if (netif_running(netdev))
                return emac_reinit_locked(adpt);

        return 0;
}

static void emac_get_pauseparam(struct net_device *netdev,
                                struct ethtool_pauseparam *pause)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        pause->autoneg = adpt->automatic ? AUTONEG_ENABLE : AUTONEG_DISABLE;
        pause->rx_pause = adpt->rx_flow_control ? 1 : 0;
        pause->tx_pause = adpt->tx_flow_control ? 1 : 0;
}

static int emac_set_pauseparam(struct net_device *netdev,
                               struct ethtool_pauseparam *pause)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        adpt->automatic = pause->autoneg == AUTONEG_ENABLE;
        adpt->rx_flow_control = pause->rx_pause != 0;
        adpt->tx_flow_control = pause->tx_pause != 0;

        if (netif_running(netdev))
                return emac_reinit_locked(adpt);

        return 0;
}

/* Selected registers that might want to track during runtime. */
static const u16 emac_regs[] = {
        EMAC_DMA_MAS_CTRL,
        EMAC_MAC_CTRL,
        EMAC_TXQ_CTRL_0,
        EMAC_RXQ_CTRL_0,
        EMAC_DMA_CTRL,
        EMAC_INT_MASK,
        EMAC_AXI_MAST_CTRL,
        EMAC_CORE_HW_VERSION,
        EMAC_MISC_CTRL,
};

/* Every time emac_regs[] above is changed, increase this version number. */
#define EMAC_REGS_VERSION       0

#define EMAC_MAX_REG_SIZE       ARRAY_SIZE(emac_regs)

static void emac_get_regs(struct net_device *netdev,
                          struct ethtool_regs *regs, void *buff)
{
        struct emac_adapter *adpt = netdev_priv(netdev);
        u32 *val = buff;
        unsigned int i;

        regs->version = EMAC_REGS_VERSION;
        regs->len = EMAC_MAX_REG_SIZE * sizeof(u32);

        for (i = 0; i < EMAC_MAX_REG_SIZE; i++)
                val[i] = readl(adpt->base + emac_regs[i]);
}

static int emac_get_regs_len(struct net_device *netdev)
{
        return EMAC_MAX_REG_SIZE * sizeof(u32);
}

#define EMAC_PRIV_ENABLE_SINGLE_PAUSE   BIT(0)

static int emac_set_priv_flags(struct net_device *netdev, u32 flags)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        adpt->single_pause_mode = !!(flags & EMAC_PRIV_ENABLE_SINGLE_PAUSE);

        if (netif_running(netdev))
                return emac_reinit_locked(adpt);

        return 0;
}

static u32 emac_get_priv_flags(struct net_device *netdev)
{
        struct emac_adapter *adpt = netdev_priv(netdev);

        return adpt->single_pause_mode ? EMAC_PRIV_ENABLE_SINGLE_PAUSE : 0;
}

static const struct ethtool_ops emac_ethtool_ops = {
        .get_link_ksettings = phy_ethtool_get_link_ksettings,
        .set_link_ksettings = phy_ethtool_set_link_ksettings,

        .get_msglevel    = emac_get_msglevel,
        .set_msglevel    = emac_set_msglevel,

        .get_sset_count  = emac_get_sset_count,
        .get_strings = emac_get_strings,
        .get_ethtool_stats = emac_get_ethtool_stats,

        .get_ringparam = emac_get_ringparam,
        .set_ringparam = emac_set_ringparam,

        .get_pauseparam = emac_get_pauseparam,
        .set_pauseparam = emac_set_pauseparam,

        .nway_reset = emac_nway_reset,

        .get_link = ethtool_op_get_link,

        .get_regs_len    = emac_get_regs_len,
        .get_regs        = emac_get_regs,

        .set_priv_flags = emac_set_priv_flags,
        .get_priv_flags = emac_get_priv_flags,
};

void emac_set_ethtool_ops(struct net_device *netdev)
{
        netdev->ethtool_ops = &emac_ethtool_ops;
}