Commit a97c69ba authored by Łukasz Stelmach's avatar Łukasz Stelmach Committed by Jakub Kicinski

net: ax88796c: ASIX AX88796C SPI Ethernet Adapter Driver

ASIX AX88796[1] is a versatile ethernet adapter chip, that can be
connected to a CPU with a 8/16-bit bus or with an SPI. This driver
supports SPI connection.

The driver has been ported from the vendor kernel for ARTIK5[2]
boards. Several changes were made to adapt it to the current kernel
which include:

+ updated DT configuration,
+ clock configuration moved to DT,
+ new timer, ethtool and gpio APIs,
+ dev_* instead of pr_* and custom printk() wrappers,
+ removed awkward vendor power managemtn.
+ introduced ethtool tunable to control SPI compression

[1] https://www.asix.com.tw/products.php?op=pItemdetail&PItemID=104;65;86&PLine=65
[2] https://git.tizen.org/cgit/profile/common/platform/kernel/linux-3.10-artik/

The other ax88796 driver is for NE2000 compatible AX88796L chip. These
chips are not compatible. Hence, two separate drivers are required.
Signed-off-by: default avatarŁukasz Stelmach <l.stelmach@samsung.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent b13c7a88
......@@ -2899,6 +2899,12 @@ S: Maintained
F: Documentation/hwmon/asc7621.rst
F: drivers/hwmon/asc7621.c
ASIX AX88796C SPI ETHERNET ADAPTER
M: Łukasz Stelmach <l.stelmach@samsung.com>
S: Maintained
F: Documentation/devicetree/bindings/net/asix,ax88796c.yaml
F: drivers/net/ethernet/asix/ax88796c_*
ASPEED PINCTRL DRIVERS
M: Andrew Jeffery <andrew@aj.id.au>
L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)
......
......@@ -33,6 +33,7 @@ source "drivers/net/ethernet/apm/Kconfig"
source "drivers/net/ethernet/apple/Kconfig"
source "drivers/net/ethernet/aquantia/Kconfig"
source "drivers/net/ethernet/arc/Kconfig"
source "drivers/net/ethernet/asix/Kconfig"
source "drivers/net/ethernet/atheros/Kconfig"
source "drivers/net/ethernet/broadcom/Kconfig"
source "drivers/net/ethernet/brocade/Kconfig"
......
......@@ -19,6 +19,7 @@ obj-$(CONFIG_NET_XGENE) += apm/
obj-$(CONFIG_NET_VENDOR_APPLE) += apple/
obj-$(CONFIG_NET_VENDOR_AQUANTIA) += aquantia/
obj-$(CONFIG_NET_VENDOR_ARC) += arc/
obj-$(CONFIG_NET_VENDOR_ASIX) += asix/
obj-$(CONFIG_NET_VENDOR_ATHEROS) += atheros/
obj-$(CONFIG_NET_VENDOR_CADENCE) += cadence/
obj-$(CONFIG_NET_VENDOR_BROADCOM) += broadcom/
......
#
# Asix network device configuration
#
config NET_VENDOR_ASIX
bool "Asix devices"
default y
help
If you have a network (Ethernet, non-USB, not NE2000 compatible)
interface based on a chip from ASIX, say Y.
if NET_VENDOR_ASIX
config SPI_AX88796C
tristate "Asix AX88796C-SPI support"
select PHYLIB
depends on SPI
depends on GPIOLIB
help
Say Y here if you intend to use ASIX AX88796C attached in SPI mode.
config SPI_AX88796C_COMPRESSION
bool "SPI transfer compression"
default n
depends on SPI_AX88796C
help
Say Y here to enable SPI transfer compression. It saves up
to 24 dummy cycles during each transfer which may noticeably
speed up short transfers. This sets the default value that is
inherited by network interfaces during probe. It can be
changed at run time via spi-compression ethtool tunable.
If unsure say N.
endif # NET_VENDOR_ASIX
#
# Makefile for the Asix network device drivers.
#
obj-$(CONFIG_SPI_AX88796C) += ax88796c.o
ax88796c-y := ax88796c_main.o ax88796c_ioctl.o ax88796c_spi.o
// 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;
}
/* 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
*/
#ifndef _AX88796C_IOCTL_H
#define _AX88796C_IOCTL_H
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include "ax88796c_main.h"
extern const struct ethtool_ops ax88796c_ethtool_ops;
bool ax88796c_check_power(const struct ax88796c_device *ax_local);
bool ax88796c_check_power_and_wake(struct ax88796c_device *ax_local);
void ax88796c_set_power_saving(struct ax88796c_device *ax_local, u8 ps_level);
int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc);
int ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val);
int ax88796c_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
#endif
This diff is collapsed.
This diff is collapsed.
// 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/string.h>
#include <linux/spi/spi.h>
#include "ax88796c_spi.h"
const u8 ax88796c_rx_cmd_buf[5] = {AX_SPICMD_READ_RXQ, 0xFF, 0xFF, 0xFF, 0xFF};
const u8 ax88796c_tx_cmd_buf[4] = {AX_SPICMD_WRITE_TXQ, 0xFF, 0xFF, 0xFF};
/* driver bus management functions */
int axspi_wakeup(struct axspi_data *ax_spi)
{
int ret;
ax_spi->cmd_buf[0] = AX_SPICMD_EXIT_PWD; /* OP */
ret = spi_write(ax_spi->spi, ax_spi->cmd_buf, 1);
if (ret)
dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret);
return ret;
}
int axspi_read_status(struct axspi_data *ax_spi, struct spi_status *status)
{
int ret;
/* OP */
ax_spi->cmd_buf[0] = AX_SPICMD_READ_STATUS;
ret = spi_write_then_read(ax_spi->spi, ax_spi->cmd_buf, 1, (u8 *)&status, 3);
if (ret)
dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret);
else
le16_to_cpus(&status->isr);
return ret;
}
int axspi_read_rxq(struct axspi_data *ax_spi, void *data, int len)
{
struct spi_transfer *xfer = ax_spi->spi_rx_xfer;
int ret;
memcpy(ax_spi->cmd_buf, ax88796c_rx_cmd_buf, 5);
xfer->tx_buf = ax_spi->cmd_buf;
xfer->rx_buf = NULL;
xfer->len = ax_spi->comp ? 2 : 5;
xfer->bits_per_word = 8;
spi_message_add_tail(xfer, &ax_spi->rx_msg);
xfer++;
xfer->rx_buf = data;
xfer->tx_buf = NULL;
xfer->len = len;
xfer->bits_per_word = 8;
spi_message_add_tail(xfer, &ax_spi->rx_msg);
ret = spi_sync(ax_spi->spi, &ax_spi->rx_msg);
if (ret)
dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret);
return ret;
}
int axspi_write_txq(const struct axspi_data *ax_spi, void *data, int len)
{
return spi_write(ax_spi->spi, data, len);
}
u16 axspi_read_reg(struct axspi_data *ax_spi, u8 reg)
{
int ret;
int len = ax_spi->comp ? 3 : 4;
ax_spi->cmd_buf[0] = 0x03; /* OP code read register */
ax_spi->cmd_buf[1] = reg; /* register address */
ax_spi->cmd_buf[2] = 0xFF; /* dumy cycle */
ax_spi->cmd_buf[3] = 0xFF; /* dumy cycle */
ret = spi_write_then_read(ax_spi->spi,
ax_spi->cmd_buf, len,
ax_spi->rx_buf, 2);
if (ret) {
dev_err(&ax_spi->spi->dev,
"%s() failed: ret = %d\n", __func__, ret);
return 0xFFFF;
}
le16_to_cpus((u16 *)ax_spi->rx_buf);
return *(u16 *)ax_spi->rx_buf;
}
int axspi_write_reg(struct axspi_data *ax_spi, u8 reg, u16 value)
{
int ret;
memset(ax_spi->cmd_buf, 0, sizeof(ax_spi->cmd_buf));
ax_spi->cmd_buf[0] = AX_SPICMD_WRITE_REG; /* OP code read register */
ax_spi->cmd_buf[1] = reg; /* register address */
ax_spi->cmd_buf[2] = value;
ax_spi->cmd_buf[3] = value >> 8;
ret = spi_write(ax_spi->spi, ax_spi->cmd_buf, 4);
if (ret)
dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret);
return ret;
}
/* 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
*/
#ifndef _AX88796C_SPI_H
#define _AX88796C_SPI_H
#include <linux/spi/spi.h>
#include <linux/types.h>
/* Definition of SPI command */
#define AX_SPICMD_WRITE_TXQ 0x02
#define AX_SPICMD_READ_REG 0x03
#define AX_SPICMD_READ_STATUS 0x05
#define AX_SPICMD_READ_RXQ 0x0B
#define AX_SPICMD_BIDIR_WRQ 0xB2
#define AX_SPICMD_WRITE_REG 0xD8
#define AX_SPICMD_EXIT_PWD 0xAB
extern const u8 ax88796c_rx_cmd_buf[];
extern const u8 ax88796c_tx_cmd_buf[];
struct axspi_data {
struct spi_device *spi;
struct spi_message rx_msg;
struct spi_transfer spi_rx_xfer[2];
u8 cmd_buf[6];
u8 rx_buf[6];
u8 comp;
};
struct spi_status {
u16 isr;
u8 status;
# define AX_STATUS_READY 0x80
};
int axspi_read_rxq(struct axspi_data *ax_spi, void *data, int len);
int axspi_write_txq(const struct axspi_data *ax_spi, void *data, int len);
u16 axspi_read_reg(struct axspi_data *ax_spi, u8 reg);
int axspi_write_reg(struct axspi_data *ax_spi, u8 reg, u16 value);
int axspi_read_status(struct axspi_data *ax_spi, struct spi_status *status);
int axspi_wakeup(struct axspi_data *ax_spi);
static inline u16 AX_READ(struct axspi_data *ax_spi, u8 offset)
{
return axspi_read_reg(ax_spi, offset);
}
static inline int AX_WRITE(struct axspi_data *ax_spi, u16 value, u8 offset)
{
return axspi_write_reg(ax_spi, offset, value);
}
static inline int AX_READ_STATUS(struct axspi_data *ax_spi,
struct spi_status *status)
{
return axspi_read_status(ax_spi, status);
}
static inline int AX_WAKEUP(struct axspi_data *ax_spi)
{
return axspi_wakeup(ax_spi);
}
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment