diff options
Diffstat (limited to 'drivers/net/ethernet/asix/ax88796c_ioctl.c')
-rw-r--r-- | drivers/net/ethernet/asix/ax88796c_ioctl.c | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.c b/drivers/net/ethernet/asix/ax88796c_ioctl.c new file mode 100644 index 000000000..916ae380a --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.c @@ -0,0 +1,239 @@ +// 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 <linux/bitmap.h> +#include <linux/iopoll.h> +#include <linux/phy.h> +#include <linux/netdevice.h> + +#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; +} |