162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2010 ASIX Electronics Corporation 462306a36Sopenharmony_ci * Copyright (c) 2020 Samsung Electronics Co., Ltd. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * ASIX AX88796C SPI Fast Ethernet Linux driver 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) "ax88796c: " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/bitmap.h> 1262306a36Sopenharmony_ci#include <linux/iopoll.h> 1362306a36Sopenharmony_ci#include <linux/phy.h> 1462306a36Sopenharmony_ci#include <linux/netdevice.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "ax88796c_main.h" 1762306a36Sopenharmony_ci#include "ax88796c_ioctl.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic const char ax88796c_priv_flag_names[][ETH_GSTRING_LEN] = { 2062306a36Sopenharmony_ci "SPICompression", 2162306a36Sopenharmony_ci}; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic void 2462306a36Sopenharmony_ciax88796c_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *info) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci /* Inherit standard device info */ 2762306a36Sopenharmony_ci strncpy(info->driver, DRV_NAME, sizeof(info->driver)); 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic u32 ax88796c_get_msglevel(struct net_device *ndev) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci return ax_local->msg_enable; 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic void ax88796c_set_msglevel(struct net_device *ndev, u32 level) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci ax_local->msg_enable = level; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void 4562306a36Sopenharmony_ciax88796c_get_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *pause) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci pause->tx_pause = !!(ax_local->flowctrl & AX_FC_TX); 5062306a36Sopenharmony_ci pause->rx_pause = !!(ax_local->flowctrl & AX_FC_RX); 5162306a36Sopenharmony_ci pause->autoneg = (ax_local->flowctrl & AX_FC_ANEG) ? 5262306a36Sopenharmony_ci AUTONEG_ENABLE : 5362306a36Sopenharmony_ci AUTONEG_DISABLE; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int 5762306a36Sopenharmony_ciax88796c_set_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *pause) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 6062306a36Sopenharmony_ci int fc; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* The following logic comes from phylink_ethtool_set_pauseparam() */ 6362306a36Sopenharmony_ci fc = pause->tx_pause ? AX_FC_TX : 0; 6462306a36Sopenharmony_ci fc |= pause->rx_pause ? AX_FC_RX : 0; 6562306a36Sopenharmony_ci fc |= pause->autoneg ? AX_FC_ANEG : 0; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci ax_local->flowctrl = fc; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (pause->autoneg) { 7062306a36Sopenharmony_ci phy_set_asym_pause(ax_local->phydev, pause->tx_pause, 7162306a36Sopenharmony_ci pause->rx_pause); 7262306a36Sopenharmony_ci } else { 7362306a36Sopenharmony_ci int maccr = 0; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci phy_set_asym_pause(ax_local->phydev, 0, 0); 7662306a36Sopenharmony_ci maccr |= (ax_local->flowctrl & AX_FC_RX) ? MACCR_RXFC_ENABLE : 0; 7762306a36Sopenharmony_ci maccr |= (ax_local->flowctrl & AX_FC_TX) ? MACCR_TXFC_ENABLE : 0; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci mutex_lock(&ax_local->spi_lock); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci maccr |= AX_READ(&ax_local->ax_spi, P0_MACCR) & 8262306a36Sopenharmony_ci ~(MACCR_TXFC_ENABLE | MACCR_RXFC_ENABLE); 8362306a36Sopenharmony_ci AX_WRITE(&ax_local->ax_spi, maccr, P0_MACCR); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci mutex_unlock(&ax_local->spi_lock); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int ax88796c_get_regs_len(struct net_device *ndev) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci return AX88796C_REGDUMP_LEN + AX88796C_PHY_REGDUMP_LEN; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic void 9762306a36Sopenharmony_ciax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 10062306a36Sopenharmony_ci int offset, i; 10162306a36Sopenharmony_ci u16 *p = _p; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci memset(p, 0, ax88796c_get_regs_len(ndev)); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci mutex_lock(&ax_local->spi_lock); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci for (offset = 0; offset < AX88796C_REGDUMP_LEN; offset += 2) { 10862306a36Sopenharmony_ci if (!test_bit(offset / 2, ax88796c_no_regs_mask)) 10962306a36Sopenharmony_ci *p = AX_READ(&ax_local->ax_spi, offset); 11062306a36Sopenharmony_ci p++; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci mutex_unlock(&ax_local->spi_lock); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { 11662306a36Sopenharmony_ci *p = phy_read(ax_local->phydev, i); 11762306a36Sopenharmony_ci p++; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic void 12262306a36Sopenharmony_ciax88796c_get_strings(struct net_device *ndev, u32 stringset, u8 *data) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci switch (stringset) { 12562306a36Sopenharmony_ci case ETH_SS_PRIV_FLAGS: 12662306a36Sopenharmony_ci memcpy(data, ax88796c_priv_flag_names, 12762306a36Sopenharmony_ci sizeof(ax88796c_priv_flag_names)); 12862306a36Sopenharmony_ci break; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int 13362306a36Sopenharmony_ciax88796c_get_sset_count(struct net_device *ndev, int stringset) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int ret = 0; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci switch (stringset) { 13862306a36Sopenharmony_ci case ETH_SS_PRIV_FLAGS: 13962306a36Sopenharmony_ci ret = ARRAY_SIZE(ax88796c_priv_flag_names); 14062306a36Sopenharmony_ci break; 14162306a36Sopenharmony_ci default: 14262306a36Sopenharmony_ci ret = -EOPNOTSUPP; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return ret; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int ax88796c_set_priv_flags(struct net_device *ndev, u32 flags) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci if (flags & ~AX_PRIV_FLAGS_MASK) 15362306a36Sopenharmony_ci return -EOPNOTSUPP; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if ((ax_local->priv_flags ^ flags) & AX_CAP_COMP) 15662306a36Sopenharmony_ci if (netif_running(ndev)) 15762306a36Sopenharmony_ci return -EBUSY; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ax_local->priv_flags = flags; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return 0; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic u32 ax88796c_get_priv_flags(struct net_device *ndev) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct ax88796c_device *ax_local = to_ax88796c_device(ndev); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return ax_local->priv_flags; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ciint ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct ax88796c_device *ax_local = mdiobus->priv; 17462306a36Sopenharmony_ci int ret; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci mutex_lock(&ax_local->spi_lock); 17762306a36Sopenharmony_ci AX_WRITE(&ax_local->ax_spi, MDIOCR_RADDR(loc) 17862306a36Sopenharmony_ci | MDIOCR_FADDR(phy_id) | MDIOCR_READ, P2_MDIOCR); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ret = read_poll_timeout(AX_READ, ret, 18162306a36Sopenharmony_ci (ret != 0), 18262306a36Sopenharmony_ci 0, jiffies_to_usecs(HZ / 100), false, 18362306a36Sopenharmony_ci &ax_local->ax_spi, P2_MDIOCR); 18462306a36Sopenharmony_ci if (!ret) 18562306a36Sopenharmony_ci ret = AX_READ(&ax_local->ax_spi, P2_MDIODR); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci mutex_unlock(&ax_local->spi_lock); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ciint 19362306a36Sopenharmony_ciax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct ax88796c_device *ax_local = mdiobus->priv; 19662306a36Sopenharmony_ci int ret; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci mutex_lock(&ax_local->spi_lock); 19962306a36Sopenharmony_ci AX_WRITE(&ax_local->ax_spi, val, P2_MDIODR); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci AX_WRITE(&ax_local->ax_spi, 20262306a36Sopenharmony_ci MDIOCR_RADDR(loc) | MDIOCR_FADDR(phy_id) 20362306a36Sopenharmony_ci | MDIOCR_WRITE, P2_MDIOCR); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci ret = read_poll_timeout(AX_READ, ret, 20662306a36Sopenharmony_ci ((ret & MDIOCR_VALID) != 0), 0, 20762306a36Sopenharmony_ci jiffies_to_usecs(HZ / 100), false, 20862306a36Sopenharmony_ci &ax_local->ax_spi, P2_MDIOCR); 20962306a36Sopenharmony_ci mutex_unlock(&ax_local->spi_lock); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci return ret; 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ciconst struct ethtool_ops ax88796c_ethtool_ops = { 21562306a36Sopenharmony_ci .get_drvinfo = ax88796c_get_drvinfo, 21662306a36Sopenharmony_ci .get_link = ethtool_op_get_link, 21762306a36Sopenharmony_ci .get_msglevel = ax88796c_get_msglevel, 21862306a36Sopenharmony_ci .set_msglevel = ax88796c_set_msglevel, 21962306a36Sopenharmony_ci .get_link_ksettings = phy_ethtool_get_link_ksettings, 22062306a36Sopenharmony_ci .set_link_ksettings = phy_ethtool_set_link_ksettings, 22162306a36Sopenharmony_ci .nway_reset = phy_ethtool_nway_reset, 22262306a36Sopenharmony_ci .get_pauseparam = ax88796c_get_pauseparam, 22362306a36Sopenharmony_ci .set_pauseparam = ax88796c_set_pauseparam, 22462306a36Sopenharmony_ci .get_regs_len = ax88796c_get_regs_len, 22562306a36Sopenharmony_ci .get_regs = ax88796c_get_regs, 22662306a36Sopenharmony_ci .get_strings = ax88796c_get_strings, 22762306a36Sopenharmony_ci .get_sset_count = ax88796c_get_sset_count, 22862306a36Sopenharmony_ci .get_priv_flags = ax88796c_get_priv_flags, 22962306a36Sopenharmony_ci .set_priv_flags = ax88796c_set_priv_flags, 23062306a36Sopenharmony_ci}; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciint ax88796c_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci int ret; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci ret = phy_mii_ioctl(ndev->phydev, ifr, cmd); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return ret; 23962306a36Sopenharmony_ci} 240