// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2010 ASIX Electronics Corporation * Copyright (c) 2020 Samsung Electronics Co., Ltd. * * ASIX AX88796C SPI Fast Ethernet Linux driver */ #define pr_fmt(fmt) "ax88796c: " fmt #include #include #include #include #include "ax88796c_main.h" #include "ax88796c_ioctl.h" static const char ax88796c_priv_flag_names[][ETH_GSTRING_LEN] = { "SPICompression", }; static void ax88796c_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *info) { /* Inherit standard device info */ strncpy(info->driver, DRV_NAME, sizeof(info->driver)); } static u32 ax88796c_get_msglevel(struct net_device *ndev) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); return ax_local->msg_enable; } static void ax88796c_set_msglevel(struct net_device *ndev, u32 level) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); ax_local->msg_enable = level; } static void ax88796c_get_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *pause) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); pause->tx_pause = !!(ax_local->flowctrl & AX_FC_TX); pause->rx_pause = !!(ax_local->flowctrl & AX_FC_RX); pause->autoneg = (ax_local->flowctrl & AX_FC_ANEG) ? AUTONEG_ENABLE : AUTONEG_DISABLE; } static int ax88796c_set_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *pause) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); int fc; /* The following logic comes from phylink_ethtool_set_pauseparam() */ fc = pause->tx_pause ? AX_FC_TX : 0; fc |= pause->rx_pause ? AX_FC_RX : 0; fc |= pause->autoneg ? AX_FC_ANEG : 0; ax_local->flowctrl = fc; if (pause->autoneg) { phy_set_asym_pause(ax_local->phydev, pause->tx_pause, pause->rx_pause); } else { int maccr = 0; phy_set_asym_pause(ax_local->phydev, 0, 0); maccr |= (ax_local->flowctrl & AX_FC_RX) ? MACCR_RXFC_ENABLE : 0; maccr |= (ax_local->flowctrl & AX_FC_TX) ? MACCR_TXFC_ENABLE : 0; mutex_lock(&ax_local->spi_lock); maccr |= AX_READ(&ax_local->ax_spi, P0_MACCR) & ~(MACCR_TXFC_ENABLE | MACCR_RXFC_ENABLE); AX_WRITE(&ax_local->ax_spi, maccr, P0_MACCR); mutex_unlock(&ax_local->spi_lock); } return 0; } static int ax88796c_get_regs_len(struct net_device *ndev) { return AX88796C_REGDUMP_LEN + AX88796C_PHY_REGDUMP_LEN; } static void ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); int offset, i; u16 *p = _p; memset(p, 0, ax88796c_get_regs_len(ndev)); mutex_lock(&ax_local->spi_lock); for (offset = 0; offset < AX88796C_REGDUMP_LEN; offset += 2) { if (!test_bit(offset / 2, ax88796c_no_regs_mask)) *p = AX_READ(&ax_local->ax_spi, offset); p++; } mutex_unlock(&ax_local->spi_lock); for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { *p = phy_read(ax_local->phydev, i); p++; } } static void ax88796c_get_strings(struct net_device *ndev, u32 stringset, u8 *data) { switch (stringset) { case ETH_SS_PRIV_FLAGS: memcpy(data, ax88796c_priv_flag_names, sizeof(ax88796c_priv_flag_names)); break; } } static int ax88796c_get_sset_count(struct net_device *ndev, int stringset) { int ret = 0; switch (stringset) { case ETH_SS_PRIV_FLAGS: ret = ARRAY_SIZE(ax88796c_priv_flag_names); break; default: ret = -EOPNOTSUPP; } return ret; } static int ax88796c_set_priv_flags(struct net_device *ndev, u32 flags) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); if (flags & ~AX_PRIV_FLAGS_MASK) return -EOPNOTSUPP; if ((ax_local->priv_flags ^ flags) & AX_CAP_COMP) if (netif_running(ndev)) return -EBUSY; ax_local->priv_flags = flags; return 0; } static u32 ax88796c_get_priv_flags(struct net_device *ndev) { struct ax88796c_device *ax_local = to_ax88796c_device(ndev); return ax_local->priv_flags; } int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) { struct ax88796c_device *ax_local = mdiobus->priv; int ret; mutex_lock(&ax_local->spi_lock); AX_WRITE(&ax_local->ax_spi, MDIOCR_RADDR(loc) | MDIOCR_FADDR(phy_id) | MDIOCR_READ, P2_MDIOCR); ret = read_poll_timeout(AX_READ, ret, (ret != 0), 0, jiffies_to_usecs(HZ / 100), false, &ax_local->ax_spi, P2_MDIOCR); if (!ret) ret = AX_READ(&ax_local->ax_spi, P2_MDIODR); mutex_unlock(&ax_local->spi_lock); return ret; } int ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) { struct ax88796c_device *ax_local = mdiobus->priv; int ret; mutex_lock(&ax_local->spi_lock); AX_WRITE(&ax_local->ax_spi, val, P2_MDIODR); AX_WRITE(&ax_local->ax_spi, MDIOCR_RADDR(loc) | MDIOCR_FADDR(phy_id) | MDIOCR_WRITE, P2_MDIOCR); ret = read_poll_timeout(AX_READ, ret, ((ret & MDIOCR_VALID) != 0), 0, jiffies_to_usecs(HZ / 100), false, &ax_local->ax_spi, P2_MDIOCR); mutex_unlock(&ax_local->spi_lock); return ret; } const struct ethtool_ops ax88796c_ethtool_ops = { .get_drvinfo = ax88796c_get_drvinfo, .get_link = ethtool_op_get_link, .get_msglevel = ax88796c_get_msglevel, .set_msglevel = ax88796c_set_msglevel, .get_link_ksettings = phy_ethtool_get_link_ksettings, .set_link_ksettings = phy_ethtool_set_link_ksettings, .nway_reset = phy_ethtool_nway_reset, .get_pauseparam = ax88796c_get_pauseparam, .set_pauseparam = ax88796c_set_pauseparam, .get_regs_len = ax88796c_get_regs_len, .get_regs = ax88796c_get_regs, .get_strings = ax88796c_get_strings, .get_sset_count = ax88796c_get_sset_count, .get_priv_flags = ax88796c_get_priv_flags, .set_priv_flags = ax88796c_set_priv_flags, }; int ax88796c_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) { int ret; ret = phy_mii_ioctl(ndev->phydev, ifr, cmd); return ret; }