root/drivers/net/ethernet/asix/ax88796c_ioctl.c
// 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 */
        strscpy(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;
}