288 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			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;
 | |
| }
 |